mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
174 lines
5.2 KiB
TypeScript
174 lines
5.2 KiB
TypeScript
import { app } from '../../scripts/app'
|
|
|
|
// Allows you to edit the attention weight by holding ctrl (or cmd) and using the up/down arrow keys
|
|
|
|
app.registerExtension({
|
|
name: 'Comfy.EditAttention',
|
|
init() {
|
|
const editAttentionDelta = app.ui.settings.addSetting({
|
|
id: 'Comfy.EditAttention.Delta',
|
|
category: ['Comfy', 'EditTokenWeight', 'Delta'],
|
|
name: 'Ctrl+up/down precision',
|
|
type: 'slider',
|
|
attrs: {
|
|
min: 0.01,
|
|
max: 0.5,
|
|
step: 0.01
|
|
},
|
|
defaultValue: 0.05
|
|
})
|
|
|
|
function incrementWeight(weight: string, delta: number): string {
|
|
const floatWeight = parseFloat(weight)
|
|
if (isNaN(floatWeight)) return weight
|
|
const newWeight = floatWeight + delta
|
|
return String(Number(newWeight.toFixed(10)))
|
|
}
|
|
|
|
type Enclosure = {
|
|
start: number
|
|
end: number
|
|
}
|
|
|
|
function findNearestEnclosure(
|
|
text: string,
|
|
cursorPos: number
|
|
): Enclosure | null {
|
|
let start = cursorPos,
|
|
end = cursorPos
|
|
let openCount = 0,
|
|
closeCount = 0
|
|
|
|
// Find opening parenthesis before cursor
|
|
while (start >= 0) {
|
|
start--
|
|
if (text[start] === '(' && openCount === closeCount) break
|
|
if (text[start] === '(') openCount++
|
|
if (text[start] === ')') closeCount++
|
|
}
|
|
if (start < 0) return null
|
|
|
|
openCount = 0
|
|
closeCount = 0
|
|
|
|
// Find closing parenthesis after cursor
|
|
while (end < text.length) {
|
|
if (text[end] === ')' && openCount === closeCount) break
|
|
if (text[end] === '(') openCount++
|
|
if (text[end] === ')') closeCount++
|
|
end++
|
|
}
|
|
if (end === text.length) return null
|
|
|
|
return { start: start + 1, end: end }
|
|
}
|
|
|
|
function addWeightToParentheses(text: string): string {
|
|
const parenRegex = /^\((.*)\)$/
|
|
const parenMatch = text.match(parenRegex)
|
|
|
|
const floatRegex = /:([+-]?(\d*\.)?\d+([eE][+-]?\d+)?)/
|
|
const floatMatch = text.match(floatRegex)
|
|
|
|
if (parenMatch && !floatMatch) {
|
|
return `(${parenMatch[1]}:1.0)`
|
|
} else {
|
|
return text
|
|
}
|
|
}
|
|
|
|
function editAttention(event: KeyboardEvent) {
|
|
// @ts-expect-error Runtime narrowing not impl.
|
|
const inputField: HTMLTextAreaElement = event.composedPath()[0]
|
|
const delta = parseFloat(editAttentionDelta.value)
|
|
|
|
if (inputField.tagName !== 'TEXTAREA') return
|
|
if (!(event.key === 'ArrowUp' || event.key === 'ArrowDown')) return
|
|
if (!event.ctrlKey && !event.metaKey) return
|
|
|
|
event.preventDefault()
|
|
|
|
let start = inputField.selectionStart
|
|
let end = inputField.selectionEnd
|
|
let selectedText = inputField.value.substring(start, end)
|
|
|
|
// If there is no selection, attempt to find the nearest enclosure, or select the current word
|
|
if (!selectedText) {
|
|
const nearestEnclosure = findNearestEnclosure(inputField.value, start)
|
|
if (nearestEnclosure) {
|
|
start = nearestEnclosure.start
|
|
end = nearestEnclosure.end
|
|
selectedText = inputField.value.substring(start, end)
|
|
} else {
|
|
// Select the current word, find the start and end of the word
|
|
const delimiters = ' .,\\/!?%^*;:{}=-_`~()\r\n\t'
|
|
|
|
while (
|
|
!delimiters.includes(inputField.value[start - 1]) &&
|
|
start > 0
|
|
) {
|
|
start--
|
|
}
|
|
|
|
while (
|
|
!delimiters.includes(inputField.value[end]) &&
|
|
end < inputField.value.length
|
|
) {
|
|
end++
|
|
}
|
|
|
|
selectedText = inputField.value.substring(start, end)
|
|
if (!selectedText) return
|
|
}
|
|
}
|
|
|
|
// If the selection ends with a space, remove it
|
|
if (selectedText[selectedText.length - 1] === ' ') {
|
|
selectedText = selectedText.substring(0, selectedText.length - 1)
|
|
end -= 1
|
|
}
|
|
|
|
// If there are parentheses left and right of the selection, select them
|
|
if (
|
|
inputField.value[start - 1] === '(' &&
|
|
inputField.value[end] === ')'
|
|
) {
|
|
start -= 1
|
|
end += 1
|
|
selectedText = inputField.value.substring(start, end)
|
|
}
|
|
|
|
// If the selection is not enclosed in parentheses, add them
|
|
if (
|
|
selectedText[0] !== '(' ||
|
|
selectedText[selectedText.length - 1] !== ')'
|
|
) {
|
|
selectedText = `(${selectedText})`
|
|
}
|
|
|
|
// If the selection does not have a weight, add a weight of 1.0
|
|
selectedText = addWeightToParentheses(selectedText)
|
|
|
|
// Increment the weight
|
|
const weightDelta = event.key === 'ArrowUp' ? delta : -delta
|
|
const updatedText = selectedText.replace(
|
|
/\((.*):([+-]?\d+(?:\.\d+)?)\)/,
|
|
(_, text, weight) => {
|
|
weight = incrementWeight(weight, weightDelta)
|
|
if (weight == 1) {
|
|
return text
|
|
} else {
|
|
return `(${text}:${weight})`
|
|
}
|
|
}
|
|
)
|
|
|
|
inputField.setSelectionRange(start, end)
|
|
// Intentional use of deprecated: https://developer.mozilla.org/docs/Web/API/Document/execCommand#using_inserttext
|
|
document.execCommand('insertText', false, updatedText)
|
|
inputField.setSelectionRange(start, start + updatedText.length)
|
|
}
|
|
window.addEventListener('keydown', editAttention)
|
|
}
|
|
})
|