Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4066f58d25 | ||
|
|
ce07964a68 | ||
|
|
d39a915888 | ||
|
|
5aa72379e8 | ||
|
|
8257ad5109 | ||
|
|
b8c250e836 | ||
|
|
4be66d7132 | ||
|
|
7f089527c5 | ||
|
|
7d8190ea07 | ||
|
|
9e2f174bcf | ||
|
|
199ee05f12 | ||
|
|
06f0157635 | ||
|
|
f96fac44a2 | ||
|
|
729574cf3b | ||
|
|
e4decf948b | ||
|
|
24dd6901ed | ||
|
|
aa192f51f0 | ||
|
|
300ee91cc1 | ||
|
|
3f3561a02b | ||
|
|
c3822bc4f8 | ||
|
|
d480c586d0 | ||
|
|
e0ce4beb21 | ||
|
|
f8cbd27c5c |
@@ -28,7 +28,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-hub/stylelint@master
|
||||
env:
|
||||
PATTERN : 'web/css/*.css'
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run stylelint
|
||||
run: npx stylelint "web/css/*.css"
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## English • [日本語](docs/README_jp.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
@@ -169,6 +169,7 @@ For example, by preparing the following CSV, you can quickly insert correspondin
|
||||
- **Max suggestions**: Maximum number of autocomplete suggestions to display.
|
||||
- **Auto-Insert Comma**: Automatically insert a comma after tags when inserting from autocomplete.
|
||||
- **Replace '_' with 'Space'**: Replaces underscores with spaces when inserting tags. This setting also affects related tag display.
|
||||
- **String to add before artist tags**: Text to prepend when inserting an artist tag. For Anima models, specify `@`.
|
||||
- **Enable Loras and Embeddings**: Display Lora and Embedding in the suggestions.
|
||||
- **Use Fast Search**: Switch autocomplete suggestions search to fast processing (see [About Fast Search for Autocomplete](#about-fast-search-for-autocomplete) for details).
|
||||
|
||||
@@ -189,7 +190,8 @@ For example, by preparing the following CSV, you can quickly insert correspondin
|
||||
- **Auto Format Trigger**: Choose when formatting is applied.
|
||||
- **Auto**: Format automatically when leaving text field
|
||||
- **Manual**: Format only via keyboard shortcut (default: `Alt+Shift+F`)
|
||||
- **Use Trailing Comma**: If enabled, ensures all lines end with a trailing comma when formatting. If disabled, removes trailing commas.
|
||||
- **Use Trailing Comma**: When enabled, ensures all lines end with a trailing comma when formatting. If disabled, removes trailing commas.
|
||||
- **Trim Surrounding Spaces**: When enabled, trim any blank lines or spaces from the beginning and end of the prompt.
|
||||
|
||||
## Advanced Settings
|
||||
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
# ComfyUI-Autocomplete-Plus
|
||||
|
||||

|
||||

|
||||
|
||||
## 概要
|
||||
|
||||
@@ -167,6 +167,7 @@ worst_quality,5,9999999,
|
||||
- **Max Suggestions**: オートコンプリート候補の最大表示件数
|
||||
- **Auto-Insert Comma**: タグの挿入時、末尾にカンマを追加する
|
||||
- **Replace '_' with 'Space'**: タグ挿入時にアンダースコアをスペースに置き換えます。この設定は関連タグ表示にも影響します
|
||||
- **String to add before artist tags**: アーティストタグを挿入する際に付加する文字列。 Anima モデルの場合は「@」を指定します
|
||||
- **Enable Loras and Embeddings**: LoraとEmbeddingを候補に表示する
|
||||
- **Use Fast Search**: オートコンプリート候補の検索を高速な処理に切り替える(詳細は [オートコンプリートの高速検索について](#オートコンプリートの高速検索について) を確認してください)
|
||||
|
||||
@@ -187,7 +188,8 @@ worst_quality,5,9999999,
|
||||
- **Auto Format Trigger**: フォーマットを適用するタイミングを選択します
|
||||
- **自動**: テキスト欄からフォーカスが外れた際に自動でフォーマットします
|
||||
- **手動**: キーボードショートカットでのみフォーマットします(デフォルト: `Alt+Shift+F`)
|
||||
- **行末にカンマを使用**: 有効にすると、フォーマット時にすべての行末がカンマで終わるようになります。\n無効にすると行末のカンマが削除されます
|
||||
- **行末にカンマを使用**: 有効時、フォーマット時にすべての行末がカンマで終わるようになります。\n無効にすると行末のカンマが削除されます
|
||||
- **先頭・末尾の空白を削除**: 有効時、プロンプトの先頭と末尾にある空行やスペースをすべて削除します
|
||||
|
||||
## 上級者向け設定
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
"AutocompletePlus_Autocompletion_EnableModels": {
|
||||
"name": "Enable Loras and Embeddings"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_PrefixArtist": {
|
||||
"name": "String to add before artist tags",
|
||||
"tooltip": "Text to prepend when inserting an artist tag via autocomplete.\ne.g. '@' -> '@artist_name'."
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_ReplaceUnderscoreWithSpace": {
|
||||
"name": "Replace '_' with 'Space'",
|
||||
"tooltip": "This setting also affects related tags display."
|
||||
@@ -85,5 +89,9 @@
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||
"name": "Enable Auto Format"
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||
"name": "Trim Surrounding Spaces",
|
||||
"tooltip": "When enabled, trim any blank lines from the beginning and end of the prompt."
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
"AutocompletePlus_Autocompletion_EnableModels": {
|
||||
"name": "LoraとEmbeddingを候補に表示する"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_PrefixArtist": {
|
||||
"name": "アーティストタグの前に追加する文字列",
|
||||
"tooltip": "オートコンプリートからアーティストタグを挿入する際に付加する文字列。\n例: '@' -> '@artist_name'"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_ReplaceUnderscoreWithSpace": {
|
||||
"name": "'_' を 'スペース' に置き換える",
|
||||
"tooltip": "この設定は関連タグ表示にも影響します"
|
||||
@@ -85,5 +89,9 @@
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||
"name": "自動フォーマット機能の有効化"
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||
"name": "先頭・末尾の空白を削除",
|
||||
"tooltip": "有効時、プロンプトの先頭と末尾にある空行やスペースをすべて削除します。"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
"AutocompletePlus_Autocompletion_EnableModels": {
|
||||
"name": "顯示Lora和Embedding"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_PrefixArtist": {
|
||||
"name": "在藝術家標籤前添加的字串",
|
||||
"tooltip": "透過自動完成插入藝術家標籤時,在標籤前添加的文字。\n例: '@' -> '@artist_name'"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_ReplaceUnderscoreWithSpace": {
|
||||
"name": "將'_'替換為'空格'",
|
||||
"tooltip": "此設定也會影響相關標籤顯示"
|
||||
@@ -85,5 +89,9 @@
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||
"name": "啟用自動格式化"
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||
"name": "去除首尾空白",
|
||||
"tooltip": "啟用時,清除提示詞開頭和結尾的所有空行和空格。"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
"AutocompletePlus_Autocompletion_EnableModels": {
|
||||
"name": "显示Lora和Embedding"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_PrefixArtist": {
|
||||
"name": "在艺术家标签前添加的字符串",
|
||||
"tooltip": "通过自动完成插入艺术家标签时,在标签前添加的文本。\n例: '@' -> '@artist_name'"
|
||||
},
|
||||
"AutocompletePlus_Autocompletion_ReplaceUnderscoreWithSpace": {
|
||||
"name": "将'_'替换为'空格'",
|
||||
"tooltip": "此设置也会影响相关标签显示"
|
||||
@@ -85,5 +89,9 @@
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||
"name": "启用自动格式化"
|
||||
},
|
||||
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||
"name": "去除首尾空白",
|
||||
"tooltip": "启用时,清除提示词开头和结尾的所有空行和空格。"
|
||||
}
|
||||
}
|
||||
Generated
+16
-15
@@ -4,6 +4,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ComfyUI-Autocomplete-Plus",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
@@ -2005,9 +2006,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2266,9 +2267,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/reporters/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -4790,9 +4791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-config/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -5130,9 +5131,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runtime/node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -5335,9 +5336,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-autocomplete-plus"
|
||||
description = "Autocomplete and Related Tag display for ComfyUI"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["",]
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ describe('AutoFormatter Functions', () => {
|
||||
inputName
|
||||
});
|
||||
|
||||
// Store original setting value to restore after tests
|
||||
const originalTrimSurroundingSpaces = settingValues.trimSurroundingSpaces;
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original setting after each test
|
||||
settingValues.trimSurroundingSpaces = originalTrimSurroundingSpaces;
|
||||
});
|
||||
|
||||
test('should return false for empty text', () => {
|
||||
expect(shouldAutoFormat('', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
expect(shouldAutoFormat(' ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
@@ -42,20 +50,73 @@ describe('AutoFormatter Functions', () => {
|
||||
expect(shouldAutoFormat('hello world', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
expect(shouldAutoFormat('tag1 tag2', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
});
|
||||
|
||||
describe('blocklist priority with trimSurroundingSpaces', () => {
|
||||
test('should prioritize blocklist over trimSurroundingSpaces when disabled', () => {
|
||||
settingValues.trimSurroundingSpaces = false;
|
||||
|
||||
// Blocklisted nodes should return false even with surrounding spaces
|
||||
expect(shouldAutoFormat(' some code ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||
expect(shouldAutoFormat('\nsome code\n', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||
expect(shouldAutoFormat(' 0,0,0,1,1,1 ', mockNodeInfo('LoraLoaderBlockWeight //Inspire', 'block_vector'))).toBe(false);
|
||||
});
|
||||
|
||||
test('should prioritize blocklist over trimSurroundingSpaces when enabled', () => {
|
||||
settingValues.trimSurroundingSpaces = true;
|
||||
|
||||
// Blocklisted nodes should return false even with trimSurroundingSpaces enabled and surrounding spaces
|
||||
expect(shouldAutoFormat(' some code ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||
expect(shouldAutoFormat('\nsome code\n', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||
expect(shouldAutoFormat(' function() { } ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||
expect(shouldAutoFormat(' 0,0,0,1,1,1 ', mockNodeInfo('LoraLoaderBlockWeight //Inspire', 'block_vector'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trimSurroundingSpaces with non-comma-separated text', () => {
|
||||
test('should return false for non-comma-separated text when trimSurroundingSpaces is disabled', () => {
|
||||
settingValues.trimSurroundingSpaces = false;
|
||||
|
||||
// Text without word+comma pattern should return false
|
||||
expect(shouldAutoFormat(' hello world ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
expect(shouldAutoFormat('\nhello world\n', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
expect(shouldAutoFormat(' tag1 tag2 ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
});
|
||||
|
||||
test('should return true for non-comma-separated text with surrounding spaces when trimSurroundingSpaces is enabled', () => {
|
||||
settingValues.trimSurroundingSpaces = true;
|
||||
|
||||
// Text with surrounding spaces should return true to format (trim them)
|
||||
expect(shouldAutoFormat(' hello world ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||
expect(shouldAutoFormat('\nhello world\n', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||
expect(shouldAutoFormat(' tag1 tag2 ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||
expect(shouldAutoFormat(' \nsome text\n ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for text without surrounding spaces even when trimSurroundingSpaces is enabled', () => {
|
||||
settingValues.trimSurroundingSpaces = true;
|
||||
|
||||
// Text without word+comma pattern and without surrounding spaces should return false
|
||||
expect(shouldAutoFormat('hello world', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
expect(shouldAutoFormat('tag1 tag2', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatPromptText', () => {
|
||||
// Store original setting value to restore after tests
|
||||
const originalUseTrailingComma = settingValues.useTrailingComma;
|
||||
const originalTrimSurroundingSpaces = settingValues.trimSurroundingSpaces;
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original setting after each test
|
||||
settingValues.useTrailingComma = originalUseTrailingComma;
|
||||
settingValues.trimSurroundingSpaces = originalTrimSurroundingSpaces;
|
||||
});
|
||||
|
||||
describe('with useTrailingComma enabled', () => {
|
||||
beforeEach(() => {
|
||||
settingValues.useTrailingComma = true;
|
||||
settingValues.trimSurroundingSpaces = false;
|
||||
});
|
||||
|
||||
test('should format text by adding comma and space after tags', () => {
|
||||
@@ -101,6 +162,7 @@ describe('AutoFormatter Functions', () => {
|
||||
describe('with useTrailingComma disabled', () => {
|
||||
beforeEach(() => {
|
||||
settingValues.useTrailingComma = false;
|
||||
settingValues.trimSurroundingSpaces = false;
|
||||
});
|
||||
|
||||
test('should format text by adding comma and space after tags without trailing comma', () => {
|
||||
@@ -143,6 +205,59 @@ describe('AutoFormatter Functions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with trimSurroundingSpaces enabled', () => {
|
||||
beforeEach(() => {
|
||||
settingValues.trimSurroundingSpaces = true;
|
||||
settingValues.useTrailingComma = false;
|
||||
});
|
||||
|
||||
test('should remove trailing newlines and spaces', () => {
|
||||
const input = 'tag1, tag2\n\n ';
|
||||
const expected = 'tag1, tag2';
|
||||
expect(formatPromptText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should remove leading newlines and spaces', () => {
|
||||
const input = ' \n\ntag1, tag2';
|
||||
const expected = 'tag1, tag2';
|
||||
expect(formatPromptText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should remove both leading and trailing whitespaces but keep middle empty lines', () => {
|
||||
const input = ' \n\ntag1\n\ntag2\n\n ';
|
||||
const expected = 'tag1\n\ntag2';
|
||||
expect(formatPromptText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should return empty string if input is only whitespace', () => {
|
||||
const input = ' \n ';
|
||||
expect(formatPromptText(input)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with trimSurroundingSpaces disabled', () => {
|
||||
beforeEach(() => {
|
||||
settingValues.trimSurroundingSpaces = false;
|
||||
settingValues.useTrailingComma = false;
|
||||
});
|
||||
|
||||
test('should preserve trailing newlines', () => {
|
||||
const input = 'tag1, tag2\n\n';
|
||||
const expected = 'tag1, tag2\n\n';
|
||||
expect(formatPromptText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should preserve leading spaces (as first line content)', () => {
|
||||
const input = ' \ntag1';
|
||||
const expected = '\ntag1';
|
||||
expect(formatPromptText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
test('should handle input with only spaces', () => {
|
||||
expect(formatPromptText(' ')).toBe(' ');
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle empty input', () => {
|
||||
expect(formatPromptText('')).toBe('');
|
||||
expect(formatPromptText(null)).toBe(null);
|
||||
|
||||
+40
-14
@@ -7,15 +7,17 @@ import { NodeInfo } from './node-info.js';
|
||||
*
|
||||
* Format conditions:
|
||||
* 1. Skip formatting if node is in blocklist
|
||||
* 2. Skip formatting if text contains only numbers or single letters (separated by commas)
|
||||
* 3. Format if text contains "word + comma" pattern at least twice
|
||||
* 4. Otherwise, don't format
|
||||
* 2. If text is empty after trimming, format only if trimSurroundingSpaces is enabled, otherwise don't format
|
||||
* 3. Skip formatting if text contains only numbers or single letters (separated by commas)
|
||||
* 4. Format if text contains "word + comma" pattern
|
||||
* 5. Format if trimSurroundingSpaces is enabled and there are surrounding spaces or line breaks
|
||||
* 6. Otherwise, don't format
|
||||
*
|
||||
* @param {NodeInfo} nodeInfo - The node information.
|
||||
* @returns {boolean} - True if the text should be formatted, false otherwise.
|
||||
*/
|
||||
function shouldAutoFormat(text, nodeInfo) {
|
||||
if (!text || text.trim().length === 0) return false;
|
||||
if (!text) return false;
|
||||
|
||||
// 1. Check if the node name is in the blocklist
|
||||
const blocklist = [
|
||||
@@ -34,10 +36,14 @@ function shouldAutoFormat(text, nodeInfo) {
|
||||
// console.debug(`[Autocomplete-Plus] auto-formatter on blur => nodeType: ${nodeInfo.nodeType}, inputName: ${nodeInfo.inputName}`);
|
||||
}
|
||||
|
||||
|
||||
const trimmedText = text.trim();
|
||||
|
||||
// 2. Check if the text is purely numeric data or single-letter placeholders with commas
|
||||
// 2. Check if the text is completely empty after trimming
|
||||
if (trimmedText.length === 0) {
|
||||
return settingValues.trimSurroundingSpaces;
|
||||
}
|
||||
|
||||
// 3. Check if the text is purely numeric data or single-letter placeholders with commas
|
||||
// (e.g., "0,0,0,1,1,1" or "0.5, -1.2, 0.8" or "A,B,R" for LoRA Block Weight)
|
||||
const elements = trimmedText.split(',').map(el => el.trim());
|
||||
const isSingleLetterOrNumeric = elements.every(el => {
|
||||
@@ -50,15 +56,18 @@ function shouldAutoFormat(text, nodeInfo) {
|
||||
return false; // Don't format numeric data or single-letter template patterns
|
||||
}
|
||||
|
||||
// 3. Check if the text contains the pattern "word + comma"
|
||||
// 4. If text contains "word + comma" pattern, format it
|
||||
const wordCommaPattern = /\w+\s*,/g;
|
||||
const matches = trimmedText.match(wordCommaPattern);
|
||||
|
||||
if (matches == null) {
|
||||
return false;
|
||||
if (trimmedText.match(wordCommaPattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true; // Text should be formatted
|
||||
// 5. If trimSurroundingSpaces is enabled and there are surrounding spaces, format to trim them
|
||||
if (settingValues.trimSurroundingSpaces && text !== trimmedText) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Otherwise, don't format
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,10 +77,27 @@ function shouldAutoFormat(text, nodeInfo) {
|
||||
* @returns {string} - The formatted text.
|
||||
*/
|
||||
export function formatPromptText(text) {
|
||||
if (!text || text.trim().length === 0) return text;
|
||||
// Handle null or undefined input
|
||||
if (text == null) return text;
|
||||
|
||||
// If the text is empty or contains only spaces...
|
||||
if (text.trim().length === 0) {
|
||||
if (settingValues.trimSurroundingSpaces) {
|
||||
// ...And trimSurroundingSpaces is enabled, return an empty string
|
||||
return '';
|
||||
} else {
|
||||
// ...Otherwise, return the original text
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
let processedText = text;
|
||||
if (settingValues.trimSurroundingSpaces) {
|
||||
processedText = text.trim();
|
||||
}
|
||||
|
||||
// Split text into individual lines for processing
|
||||
const lines = text.split('\n');
|
||||
const lines = processedText.split('\n');
|
||||
const formattedLines = [];
|
||||
|
||||
for (const line of lines) {
|
||||
|
||||
+24
-2
@@ -351,11 +351,13 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
|
||||
const needsSpaceBefore = text[replaceStart - 1] === ',';
|
||||
const prefix = needsSpaceBefore ? ' ' : '';
|
||||
|
||||
const prefixArtist = tagDataToInsert.categoryText == 'artist' ? settingValues.prefixArtist : '';
|
||||
|
||||
// Standard separator (comma + space, or empty if autoInsertComma is disabled)
|
||||
const needsSuffixAfter = !",:".includes(text[replaceEnd]); // TODO: If ":" is part of the emoticon, a suffix is required (e.g. ":o")
|
||||
const suffix = (needsSuffixAfter && settingValues.autoInsertComma) ? ', ' : '';
|
||||
|
||||
const textToInsertWithAffixes = prefix + normalizedTag + suffix;
|
||||
const textToInsertWithAffixes = prefix + prefixArtist + normalizedTag + suffix;
|
||||
|
||||
// --- Use execCommand for Undo support ---
|
||||
// 1. Select the text range to be replaced
|
||||
@@ -939,6 +941,25 @@ export class AutocompleteEventHandler {
|
||||
constructor() {
|
||||
this.autocompleteUI = new AutocompleteUI();
|
||||
this.keyDownWithModifier = new Map(); // Keep track of keydown events with modifiers
|
||||
this._debounceTimer = null; // Timer ID for debounced search
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls autocompleteUI.updateDisplay() with debounce for sequential search, or immediately for fast search.
|
||||
* @param {HTMLTextAreaElement} target
|
||||
*/
|
||||
_triggerUpdateDisplay(target) {
|
||||
if (settingValues.useFastSearch || settingValues.searchDebounceTime <= 0) {
|
||||
// FastSearch or debounce disabled: immediate update
|
||||
this.autocompleteUI.updateDisplay(target);
|
||||
} else {
|
||||
// Sequential search: debounced update
|
||||
clearTimeout(this._debounceTimer);
|
||||
this._debounceTimer = setTimeout(() => {
|
||||
this.autocompleteUI.updateDisplay(target);
|
||||
this._debounceTimer = null;
|
||||
}, settingValues.searchDebounceTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -952,6 +973,7 @@ export class AutocompleteEventHandler {
|
||||
|
||||
const partialTag = getCurrentPartialTag(event.target);
|
||||
if (partialTag.length <= 0) {
|
||||
clearTimeout(this._debounceTimer); // Cancel pending debounced search
|
||||
this.autocompleteUI.hide();
|
||||
}
|
||||
}
|
||||
@@ -1062,7 +1084,7 @@ export class AutocompleteEventHandler {
|
||||
// and default action is not prevented, update the display.
|
||||
// This will typically be for character inputs, Delete, Backspace or IME composition.
|
||||
if (!event.defaultPrevented) {
|
||||
this.autocompleteUI.updateDisplay(event.target);
|
||||
this._triggerUpdateDisplay(event.target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-8
@@ -50,16 +50,19 @@ function initializeEventHandlers() {
|
||||
ComfyWidgets.STRING = function (node, inputName, inputData, appInstance) { // Use appInstance to avoid conflict with global app
|
||||
const result = originalStringWidget.apply(this, arguments);
|
||||
|
||||
// Check if the widget has an inputEl and if it's a TEXTAREA
|
||||
// Check if the widget has an element and if it's a TEXTAREA
|
||||
// This is to ensure we are targeting multiline text inputs, related to '.comfy-multiline-input'
|
||||
if (result && result.widget
|
||||
&& result.widget.inputEl && result.widget.inputEl.tagName === 'TEXTAREA' && !result.widget.inputEl.readOnly) {
|
||||
const widgetConfig = inputData && inputData[1] ? inputData[1] : {};
|
||||
// Future: Add checks for Autocomplete Plus specific configurations if needed
|
||||
// e.g., if (widgetConfig["AutocompletePlus.enabled"] === false) return result;
|
||||
if (result && result.widget) {
|
||||
// fallback for older Comfyui frontend versions
|
||||
const inputEl = result.widget.element ?? result.widget.inputEl;
|
||||
if (inputEl && inputEl.tagName === 'TEXTAREA' && !inputEl.readOnly) {
|
||||
const widgetConfig = inputData && inputData[1] ? inputData[1] : {};
|
||||
// Future: Add checks for Autocomplete Plus specific configurations if needed
|
||||
// e.g., if (widgetConfig["AutocompletePlus.enabled"] === false) return result;
|
||||
|
||||
const nodeInfo = new NodeInfo(node.comfyClass || node.constructor.name, inputName);
|
||||
attachListeners(result.widget.inputEl, nodeInfo);
|
||||
const nodeInfo = new NodeInfo(node.comfyClass || node.constructor.name, inputName);
|
||||
attachListeners(inputEl, nodeInfo);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
@@ -377,6 +380,17 @@ app.registerExtension({
|
||||
settingValues.enableModels = newVal;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: id + ".Autocompletion.PrefixArtist",
|
||||
name: "String to add before artist tags",
|
||||
tooltip: "Text to prepend when inserting an artist tag via autocomplete.\ne.g. '@' -> '@artist_name'.",
|
||||
type: "text",
|
||||
defaultValue: '',
|
||||
category: [name, "Autocompletion", "String to add before artist tags"],
|
||||
onChange: (newVal, oldVal) => {
|
||||
settingValues.prefixArtist = newVal;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: id + ".Autocompletion.ReplaceUnderscoreWithSpace",
|
||||
name: "Replace '_' with 'Space'",
|
||||
@@ -490,6 +504,17 @@ app.registerExtension({
|
||||
},
|
||||
|
||||
// --- Auto format settings ---
|
||||
{
|
||||
id: id + '.AutoFormatter.TrimSurroundingSpaces',
|
||||
name: 'Trim Surrounding Spaces',
|
||||
tooltip: 'When enabled, trim any blank lines from the beginning and end of the prompt.',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
category: [name, 'AutoFormatter', 'Trim Surrounding Spaces'],
|
||||
onChange: (newVal, oldVal) => {
|
||||
settingValues.trimSurroundingSpaces = newVal;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: id + '.AutoFormatter.UseTrailingComma',
|
||||
name: 'Use Trailing Comma',
|
||||
|
||||
@@ -10,7 +10,9 @@ export const settingValues = {
|
||||
enableModels: true, // Enable Lora and Embedding suggestions
|
||||
useFastSearch: false,
|
||||
replaceUnderscoreWithSpace: true, // Replace underscores with spaces in tag insertion
|
||||
prefixArtist: '', // Prefix to be attached before artist tags
|
||||
autoInsertComma: true,
|
||||
searchDebounceTime: 100, // Sequential search debounce time in milliseconds
|
||||
|
||||
// Related tags feature settings
|
||||
enableRelatedTags: true,
|
||||
@@ -25,6 +27,7 @@ export const settingValues = {
|
||||
enableAutoFormat: true,
|
||||
autoFormatTrigger: 'auto', // Options: 'auto' (format on blur + shortcut), 'manual' (shortcut only)
|
||||
useTrailingComma: false, // Whether to add comma at the end of each line
|
||||
trimSurroundingSpaces: false, // Trim spaces around each tag
|
||||
|
||||
|
||||
// Internal logic settings
|
||||
|
||||
Reference in New Issue
Block a user