parent
57a388db6c
commit
4b0942d230
14
README.md
14
README.md
|
|
@ -1,4 +1,6 @@
|
|||
# SD Webui Prompt Format
|
||||
# SD Webui Prompt Format
|
||||
[English|[中文](README_ZH.md)]
|
||||
|
||||
This is an Extension for the [Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), which helps formatting prompts.
|
||||
|
||||
<p align="center"><img src="Demo.jpg" width=512></p>
|
||||
|
|
@ -19,8 +21,10 @@ Sometimes, when you type too fast or copy prompts from all over the places, you
|
|||
- *Some newer anime checkpoints claim to eliminate the need of using underscores*
|
||||
- [x] Respect line breaks
|
||||
- **Note:** `Remove Duplicates` only checks within the same line
|
||||
- [x] **[New]** Pressing `Ctrl + \` to quickly escape the **brackets** of the hovered tag
|
||||
- Normally, **brackets** *(parentheses)* are used to increase the weight of a prompt. Therefore, for tags like `mejiro mcqueen (umamusume)`, you will need to escape it like `mejiro mcqueen \(umamusume\)`.
|
||||
- [x] Pressing `Ctrl + \` to quickly escape the **parentheses** of the hovered tag *(the words where the caret is)*
|
||||
- Normally, **parentheses** are used to increase the weight of a prompt. Therefore, for tags like `mejiro mcqueen (umamusume)`, you will need to escape it like `mejiro mcqueen \(umamusume\)`.
|
||||
- [X] **[New]** Toggle between auto formatting and manual formatting
|
||||
- In `Auto`: The process is ran whenever you press **Generate**
|
||||
- In `Manual`: The process is only ran when you press the **Format** button
|
||||
|
||||
|
||||
<sup><b>Note:</b> This is purely visual. The actual prompt is unchanged until you manually edit the text again.</sup>
|
||||
<sup><b>Note:</b> The formatting is purely visual. The actual prompt is unchanged until you manually edit the texts again.</sup>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# SD Webui Prompt Format
|
||||
[[English](README.md)|中文]
|
||||
|
||||
這是一個[Automatic1111 Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)的插件,可以幫忙校正咒語。
|
||||
|
||||
<p align="center"><img src="Demo.jpg" width=512></p>
|
||||
|
||||
> 示範圖
|
||||
|
||||
有時候打字太快,或是從各地東拼西湊咒語,常造成多個重複的空格或逗點。這項插件可以幫忙移除它們。
|
||||
|
||||
## 功能實作
|
||||
- [x] 在`txt2img`和`img2img`都有用
|
||||
- [x] 移除多餘**空格**和**逗點**
|
||||
- [x] 修改錯誤的**括弧**
|
||||
- [x] 開啟`Remove Duplicates`會把咒語中重複的單詞消除
|
||||
- **注意:** 只對單詞類咒語有效
|
||||
- **例.** `1girl, solo, smile, 1girl` 會變成 `1girl, solo, smile`
|
||||
- **例.** `a girl smiling, a girl standing` 則不變
|
||||
- [x] 開啟`Remove Underscores`會將 `_` 換成**空格**
|
||||
- *一些較新的動漫模型聲稱不用再加底線*
|
||||
- [x] 保留咒語的換行
|
||||
- **注意:** 上述的`Remove Duplicates`只在同一行中有效
|
||||
- [x] 按下`Ctrl + \`來跳脫目前游標所在的單字
|
||||
- 平時,**括弧**是用來強調單字。所以若使用像是`mejiro mcqueen (umamusume)`的咒語,便必須跳脫成`mejiro mcqueen \(umamusume\)`
|
||||
- [X] **[New]** 按下`Auto Format`以在手動與自動間切換
|
||||
- `自動`: 每次按下 **生成 (Generate)** 時處裡
|
||||
- `手動`: 手動按下`Format`時才處裡
|
||||
|
||||
<sup><b>注意:</b> 上述美化只是視覺效果。唯有再次手動編輯後,咒語才會更新。</sup>
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
class LeFormatter {
|
||||
|
||||
static manualButton(text, id, { onClick }) {
|
||||
const button = gradioApp().getElementById(id).cloneNode()
|
||||
|
||||
button.id = 'manual-format'
|
||||
button.classList.remove('gr-button-lg', 'gr-button-primary', 'lg', 'primary')
|
||||
button.classList.add('secondary')
|
||||
button.textContent = text
|
||||
button.addEventListener('click', onClick)
|
||||
|
||||
return button
|
||||
}
|
||||
|
||||
static injectButton(id, { onClick }) {
|
||||
const button = gradioApp().getElementById(id)
|
||||
button.addEventListener('click', onClick)
|
||||
}
|
||||
|
||||
static checkbox(text, { onChange }) {
|
||||
static checkbox(text, def, { onChange }) {
|
||||
const label = document.createElement('label')
|
||||
label.style.display = 'flex'
|
||||
label.style.alignItems = 'center'
|
||||
label.style.margin = '2px 8px'
|
||||
|
||||
const checkbox = gradioApp().querySelector('input[type=checkbox]').cloneNode()
|
||||
checkbox.checked = false
|
||||
checkbox.checked = def
|
||||
checkbox.addEventListener('change', (event) => {
|
||||
onChange(event.target.checked)
|
||||
})
|
||||
|
|
@ -27,154 +39,178 @@ class LeFormatter {
|
|||
return label
|
||||
}
|
||||
|
||||
}
|
||||
static formatString(input, dedupe, removeUnderscore) {
|
||||
// Fix Bracket & Comma
|
||||
input = input.replace(/,\)/g, '),').replace(/,\]/g, '],').replace(/\(,/g, ',(').replace(/\[,/g, ',[')
|
||||
|
||||
function injectBracketEscape(id) {
|
||||
const textarea = gradioApp().getElementById(id).querySelector('textarea')
|
||||
// Remove Commas
|
||||
let tags = input.split(',').map(word => (removeUnderscore ? word.replace(/_/g, ' ').trim() : word.trim())).filter(word => word !== '')
|
||||
|
||||
textarea.addEventListener('keydown', (event) => {
|
||||
if (event.ctrlKey && event.key === '\\') {
|
||||
event.preventDefault()
|
||||
// Remove Duplicate
|
||||
input = dedupe ? [...new Set(tags)].join(', ') : tags.join(', ')
|
||||
|
||||
let cursorPosition = textarea.selectionStart;
|
||||
// Remove Spaces
|
||||
input = input.replace(/\s+/g, ' ')
|
||||
|
||||
if (textarea.selectionStart !== textarea.selectionEnd)
|
||||
cursorPosition++
|
||||
// Fix Bracket & Space
|
||||
input = input.replace(/ \)/g, ')').replace(/ \]/g, ']').replace(/\( /g, '(').replace(/\[ /g, '[')
|
||||
|
||||
let result = pf_GrabBrackets(textarea.value, cursorPosition)
|
||||
// Fix Empty Bracket
|
||||
input = input.replace(/\(\s+\)/g, '').replace(/\[\s+\]/g, '')
|
||||
|
||||
if (result) {
|
||||
const original = textarea.value
|
||||
while (input.includes('()'))
|
||||
input = input.replace(/\(\s*\)/g, '')
|
||||
while (input.includes('[]'))
|
||||
input = input.replace(/\[\s*\]/g, '')
|
||||
|
||||
if (result[0] !== 0 && textarea.value[result[0] - 1] === '\\' && textarea.value[result[1] - 1] === '\\') {
|
||||
textarea.value = original.slice(0, result[0] - 1) + original.slice(result[0] - 1, result[1]).replace(/\\/g, '') + original.slice(result[1])
|
||||
textarea.selectionStart = result[0] - 1
|
||||
textarea.selectionEnd = result[1] - 1
|
||||
}
|
||||
else {
|
||||
textarea.value = original.slice(0, result[0]) + '\\' + original.slice(result[0], result[1]) + '\\' + original.slice(result[1])
|
||||
textarea.selectionStart = result[0]
|
||||
textarea.selectionEnd = result[1] + 3
|
||||
}
|
||||
return input.trim()
|
||||
}
|
||||
|
||||
// updateInput(textarea)
|
||||
static grabBrackets(str, index) {
|
||||
let openBracket = -1
|
||||
let closeBracket = -1
|
||||
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (str[i] === '(') {
|
||||
openBracket = i
|
||||
break;
|
||||
}
|
||||
if (str[i] === ')' && i !== index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pf_GrabBrackets(str, index) {
|
||||
let openBracket = -1;
|
||||
let closeBracket = -1;
|
||||
for (let i = index; i < str.length; i++) {
|
||||
if (str[i] === ')') {
|
||||
closeBracket = i
|
||||
break;
|
||||
}
|
||||
if (str[i] === '(' && i !== index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (str[i] === '(') {
|
||||
openBracket = i
|
||||
break;
|
||||
}
|
||||
if (str[i] === ')' && i !== index) {
|
||||
break;
|
||||
}
|
||||
if (openBracket !== -1 && closeBracket !== -1 && openBracket !== closeBracket)
|
||||
return [openBracket, closeBracket]
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = index; i < str.length; i++) {
|
||||
if (str[i] === ')') {
|
||||
closeBracket = i
|
||||
break;
|
||||
}
|
||||
if (str[i] === '(' && i !== index) {
|
||||
break;
|
||||
}
|
||||
static injectBracketEscape(id) {
|
||||
const textarea = gradioApp().getElementById(id).querySelector('textarea')
|
||||
|
||||
textarea.addEventListener('keydown', (event) => {
|
||||
if (event.ctrlKey && event.key === '\\') {
|
||||
event.preventDefault()
|
||||
|
||||
let cursorPosition = textarea.selectionStart
|
||||
|
||||
if (textarea.selectionStart !== textarea.selectionEnd)
|
||||
cursorPosition++
|
||||
|
||||
let result = LeFormatter.grabBrackets(textarea.value, cursorPosition)
|
||||
|
||||
if (result) {
|
||||
const original = textarea.value
|
||||
|
||||
if (result[0] !== 0 && textarea.value[result[0] - 1] === '\\' && textarea.value[result[1] - 1] === '\\') {
|
||||
textarea.value = original.slice(0, result[0] - 1) + original.slice(result[0] - 1, result[1]).replace(/\\/g, '') + original.slice(result[1])
|
||||
textarea.selectionStart = result[0] - 1
|
||||
textarea.selectionEnd = result[1] - 1
|
||||
}
|
||||
else {
|
||||
textarea.value = original.slice(0, result[0]) + '\\' + original.slice(result[0], result[1]) + '\\' + original.slice(result[1])
|
||||
textarea.selectionStart = result[0]
|
||||
textarea.selectionEnd = result[1] + 3
|
||||
}
|
||||
|
||||
updateInput(textarea)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (openBracket !== -1 && closeBracket !== -1 && openBracket !== closeBracket)
|
||||
return [openBracket, closeBracket];
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
function fixBracketComma(input) {
|
||||
return input.replace(/,\)/g, '),').replace(/,\]/g, '],').replace(/\(,/g, ',(').replace(/\[,/g, ',[');
|
||||
}
|
||||
|
||||
function fixBracketSpace(input) {
|
||||
return input.replace(/ \)/g, ')').replace(/ \]/g, ']').replace(/\( /g, '(').replace(/\[ /g, '[');
|
||||
}
|
||||
|
||||
function fixBracketEmpty(input) {
|
||||
let temp = input.replace(/\(\s+\)/g, '').replace(/\[\s+\]/g, '')
|
||||
|
||||
while (temp.includes('()'))
|
||||
temp = temp.replace(/\(\s*\)/g, '')
|
||||
while (temp.includes('[]'))
|
||||
temp = temp.replace(/\[\s*\]/g, '')
|
||||
return temp
|
||||
}
|
||||
|
||||
function formatString(input, dedupe, deunderline) {
|
||||
const tags = fixBracketComma(input).split(',').map(word => (deunderline ? word.replace(/_/g, ' ').trim() : word.trim())).filter(word => word !== '');
|
||||
const sentence = dedupe ? [...new Set(tags)].join(', ') : tags.join(', ');
|
||||
return fixBracketEmpty(fixBracketSpace(sentence.replace(/\s+/g, ' ')).trim());
|
||||
}
|
||||
}
|
||||
|
||||
onUiLoaded(async () => {
|
||||
|
||||
// SETTINGS
|
||||
const iterations = 1
|
||||
// SETTINGS
|
||||
|
||||
const Modes = ['txt', 'img']
|
||||
let autoRun = true
|
||||
let dedupe = false
|
||||
let deunderline = false
|
||||
let removeUnderscore = false
|
||||
|
||||
const dedupeCB = LeFormatter.checkbox('Remove Duplicates', {
|
||||
const manualBtn = LeFormatter.manualButton('Format', 'txt2img_generate', {
|
||||
onClick: () => {
|
||||
const ids = ['txt2img_prompt', 'txt2img_neg_prompt', 'img2img_prompt', 'img2img_neg_prompt']
|
||||
|
||||
ids.forEach((id) => {
|
||||
const textArea = gradioApp().getElementById(id).querySelector('textarea')
|
||||
|
||||
let lines = textArea.value.split('\n')
|
||||
|
||||
for (let i = 0; i < lines.length; i++)
|
||||
lines[i] = LeFormatter.formatString(lines[i], dedupe, removeUnderscore)
|
||||
|
||||
textArea.value = lines.join('\n')
|
||||
updateInput(textArea)
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const autoCB = LeFormatter.checkbox('Auto Format', autoRun, {
|
||||
onChange: (checked) => {
|
||||
autoRun = checked
|
||||
manualBtn.style.display = autoRun ? 'none' : 'block'
|
||||
}
|
||||
})
|
||||
|
||||
const dedupeCB = LeFormatter.checkbox('Remove Duplicates', dedupe, {
|
||||
onChange: (checked) => { dedupe = checked }
|
||||
})
|
||||
|
||||
const underlineCB = LeFormatter.checkbox('Remove Underscores', {
|
||||
onChange: (checked) => { deunderline = checked }
|
||||
const underlineCB = LeFormatter.checkbox('Remove Underscores', removeUnderscore, {
|
||||
onChange: (checked) => { removeUnderscore = checked }
|
||||
})
|
||||
|
||||
manualBtn.style.display = 'none'
|
||||
|
||||
const formatter = document.createElement('div')
|
||||
formatter.id = 'le-formatter'
|
||||
formatter.style.display = 'flex';
|
||||
formatter.style.flex.direction = 'row';
|
||||
formatter.style.display = 'flex'
|
||||
formatter.style.flex.direction = 'row'
|
||||
|
||||
formatter.appendChild(autoCB)
|
||||
formatter.appendChild(manualBtn)
|
||||
formatter.appendChild(dedupeCB)
|
||||
formatter.appendChild(underlineCB)
|
||||
|
||||
const tools = document.getElementById('quicksettings')
|
||||
tools.after(formatter)
|
||||
|
||||
const Modes = ['txt', 'img']
|
||||
|
||||
Modes.forEach((mode) => {
|
||||
|
||||
LeFormatter.injectButton(mode + '2img_generate', {
|
||||
onClick: () => {
|
||||
if (!autoRun)
|
||||
return;
|
||||
|
||||
const ids = [mode + '2img_prompt', mode + '2img_neg_prompt']
|
||||
const textAreas = [gradioApp().getElementById(ids[0]).querySelector('textarea'), gradioApp().getElementById(ids[1]).querySelector('textarea')]
|
||||
|
||||
let lines = [textAreas[0].value.split('\n'), textAreas[1].value.split('\n')]
|
||||
|
||||
for (let i = 0; i < lines[0].length; i++)
|
||||
for (let it = 0; it < iterations; it++)
|
||||
lines[0][i] = formatString(lines[0][i], dedupe, deunderline)
|
||||
for (let m = 0; m < 2; m++) {
|
||||
|
||||
for (let i = 0; i < lines[1].length; i++)
|
||||
for (let it = 0; it < iterations; it++)
|
||||
lines[1][i] = formatString(lines[1][i], dedupe, deunderline)
|
||||
for (let i = 0; i < lines[0].length; i++)
|
||||
lines[m][i] = LeFormatter.formatString(lines[m][i], dedupe, removeUnderscore)
|
||||
|
||||
|
||||
textAreas[0].value = lines[0].join('\n')
|
||||
// updateInput(textAreas[0])
|
||||
|
||||
textAreas[1].value = lines[1].join('\n')
|
||||
// updateInput(textAreas[1])
|
||||
textAreas[m].value = lines[m].join('\n')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
injectBracketEscape(mode + '2img_prompt')
|
||||
injectBracketEscape(mode + '2img_neg_prompt')
|
||||
LeFormatter.injectBracketEscape(mode + '2img_prompt')
|
||||
LeFormatter.injectBracketEscape(mode + '2img_neg_prompt')
|
||||
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue