♻️ refactor: refactor project
parent
e116458d10
commit
b9046f13a2
|
|
@ -1,19 +1,16 @@
|
|||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_style = tab
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
VITE_CONTEXT=PROD
|
||||
|
|
@ -28,3 +28,4 @@ logs
|
|||
# add other ignore file below
|
||||
javascript
|
||||
style.css
|
||||
vite-env.d.ts
|
||||
25
.eslintrc.js
25
.eslintrc.js
|
|
@ -1,12 +1,17 @@
|
|||
const config = require('@lobehub/lint').eslint;
|
||||
|
||||
config.rules['indent'] = ['off', 2];
|
||||
|
||||
module.exports = {
|
||||
...require('@lobehub/lint').eslint,
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'no-undef': 0,
|
||||
'unicorn/prefer-add-event-listener': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
...config,
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'no-undef': 0,
|
||||
'object-curly-spacing': 0,
|
||||
'unicorn/prefer-add-event-listener': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
62
.umirc.ts
62
.umirc.ts
|
|
@ -1,62 +0,0 @@
|
|||
// @ts-ignore
|
||||
import browserslist from 'browserslist';
|
||||
// @ts-ignore
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
// @ts-ignore
|
||||
import lightningcss from 'lightningcss';
|
||||
import { defineConfig } from 'umi';
|
||||
import WebpackShellPlugin from 'webpack-shell-plugin-next';
|
||||
|
||||
const mac = [
|
||||
'rm ./javascript/index.js',
|
||||
'rm ./style.css',
|
||||
'cp ./dist/index.js ./javascript/index.js',
|
||||
'cp ./dist/index.css ./style.css',
|
||||
];
|
||||
|
||||
const win = [
|
||||
'del javascript\\index.js',
|
||||
'del style.css',
|
||||
'copy dist\\index.js javascript\\index.js',
|
||||
'copy dist\\index.css style.css',
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
routes: [{ path: '/', component: 'index' }],
|
||||
npmClient: 'pnpm',
|
||||
mpa: {},
|
||||
codeSplitting: false,
|
||||
define: {
|
||||
'process.env': process.env,
|
||||
},
|
||||
extraBabelPlugins: [
|
||||
[
|
||||
'babel-plugin-styled-components',
|
||||
{
|
||||
minify: true,
|
||||
transpileTemplateLiterals: true,
|
||||
displayName: process.env.NODE_ENV === 'development',
|
||||
pure: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
chainWebpack(memo) {
|
||||
memo.plugin('minimizer').use(CssMinimizerPlugin, [
|
||||
{
|
||||
minify: CssMinimizerPlugin.lightningCssMinify,
|
||||
minimizerOptions: {
|
||||
targets: lightningcss.browserslistToTargets(browserslist('>= 0.25%')),
|
||||
},
|
||||
},
|
||||
]);
|
||||
memo.plugin('shell').use(WebpackShellPlugin, [
|
||||
{
|
||||
onBuildExit: {
|
||||
scripts: process.platform === 'win32' ? win : mac,
|
||||
blocking: false,
|
||||
parallel: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
});
|
||||
1190
javascript/index.js
1190
javascript/index.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
35
package.json
35
package.json
|
|
@ -2,6 +2,13 @@
|
|||
"name": "sd-webui-kitchen-theme",
|
||||
"version": "1.8.3",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"stable-diffusion-webui"
|
||||
],
|
||||
"homepage": "https://github.com/canisminor1990/sd-webui-kitchen-theme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/canisminor1990/sd-webui-kitchen-theme/issues/new"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/canisminor1990/sd-webui-kitchen-theme.git"
|
||||
|
|
@ -10,8 +17,8 @@
|
|||
"author": "canisminor1990 <i@canisminor.cc>",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "umi build",
|
||||
"dev": "umi build",
|
||||
"build": "tsc && vite build",
|
||||
"dev": "vite",
|
||||
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
|
||||
"lint:md": "remark . --quiet --output",
|
||||
"lint:style": "stylelint \"src/**/*.{css,less,js,jsx,ts,tsx}\" --fix",
|
||||
|
|
@ -19,8 +26,6 @@
|
|||
"prettier": "prettier -c --write \"**/**\"",
|
||||
"release": "semantic-release",
|
||||
"sd-debug": "cd ../../ && ./webui.sh",
|
||||
"setup": "umi setup",
|
||||
"start": "umi build",
|
||||
"test": "npm run lint",
|
||||
"type-check": "tsc -p tsconfig-check.json"
|
||||
},
|
||||
|
|
@ -49,18 +54,17 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5",
|
||||
"@babel/plugin-syntax-import-assertions": "^7",
|
||||
"@commitlint/cli": "^17",
|
||||
"@lobehub/lint": "latest",
|
||||
"@lobehub/ui": "latest",
|
||||
"@types/lodash-es": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dnd": "^3",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-rnd": "^8",
|
||||
"@types/react-tag-input": "^6",
|
||||
"@types/styled-components": "^5",
|
||||
"@umijs/lint": "^4",
|
||||
"@vitejs/plugin-react": "^4",
|
||||
"ahooks": "^3",
|
||||
"antd": "^5",
|
||||
"antd-style": "latest",
|
||||
|
|
@ -70,6 +74,7 @@
|
|||
"concurrently": "^8",
|
||||
"css-minimizer-webpack-plugin": "^5",
|
||||
"eslint": "^8",
|
||||
"fast-deep-equal": "^3",
|
||||
"husky": "^8",
|
||||
"lightningcss": "^1",
|
||||
"lint-staged": "^13",
|
||||
|
|
@ -79,10 +84,7 @@
|
|||
"polished": "^4",
|
||||
"prettier": "^2",
|
||||
"query-string": "^8",
|
||||
"re-resizable": "^6",
|
||||
"react": "^18",
|
||||
"react-dnd": "^16",
|
||||
"react-dnd-html5-backend": "^16",
|
||||
"react-dom": "^18",
|
||||
"react-layout-kit": "^1",
|
||||
"react-rnd": "^10",
|
||||
|
|
@ -93,9 +95,18 @@
|
|||
"styled-components": "latest",
|
||||
"stylelint": "^15",
|
||||
"typescript": "^5",
|
||||
"umi": "^4",
|
||||
"use-merge-value": "^1",
|
||||
"vite": "^4.3.9",
|
||||
"webpack-shell-plugin-next": "^2",
|
||||
"zustand": "^4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"antd": ">=5",
|
||||
"antd-style": ">=3",
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { memo, useEffect, useState } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import Layout from '@/layouts';
|
||||
import Index from '@/pages';
|
||||
import formatPrompt from '@/script/formatPrompt';
|
||||
import promptBracketChecker from '@/script/promptBracketChecker';
|
||||
import setupHead from '@/script/setupHead';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const App = memo(() => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const setCurrentTab = useAppStore((st) => st.setCurrentTab, shallow);
|
||||
useEffect(() => {
|
||||
setupHead();
|
||||
onUiLoaded(() => {
|
||||
formatPrompt();
|
||||
promptBracketChecker();
|
||||
setLoading(false);
|
||||
});
|
||||
onUiUpdate(() => {
|
||||
setCurrentTab();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <Layout>{!loading && <Index />}</Layout>;
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import RefreshRuntime from '/@react-refresh';
|
||||
|
||||
const RefreshSig = (type) => type;
|
||||
|
||||
RefreshRuntime.injectIntoGlobalHook(window);
|
||||
window.$RefreshReg$ = () => {};
|
||||
window.$RefreshSig$ = () => RefreshSig;
|
||||
window.__vite_plugin_react_preamble_installed__ = true;
|
||||
|
||||
console.log('🚧 Injecting React Refresh');
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import {FloatButton} from 'antd';
|
||||
import {type ReactNode, memo, 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;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
[id$='2img_prompt'] textarea {
|
||||
max-height: ${({isPromptResizable}) => (isPromptResizable ? 'unset' : '84px')};
|
||||
}
|
||||
|
||||
[id$='2img_neg_prompt'] textarea {
|
||||
max-height: ${({isPromptResizable}) => (isPromptResizable ? 'unset' : '84px')};
|
||||
}
|
||||
`;
|
||||
|
||||
interface ContentProps {
|
||||
children: ReactNode;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const Content = memo<ContentProps>(({children}) => {
|
||||
const reference: any = useRef(null);
|
||||
const [setting] = useAppStore((st) => [st.setting], shallow);
|
||||
|
||||
return (
|
||||
<ContentView isPromptResizable={setting.promotTextarea === 'resizable'} ref={reference}>
|
||||
{children}
|
||||
<FloatButton.BackTop target={() => reference.current} />
|
||||
</ContentView>
|
||||
);
|
||||
});
|
||||
|
||||
export default Content;
|
||||
|
|
@ -1,228 +0,0 @@
|
|||
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 {memo, 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 type {DivProps} from '@/types';
|
||||
|
||||
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 {
|
||||
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 = memo<DraggablePanelProps>(
|
||||
({
|
||||
pin = 'true',
|
||||
mode = 'fixed',
|
||||
children,
|
||||
placement = 'right',
|
||||
resize,
|
||||
style,
|
||||
size,
|
||||
defaultSize: customizeDefaultSize,
|
||||
minWidth,
|
||||
minHeight,
|
||||
onSizeChange,
|
||||
onSizeDragging,
|
||||
expandable = true,
|
||||
expand,
|
||||
defaultExpand = true,
|
||||
onExpandChange,
|
||||
className,
|
||||
showHandlerWhenUnexpand,
|
||||
destroyOnClose,
|
||||
hanlderStyle,
|
||||
}) => {
|
||||
const reference = useRef(null);
|
||||
const isHovering = useHover(reference);
|
||||
const isVertical = placement === 'top' || placement === 'bottom';
|
||||
|
||||
const {styles, cx} = useStyle('draggable-panel');
|
||||
|
||||
const [isExpand, setIsExpand] = useControlledState(defaultExpand, {
|
||||
onChange: onExpandChange,
|
||||
value: expand,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (pin) return;
|
||||
if (isHovering && !isExpand) {
|
||||
setIsExpand(true);
|
||||
} else if (!isHovering && isExpand) {
|
||||
setIsExpand(false);
|
||||
}
|
||||
}, [pin, isHovering, isExpand]);
|
||||
|
||||
const [showExpand, setShowExpand] = useState(true);
|
||||
|
||||
const canResizing = resize !== false && isExpand;
|
||||
|
||||
const resizeHandleClassNames: HandleClassName = useMemo(() => {
|
||||
if (!canResizing) return {};
|
||||
|
||||
return {
|
||||
[revesePlacement(placement)]: styles[`${revesePlacement(placement)}Handle`],
|
||||
};
|
||||
}, [canResizing, placement]);
|
||||
|
||||
const resizing = {
|
||||
bottom: false,
|
||||
bottomLeft: false,
|
||||
bottomRight: false,
|
||||
left: false,
|
||||
right: false,
|
||||
top: false,
|
||||
topLeft: false,
|
||||
topRight: false,
|
||||
[revesePlacement(placement)]: true,
|
||||
...(resize as Enable),
|
||||
};
|
||||
|
||||
const defaultSize: Size = useMemo(() => {
|
||||
if (isVertical) {
|
||||
return {
|
||||
height: DEFAULT_HEIGHT,
|
||||
width: '100%',
|
||||
...customizeDefaultSize,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
height: '100%',
|
||||
width: DEFAULT_WIDTH,
|
||||
...customizeDefaultSize,
|
||||
};
|
||||
}, [isVertical]);
|
||||
|
||||
const sizeProps = isExpand ?
|
||||
{
|
||||
defaultSize,
|
||||
minHeight: typeof minHeight === 'number' ? Math.max(minHeight, 0) : undefined,
|
||||
minWidth: typeof minWidth === 'number' ? Math.max(minWidth, 0) : 280,
|
||||
size: size as Size,
|
||||
} :
|
||||
isVertical ?
|
||||
{
|
||||
minHeight: 0,
|
||||
size: {height: 0},
|
||||
} :
|
||||
{
|
||||
minWidth: 0,
|
||||
size: {width: 0},
|
||||
};
|
||||
|
||||
const {Arrow, className: arrowPlacement} = useMemo(() => {
|
||||
switch (placement) {
|
||||
case 'top': {
|
||||
return {Arrow: ChevronDown, className: 'Bottom'};
|
||||
}
|
||||
case 'bottom': {
|
||||
return {Arrow: ChevronUp, className: 'Top'};
|
||||
}
|
||||
case 'right': {
|
||||
return {Arrow: ChevronLeft, className: 'Left'};
|
||||
}
|
||||
case 'left': {
|
||||
return {Arrow: ChevronRight, className: 'Right'};
|
||||
}
|
||||
}
|
||||
}, [styles, placement]);
|
||||
|
||||
const handler = (
|
||||
// @ts-ignore
|
||||
<Center
|
||||
// @ts-ignore
|
||||
className={cx(styles[`toggle${arrowPlacement}`])}
|
||||
style={{opacity: isExpand ? (pin ? undefined : 0) : showHandlerWhenUnexpand ? 1 : 0}}
|
||||
>
|
||||
<Center
|
||||
onClick={() => {
|
||||
setIsExpand(!isExpand);
|
||||
}}
|
||||
style={hanlderStyle}
|
||||
>
|
||||
<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}
|
||||
className={styles.panel}
|
||||
enable={canResizing ? (resizing as Enable) : undefined}
|
||||
handleClasses={resizeHandleClassNames}
|
||||
onResize={(_, direction, reference_, delta) => {
|
||||
onSizeDragging?.(delta, {
|
||||
height: reference_.style.height,
|
||||
width: reference_.style.width,
|
||||
});
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setShowExpand(false);
|
||||
}}
|
||||
onResizeStop={(e, direction, reference_, delta) => {
|
||||
setShowExpand(true);
|
||||
onSizeChange?.(delta, {
|
||||
height: reference_.style.height,
|
||||
width: reference_.style.width,
|
||||
});
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
styles.container,
|
||||
// @ts-ignore
|
||||
styles[mode === 'fixed' ? 'fixed' : `${placement}Float`],
|
||||
className,
|
||||
)}
|
||||
ref={reference}
|
||||
style={{[`border${arrowPlacement}Width`]: 1}}
|
||||
>
|
||||
{expandable && showExpand && handler}
|
||||
{destroyOnClose ? isExpand && inner : inner}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default DraggablePanel;
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
import {createStyles, css, cx} from 'antd-style';
|
||||
|
||||
const offset = 17;
|
||||
const toggleLength = 40;
|
||||
const toggleShort = 16;
|
||||
|
||||
export const useStyle = createStyles(({token}, prefix: string) => {
|
||||
const commonHandle = css`
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
transition: all 0.2s ${token.motionEaseOut};
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
&::before {
|
||||
background: ${token.colorPrimary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const commonToggle = css`
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ${token.motionEaseOut};
|
||||
|
||||
&:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
> div {
|
||||
cursor: pointer;
|
||||
|
||||
position: absolute;
|
||||
|
||||
color: ${token.colorTextTertiary};
|
||||
|
||||
background: ${token.colorFillTertiary};
|
||||
border-color: ${token.colorBorderSecondary};
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
|
||||
transition: all 0.2s ${token.motionEaseOut};
|
||||
|
||||
&:hover {
|
||||
color: ${token.colorTextSecondary};
|
||||
background: ${token.colorFillSecondary};
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: ${token.colorText};
|
||||
background: ${token.colorFill};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const float = css`
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
`;
|
||||
|
||||
return {
|
||||
bottomFloat: cx(
|
||||
float,
|
||||
css`
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
`,
|
||||
),
|
||||
bottomHandle: cx(
|
||||
`${prefix}-bottom-handle`,
|
||||
css`
|
||||
${commonHandle};
|
||||
|
||||
&::before {
|
||||
bottom: 50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
`,
|
||||
),
|
||||
container: cx(
|
||||
prefix,
|
||||
css`
|
||||
flex-shrink: 0;
|
||||
border: 0 solid ${token.colorBorderSecondary};
|
||||
|
||||
&:hover {
|
||||
.${prefix}-toggle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`,
|
||||
),
|
||||
fixed: css`
|
||||
position: relative;
|
||||
`,
|
||||
handlerIcon: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ${token.motionEaseOut};
|
||||
`,
|
||||
leftFloat: cx(
|
||||
float,
|
||||
css`
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
`,
|
||||
),
|
||||
leftHandle: cx(
|
||||
css`
|
||||
${commonHandle};
|
||||
|
||||
&::before {
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
`${prefix}-left-handle`,
|
||||
),
|
||||
panel: cx(
|
||||
`${prefix}-fixed`,
|
||||
css`
|
||||
overflow: hidden;
|
||||
background: ${token.colorBgContainer};
|
||||
transition: all 0.2s ${token.motionEaseOut};
|
||||
`,
|
||||
),
|
||||
rightFloat: cx(
|
||||
float,
|
||||
css`
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
`,
|
||||
),
|
||||
rightHandle: cx(
|
||||
css`
|
||||
${commonHandle};
|
||||
&::before {
|
||||
right: 50%;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
`${prefix}-right-handle`,
|
||||
),
|
||||
toggleBottom: cx(
|
||||
`${prefix}-toggle`,
|
||||
`${prefix}-toggle-bottom`,
|
||||
commonToggle,
|
||||
css`
|
||||
bottom: -${offset}px;
|
||||
width: 100%;
|
||||
height: ${toggleShort}px;
|
||||
|
||||
> div {
|
||||
left: 50%;
|
||||
|
||||
width: ${toggleLength}px;
|
||||
height: 16px;
|
||||
margin-left: -20px;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
`,
|
||||
),
|
||||
toggleLeft: cx(
|
||||
`${prefix}-toggle`,
|
||||
`${prefix}-toggle-left`,
|
||||
commonToggle,
|
||||
css`
|
||||
left: -${offset}px;
|
||||
width: ${toggleShort}px;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
top: 50%;
|
||||
|
||||
width: ${toggleShort}px;
|
||||
height: ${toggleLength}px;
|
||||
margin-top: -20px;
|
||||
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
`,
|
||||
),
|
||||
toggleRight: cx(
|
||||
`${prefix}-toggle`,
|
||||
`${prefix}-toggle-right`,
|
||||
commonToggle,
|
||||
css`
|
||||
right: -${offset}px;
|
||||
width: ${toggleShort}px;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
top: 50%;
|
||||
|
||||
width: ${toggleShort}px;
|
||||
height: ${toggleLength}px;
|
||||
margin-top: -20px;
|
||||
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
`,
|
||||
),
|
||||
toggleTop: cx(
|
||||
`${prefix}-toggle`,
|
||||
`${prefix}-toggle-top`,
|
||||
commonToggle,
|
||||
css`
|
||||
top: -${offset}px;
|
||||
width: 100%;
|
||||
height: ${toggleShort}px;
|
||||
|
||||
> div {
|
||||
left: 50%;
|
||||
|
||||
width: ${toggleLength}px;
|
||||
height: ${toggleShort}px;
|
||||
margin-left: -20px;
|
||||
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
`,
|
||||
),
|
||||
topFloat: cx(
|
||||
float,
|
||||
css`
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
`,
|
||||
),
|
||||
topHandle: cx(
|
||||
`${prefix}-top-handle`,
|
||||
css`
|
||||
${commonHandle};
|
||||
|
||||
&::before {
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
`,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import {placementType} from './index';
|
||||
|
||||
export const revesePlacement = (placement: placementType) => {
|
||||
switch (placement) {
|
||||
case 'bottom': {
|
||||
return 'top';
|
||||
}
|
||||
case 'top': {
|
||||
return 'bottom';
|
||||
}
|
||||
case 'right': {
|
||||
return 'left';
|
||||
}
|
||||
case 'left': {
|
||||
return 'right';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import {ZoomInOutlined} from '@ant-design/icons';
|
||||
import {DraggablePanel} from '@lobehub/ui';
|
||||
import {Slider} from 'antd';
|
||||
import {useResponsive} from 'antd-style';
|
||||
import {type CSSProperties, type ReactNode, memo, useEffect, useState} from 'react';
|
||||
import styled, {createGlobalStyle} from 'styled-components';
|
||||
import {shallow} from 'zustand/shallow';
|
||||
|
||||
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: var(--fill-available);
|
||||
`;
|
||||
|
||||
const SidebarView = styled.div<{ size: number }>`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
|
||||
#txt2img_extra_networks,
|
||||
#img2img_extra_networks {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.extra-network-cards .actions .name {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.extra-network-cards,
|
||||
.extra-network-thumbs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(${({size}) => size}px, 1fr));
|
||||
|
||||
> .card {
|
||||
width: var(--fill-available) !important;
|
||||
height: ${({size}) => size * 1.5}px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
display: flex;
|
||||
flex: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
border-top: 1px solid var(--color-border);
|
||||
`;
|
||||
|
||||
const ZoomSlider = styled(Slider)`
|
||||
flex: 1;
|
||||
margin-left: 16px;
|
||||
`;
|
||||
|
||||
interface SidebarProps {
|
||||
children: ReactNode;
|
||||
loading?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const Sidebar = memo<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);
|
||||
|
||||
useEffect(() => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<SidebarView size={size}>{children}</SidebarView>
|
||||
<Footer>
|
||||
<ZoomInOutlined />
|
||||
<ZoomSlider defaultValue={size} max={256} min={64} onChange={setSize} step={8} />
|
||||
</Footer>
|
||||
</View>
|
||||
</DraggablePanel>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Sidebar;
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import {memo, useEffect} from 'react';
|
||||
|
||||
interface GiscusProps {
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
const Giscus = memo<GiscusProps>(({themeMode}) => {
|
||||
useEffect(() => {
|
||||
// giscus
|
||||
const giscus: HTMLScriptElement = document.createElement('script');
|
||||
giscus.src = 'https://giscus.app/client.js';
|
||||
giscus.dataset.repo = 'canisminor1990/sd-webui-kitchen-theme';
|
||||
giscus.dataset.repoId = 'R_kgDOJCPcNg';
|
||||
giscus.dataset.mapping = 'number';
|
||||
giscus.dataset.term = '53';
|
||||
giscus.dataset.reactionsEnabled = '1';
|
||||
giscus.dataset.emitMetadata = '0';
|
||||
giscus.dataset.inputPosition = 'bottom';
|
||||
giscus.dataset.theme = themeMode;
|
||||
giscus.dataset.lang = 'en';
|
||||
giscus.crossOrigin = 'anonymous';
|
||||
giscus.async = true;
|
||||
document.querySelectorAll('head')[0].append(giscus);
|
||||
}, []);
|
||||
|
||||
return <div className="giscus" id="giscus" />;
|
||||
});
|
||||
|
||||
export default Giscus;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import {TabsNav, type TabsNavProps} from '@lobehub/ui';
|
||||
import {memo, useEffect, useState} from 'react';
|
||||
|
||||
const getNavButtons: HTMLButtonElement[] | any = () =>
|
||||
gradioApp().querySelectorAll('#tabs > .tab-nav:first-child button') || [];
|
||||
|
||||
const onChange: TabsNavProps['onChange'] = (activeKey) => {
|
||||
const buttons = getNavButtons();
|
||||
buttons[Number(activeKey)]?.click();
|
||||
};
|
||||
|
||||
const Nav = memo(() => {
|
||||
const [items, setItems] = useState<TabsNavProps['items']>([]);
|
||||
|
||||
useEffect(() => {
|
||||
onUiLoaded(() => {
|
||||
const buttons = getNavButtons();
|
||||
const list: TabsNavProps['items'] | any = [];
|
||||
buttons.forEach((button: HTMLButtonElement | any, index: number) => {
|
||||
button.id = `kitchen-nav-${index}`;
|
||||
list.push({
|
||||
key: String(index),
|
||||
label: button.textContent,
|
||||
});
|
||||
});
|
||||
setItems(list.filter(Boolean));
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <TabsNav items={items} onChange={onChange} />;
|
||||
});
|
||||
|
||||
export default Nav;
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import {SettingOutlined} from '@ant-design/icons';
|
||||
import {Button, Divider, Form, InputNumber, Popover, Segmented, Space, Switch} from 'antd';
|
||||
import {memo, useCallback} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {shallow} from 'zustand/shallow';
|
||||
|
||||
import {WebuiSetting, defaultSetting, useAppStore} from '@/store';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
******************************************************/
|
||||
|
||||
const FormItem = styled(Form.Item)`
|
||||
margin-bottom: 8px;
|
||||
|
||||
.ant-row {
|
||||
justify-content: space-between;
|
||||
|
||||
> div {
|
||||
flex: unset !important;
|
||||
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 = memo(() => {
|
||||
const [setting, onSetSetting] = useAppStore((st) => [st.setting, st.onSetSetting], shallow);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
onSetSetting(defaultSetting);
|
||||
gradioApp().querySelector('#settings_restart_gradio')?.click();
|
||||
}, []);
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value);
|
||||
gradioApp().querySelector('#settings_restart_gradio')?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<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">
|
||||
<Segmented options={['scroll', 'resizable']} />
|
||||
</FormItem>
|
||||
<Divider style={{margin: '4px 0 8px'}} />
|
||||
<SubTitle>Sidebar</SubTitle>
|
||||
<FormItem label="Default expand" name="sidebarExpand" valuePropName="checked">
|
||||
<Switch />
|
||||
</FormItem>
|
||||
<FormItem label="Display mode" name="sidebarFixedMode">
|
||||
<Segmented options={['fixed', 'float']} />
|
||||
</FormItem>
|
||||
<FormItem label="Default width" name="sidebarWidth">
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<Divider style={{margin: '4px 0 8px'}} />
|
||||
<SubTitle>ExtraNetwork Sidebar</SubTitle>
|
||||
<FormItem label="Enable" name="enableExtraNetworkSidebar" valuePropName="checked">
|
||||
<Switch />
|
||||
</FormItem>
|
||||
<FormItem label="Display mode" name="extraNetworkFixedMode" valuePropName="checked">
|
||||
<Segmented options={['fixed', 'float']} />
|
||||
</FormItem>
|
||||
<FormItem label="Default expand" name="extraNetworkSidebarExpand" valuePropName="checked">
|
||||
<Switch />
|
||||
</FormItem>
|
||||
<FormItem label="Default width" name="extraNetworkSidebarWidth">
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<FormItem label="Default card size" name="extraNetworkCardSize">
|
||||
<InputNumber />
|
||||
</FormItem>
|
||||
<Divider style={{margin: '4px 0 8px'}} />
|
||||
<SubTitle>Other</SubTitle>
|
||||
<FormItem label="Use svg icons" name="svgIcon" valuePropName="checked">
|
||||
<Switch />
|
||||
</FormItem>
|
||||
<Divider style={{margin: '4px 0 16px'}} />
|
||||
<FormItem>
|
||||
<Space>
|
||||
<Button htmlType="button" onClick={onReset} style={{borderRadius: 4}}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button htmlType="submit" style={{borderRadius: 4}} type="primary">
|
||||
Apply and restart UI
|
||||
</Button>
|
||||
</Space>
|
||||
</FormItem>
|
||||
</Form>
|
||||
}
|
||||
title={<Title>⚙ Setting</Title>}
|
||||
trigger="click"
|
||||
>
|
||||
<Button icon={<SettingOutlined />} title="Setting" />
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
export default Setting;
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
import {BoldOutlined, GithubOutlined} from '@ant-design/icons';
|
||||
import {Header as H} from '@lobehub/ui';
|
||||
import {Button, Modal, Space} from 'antd';
|
||||
import qs from 'query-string';
|
||||
import {type ReactNode, memo, useCallback, useState} from 'react';
|
||||
import {shallow} from 'zustand/shallow';
|
||||
|
||||
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';
|
||||
|
||||
interface HeaderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Header = memo<HeaderProps>(({children}) => {
|
||||
const [themeMode] = useAppStore((st) => [st.themeMode], shallow);
|
||||
const [isModalOpen, setIsModalOpen] = useState(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 showModal = () => setIsModalOpen(true);
|
||||
|
||||
const handleCancel = () => setIsModalOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<H
|
||||
actions={
|
||||
<Space.Compact>
|
||||
<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"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button icon={<BoldOutlined />} title="Birme" />
|
||||
</a>
|
||||
<Button icon={<GithubOutlined />} onClick={showModal} title="Feedback" />
|
||||
<Setting />
|
||||
<Button icon={themeIcon[themeMode]} onClick={handleSetTheme} title="Switch Theme" />
|
||||
</Space.Compact>
|
||||
}
|
||||
logo={
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Logo themeMode={themeMode} />
|
||||
</a>
|
||||
}
|
||||
nav={
|
||||
<>
|
||||
<Nav />
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Modal
|
||||
footer={false}
|
||||
onCancel={handleCancel}
|
||||
open={isModalOpen}
|
||||
title={
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Space>
|
||||
<GithubOutlined />
|
||||
{'canisminor1990/sd-webui-kitchen-theme'}
|
||||
</Space>
|
||||
</a>
|
||||
}
|
||||
>
|
||||
<Giscus themeMode={themeMode} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Header;
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
export const themeIcon = {
|
||||
dark: (
|
||||
<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>
|
||||
),
|
||||
light: (
|
||||
<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>
|
||||
),
|
||||
};
|
||||
|
||||
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 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,101 +0,0 @@
|
|||
import {memo, useCallback, useState} from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import TagList, {PromptType, TagItem} from './TagList';
|
||||
import {formatPrompt} from './utils';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
******************************************************/
|
||||
|
||||
const View = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const ButtonGroup = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding: var(--input-padding);
|
||||
|
||||
font-size: var(--input-text-size);
|
||||
font-weight: var(--input-text-weight);
|
||||
line-height: var(--line-sm);
|
||||
|
||||
background: var(--button-secondary-background-fill);
|
||||
border: var(--button-border-width) solid var(--button-secondary-border-color);
|
||||
border-radius: var(--input-radius);
|
||||
`;
|
||||
|
||||
/******************************************************
|
||||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
interface PromptProps {
|
||||
type: PromptType;
|
||||
}
|
||||
|
||||
const Prompt = memo<PromptProps>(({type}) => {
|
||||
const [tags, setTags] = useState<TagItem[]>([]);
|
||||
|
||||
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));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setValue = useCallback(() => {
|
||||
try {
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
|
||||
if (textarea) textarea.value = tags.map((t) => t.text).join(', ');
|
||||
updateInput(textarea);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [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);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
|
||||
<ButtonGroup>
|
||||
<Button onClick={getValue} title="Load Prompt">
|
||||
🔄
|
||||
</Button>
|
||||
<Button onClick={setValue} title="Set Prompt">
|
||||
➡️
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default Prompt;
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import {DraggablePanel} from '@lobehub/ui';
|
||||
import {useResponsive} from 'antd-style';
|
||||
import {type CSSProperties, type ReactNode, memo, useEffect, useState} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {shallow} from 'zustand/shallow';
|
||||
|
||||
import {useAppStore} from '@/store';
|
||||
|
||||
import PromptGroup from './PromptGroup';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
******************************************************/
|
||||
|
||||
const SidebarView = styled.div`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: var(--fill-available);
|
||||
padding: 16px;
|
||||
`;
|
||||
|
||||
/******************************************************
|
||||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
interface SidebarProps {
|
||||
children: ReactNode;
|
||||
loading?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const Sidebar = memo<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);
|
||||
|
||||
useEffect(() => {
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<SidebarView>
|
||||
{!loading && <PromptGroup />}
|
||||
{children}
|
||||
</SidebarView>
|
||||
</DraggablePanel>
|
||||
);
|
||||
});
|
||||
|
||||
export default Sidebar;
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
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;
|
||||
if (newTag.text.includes('<lora')) {
|
||||
newTag.className = 'ReactTags__lora';
|
||||
} else if (newTag.text.includes('<hypernet')) {
|
||||
newTag.className = 'ReactTags__hypernet';
|
||||
} else if (newTag.text.includes('<embedding')) {
|
||||
newTag.className = 'ReactTags__embedding';
|
||||
} else {
|
||||
newTag.className = undefined;
|
||||
}
|
||||
return newTag;
|
||||
};
|
||||
|
||||
export const formatPrompt = (value: string) => {
|
||||
const text = Converter.convertStr(value);
|
||||
const textArray = Converter.convertStr2Array(text).map((item) => {
|
||||
if (item.includes('<')) return item;
|
||||
const newItem = item
|
||||
.replaceAll(/\s+/g, ' ')
|
||||
.replaceAll(/,|\.\|。/g, ',')
|
||||
.replaceAll(/“|‘|”|"|\/'/g, '')
|
||||
.replaceAll(', ', ',')
|
||||
.replaceAll(',,', ',')
|
||||
.replaceAll(',', ', ');
|
||||
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 = {
|
||||
negative: genSuggestions(negativeData),
|
||||
positive: genSuggestions(positiveData),
|
||||
};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export { default as Content } from './Content';
|
||||
export { default as DraggablePanel } from './DraggablePanel';
|
||||
export { default as ExtraNetworkSidebar } from './ExtraNetworkSidebar';
|
||||
export { default as Header } from './Header';
|
||||
export { default as Sidebar } from './Sidebar';
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import {useEffect, useState} from 'react';
|
||||
|
||||
function checkIsDarkMode() {
|
||||
try {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function useIsDarkMode() {
|
||||
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
|
||||
|
||||
useEffect(() => {
|
||||
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const listener = (event: any) => {
|
||||
setIsDarkMode(event.matches);
|
||||
};
|
||||
|
||||
mqList.addEventListener('change', listener);
|
||||
|
||||
return () => {
|
||||
mqList.removeEventListener('change', listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isDarkMode;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
function checkIsDarkMode() {
|
||||
try {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function useIsDarkMode() {
|
||||
const [isDarkMode, setIsDarkMode] = useState(checkIsDarkMode());
|
||||
|
||||
useEffect(() => {
|
||||
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
const listener = (event: any) => {
|
||||
setIsDarkMode(event.matches);
|
||||
};
|
||||
|
||||
mqList.addEventListener('change', listener);
|
||||
|
||||
return () => {
|
||||
mqList.removeEventListener('change', listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isDarkMode;
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
import { createGlobalStyle, css } from 'antd-style';
|
||||
import { readableColor } from 'polished';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
${({ theme }) => {
|
||||
const checkBoxIcon = `data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='${encodeURIComponent(
|
||||
theme.colorBgLayout,
|
||||
)}' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e`;
|
||||
const radioIcon = `data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='${encodeURIComponent(
|
||||
theme.colorBgLayout,
|
||||
)}' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e`;
|
||||
return css`
|
||||
:root,
|
||||
.dark {
|
||||
--primary-50: ${theme.geekblue1};
|
||||
--primary-100: ${theme.geekblue2};
|
||||
--primary-200: ${theme.geekblue3};
|
||||
--primary-300: ${theme.geekblue4};
|
||||
--primary-400: ${theme.geekblue5};
|
||||
--primary-500: ${theme.geekblue6};
|
||||
--primary-600: ${theme.geekblue7};
|
||||
--primary-700: ${theme.geekblue8};
|
||||
--primary-800: ${theme.geekblue9};
|
||||
--primary-900: ${theme.geekblue10};
|
||||
--primary-950: ${theme.geekblue11};
|
||||
--secondary-50: ${theme.geekblue1};
|
||||
--secondary-100: ${theme.geekblue2};
|
||||
--secondary-200: ${theme.geekblue3};
|
||||
--secondary-300: ${theme.geekblue4};
|
||||
--secondary-400: ${theme.geekblue5};
|
||||
--secondary-500: ${theme.geekblue6};
|
||||
--secondary-600: ${theme.geekblue7};
|
||||
--secondary-700: ${theme.geekblue8};
|
||||
--secondary-800: ${theme.geekblue9};
|
||||
--secondary-900: ${theme.geekblue10};
|
||||
--secondary-950: ${theme.geekblue11};
|
||||
--neutral-50: ${theme.colorText};
|
||||
--neutral-100: ${theme.colorText};
|
||||
--neutral-200: ${theme.colorTextSecondary};
|
||||
--neutral-300: ${theme.colorTextTertiary};
|
||||
--neutral-400: ${theme.colorTextQuaternary};
|
||||
--neutral-500: ${theme.colorFill};
|
||||
--neutral-600: ${theme.colorFillSecondary};
|
||||
--neutral-700: ${theme.colorFillTertiary};
|
||||
--neutral-800: ${theme.colorFillQuaternary};
|
||||
--neutral-900: ${theme.colorBgElevated};
|
||||
--neutral-950: ${theme.colorBgContainer};
|
||||
--spacing-xxs: ${theme.paddingXXS / 4}px;
|
||||
--spacing-xs: ${theme.paddingXS / 4}px;
|
||||
--spacing-sm: ${theme.paddingSM / 4}px;
|
||||
--spacing-md: ${theme.paddingMD / 4}px;
|
||||
--spacing-lg: ${theme.paddingLG / 4}px;
|
||||
--spacing-xl: ${theme.paddingXL / 4}px;
|
||||
--spacing-xxl: ${theme.paddingXL / 4}px;
|
||||
--radius-xxs: ${theme.borderRadiusXS}px;
|
||||
--radius-xs: ${theme.borderRadiusXS}px;
|
||||
--radius-sm: ${theme.borderRadiusSM}px;
|
||||
--radius-md: ${theme.borderRadius}px;
|
||||
--radius-lg: ${theme.borderRadius}px;
|
||||
--radius-xl: ${theme.borderRadiusLG}px;
|
||||
--radius-xxl: ${theme.borderRadiusLG}px;
|
||||
--text-xxs: ${theme.fontSizeSM}px;
|
||||
--text-xs: ${theme.fontSizeSM}px;
|
||||
--text-sm: ${theme.fontSize}px;
|
||||
--text-md: ${theme.fontSize}px;
|
||||
--text-lg: ${theme.fontSizeLG}px;
|
||||
--text-xl: ${theme.fontSizeXL}px;
|
||||
--text-xxl: ${theme.fontSizeXL}px;
|
||||
--font: ${theme.fontFamily};
|
||||
--font-mono: ${theme.fontFamilyCode};
|
||||
--body-background-fill: ${theme.colorBgLayout};
|
||||
--body-text-color: ${theme.colorText};
|
||||
--body-text-size: ${theme.fontSize}px;
|
||||
--body-text-weight: 400;
|
||||
--embed-radius: var(--radius-lg);
|
||||
--color-accent: ${theme.colorPrimary};
|
||||
--color-accent-soft: ${theme.colorPrimaryHover};
|
||||
--background-fill-primary: ${theme.colorBgLayout};
|
||||
--background-fill-secondary: var(--neutral-50);
|
||||
--border-color-accent: ${theme.colorBorder};
|
||||
--border-color-primary: ${theme.colorBorderSecondary};
|
||||
--link-text-color: ${theme.colorInfoText};
|
||||
--link-text-color-active: ${theme.colorInfoTextActive};
|
||||
--link-text-color-hover: ${theme.colorInfoTextHover};
|
||||
--link-text-color-visited: ${theme.colorInfoText};
|
||||
--body-text-color-subdued: ${theme.colorTextDescription};
|
||||
--shadow-drop: ${theme.boxShadowSecondary};
|
||||
--shadow-drop-lg: ${theme.boxShadow};
|
||||
--shadow-inset: ${theme.boxShadowSecondary} inset;
|
||||
--shadow-spread: 3px;
|
||||
--block-background-fill: ${theme.colorBgContainer};
|
||||
--block-border-color: ${theme.colorBorderSecondary};
|
||||
--block-border-width: 1px;
|
||||
--block-info-text-color: ${theme.colorTextSecondary};
|
||||
--block-info-text-size: var(--text-sm);
|
||||
--block-info-text-weight: 400;
|
||||
--block-label-background-fill: ${theme.colorFillSecondary};
|
||||
--block-label-border-color: ${theme.colorBorderSecondary};
|
||||
--block-label-border-width: 1px;
|
||||
--block-label-shadow: ${theme.boxShadowTertiary};
|
||||
--block-label-text-color: ${theme.colorText}
|
||||
--block-label-margin: 0;
|
||||
--block-label-padding: var(--spacing-sm) var(--spacing-lg);
|
||||
--block-label-radius: ${theme.borderRadius}px;
|
||||
--block-label-right-radius: ${theme.borderRadius}px;
|
||||
--block-label-text-size: var(--text-sm);
|
||||
--block-label-text-weight: 400;
|
||||
--block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
|
||||
--block-radius: var(--radius-lg);
|
||||
--block-shadow: ${theme.boxShadowSecondary};
|
||||
--block-title-background-fill: none;
|
||||
--block-title-border-color: none;
|
||||
--block-title-border-width: 0;
|
||||
--block-title-text-color: ${theme.colorTextDescription};
|
||||
--block-title-padding: 0;
|
||||
--block-title-radius: none;
|
||||
--block-title-text-size: var(--text-md);
|
||||
--block-title-text-weight: 400;
|
||||
--container-radius: var(--radius-lg);
|
||||
--form-gap-width: 1px;
|
||||
--layout-gap: var(--spacing-xxl);
|
||||
--panel-background-fill: ${theme.colorBgContainer};
|
||||
--panel-border-color: ${theme.colorBorderSecondary};
|
||||
--panel-border-width: 0;
|
||||
--section-header-text-size: var(--text-md);
|
||||
--section-header-text-weight: 400;
|
||||
--chatbot-code-background-color: ${theme.colorBgContainer};
|
||||
--checkbox-background-color: ${theme.colorFillTertiary};
|
||||
--checkbox-background-color-focus: ${theme.colorFillSecondary};
|
||||
--checkbox-background-color-hover: ${theme.colorFillSecondary};
|
||||
--checkbox-background-color-selected: ${theme.colorText};
|
||||
--checkbox-border-color: ${theme.colorBorderSecondary};
|
||||
--checkbox-border-color-focus: ${theme.colorBorder};
|
||||
--checkbox-border-color-hover: ${theme.colorBorder};
|
||||
--checkbox-border-color-selected: ${theme.colorText};
|
||||
--checkbox-border-radius: ${theme.borderRadiusXS}px;
|
||||
--checkbox-border-width: 1px;
|
||||
--checkbox-label-background-fill: linear-gradient(
|
||||
to top,
|
||||
${theme.colorFillTertiary},
|
||||
${theme.colorBgLayout}
|
||||
);
|
||||
--checkbox-label-background-fill-hover: linear-gradient(
|
||||
to top,
|
||||
${theme.colorFillSecondary},
|
||||
${theme.colorBgLayout}
|
||||
);
|
||||
--checkbox-label-background-fill-selected: ${theme.colorFillSecondary};
|
||||
--checkbox-label-border-color: ${theme.colorBorderSecondary};
|
||||
--checkbox-label-border-color-hover: ${theme.colorBorder};
|
||||
--checkbox-label-border-width: 1px;
|
||||
--checkbox-label-gap: var(--spacing-lg);
|
||||
--checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
|
||||
--checkbox-label-shadow: none;
|
||||
--checkbox-label-text-size: var(--text-md);
|
||||
--checkbox-label-text-weight: 400;
|
||||
--checkbox-check: url(${checkBoxIcon});
|
||||
--radio-circle: url(${radioIcon});
|
||||
--checkbox-shadow: none;
|
||||
--checkbox-label-text-color: ${theme.colorTextDescription};
|
||||
--checkbox-label-text-color-selected: ${theme.colorText};
|
||||
--error-background-fill: linear-gradient(
|
||||
to right,
|
||||
${theme.colorErrorBg},
|
||||
${theme.colorFillSecondary}
|
||||
);
|
||||
--error-border-color: ${theme.colorErrorBorder};
|
||||
--error-border-width: 1px;
|
||||
--error-text-color: ${theme.colorErrorText};
|
||||
--input-background-fill: ${theme.colorFillTertiary};
|
||||
--input-background-fill-focus: ${theme.colorFillSecondary};
|
||||
--input-background-fill-hover: ${theme.colorFillSecondary};
|
||||
--input-border-color: ${theme.colorBorderSecondary};
|
||||
--input-border-color-focus: ${theme.colorBorder};
|
||||
--input-border-color-hover: ${theme.colorBorder};
|
||||
--input-border-width: 1px;
|
||||
--input-padding: var(--spacing-xl);
|
||||
--input-placeholder-color: ${theme.colorTextQuaternary};
|
||||
--input-radius: var(--radius-lg);
|
||||
--input-shadow: none;
|
||||
--input-shadow-focus: none;
|
||||
--input-text-size: var(--text-md);
|
||||
--input-text-weight: 400;
|
||||
--loader-color: ${theme.colorFillSecondary};
|
||||
--prose-text-size: var(--text-md);
|
||||
--prose-text-weight: 400;
|
||||
--prose-header-text-weight: 600;
|
||||
--slider-color: ${theme.colorPrimary};
|
||||
--stat-background-fill: linear-gradient(to right,
|
||||
${theme.colorInfo},
|
||||
${theme.colorInfoHover});
|
||||
--table-border-color: ${theme.colorBorderSecondary};
|
||||
--table-even-background-fill: transparent;
|
||||
--table-odd-background-fill: ${theme.colorFillTertiary};
|
||||
--table-radius: var(--radius-lg);
|
||||
--table-row-focus: ${theme.colorFillSecondary};
|
||||
--button-border-width: 1px;
|
||||
--button-cancel-background-fill: ${theme.colorError};
|
||||
--button-cancel-background-fill-hover: ${theme.colorErrorHover};
|
||||
--button-cancel-border-color: ${theme.colorErrorBorder};
|
||||
--button-cancel-border-color-hover: ${theme.colorErrorBorderHover};
|
||||
--button-cancel-text-color: ${readableColor(theme.colorError)};
|
||||
--button-cancel-text-color-hover: ${readableColor(theme.colorError)};
|
||||
--button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
|
||||
--button-large-radius: var(--radius-lg);
|
||||
--button-large-text-size: var(--text-lg);
|
||||
--button-large-text-weight: 600;
|
||||
--button-primary-background-fill: ${theme.colorPrimary};
|
||||
--button-primary-background-fill-hover: ${theme.colorPrimaryHover};
|
||||
--button-primary-border-color: ${theme.colorPrimaryBorder};
|
||||
--button-primary-border-color-hover: ${theme.colorPrimaryBorderHover};
|
||||
--button-primary-text-color: ${readableColor(theme.colorPrimary)};
|
||||
--button-primary-text-color-hover: ${readableColor(theme.colorPrimary)};
|
||||
--button-secondary-background-fill: ${theme.colorFillSecondary};
|
||||
--button-secondary-background-fill-hover: ${theme.colorFill};;
|
||||
--button-secondary-border-color: ${theme.colorBorderSecondary};
|
||||
--button-secondary-border-color-hover: ${theme.colorBorder};
|
||||
--button-secondary-text-color: ${theme.colorTextSecondary};
|
||||
--button-secondary-text-color-hover: ${theme.colorText};
|
||||
--button-shadow: none;
|
||||
--button-shadow-active: none;
|
||||
--button-shadow-hover: none;
|
||||
--button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
|
||||
--button-small-radius: var(--radius-lg);
|
||||
--button-small-text-size: var(--text-md);
|
||||
--button-small-text-weight: 400;
|
||||
--button-transition: none;
|
||||
}
|
||||
|
||||
.ant-tooltip-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
min-height: unset;
|
||||
padding: 4px 8px;
|
||||
|
||||
color: ${theme.colorBgLayout};
|
||||
|
||||
background-color: ${theme.colorText} !important;
|
||||
border-radius: ${theme.borderRadiusSM}px;
|
||||
}
|
||||
|
||||
.ant-tooltip-arrow {
|
||||
&::before,
|
||||
&::after {
|
||||
background: ${theme.colorText} !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: ${theme.colorFillSecondary};
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colorFill};
|
||||
}
|
||||
}
|
||||
|
||||
input[type="range"]{
|
||||
cursor: pointer;
|
||||
|
||||
height: 3px;
|
||||
margin-top: 8px;
|
||||
|
||||
appearance: none;
|
||||
background: ${theme.colorBorder};
|
||||
border-radius: ${theme.borderRadiusXS}px;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
width: 12px;
|
||||
height:16px;
|
||||
|
||||
appearance: none;
|
||||
background: ${theme.colorBgElevated};
|
||||
border: 1px solid ${theme.colorBorder};
|
||||
border-radius: ${theme.borderRadiusSM}px;
|
||||
|
||||
&:hover,&:active {
|
||||
background: ${theme.colorText};
|
||||
border-color: ${theme.colorText};
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colorTextDescription};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${theme.colorPrimary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
}}
|
||||
`;
|
||||
|
||||
export default GlobalStyle;
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { DivProps, ThemeProvider } from '@lobehub/ui';
|
||||
import qs from 'query-string';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useIsDarkMode } from '@/hooks/useIsDarkMode';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import GlobalStyle from './GlobalStyle';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const Layout = memo<DivProps>(({ children }) => {
|
||||
const { onSetThemeMode, onInit } = useAppStore(
|
||||
(st) => ({ onInit: st.onInit, onSetThemeMode: st.onSetThemeMode }),
|
||||
shallow,
|
||||
);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [appearance, setAppearance] = useState<'light' | 'dark'>('light');
|
||||
const [first, setFirst] = useState(true);
|
||||
|
||||
const { styles } = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
onInit();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
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(isDarkMode ? 'dark' : 'light');
|
||||
document.body.classList.add(isDarkMode ? 'dark' : 'light');
|
||||
onSetThemeMode(isDarkMode ? 'dark' : 'light');
|
||||
}, [isDarkMode]);
|
||||
useEffect(() => {
|
||||
if (first) {
|
||||
setFirst(false);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
}, [isDarkMode]);
|
||||
return (
|
||||
<ThemeProvider themeMode={appearance}>
|
||||
<GlobalStyle />
|
||||
<div className={styles}>{children}</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
});
|
||||
|
||||
export default Layout;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => {
|
||||
console.log(token.colorText);
|
||||
return css``;
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import App from './App';
|
||||
|
||||
if (window.global === undefined) window.global = window;
|
||||
|
||||
if (window.location.href.includes('dev') && import.meta.env.VITE_CONTEXT !== 'DEV') {
|
||||
console.log('🚧 Theme Loader in Dev Mode');
|
||||
} else {
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
const root = document.createElement('div');
|
||||
root.setAttribute('id', 'root');
|
||||
try {
|
||||
gradioApp()?.append(root);
|
||||
} catch {
|
||||
document.querySelector('gradio-app')?.append(root);
|
||||
}
|
||||
const client = createRoot(root);
|
||||
client.render(<App />);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { useResponsive } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
|
||||
import dragablePanel from '@/script/draggablePanel';
|
||||
import { useAppStore } from '@/store';
|
||||
import { DivProps } from '@/types';
|
||||
|
||||
import { useStyles } from './style';
|
||||
|
||||
const Content = memo<DivProps>(({ className, ...props }) => {
|
||||
const mainReference: any = useRef<HTMLElement>();
|
||||
const setting = useAppStore((st) => st.setting, isEqual);
|
||||
const { cx, styles } = useStyles({ isPromptResizable: setting.promotTextarea === 'resizable' });
|
||||
const { mobile } = useResponsive();
|
||||
|
||||
useEffect(() => {
|
||||
// Content
|
||||
const main = gradioApp().querySelector('.app');
|
||||
if (main) mainReference.current?.append(main);
|
||||
if (!mobile) dragablePanel();
|
||||
}, []);
|
||||
|
||||
return <div className={cx(styles.container, className)} ref={mainReference} {...props} />;
|
||||
});
|
||||
|
||||
export default Content;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(
|
||||
({ css }, { isPromptResizable }: { isPromptResizable: boolean }) => ({
|
||||
container: css`
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
|
||||
[id$='2img_prompt'] textarea {
|
||||
max-height: ${isPromptResizable ? 'unset' : '84px'};
|
||||
}
|
||||
|
||||
[id$='2img_neg_prompt'] textarea {
|
||||
max-height: ${isPromptResizable ? 'unset' : '84px'};
|
||||
}
|
||||
`,
|
||||
}),
|
||||
);
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { Icon } from '@lobehub/ui';
|
||||
import { Skeleton, Slider } from 'antd';
|
||||
import { ZoomIn } from 'lucide-react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useStyles } from '@/pages/ExtraNetworkSidebar/style';
|
||||
import civitaiHelperFix from '@/script/civitaiHelperFix';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const Inner = memo(() => {
|
||||
const txt2imgExtraNetworkSidebarReference: any = useRef<HTMLElement>();
|
||||
const img2imgExtraNetworkSidebarReference: any = useRef<HTMLElement>();
|
||||
const [extraLoading, setExtraLoading] = useState(true);
|
||||
const [setting, currentTab] = useAppStore((st) => [st.setting, st.currentTab], shallow);
|
||||
const [size, setSize] = useState<number>(setting?.extraNetworkCardSize || 86);
|
||||
const { styles } = useStyles({ size });
|
||||
useEffect(() => {
|
||||
if (setting.enableExtraNetworkSidebar) {
|
||||
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks');
|
||||
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks');
|
||||
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
|
||||
txt2imgExtraNetworkSidebarReference.current?.append(txt2imgExtraNetworks);
|
||||
img2imgExtraNetworkSidebarReference.current?.append(img2imgExtraNetworks);
|
||||
}
|
||||
if (document.querySelector('#txt2img_lora_cards')) {
|
||||
civitaiHelperFix();
|
||||
setExtraLoading(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const t2indexButton: any = document.querySelector('#txt2img_extra_refresh');
|
||||
const index2indexButton: any = document.querySelector('#img2img_extra_refresh');
|
||||
t2indexButton.click();
|
||||
index2indexButton.click();
|
||||
setExtraLoading(false);
|
||||
try {
|
||||
const civitaiButton = document.querySelectorAll(
|
||||
'button[title="Refresh Civitai Helper\'s additional buttons"]',
|
||||
) as NodeListOf<HTMLButtonElement>;
|
||||
if (civitaiButton) {
|
||||
for (const button of civitaiButton) {
|
||||
button.onclick = civitaiHelperFix;
|
||||
}
|
||||
}
|
||||
civitaiHelperFix();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.list}>
|
||||
{extraLoading && <Skeleton active />}
|
||||
<div style={extraLoading ? { display: 'none' } : {}}>
|
||||
<div
|
||||
id="txt2img-extra-netwrok-sidebar"
|
||||
ref={txt2imgExtraNetworkSidebarReference}
|
||||
style={currentTab === 'tab_img2img' ? { display: 'none' } : {}}
|
||||
/>
|
||||
<div
|
||||
id="img2img-extra-netwrok-sidebar"
|
||||
ref={img2imgExtraNetworkSidebarReference}
|
||||
style={currentTab === 'tab_img2img' ? {} : { display: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<Icon icon={ZoomIn} />
|
||||
<Slider
|
||||
defaultValue={size}
|
||||
max={256}
|
||||
min={64}
|
||||
onChange={setSize}
|
||||
step={8}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Inner;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { DraggablePanel, LayoutSidebarInner } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
||||
import { useAppStore } from '@/store';
|
||||
import { DivProps } from '@/types/index';
|
||||
|
||||
import Inner from './Inner';
|
||||
import { useStyles } from './style';
|
||||
|
||||
export interface ExtraNetworkSidebarProps extends DivProps {
|
||||
headerHeight: number;
|
||||
}
|
||||
|
||||
const ExtraNetworkSidebar = memo<ExtraNetworkSidebarProps>(({ headerHeight }) => {
|
||||
const setting = useAppStore((st) => st.setting, isEqual);
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.extraNetworkFixedMode);
|
||||
const [expand, setExpand] = useState<boolean>(setting.extraNetworkSidebarExpand);
|
||||
const { mobile } = useResponsive();
|
||||
const { styles } = useStyles({ headerHeight });
|
||||
|
||||
useEffect(() => {
|
||||
if (mobile) setExpand(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DraggablePanel
|
||||
defaultSize={{ width: setting.extraNetworkSidebarWidth }}
|
||||
expand={expand}
|
||||
minWidth={setting.extraNetworkSidebarWidth}
|
||||
mode={mode}
|
||||
onExpandChange={setExpand}
|
||||
pin={mode === 'fixed'}
|
||||
placement="right"
|
||||
>
|
||||
<LayoutSidebarInner>
|
||||
<div className={styles.container}>
|
||||
<Inner />
|
||||
</div>
|
||||
</LayoutSidebarInner>
|
||||
</DraggablePanel>
|
||||
);
|
||||
});
|
||||
|
||||
export default ExtraNetworkSidebar;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
import { rgba } from 'polished';
|
||||
|
||||
export const useStyles = createStyles(
|
||||
(
|
||||
{ css, cx, stylish, token },
|
||||
{ headerHeight = 64, size = 86 }: { headerHeight?: number; size?: number },
|
||||
) => ({
|
||||
container: cx(
|
||||
stylish.blur,
|
||||
css`
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: calc(100vh - ${headerHeight}px);
|
||||
|
||||
background: ${rgba(token.colorBgLayout, 0.5)};
|
||||
|
||||
button#txt2img_extra_networks,
|
||||
button#img2img_extra_networks {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
),
|
||||
footer: css`
|
||||
display: flex;
|
||||
flex: 0;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
border-top: 1px solid ${token.colorBorderSecondary};
|
||||
`,
|
||||
list: css`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
|
||||
#txt2img_extra_networks,
|
||||
#img2img_extra_networks {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.extra-network-cards .actions .name {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.extra-network-cards,
|
||||
.extra-network-thumbs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(${size}px, 1fr));
|
||||
|
||||
> .card {
|
||||
width: var(--fill-available) !important;
|
||||
height: ${size * 1.5}px !important;
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
);
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { Icon } from '@lobehub/ui';
|
||||
import { FooterProps as FProps } from '@lobehub/ui/es/Footer';
|
||||
import { Bug, FileClock, GitFork, Github } from 'lucide-react';
|
||||
|
||||
import { homepage } from '@/../package.json';
|
||||
|
||||
export const columns: FProps['columns'] = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
description: 'AIGC Components',
|
||||
openExternal: true,
|
||||
title: '🤯 Lobe UI',
|
||||
url: 'https://github.com/lobehub/lobe-ui',
|
||||
},
|
||||
{
|
||||
description: 'Chatbot Client',
|
||||
openExternal: true,
|
||||
title: '🤯 Lobe Chat',
|
||||
url: 'https://github.com/lobehub/lobe-chat',
|
||||
},
|
||||
{
|
||||
description: 'Node Flow Editor',
|
||||
openExternal: true,
|
||||
title: '🤯 Lobe Flow',
|
||||
url: 'https://github.com/lobehub/lobe-flow',
|
||||
},
|
||||
],
|
||||
title: 'Resources',
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: <Icon icon={Bug} size="small" />,
|
||||
openExternal: true,
|
||||
title: 'Report Bug',
|
||||
url: `${homepage}/issues/new/choose`,
|
||||
},
|
||||
{
|
||||
icon: <Icon icon={GitFork} size="small" />,
|
||||
openExternal: true,
|
||||
title: 'Request Feature',
|
||||
url: `${homepage}/issues/new/choose`,
|
||||
},
|
||||
],
|
||||
title: 'Community',
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
icon: <Icon icon={Github} size="small" />,
|
||||
openExternal: true,
|
||||
title: 'GitHub',
|
||||
url: homepage,
|
||||
},
|
||||
{
|
||||
icon: <Icon icon={FileClock} size="small" />,
|
||||
openExternal: true,
|
||||
title: 'Changelog',
|
||||
url: `${homepage}/blob/main/CHANGELOG.md`,
|
||||
},
|
||||
],
|
||||
title: 'Help',
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
description: 'AI Commit CLI',
|
||||
openExternal: true,
|
||||
title: '💌 Lobe Commit',
|
||||
url: 'https://github.com/lobehub/lobe-commit',
|
||||
},
|
||||
{
|
||||
description: 'Lint Config',
|
||||
openExternal: true,
|
||||
title: '📐 Lobe Lint',
|
||||
url: 'https://github.com/lobehub/lobe-lint',
|
||||
},
|
||||
],
|
||||
title: 'More Products',
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { Footer as F } from '@lobehub/ui';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
|
||||
import { DivProps } from '@/types/index';
|
||||
|
||||
import { columns } from './data';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const Footer = memo<DivProps>(({ className, ...props }) => {
|
||||
const { cx, styles } = useStyles();
|
||||
const footerReference: any = useRef<HTMLElement>();
|
||||
|
||||
useEffect(() => {
|
||||
const footer = gradioApp().querySelector('#footer');
|
||||
if (footer) footerReference.current?.append(footer);
|
||||
}, []);
|
||||
return (
|
||||
<div className={cx(styles.footer, className)} {...props}>
|
||||
<F bottom={<div ref={footerReference} />} columns={columns} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Footer;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => ({
|
||||
footer: css`
|
||||
footer {
|
||||
display: block !important;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Modal, Popover, Space } from 'antd';
|
||||
import { Bold, Github, LucideIcon, Moon, Settings2, Sun } from 'lucide-react';
|
||||
import qs from 'query-string';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import Giscus from '@/slots/Giscus';
|
||||
import Setting from '@/slots/Setting';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
const CivitaiLogo: LucideIcon = ({ size }) => (
|
||||
<svg fill="currentColor" height={size} viewBox="0 0 16 16" width={size}>
|
||||
<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>
|
||||
);
|
||||
|
||||
interface ActionsProps {
|
||||
themeMode: 'dark' | 'light';
|
||||
}
|
||||
|
||||
const Actions = memo<ActionsProps>(() => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const themeMode = useAppStore((st) => st.themeMode, shallow);
|
||||
|
||||
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 showModal = () => setIsModalOpen(true);
|
||||
|
||||
const handleCancel = () => setIsModalOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space.Compact>
|
||||
<a href="https://civitai.com/" rel="noreferrer" target="_blank">
|
||||
<ActionIcon icon={CivitaiLogo} title="Civitai" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.birme.net/?target_width=512&target_height=512"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ActionIcon icon={Bold} title="Birme" />
|
||||
</a>
|
||||
<ActionIcon icon={Github} onClick={showModal} title="Feedback" />
|
||||
<Popover
|
||||
content={<Setting />}
|
||||
title={<h3 style={{ lineHeight: 2, margin: 0 }}>⚙ Setting</h3>}
|
||||
trigger="click"
|
||||
>
|
||||
<ActionIcon icon={Settings2} title="Setting" />
|
||||
</Popover>
|
||||
<ActionIcon
|
||||
icon={themeMode === 'light' ? Sun : Moon}
|
||||
onClick={handleSetTheme}
|
||||
title="Switch Theme"
|
||||
/>
|
||||
</Space.Compact>
|
||||
<Modal
|
||||
footer={false}
|
||||
onCancel={handleCancel}
|
||||
open={isModalOpen}
|
||||
title={
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Space>
|
||||
<GithubOutlined />
|
||||
{'canisminor1990/sd-webui-kitchen-theme'}
|
||||
</Space>
|
||||
</a>
|
||||
}
|
||||
>
|
||||
<Giscus themeMode={themeMode} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Actions;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { TabsNav, type TabsNavProps } from '@lobehub/ui';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
||||
const hideOriganlNav = () => {
|
||||
(gradioApp().querySelector('#tabs > .tab-nav:first-child') as HTMLDivElement).style.display =
|
||||
'none';
|
||||
};
|
||||
|
||||
const getNavButtons = (): HTMLButtonElement[] =>
|
||||
Array.prototype.slice.call(
|
||||
gradioApp().querySelectorAll(
|
||||
'#tabs > .tab-nav:first-child button',
|
||||
) as NodeListOf<HTMLButtonElement>,
|
||||
);
|
||||
|
||||
const onChange: TabsNavProps['onChange'] = (activeKey) => {
|
||||
const buttons = getNavButtons();
|
||||
buttons[Number(activeKey)]?.click();
|
||||
};
|
||||
|
||||
const Nav = memo(() => {
|
||||
const [items, setItems] = useState<TabsNavProps['items']>([]);
|
||||
|
||||
useEffect(() => {
|
||||
hideOriganlNav();
|
||||
const buttons = getNavButtons();
|
||||
const list: TabsNavProps['items'] = buttons.map(
|
||||
(button: HTMLButtonElement | any, index: number) => {
|
||||
button.id = `kitchen-nav-${index}`;
|
||||
return {
|
||||
key: String(index),
|
||||
label: button.textContent,
|
||||
};
|
||||
},
|
||||
);
|
||||
setItems(list.filter(Boolean));
|
||||
}, []);
|
||||
|
||||
return <TabsNav items={items} onChange={onChange} />;
|
||||
});
|
||||
|
||||
export default Nav;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { Header as H } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useAppStore } from '@/store';
|
||||
import { DivProps } from '@/types/index';
|
||||
|
||||
import Actions from './Actions';
|
||||
import Logo from './Logo';
|
||||
import Nav from './Nav';
|
||||
|
||||
const Header = memo<DivProps>(({ children }) => {
|
||||
const themeMode = useAppStore((st) => st.themeMode, shallow);
|
||||
|
||||
return (
|
||||
<H
|
||||
actions={<Actions themeMode={themeMode} />}
|
||||
logo={
|
||||
<a
|
||||
href="https://github.com/canisminor1990/sd-webui-kitchen-theme"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Logo themeMode={themeMode} />
|
||||
</a>
|
||||
}
|
||||
nav={
|
||||
<>
|
||||
<Nav />
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Header;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
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';
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { DivProps } from '@lobehub/ui';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
|
||||
import PromptGroup from '@/slots/PromptEditor';
|
||||
|
||||
const Inner = memo<DivProps>(() => {
|
||||
const sidebarReference: any = useRef<HTMLElement>();
|
||||
|
||||
useEffect(() => {
|
||||
const sidebar = gradioApp().querySelector('#quicksettings');
|
||||
if (sidebar) sidebarReference.current?.append(sidebar);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PromptGroup />
|
||||
<div ref={sidebarReference} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Inner;
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { DivProps, DraggablePanel, LayoutSidebarInner } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import Inner from './Inner';
|
||||
import { useStyles } from './style';
|
||||
|
||||
export interface QuickSettingSidebarProps extends DivProps {
|
||||
headerHeight: number;
|
||||
}
|
||||
|
||||
const QuickSettingSidebar = memo<QuickSettingSidebarProps>(({ headerHeight }) => {
|
||||
const setting = useAppStore((st) => st.setting, isEqual);
|
||||
const [mode] = useState<'fixed' | 'float'>(setting.sidebarFixedMode);
|
||||
const [expand, setExpand] = useState<boolean>(setting.sidebarExpand);
|
||||
const { mobile } = useResponsive();
|
||||
const { styles } = useStyles({ headerHeight });
|
||||
|
||||
useEffect(() => {
|
||||
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',
|
||||
}}
|
||||
>
|
||||
<LayoutSidebarInner>
|
||||
<div className={styles.container}>
|
||||
<Inner />
|
||||
</div>
|
||||
</LayoutSidebarInner>
|
||||
</DraggablePanel>
|
||||
);
|
||||
});
|
||||
|
||||
export default QuickSettingSidebar;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
import { rgba } from 'polished';
|
||||
|
||||
export const useStyles = createStyles(
|
||||
({ css, cx, stylish, token }, { headerHeight = 64 }: { headerHeight?: number }) => ({
|
||||
container: cx(
|
||||
stylish.blur,
|
||||
css`
|
||||
position: relative;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: calc(100vh - ${headerHeight}px);
|
||||
padding: 16px;
|
||||
|
||||
background: ${rgba(token.colorBgLayout, 0.5)};
|
||||
`,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { LayoutHeader, LayoutMain, LayoutSidebar } from '@lobehub/ui';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useEffect } from 'react';
|
||||
|
||||
import replaceIcon from '@/script/replaceIcon';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import Content from './Content';
|
||||
import ExtraNetworkSidebar from './ExtraNetworkSidebar';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header';
|
||||
import QuickSettingSidebar from './QuickSettingSidebar';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const HEADER_HEIGHT = 64;
|
||||
|
||||
const Index = memo(() => {
|
||||
const { styles } = useStyles(HEADER_HEIGHT);
|
||||
const setting = useAppStore((st) => st.setting, isEqual);
|
||||
|
||||
useEffect(() => {
|
||||
if (setting.svgIcon) replaceIcon();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutHeader headerHeight={HEADER_HEIGHT}>
|
||||
<Header />
|
||||
</LayoutHeader>
|
||||
<LayoutMain>
|
||||
<LayoutSidebar className={styles.sidebar} headerHeight={HEADER_HEIGHT}>
|
||||
<QuickSettingSidebar headerHeight={HEADER_HEIGHT} />
|
||||
</LayoutSidebar>
|
||||
<Content />
|
||||
{setting?.enableExtraNetworkSidebar && (
|
||||
<LayoutSidebar className={styles.sidebar} headerHeight={HEADER_HEIGHT}>
|
||||
<ExtraNetworkSidebar headerHeight={HEADER_HEIGHT} />
|
||||
</LayoutSidebar>
|
||||
)}
|
||||
</LayoutMain>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Index;
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import {Spin} from 'antd';
|
||||
import {useResponsive} from 'antd-style';
|
||||
import {memo, 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;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row !important;
|
||||
`;
|
||||
|
||||
const MainView = styled.div`
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`;
|
||||
|
||||
const LoadingBox = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const App = memo(() => {
|
||||
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 sidebarReference: any = useRef<HTMLElement>();
|
||||
const mainReference: any = useRef<HTMLElement>();
|
||||
const txt2imgExtraNetworkSidebarReference: any = useRef<HTMLElement>();
|
||||
const img2imgExtraNetworkSidebarReference: any = useRef<HTMLElement>();
|
||||
useEffect(() => {
|
||||
onUiLoaded(() => {
|
||||
// Content
|
||||
const main = gradioApp().querySelector('.app');
|
||||
if (main) mainReference.current?.append(main);
|
||||
if (!mobile) dragablePanel();
|
||||
|
||||
// Sidebar
|
||||
const sidebar = gradioApp().querySelector('#quicksettings');
|
||||
if (sidebar) sidebarReference.current?.append(sidebar);
|
||||
|
||||
// ExtraNetworkSidebar
|
||||
if (setting.enableExtraNetworkSidebar) {
|
||||
const txt2imgExtraNetworks = gradioApp().querySelector('div#txt2img_extra_networks');
|
||||
const img2imgExtraNetworks = gradioApp().querySelector('div#img2img_extra_networks');
|
||||
if (txt2imgExtraNetworks && img2imgExtraNetworks) {
|
||||
txt2imgExtraNetworkSidebarReference.current?.append(txt2imgExtraNetworks);
|
||||
img2imgExtraNetworkSidebarReference.current?.append(img2imgExtraNetworks);
|
||||
}
|
||||
}
|
||||
|
||||
// Other
|
||||
if (setting.svgIcon) replaceIcon();
|
||||
|
||||
setLoading(false);
|
||||
});
|
||||
onUiUpdate(() => {
|
||||
setCurrentTab();
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && setting.enableExtraNetworkSidebar) {
|
||||
if (document.querySelector('#txt2img_lora_cards')) {
|
||||
civitaiHelperFix();
|
||||
setExtraLoading(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const t2indexButton: any = document.querySelector('#txt2img_extra_refresh');
|
||||
const index2indexButton: any = document.querySelector('#img2img_extra_refresh');
|
||||
t2indexButton.click();
|
||||
index2indexButton.click();
|
||||
setExtraLoading(false);
|
||||
try {
|
||||
const civitaiButton = document.querySelectorAll(
|
||||
'button[title="Refresh Civitai Helper\'s additional buttons"]',
|
||||
);
|
||||
if (civitaiButton) {
|
||||
civitaiButton.forEach((button: any) => (button.onclick = civitaiHelperFix));
|
||||
}
|
||||
civitaiHelperFix();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<MainView>
|
||||
<Header>
|
||||
{loading && (
|
||||
<LoadingBox>
|
||||
<Spin size="small" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
</Header>
|
||||
<View>
|
||||
<Sidebar>
|
||||
{loading && (
|
||||
<LoadingBox>
|
||||
<Spin size="small" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
<div id="sidebar" ref={sidebarReference} style={loading ? {display: 'none'} : {}} />
|
||||
</Sidebar>
|
||||
<Content loading={loading}>
|
||||
{loading && (
|
||||
<LoadingBox>
|
||||
<Spin size="large" tip="Loading" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
<div id="content" ref={mainReference} style={loading ? {display: 'none'} : {}} />
|
||||
</Content>
|
||||
{setting?.enableExtraNetworkSidebar && (
|
||||
<ExtraNetworkSidebar>
|
||||
{extraLoading && (
|
||||
<LoadingBox>
|
||||
<Spin size="small" />
|
||||
</LoadingBox>
|
||||
)}
|
||||
<div style={extraLoading ? {display: 'none'} : {}}>
|
||||
<div
|
||||
id="txt2img-extra-netwrok-sidebar"
|
||||
ref={txt2imgExtraNetworkSidebarReference}
|
||||
style={currentTab === 'tab_img2img' ? {display: 'none'} : {}}
|
||||
/>
|
||||
<div
|
||||
id="img2img-extra-netwrok-sidebar"
|
||||
ref={img2imgExtraNetworkSidebarReference}
|
||||
style={currentTab === 'tab_img2img' ? {} : {display: 'none'}}
|
||||
/>
|
||||
</div>
|
||||
</ExtraNetworkSidebar>
|
||||
)}
|
||||
</View>
|
||||
</MainView>
|
||||
);
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { createGlobalStyle } from 'antd-style';
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
|
||||
html,body {
|
||||
--font-settings: "cv01", "tnum", "kern";
|
||||
--font-variations: "opsz" auto, tabular-nums;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: ${({ theme }) => theme.fontFamily};
|
||||
font-size: ${({ theme }) => theme.fontSize}px;
|
||||
font-feature-settings: var(--font-settings);
|
||||
font-variation-settings: var(--font-variations);
|
||||
line-height: 1;
|
||||
color: ${({ theme }) => theme.colorTextBase};
|
||||
text-size-adjust: none;
|
||||
text-rendering: optimizelegibility;
|
||||
vertical-align: baseline;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 4px;
|
||||
background-color: transparent;
|
||||
|
||||
&-thumb {
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
transition: background-color 500ms ${({ theme }) => theme.motionEaseOut};
|
||||
}
|
||||
|
||||
&-corner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: ${({ theme }) => theme.fontFamilyCode} !important;
|
||||
|
||||
* {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
*:hover, *:focus {
|
||||
::-webkit-scrollbar {
|
||||
&-thumb {
|
||||
background-color: ${({ theme }) => theme.colorFill};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default GlobalStyle;
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
import {ThemeProvider} from '@lobehub/ui';
|
||||
import qs from 'query-string';
|
||||
import {memo, useEffect, useState} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
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 '@/theme/style.less';
|
||||
|
||||
import App from './App';
|
||||
|
||||
const Root = memo(() => {
|
||||
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();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
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(isDarkMode ? 'dark' : 'light');
|
||||
document.body.classList.add(isDarkMode ? 'dark' : 'light');
|
||||
onSetThemeMode(isDarkMode ? 'dark' : 'light');
|
||||
}, [isDarkMode]);
|
||||
useEffect(() => {
|
||||
if (first) {
|
||||
setFirst(false);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
}, [isDarkMode]);
|
||||
return (
|
||||
<ThemeProvider themeMode={appearance}>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
);
|
||||
});
|
||||
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
setupHead();
|
||||
const root = document.createElement('div');
|
||||
root.setAttribute('id', 'root');
|
||||
try {
|
||||
gradioApp()?.append(root);
|
||||
} catch {
|
||||
document.querySelector('gradio-app')?.append(root);
|
||||
}
|
||||
const client = createRoot(root);
|
||||
client.render(<Root />);
|
||||
},
|
||||
{once: true},
|
||||
);
|
||||
|
||||
onUiLoaded(() => {
|
||||
formatPrompt();
|
||||
promptBracketChecker();
|
||||
});
|
||||
|
||||
export default () => false;
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { AliasToken } from 'antd/es/theme/interface';
|
||||
|
||||
const FONT_EMOJI = `"Segoe UI Emoji","Segoe UI Symbol","Apple Color Emoji","Twemoji Mozilla","Noto Color Emoji","Android Emoji"`;
|
||||
const FONT_EN = `"HarmonyOS Sans","Segoe UI","SF Pro Display",-apple-system,BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif`;
|
||||
const FONT_CN = `"HarmonyOS Sans SC","PingFang SC","Hiragino Sans GB","Microsoft Yahei UI","Microsoft Yahei","Source Han Sans CN",sans-serif`;
|
||||
const FONT_CODE = `ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace`;
|
||||
|
||||
export const baseToken: Partial<AliasToken> = {
|
||||
fontFamily: [FONT_EN, FONT_CN, FONT_EMOJI].join(','),
|
||||
fontFamilyCode: [FONT_CODE, FONT_CN, FONT_EMOJI].join(','),
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css }, headerHeight: number) => ({
|
||||
sidebar: css`
|
||||
height: calc(100vh - ${headerHeight}px);
|
||||
`,
|
||||
}));
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { memo, useEffect } from 'react';
|
||||
|
||||
import { useStyles } from './style';
|
||||
|
||||
interface GiscusProps {
|
||||
themeMode: 'light' | 'dark';
|
||||
}
|
||||
const Giscus = memo<GiscusProps>(({ themeMode }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
// giscus
|
||||
const giscus: HTMLScriptElement = document.createElement('script');
|
||||
giscus.src = 'https://giscus.app/client.js';
|
||||
giscus.dataset.repo = 'canisminor1990/sd-webui-kitchen-theme';
|
||||
giscus.dataset.repoId = 'R_kgDOJCPcNg';
|
||||
giscus.dataset.mapping = 'number';
|
||||
giscus.dataset.term = '53';
|
||||
giscus.dataset.reactionsEnabled = '1';
|
||||
giscus.dataset.emitMetadata = '0';
|
||||
giscus.dataset.inputPosition = 'bottom';
|
||||
giscus.dataset.theme = themeMode;
|
||||
giscus.dataset.lang = 'en';
|
||||
giscus.crossOrigin = 'anonymous';
|
||||
giscus.async = true;
|
||||
document.querySelectorAll('head')[0].append(giscus);
|
||||
}, []);
|
||||
|
||||
return <div className={cx('giscus', styles.container)} id="giscus" />;
|
||||
});
|
||||
|
||||
export default Giscus;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => ({
|
||||
container: css`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 60vh;
|
||||
`,
|
||||
}));
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import { memo, useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import TagList, { PromptType, TagItem } from './TagList';
|
||||
import { formatPrompt } from './utils';
|
||||
|
||||
/******************************************************
|
||||
*********************** Style *************************
|
||||
******************************************************/
|
||||
|
||||
const View = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const ButtonGroup = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding: var(--input-padding);
|
||||
|
||||
font-size: var(--input-text-size);
|
||||
font-weight: var(--input-text-weight);
|
||||
line-height: var(--line-sm);
|
||||
|
||||
background: var(--button-secondary-background-fill);
|
||||
border: var(--button-border-width) solid var(--button-secondary-border-color);
|
||||
border-radius: var(--input-radius);
|
||||
`;
|
||||
|
||||
/******************************************************
|
||||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
interface PromptProps {
|
||||
type: PromptType;
|
||||
}
|
||||
|
||||
const Prompt = memo<PromptProps>(({ type }) => {
|
||||
const [tags, setTags] = useState<TagItem[]>([]);
|
||||
|
||||
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));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setValue = useCallback(() => {
|
||||
try {
|
||||
const textarea: HTMLTextAreaElement | any = get_uiCurrentTabContent().querySelector(id);
|
||||
if (textarea) textarea.value = tags.map((t) => t.text).join(', ');
|
||||
updateInput(textarea);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, [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);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TagList setTags={setTags} setValue={setCurrentValue} tags={tags} type={type} />
|
||||
<ButtonGroup>
|
||||
<Button onClick={getValue} title="Load Prompt">
|
||||
🔄
|
||||
</Button>
|
||||
<Button onClick={setValue} title="Set Prompt">
|
||||
➡️
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default Prompt;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import {type FC, memo, useCallback, useMemo} from 'react';
|
||||
import {WithContext, ReactTagsProps as WithContextProps} from 'react-tag-input';
|
||||
import { type FC, memo, useCallback, useMemo } from 'react';
|
||||
import { WithContext, ReactTagsProps as WithContextProps } from 'react-tag-input';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {genTagType, suggestions} from './utils';
|
||||
import { genTagType, suggestions } from './utils';
|
||||
|
||||
export interface TagItem {
|
||||
className?: string;
|
||||
|
|
@ -73,14 +73,14 @@ const View = styled.div<{ type: PromptType }>`
|
|||
font-size: var(--text-sm);
|
||||
font-weight: var(--input-text-weight);
|
||||
line-height: var(--line-sm);
|
||||
color: ${({type}) => (type === 'positive' ? 'var(--green-9)' : 'var(--magenta-9)')};
|
||||
color: ${({ type }) => (type === 'positive' ? 'var(--green-9)' : 'var(--magenta-9)')};
|
||||
|
||||
background: var(--button-secondary-background-fill);
|
||||
border: var(--button-border-width) solid var(--button-secondary-border-color);
|
||||
border-radius: var(--input-radius);
|
||||
|
||||
&:hover {
|
||||
color: ${({type}) => (type === 'positive' ? 'var(--green-10)' : 'var(--magenta-10)')};
|
||||
color: ${({ type }) => (type === 'positive' ? 'var(--green-10)' : 'var(--magenta-10)')};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,8 +155,8 @@ const View = styled.div<{ type: PromptType }>`
|
|||
`;
|
||||
|
||||
const KeyCodes = {
|
||||
comma: 188,
|
||||
enter: 13,
|
||||
comma: 188,
|
||||
enter: 13,
|
||||
};
|
||||
|
||||
const delimiters = [KeyCodes.comma, KeyCodes.enter];
|
||||
|
|
@ -168,65 +168,65 @@ interface TagListProps {
|
|||
type: PromptType;
|
||||
}
|
||||
|
||||
const TagList = memo<TagListProps>(({tags, setTags, type, setValue}) => {
|
||||
const handleDelete = useCallback(
|
||||
(index_: number) => {
|
||||
const newTags = tags.filter((tag, index) => index !== index_);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
const TagList = memo<TagListProps>(({ tags, setTags, type, setValue }) => {
|
||||
const handleDelete = useCallback(
|
||||
(index_: number) => {
|
||||
const newTags = tags.filter((tag, index) => index !== index_);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleAddition = useCallback(
|
||||
(tag: TagItem) => {
|
||||
const newTags = [...tags, genTagType(tag)];
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
const handleAddition = useCallback(
|
||||
(tag: TagItem) => {
|
||||
const newTags = [...tags, genTagType(tag)];
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleDrag = useCallback(
|
||||
(tag: TagItem, currentPos: number, newPos: number) => {
|
||||
const newTags = [...tags];
|
||||
newTags.splice(currentPos, 1);
|
||||
newTags.splice(newPos, 0, genTagType(tag));
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
const handleDrag = useCallback(
|
||||
(tag: TagItem, currentPos: number, newPos: number) => {
|
||||
const newTags = [...tags];
|
||||
newTags.splice(currentPos, 1);
|
||||
newTags.splice(newPos, 0, genTagType(tag));
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
|
||||
const handleTagUpdate = useCallback(
|
||||
(index: number, tag: TagItem) => {
|
||||
const newTags = [...tags];
|
||||
newTags[index] = genTagType(tag);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
const handleTagUpdate = useCallback(
|
||||
(index: number, tag: TagItem) => {
|
||||
const newTags = [...tags];
|
||||
newTags[index] = genTagType(tag);
|
||||
setTags(newTags);
|
||||
setValue(newTags);
|
||||
},
|
||||
[tags],
|
||||
);
|
||||
|
||||
const suggestionData = useMemo(() => suggestions[type], [type]);
|
||||
const suggestionData = useMemo(() => suggestions[type], [type]);
|
||||
|
||||
return (
|
||||
<View type={type}>
|
||||
<ReactTags
|
||||
autocomplete
|
||||
delimiters={delimiters}
|
||||
editable
|
||||
handleAddition={handleAddition}
|
||||
handleDelete={handleDelete}
|
||||
handleDrag={handleDrag}
|
||||
inline
|
||||
inputFieldPosition="bottom"
|
||||
onTagUpdate={handleTagUpdate}
|
||||
suggestions={suggestionData}
|
||||
tags={tags}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View type={type}>
|
||||
<ReactTags
|
||||
autocomplete
|
||||
delimiters={delimiters}
|
||||
editable
|
||||
handleAddition={handleAddition}
|
||||
handleDelete={handleDelete}
|
||||
handleDrag={handleDrag}
|
||||
inline
|
||||
inputFieldPosition="bottom"
|
||||
onTagUpdate={handleTagUpdate}
|
||||
suggestions={suggestionData}
|
||||
tags={tags}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default TagList;
|
||||
|
|
@ -35,7 +35,7 @@ const Desc = styled.div`
|
|||
************************* Dom *************************
|
||||
******************************************************/
|
||||
|
||||
const PromptGroup = memo(() => {
|
||||
const PromptEditor = memo(() => {
|
||||
return (
|
||||
<View>
|
||||
<Desc>Positive</Desc>
|
||||
|
|
@ -46,4 +46,4 @@ const PromptGroup = memo(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default PromptGroup;
|
||||
export default PromptEditor;
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
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;
|
||||
if (newTag.text.includes('<lora')) {
|
||||
newTag.className = 'ReactTags__lora';
|
||||
} else if (newTag.text.includes('<hypernet')) {
|
||||
newTag.className = 'ReactTags__hypernet';
|
||||
} else if (newTag.text.includes('<embedding')) {
|
||||
newTag.className = 'ReactTags__embedding';
|
||||
} else {
|
||||
newTag.className = undefined;
|
||||
}
|
||||
return newTag;
|
||||
};
|
||||
|
||||
export const formatPrompt = (value: string) => {
|
||||
const text = Converter.convertStr(value);
|
||||
const textArray = Converter.convertStr2Array(text).map((item) => {
|
||||
if (item.includes('<')) return item;
|
||||
const newItem = item
|
||||
.replaceAll(/\s+/g, ' ')
|
||||
.replaceAll(/,|\.\|。/g, ',')
|
||||
.replaceAll(/“|‘|”|"|\/'/g, '')
|
||||
.replaceAll(', ', ',')
|
||||
.replaceAll(',,', ',')
|
||||
.replaceAll(',', ', ');
|
||||
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 = {
|
||||
negative: genSuggestions(negativeData),
|
||||
positive: genSuggestions(positiveData),
|
||||
};
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { Button, Divider, Form, InputNumber, Segmented, Space, Switch } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { WebuiSetting, defaultSetting, useAppStore } from '@/store';
|
||||
|
||||
import { useStyles } from './style';
|
||||
|
||||
const { Item } = Form;
|
||||
|
||||
const Setting = memo(() => {
|
||||
const setting = useAppStore((st) => st.setting, isEqual);
|
||||
const onSetSetting = useAppStore((st) => st.onSetSetting, shallow);
|
||||
const { styles } = useStyles();
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
onSetSetting(defaultSetting);
|
||||
(gradioApp().querySelector('#settings_restart_gradio') as HTMLButtonElement)?.click();
|
||||
}, []);
|
||||
|
||||
const onFinish = useCallback((value: WebuiSetting) => {
|
||||
onSetSetting(value);
|
||||
(gradioApp().querySelector('#settings_restart_gradio') as HTMLButtonElement)?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Form
|
||||
initialValues={setting}
|
||||
layout="horizontal"
|
||||
onFinish={onFinish}
|
||||
size="small"
|
||||
style={{ maxWidth: 320 }}
|
||||
>
|
||||
<Divider style={{ margin: '4px 0 8px' }} />
|
||||
<title className={styles.title}>Promot Textarea</title>
|
||||
<Item className={styles.item} label="Display mode" name="promotTextarea">
|
||||
<Segmented options={['scroll', 'resizable']} />
|
||||
</Item>
|
||||
<Divider style={{ margin: '4px 0 8px' }} />
|
||||
<title className={styles.title}>Sidebar</title>
|
||||
<Item
|
||||
className={styles.item}
|
||||
label="Default expand"
|
||||
name="sidebarExpand"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Item>
|
||||
<Item className={styles.item} label="Display mode" name="sidebarFixedMode">
|
||||
<Segmented options={['fixed', 'float']} />
|
||||
</Item>
|
||||
<Item className={styles.item} label="Default width" name="sidebarWidth">
|
||||
<InputNumber />
|
||||
</Item>
|
||||
<Divider style={{ margin: '4px 0 8px' }} />
|
||||
<title className={styles.title}>ExtraNetwork Sidebar</title>
|
||||
<Item
|
||||
className={styles.item}
|
||||
label="Enable"
|
||||
name="enableExtraNetworkSidebar"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Item>
|
||||
<Item className={styles.item} label="Display mode" name="extraNetworkFixedMode">
|
||||
<Segmented options={['fixed', 'float']} />
|
||||
</Item>
|
||||
<Item
|
||||
className={styles.item}
|
||||
label="Default expand"
|
||||
name="extraNetworkSidebarExpand"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Item>
|
||||
<Item className={styles.item} label="Default width" name="extraNetworkSidebarWidth">
|
||||
<InputNumber />
|
||||
</Item>
|
||||
<Item className={styles.item} label="Default card size" name="extraNetworkCardSize">
|
||||
<InputNumber />
|
||||
</Item>
|
||||
<Divider style={{ margin: '4px 0 8px' }} />
|
||||
<title className={styles.title}>Other</title>
|
||||
<Item className={styles.item} label="Use svg icons" name="svgIcon" valuePropName="checked">
|
||||
<Switch />
|
||||
</Item>
|
||||
<Divider style={{ margin: '4px 0 16px' }} />
|
||||
<Item className={styles.item}>
|
||||
<Space>
|
||||
<Button htmlType="button" onClick={onReset} style={{ borderRadius: 4 }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button htmlType="submit" style={{ borderRadius: 4 }} type="primary">
|
||||
Apply and restart UI
|
||||
</Button>
|
||||
</Space>
|
||||
</Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
export default Setting;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => ({
|
||||
item: css`
|
||||
.ant-row {
|
||||
justify-content: space-between;
|
||||
|
||||
> div {
|
||||
flex: unset !important;
|
||||
flex-grow: unset !important;
|
||||
}
|
||||
}
|
||||
`,
|
||||
title: css`
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
`,
|
||||
}));
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
import {Theme as AntdStyleTheme} from 'antd-style';
|
||||
import type { LobeCustomStylish, LobeCustomToken } from '@lobehub/ui';
|
||||
import 'antd-style';
|
||||
import { AntdToken } from 'antd-style/lib/types/theme';
|
||||
|
||||
declare module 'antd-style' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface CustomToken extends LobeCustomToken {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface CustomStylish extends LobeCustomStylish {}
|
||||
}
|
||||
|
||||
declare module 'styled-components' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface DefaultTheme extends AntdStyleTheme {}
|
||||
export interface DefaultTheme extends AntdToken, LobeCustomToken {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -1,19 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"importHelpers": true,
|
||||
"noEmit": true,
|
||||
"sourceMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": ["javascript"],
|
||||
"extends": "./src/.umi/tsconfig.json",
|
||||
"include": ["src", "typings.d.ts", "*.ts"]
|
||||
|
||||
"include": ["src", "typings", "*.ts", "*.d.ts", "*.tsx", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,137 @@
|
|||
import 'umi/typings';
|
||||
// This file is generated by Umi automatically
|
||||
// DO NOT CHANGE IT MANUALLY!
|
||||
type CSSModuleClasses = { readonly [key: string]: string };
|
||||
declare module '*.css' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.scss' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.sass' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.less' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.styl' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.stylus' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
|
||||
// images
|
||||
declare module '*.jpg' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.jpeg' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.png' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.gif' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react';
|
||||
|
||||
export const ReactComponent: React.FunctionComponent<
|
||||
React.SVGProps<SVGSVGElement> & { title?: string }
|
||||
>;
|
||||
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.ico' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.webp' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.avif' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
|
||||
// media
|
||||
declare module '*.mp4' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.webm' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.ogg' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.mp3' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.wav' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.flac' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.aac' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
|
||||
// fonts
|
||||
declare module '*.woff' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.woff2' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.eot' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.ttf' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.otf' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
|
||||
// other
|
||||
declare module '*.wasm' {
|
||||
const initWasm: (options: WebAssembly.Imports) => Promise<WebAssembly.Exports>;
|
||||
export default initWasm;
|
||||
}
|
||||
declare module '*.webmanifest' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.pdf' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
declare module '*.txt' {
|
||||
const source: string;
|
||||
export default source;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import react from '@vitejs/plugin-react';
|
||||
import { resolve } from 'node:path';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
base: 'dev',
|
||||
build: {
|
||||
outDir: './',
|
||||
rollupOptions: {
|
||||
input: resolve(__dirname, 'src/main.tsx'),
|
||||
output: {
|
||||
assetFileNames: `javascript/[name].[ext]`,
|
||||
chunkFileNames: `javascript/[name].js`,
|
||||
entryFileNames: `javascript/[name].js`,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: ['@babel/plugin-syntax-import-assertions'],
|
||||
},
|
||||
}),
|
||||
{
|
||||
configureServer: (server) => {
|
||||
server.middlewares.use((_request, res, next) => {
|
||||
res.setHeader('Cross-Origin-Embedder-Policy', 'unsafe-none');
|
||||
res.setHeader('Cross-Origin-Opener-Policy', 'unsafe-non');
|
||||
next();
|
||||
});
|
||||
},
|
||||
name: 'configure-response-headers',
|
||||
},
|
||||
{
|
||||
configureServer: (server) => {
|
||||
server.middlewares.use(async(_request, res, next): Promise<void> => {
|
||||
if (
|
||||
_request.originalUrl === '/dev' ||
|
||||
_request.originalUrl === '/dev?__theme=dark' ||
|
||||
_request.originalUrl === '/dev?__theme=light'
|
||||
) {
|
||||
const response = await fetch('http://127.0.0.1:7860/');
|
||||
|
||||
let updatedResponse = await response.text();
|
||||
|
||||
const toAdd = `
|
||||
<script type="module" src="/dev/src/_react_refresh.js"></script>
|
||||
<script type="module" src="/dev/src/main.tsx"></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', 'utf8');
|
||||
res.end(updatedResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue to the next middleware
|
||||
next();
|
||||
});
|
||||
},
|
||||
name: 'route-default-to-index',
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/queue/join': {
|
||||
target: 'ws://127.0.0.1:7860',
|
||||
ws: true,
|
||||
},
|
||||
'^(?!.*dev).*$': 'http://127.0.0.1:7860',
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue