250 lines
7.5 KiB
JavaScript
250 lines
7.5 KiB
JavaScript
'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')
|
||
})
|