🔧 chore(wip): try to hack react layout into webui
parent
bc550ff1b5
commit
2ce8a893fa
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'umi'
|
||||
export default defineConfig({
|
||||
routes: [{ path: '/', component: 'index' }],
|
||||
npmClient: 'yarn',
|
||||
mpa: {},
|
||||
codeSplitting: false,
|
||||
define: {
|
||||
'process.env': process.env,
|
||||
},
|
||||
})
|
||||
19
gulpfile.js
19
gulpfile.js
|
|
@ -1,19 +1,12 @@
|
|||
const gulp = require('gulp')
|
||||
const less = require('gulp-less')
|
||||
const ts = require('gulp-typescript')
|
||||
const shell = require('gulp-shell')
|
||||
|
||||
const tsProject = ts.createProject('tsconfig.json')
|
||||
gulp.task('compile', () => {
|
||||
return gulp.src('src/script/**/*.ts').pipe(tsProject()).pipe(gulp.dest('javascript'))
|
||||
})
|
||||
gulp.task('umi-build', shell.task('yarn umi build'))
|
||||
gulp.task('mv', shell.task('mv ./dist/index.js ./javascript/index.js && mv ./dist/index.css ./style.css'))
|
||||
gulp.task('clean', shell.task('rm -r dist'))
|
||||
|
||||
gulp.task('less', () => {
|
||||
return gulp.src('src/theme/*.less').pipe(less()).pipe(gulp.dest('./'))
|
||||
})
|
||||
|
||||
gulp.task('build', gulp.parallel('compile', 'less'))
|
||||
gulp.task('build', gulp.series('umi-build', 'mv', 'clean'))
|
||||
|
||||
gulp.task('watch', () => {
|
||||
gulp.watch('src/theme/**/*', gulp.parallel('less'))
|
||||
gulp.watch('src/script/**/*', gulp.parallel('compile'))
|
||||
gulp.watch('src/**/*', gulp.series('build'))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
"use strict";
|
||||
/**
|
||||
* 处理网站的 favicon 图标
|
||||
*/
|
||||
class FaviconHandler {
|
||||
/**
|
||||
* 设置网站的 favicon 图标
|
||||
*/
|
||||
static setFavicon() {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
link.type = 'image/svg+xml';
|
||||
link.href = 'https://gw.alipayobjects.com/zos/bmw-prod/51a51720-8a30-4430-b6c9-be5712364f04.svg';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
}
|
||||
onUiLoaded(() => {
|
||||
FaviconHandler.setFavicon();
|
||||
});
|
||||
|
|
@ -1,256 +0,0 @@
|
|||
"use strict";
|
||||
/**
|
||||
* 转换器工具类
|
||||
*/
|
||||
class Converter {
|
||||
/**
|
||||
* 将数字四舍五入到小数点后四位
|
||||
* @param value 数字
|
||||
* @returns 四舍五入后的数字
|
||||
*/
|
||||
static round(value) {
|
||||
return Math.round(value * 10000) / 10000;
|
||||
}
|
||||
/**
|
||||
* 将字符串中的中文冒号和括号转换成英文冒号和括号
|
||||
* @param srt 字符串
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convertStr(srt) {
|
||||
return srt.replace(/:/g, ':').replace(/(/g, '(').replace(/)/g, ')');
|
||||
}
|
||||
/**
|
||||
* 将字符串按照括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
static convertStr2Array(str) {
|
||||
// 匹配各种括号中的内容,包括括号本身
|
||||
const bracketRegex = /([()<>[\]])/g;
|
||||
/**
|
||||
* 将字符串按照各种括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByBracket = (str) => {
|
||||
const arr = [];
|
||||
let start = 0;
|
||||
let depth = 0;
|
||||
let match;
|
||||
while ((match = bracketRegex.exec(str)) !== null) {
|
||||
if (depth === 0 && match.index > start) {
|
||||
arr.push(str.substring(start, match.index));
|
||||
start = match.index;
|
||||
}
|
||||
if (match[0] === '(' || match[0] === '<' || match[0] === '[') {
|
||||
depth++;
|
||||
}
|
||||
else if (match[0] === ')' || match[0] === '>' || match[0] === ']') {
|
||||
depth--;
|
||||
}
|
||||
if (depth === 0) {
|
||||
arr.push(str.substring(start, match.index + 1));
|
||||
start = match.index + 1;
|
||||
}
|
||||
}
|
||||
if (start < str.length) {
|
||||
arr.push(str.substring(start));
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* 将字符串按照逗号和各种括号分割成数组
|
||||
* @param str 字符串
|
||||
* @returns 分割后的数组
|
||||
*/
|
||||
const splitByComma = (str) => {
|
||||
const arr = [];
|
||||
let start = 0;
|
||||
let inBracket = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str[i] === ',' && !inBracket) {
|
||||
arr.push(str.substring(start, i).trim());
|
||||
start = i + 1;
|
||||
}
|
||||
else if (str[i].match(bracketRegex)) {
|
||||
inBracket = !inBracket;
|
||||
}
|
||||
}
|
||||
arr.push(str.substring(start).trim());
|
||||
return arr;
|
||||
};
|
||||
/**
|
||||
* 清洗字符串并输出数组
|
||||
* @param str 字符串
|
||||
* @returns 清洗后的数组
|
||||
*/
|
||||
const cleanStr = (str) => {
|
||||
let arr = splitByBracket(str);
|
||||
arr = arr.flatMap((s) => splitByComma(s));
|
||||
return arr.filter((s) => s !== '');
|
||||
};
|
||||
return cleanStr(str)
|
||||
.filter((item) => {
|
||||
const pattern = /^[,\s, ]+$/;
|
||||
return !pattern.test(item);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => {
|
||||
return a.includes('<') && !b.includes('<') ? 1 : b.includes('<') && !a.includes('<') ? -1 : 0;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 将数组转换成字符串
|
||||
* @param array 数组
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convertArray2Str(array) {
|
||||
const newArray = array.map((item) => {
|
||||
if (item.includes('<'))
|
||||
return item;
|
||||
const newItem = item
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/,|\.\|。/g, ',')
|
||||
.replace(/“|‘|”|"|\/'/g, '')
|
||||
.replace(/, /g, ',')
|
||||
.replace(/,,/g, ',')
|
||||
.replace(/,/g, ', ');
|
||||
return Converter.convertStr2Array(newItem).join(', ');
|
||||
});
|
||||
return newArray.join(', ');
|
||||
}
|
||||
/**
|
||||
* 将输入的字符串转换成特定格式的字符串
|
||||
* @param input 输入的字符串
|
||||
* @returns 转换后的字符串
|
||||
*/
|
||||
static convert(input) {
|
||||
const re_attention = /\{|\[|\}|\]|[^{}[\]]+/gmu;
|
||||
let text = Converter.convertStr(input);
|
||||
const textArray = Converter.convertStr2Array(text);
|
||||
text = Converter.convertArray2Str(textArray);
|
||||
let res = [];
|
||||
const curly_bracket_multiplier = 1.05;
|
||||
const square_bracket_multiplier = 1 / 1.05;
|
||||
const brackets = {
|
||||
'{': { stack: [], multiplier: curly_bracket_multiplier },
|
||||
'[': { stack: [], multiplier: square_bracket_multiplier },
|
||||
};
|
||||
/**
|
||||
* 将指定范围内的数字乘以指定倍数
|
||||
* @param start_position 起始位置
|
||||
* @param multiplier 倍数
|
||||
*/
|
||||
function multiply_range(start_position, multiplier) {
|
||||
for (let pos = start_position; pos < res.length; pos++) {
|
||||
res[pos][1] = Converter.round(res[pos][1] * multiplier);
|
||||
}
|
||||
}
|
||||
for (const match of text.matchAll(re_attention)) {
|
||||
let word = match[0];
|
||||
if (word in brackets) {
|
||||
brackets[word].stack.push(res.length);
|
||||
}
|
||||
else if (word === '}' || word === ']') {
|
||||
const bracket = brackets[word === '}' ? '{' : '['];
|
||||
if (bracket.stack.length > 0) {
|
||||
multiply_range(bracket.stack.pop(), bracket.multiplier);
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.push([word, 1.0]);
|
||||
}
|
||||
}
|
||||
Object.keys(brackets).forEach((bracketType) => {
|
||||
brackets[bracketType].stack.forEach((pos) => {
|
||||
multiply_range(pos, brackets[bracketType].multiplier);
|
||||
});
|
||||
});
|
||||
if (res.length === 0) {
|
||||
res = [['', 1.0]];
|
||||
}
|
||||
let i = 0;
|
||||
while (i + 1 < res.length) {
|
||||
if (res[i][1] === res[i + 1][1]) {
|
||||
res[i][0] += res[i + 1][0];
|
||||
res.splice(i + 1, 1);
|
||||
}
|
||||
else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
let result = '';
|
||||
for (const [word, value] of res) {
|
||||
result += value === 1.0 ? word : `(${word}:${value.toString()})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* 触发 input 事件
|
||||
* @param target 目标元素
|
||||
*/
|
||||
static dispatchInputEvent(target) {
|
||||
let inputEvent = new Event('input');
|
||||
Object.defineProperty(inputEvent, 'target', { value: target });
|
||||
target.dispatchEvent(inputEvent);
|
||||
}
|
||||
/**
|
||||
* 点击转换按钮的事件处理函数
|
||||
* @param type 类型
|
||||
*/
|
||||
static onClickConvert(type) {
|
||||
const default_prompt = '';
|
||||
const default_negative = '';
|
||||
const prompt = gradioApp().querySelector(`#${type}_prompt > label > textarea`);
|
||||
const result = Converter.convert(prompt.value);
|
||||
prompt.value = result.match(/^masterpiece, best quality,/) === null ? default_prompt + result : result;
|
||||
Converter.dispatchInputEvent(prompt);
|
||||
const negprompt = gradioApp().querySelector(`#${type}_neg_prompt > label > textarea`);
|
||||
const negResult = Converter.convert(negprompt.value);
|
||||
negprompt.value =
|
||||
negResult.match(/^lowres,/) === null
|
||||
? negResult.length === 0
|
||||
? default_negative
|
||||
: default_negative + negResult
|
||||
: negResult;
|
||||
Converter.dispatchInputEvent(negprompt);
|
||||
}
|
||||
/**
|
||||
* 创建转换按钮
|
||||
* @param id 按钮 id
|
||||
* @param innerHTML 按钮文本
|
||||
* @param onClick 点击事件处理函数
|
||||
* @returns 新建的按钮元素
|
||||
*/
|
||||
static createButton(id, innerHTML, onClick) {
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.type = 'button';
|
||||
button.innerHTML = innerHTML;
|
||||
button.title = 'Format prompt~🪄';
|
||||
button.className = 'lg secondary gradio-button tool svelte-1ipelgc';
|
||||
button.addEventListener('click', onClick);
|
||||
return button;
|
||||
}
|
||||
/**
|
||||
* 添加转换按钮
|
||||
* @param type - 组件类型
|
||||
*/
|
||||
static addPromptButton(type) {
|
||||
const generateBtn = gradioApp().querySelector(`#${type}_generate`);
|
||||
const actionsColumn = gradioApp().querySelector(`#${type}_style_create`);
|
||||
const nai2local = gradioApp().querySelector(`#${type}_formatconvert`);
|
||||
if (!generateBtn || !actionsColumn || nai2local)
|
||||
return;
|
||||
const convertBtn = Converter.createButton(`${type}_formatconvert`, '🪄', () => Converter.onClickConvert(type));
|
||||
actionsColumn.parentNode?.append(convertBtn);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 注册UI更新回调函数
|
||||
* 在UI更新时添加提示按钮
|
||||
*/
|
||||
onUiUpdate(() => {
|
||||
Converter.addPromptButton('txt2img');
|
||||
Converter.addPromptButton('img2img');
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,63 +0,0 @@
|
|||
"use strict";
|
||||
class BracketChecker {
|
||||
textArea;
|
||||
counterElt;
|
||||
errorStrings;
|
||||
constructor(textArea, counterElt) {
|
||||
this.textArea = textArea;
|
||||
this.counterElt = counterElt;
|
||||
this.errorStrings = [
|
||||
{
|
||||
regex: '\\(',
|
||||
error: '(...) - Different number of opening and closing parentheses detected.\n',
|
||||
},
|
||||
{
|
||||
regex: '\\[',
|
||||
error: '[...] - Different number of opening and closing square brackets detected.\n',
|
||||
},
|
||||
{
|
||||
regex: '\\{',
|
||||
error: '{...} - Different number of opening and closing curly brackets detected.\n',
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 检查文本框中的括号是否匹配,并更新计数器元素的标题和样式
|
||||
*/
|
||||
check = () => {
|
||||
let title = '';
|
||||
this.errorStrings.forEach(({ regex, error }) => {
|
||||
const openMatches = (this.textArea.value.match(new RegExp(regex, 'g')) || []).length;
|
||||
const closeMatches = (this.textArea.value.match(new RegExp(regex.replace(/\(/g, ')').replace(/\[/g, ']').replace(/\{/g, '}'), 'g')) ||
|
||||
[]).length;
|
||||
if (openMatches !== closeMatches) {
|
||||
if (!this.counterElt.title.includes(error)) {
|
||||
title += error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
title = this.counterElt.title.replace(error, '');
|
||||
}
|
||||
});
|
||||
this.counterElt.title = title;
|
||||
this.counterElt.classList.toggle('error', !!title);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 初始化括号匹配检查器
|
||||
* @param id_prompt 包含文本框的元素的 ID
|
||||
* @param id_counter 显示计数器的元素的 ID
|
||||
*/
|
||||
const setupBracketChecking = (idPrompt, idCounter) => {
|
||||
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`);
|
||||
const counter = gradioApp().getElementById(idCounter);
|
||||
const bracketChecker = new BracketChecker(textarea, counter);
|
||||
textarea.addEventListener('input', bracketChecker.check);
|
||||
};
|
||||
onUiUpdate(() => {
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg'];
|
||||
elements.forEach((prompt) => {
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`);
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`);
|
||||
});
|
||||
});
|
||||
14
package.json
14
package.json
|
|
@ -9,7 +9,6 @@
|
|||
"license": "MIT",
|
||||
"author": "canisminor1990 <i@canisminor.cc>",
|
||||
"sideEffects": false,
|
||||
"main": "style.css",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"dev": "gulp watch",
|
||||
|
|
@ -45,25 +44,32 @@
|
|||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17",
|
||||
"@types/node": "^18",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@umijs/lint": "^4.0.64",
|
||||
"antd": "^5.4.2",
|
||||
"antd-style": "^3.0.0",
|
||||
"commitlint": "^17",
|
||||
"commitlint-config-gitmoji": "^2",
|
||||
"concurrently": "^8.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-less": "^5.0.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"gulp": "^4",
|
||||
"gulp-shell": "^0.8.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.1",
|
||||
"object-to-css-variables": "^0.2.1",
|
||||
"prettier": "^2",
|
||||
"prettier-plugin-organize-imports": "^3",
|
||||
"prettier-plugin-packagejson": "^2",
|
||||
"query-string": "^8.1.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"semantic-release": "^21",
|
||||
"semantic-release-config-gitmoji": "^1",
|
||||
"styled-components": "^5.3.9",
|
||||
"stylelint": "^15.4.0",
|
||||
"stylelint-less": "^1.0.6",
|
||||
"typescript": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { ThemeProvider } from 'antd-style'
|
||||
import qs from 'query-string'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Layout from './Layout'
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [appearance, setAppearance] = useState<string>('auto')
|
||||
useEffect(() => {
|
||||
setAppearance(String(qs.parseUrl(window.location.href).query.__theme) || 'auto')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ThemeProvider appearance={appearance}>
|
||||
<Layout />
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Layout } from 'antd'
|
||||
import React from 'react'
|
||||
|
||||
const { Header } = Layout
|
||||
|
||||
const LayoutView: React.FC = () => {
|
||||
// const header = gradioApp().getElementById('quicksettings')
|
||||
const header = document.getElementById('quicksettings')
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Header>{header}</Header>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutView
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import favicon from '../../script/favicon'
|
||||
import formatPrompt from '../../script/format-prompt'
|
||||
import promptBracketChecker from '../../script/prompt-bracket-checker'
|
||||
import '../../theme/style.less'
|
||||
import App from './App'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const root = document.createElement('div')
|
||||
root.setAttribute('id', 'root')
|
||||
document.body.append(root)
|
||||
const client = createRoot(root)
|
||||
client.render(<App />)
|
||||
})
|
||||
|
||||
onUiLoaded(() => {
|
||||
favicon()
|
||||
window.init = true
|
||||
})
|
||||
|
||||
onUiUpdate(() => {
|
||||
formatPrompt()
|
||||
promptBracketChecker()
|
||||
})
|
||||
|
||||
export default () => null
|
||||
|
|
@ -14,6 +14,8 @@ class FaviconHandler {
|
|||
}
|
||||
}
|
||||
|
||||
onUiLoaded(() => {
|
||||
onUiLoaded(() => {})
|
||||
|
||||
export default () => {
|
||||
FaviconHandler.setFavicon()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,7 +269,9 @@ class Converter {
|
|||
* 注册UI更新回调函数
|
||||
* 在UI更新时添加提示按钮
|
||||
*/
|
||||
onUiUpdate(() => {
|
||||
onUiUpdate(() => {})
|
||||
|
||||
export default () => {
|
||||
Converter.addPromptButton('txt2img')
|
||||
Converter.addPromptButton('img2img')
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,10 +63,12 @@ const setupBracketChecking = (idPrompt: string, idCounter: string): void => {
|
|||
textarea.addEventListener('input', bracketChecker.check)
|
||||
}
|
||||
|
||||
onUiUpdate(() => {
|
||||
onUiUpdate(() => {})
|
||||
|
||||
export default () => {
|
||||
const elements = ['txt2img', 'txt2img_neg', 'img2img', 'img2img_neg']
|
||||
elements.forEach((prompt) => {
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_token_counter`)
|
||||
setupBracketChecking(`${prompt}_prompt`, `${prompt}_negative_token_counter`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
@import 'components/container';
|
||||
@import 'components/scrollbar';
|
||||
@import 'components/options';
|
||||
@import 'components/header';
|
||||
//@import 'components/header';
|
||||
@import 'components/modal';
|
||||
@import 'components/sliders';
|
||||
@import 'components/button';
|
||||
|
|
@ -44,22 +44,27 @@ code {
|
|||
h1 {
|
||||
font-size: var(--font-size-heading1);
|
||||
line-height: var(--line-height-heading1);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--font-size-heading2);
|
||||
line-height: var(--line-height-heading2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--font-size-heading3);
|
||||
line-height: var(--line-height-heading3);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--font-size-heading4);
|
||||
line-height: var(--line-height-heading4);
|
||||
color: var(--color-text);
|
||||
}
|
||||
h5 {
|
||||
font-size: var(--font-size-heading5);
|
||||
line-height: var(--line-height-heading5);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Theme Fix */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"extends": "./src/.umi-production/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue