Compare commits

...
13 Commits
Author SHA1 Message Date
newtextdoc1111 c3e661439f Merge pull request #27 from newtextdoc1111/bugfix/#25_fix_natural_language_performance
Bugfix/#25 fix natural language performance
2025-07-13 22:57:01 +09:00
newtextdoc1111 ea2ff76fab fix: Bump version to 1.2.1 2025-07-13 22:48:48 +09:00
newtextdoc1111 8b38ade1b9 fix: Add optional chaining to prevent errors in isLongText function 2025-07-13 22:48:36 +09:00
newtextdoc1111 944ecff9ce fix: Implement maximum tag length check for performance issue 2025-07-13 22:21:21 +09:00
newtextdoc1111 a9273fb74d docs: Update README to clarify steps for disabling CSV update check on startup 2025-07-06 20:01:09 +09:00
newtextdoc1111 cf30c94faa Merge pull request #24 from newtextdoc1111/feature/#23_add_check_csv_updates_on_startup_settings 2025-07-06 19:17:09 +09:00
newtextdoc1111 828c08df0c fix: Bump version to 1.2.0 in pyproject.toml 2025-07-06 19:11:12 +09:00
newtextdoc1111 3dbfd29274 docs: Added disable CSV update check on startup section 2025-07-06 19:10:06 +09:00
newtextdoc1111 86527e35b4 feat: Add check_updates_on_startup option to control CSV update checks 2025-07-06 19:08:24 +09:00
newtextdoc1111 1bc609893b refactor: Use constant for HuggingFace URL 2025-07-06 19:07:55 +09:00
newtextdoc1111 a536d58cf2 docs: Remove recent updates section from README files 2025-07-06 18:23:04 +09:00
newtextdoc1111 4c921da6cb fix: Update CSV file status logging format for better readability 2025-07-05 14:29:30 +09:00
newtextdoc1111 98bf626ec1 refactor: Format code with ruff and update configuration 2025-07-05 13:40:17 +09:00
11 changed files with 240 additions and 130 deletions
+27 -5
View File
@@ -8,11 +8,6 @@
**ComfyUI-Autocomplete-Plus** is a custom node that provides multiple input assistance features for any text area in [ComfyUI](https://github.com/comfyanonymous/ComfyUI). Currently, it supports Danbooru and e621 tags (e621 does not support some functions).
## Recent Updates :fire:
- Added automatic and manual update check functionality for CSV files
- Fixed bug where autocomplete suggestions wouldn't display at certain timings with Microsoft IME
- Support for loading and displaying e621 tag CSV
## Features
- **:zap:No setup required**: Automatically downloads CSV data optimized for Danbooru tags.
@@ -150,6 +145,33 @@ When the browser is reloaded, you can check the list of loaded CSV files in the
- **Check CSV updates**: Click the "Check Now" button to check if new CSV files are available in HuggingFace and download them if necessary.
## Advanced Settings
### Disabling CSV Update Check on Startup
By default, ComfyUI performs CSV file update checks and downloads at regular intervals during startup.
When starting in an environment without internet access, startup may be delayed until a timeout occurs.
You can skip the check process during ComfyUI startup by following these steps:
1. Start ComfyUI once with this custom node installed to generate the `csv_meta.json` file.
The `csv_meta.json` file is created directly under this custom node's folder.
2. Open `csv_meta.json` in a text editor and change the value of `check_updates_on_startup` from `true` to `false` and save.
If `check_updates_on_startup` does not exist, add it under `version`.
**`csv_meta.json` after modification:**
```json
{
"version": 1,
"check_updates_on_startup": false,
...
}
```
**Additional notes:**
- The check process will not be performed until the value of `check_updates_on_startup` is changed back to `true` or the `version` is switched.
- Even when `check_updates_on_startup` is `false`, manual checking is still possible by pressing the `Check CSV updates` button in the Autocomplete Plus settings.
## Known Issues
### Performance
+1 -1
View File
@@ -1,4 +1,4 @@
from .modules.api import *
from .modules.api import *
from .modules import downloader
# check and download necessary csv files
+27 -5
View File
@@ -6,11 +6,6 @@
**ComfyUI-Autocomplete-Plus** は、[ComfyUI](https://github.com/comfyanonymous/ComfyUI) の任意のテキストエリアに複数の入力支援機能を提供するカスタムノードです。現在はDanbooruとe621のタグに対応しています(e621は一部の機能が未対応です)。
## 最近の更新 :fire:
- CSV ファイルの自動・手動更新チェック機能の追加
- Microsoft IMEでオートコンプリート候補が表示されないタイミングがある不具合の修正
- e621タグ CSV の読み込みと表示のサポート
## 特徴
- **:zap:セットアップ不要**: Danbooruタグに最適化された CSV データを自動でダウンロード
@@ -148,6 +143,33 @@ worst_quality,5,9999999,
- **Check CSV updates**: 「Check Now」ボタンを押すと新しい CSV ファイルがHuggingFaceにあるか確認し、必要に応じてダウンロードを行います
## 上級者向け設定
### 起動時のCSV更新チェックを無効化する
デフォルトの動作では、ComfyUI起動時に一定の間隔で CSV ファイルの更新チェックとダウンロード行います。
インターネットにアクセス出来ない環境で起動した場合、タイムアウトが発生するまで起動が遅延する事があります。
以下の手順を行う事により、ComfyUI起動時のチェック処理をスキップする事が出来ます。
1. このカスタムノードをインストールした状態でComfyUIを一度起動し、 `csv_meta.json` ファイルを生成する
`csv_meta.json` はこのカスタムノードのフォルダー直下に作成されます
2. `csv_meat.json` をテキストエディターで開き、`check_updates_on_startup` の値を `true` -> `false` に変更し保存する
`check_updates_on_startup` が存在しない場合、 `version` の下に追記してください
**変更後の `csv_meta.json`:**
```json
{
"version": 1,
"check_updates_on_startup": false,
...
}
```
**補足事項:**
- `check_updates_on_startup` の値を再び `true` にするか、 `version` が切り替わるまでチェック処理は行われなくなります
- `check_updates_on_startup` が `false` でも、Autocompelte Plusの設定から `Check CSV updates` のボタンを押す事で手動チェックが可能です
## 既知の問題
### パフォーマンス
+85 -83
View File
@@ -10,11 +10,12 @@ from . import downloader as dl
# os.path.join(..., '..', 'data') goes up one level and then into 'data'
DATA_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "data"))
DANBOORU_PREFIX = 'danbooru'
E621_PREFIX = 'e621'
DANBOORU_PREFIX = "danbooru"
E621_PREFIX = "e621"
TAGS_SUFFIX = "tags"
COOCCURRENCE_SUFFIX = "tags_cooccurrence"
TAGS_SUFFIX = 'tags'
COOCCURRENCE_SUFFIX = 'tags_cooccurrence'
def get_csv_file_status():
"""
@@ -23,17 +24,17 @@ def get_csv_file_status():
data = {
DANBOORU_PREFIX: {
'base_tags': False,
'extra_tags': [],
'base_cooccurrence': False,
'extra_cooccurrence': [],
"base_tags": False,
"extra_tags": [],
"base_cooccurrence": False,
"extra_cooccurrence": [],
},
E621_PREFIX: {
'base_tags': False,
'extra_tags': [],
'base_cooccurrence': False,
'extra_cooccurrence': [],
}
"base_tags": False,
"extra_tags": [],
"base_cooccurrence": False,
"extra_cooccurrence": [],
},
}
for prefix in [DANBOORU_PREFIX, E621_PREFIX]:
@@ -46,27 +47,28 @@ def get_csv_file_status():
tags_extra_files = []
cooccurrence_extra_files = []
all_csv_files = [f for f in os.listdir(DATA_DIR) if f.startswith(prefix) and f.endswith('.csv')]
all_csv_files = [f for f in os.listdir(DATA_DIR) if f.startswith(prefix) and f.endswith(".csv")]
# Create extra CSV files list
for filename in all_csv_files:
if filename in [base_tags_file, base_cooccurrence_file]:
continue # Skip base files
continue # Skip base files
if COOCCURRENCE_SUFFIX in filename.lower():
cooccurrence_extra_files.append(filename)
elif TAGS_SUFFIX in filename.lower():
tags_extra_files.append(filename)
data[prefix] = {
'base_tags': tags_base_exists,
'extra_tags': tags_extra_files,
'base_cooccurrence': cooccurrence_base_exists,
'extra_cooccurrence': cooccurrence_extra_files,
"base_tags": tags_base_exists,
"extra_tags": tags_extra_files,
"base_cooccurrence": cooccurrence_base_exists,
"extra_cooccurrence": cooccurrence_extra_files,
}
# Return the lists of extra files
return data
def get_last_check_time_from_metadata():
"""
Helper function to get the last remote check timestamp from csv_meta.json.
@@ -75,23 +77,25 @@ def get_last_check_time_from_metadata():
try:
if not os.path.exists(dl.CSV_META_FILE):
return None
with open(dl.CSV_META_FILE, 'r', encoding='utf-8') as f:
with open(dl.CSV_META_FILE, "r", encoding="utf-8") as f:
metadata = json.load(f)
datasets = metadata.get("hf_datasets", [])
if datasets and len(datasets) > 0:
return datasets[0].get("last_remote_check_timestamp")
return None
except (IOError, json.JSONDecodeError) as e:
print(f"[Autocomplete-Plus] Error reading csv_meta.json: {e}")
return None
# --- API Endpoints ---
@server.PromptServer.instance.routes.get('/autocomplete-plus/csv')
@server.PromptServer.instance.routes.get("/autocomplete-plus/csv")
async def get_csv_list(_request):
"""
Returns CSV file status.
@@ -102,43 +106,48 @@ async def get_csv_list(_request):
response = {
DANBOORU_PREFIX: {
'base_tags': csv_file_status[DANBOORU_PREFIX]['base_tags'],
'extra_tags': csv_file_status[DANBOORU_PREFIX]['extra_tags'],
'base_cooccurrence': csv_file_status[DANBOORU_PREFIX]['base_cooccurrence'],
'extra_cooccurrence': csv_file_status[DANBOORU_PREFIX]['extra_cooccurrence'],
"base_tags": csv_file_status[DANBOORU_PREFIX]["base_tags"],
"extra_tags": csv_file_status[DANBOORU_PREFIX]["extra_tags"],
"base_cooccurrence": csv_file_status[DANBOORU_PREFIX]["base_cooccurrence"],
"extra_cooccurrence": csv_file_status[DANBOORU_PREFIX]["extra_cooccurrence"],
},
E621_PREFIX: {
'base_tags': csv_file_status[E621_PREFIX]['base_tags'],
'extra_tags': csv_file_status[E621_PREFIX]['extra_tags'],
'base_cooccurrence': csv_file_status[E621_PREFIX]['base_cooccurrence'],
'extra_cooccurrence': csv_file_status[E621_PREFIX]['extra_cooccurrence'],
}
"base_tags": csv_file_status[E621_PREFIX]["base_tags"],
"extra_tags": csv_file_status[E621_PREFIX]["extra_tags"],
"base_cooccurrence": csv_file_status[E621_PREFIX]["base_cooccurrence"],
"extra_cooccurrence": csv_file_status[E621_PREFIX]["extra_cooccurrence"],
},
}
# Print csv file status to the console for debugging
print(f"""[Autocomplete-Plus] CSV file status:
* Danbooru -> base: {response[DANBOORU_PREFIX]['base_tags']}, extra: {", ".join(response[DANBOORU_PREFIX]['extra_tags'])}
* E621 -> base: {response[E621_PREFIX]['base_tags']}, extra: {", ".join(response[E621_PREFIX]['extra_tags'])}""")
* Danbooru -> base: {response[DANBOORU_PREFIX]["base_tags"]}, extra: [{", ".join(response[DANBOORU_PREFIX]["extra_tags"])}]
* E621 -> base: {response[E621_PREFIX]["base_tags"]}, extra: [{", ".join(response[E621_PREFIX]["extra_tags"])}]""")
return web.json_response(response)
@server.PromptServer.instance.routes.get('/autocomplete-plus/csv/{source}/{suffix}/base')
@server.PromptServer.instance.routes.get("/autocomplete-plus/csv/{source}/{suffix}/base")
async def get_base_tags_file(request):
"""
Returns the base tags CSV file.
"""
source = str(request.match_info['source'])
suffix = str(request.match_info['suffix'])
if source not in [DANBOORU_PREFIX, E621_PREFIX] or suffix not in [TAGS_SUFFIX, COOCCURRENCE_SUFFIX]:
source = str(request.match_info["source"])
suffix = str(request.match_info["suffix"])
if source not in [DANBOORU_PREFIX, E621_PREFIX] or suffix not in [
TAGS_SUFFIX,
COOCCURRENCE_SUFFIX,
]:
return web.json_response({"error": "Invalid tag source or suffix"}, status=400)
file_path = os.path.join(DATA_DIR, f"{source}_{suffix}.csv")
if not os.path.exists(file_path):
return web.json_response({"error": "Base tags file not found"}, status=404)
return web.FileResponse(file_path)
@server.PromptServer.instance.routes.get('/autocomplete-plus/csv/{source}/{suffix}/extra/{index}')
@server.PromptServer.instance.routes.get("/autocomplete-plus/csv/{source}/{suffix}/extra/{index}")
async def get_extra_tags_file(request):
"""
Returns the extra tags CSV file at the specified index.
@@ -146,16 +155,19 @@ async def get_extra_tags_file(request):
try:
csv_file_status = get_csv_file_status()
source = str(request.match_info['source'])
suffix = str(request.match_info['suffix'])
if source not in [DANBOORU_PREFIX, E621_PREFIX] or suffix not in [TAGS_SUFFIX, COOCCURRENCE_SUFFIX]:
source = str(request.match_info["source"])
suffix = str(request.match_info["suffix"])
if source not in [DANBOORU_PREFIX, E621_PREFIX] or suffix not in [
TAGS_SUFFIX,
COOCCURRENCE_SUFFIX,
]:
return web.json_response({"error": "Invalid tag source or suffix"}, status=400)
index = int(request.match_info['index'])
if index < 0 or index >= len(csv_file_status[source][f'extra_{suffix}']):
index = int(request.match_info["index"])
if index < 0 or index >= len(csv_file_status[source][f"extra_{suffix}"]):
return web.json_response({"error": "Invalid index"}, status=404)
file_path = os.path.join(DATA_DIR, csv_file_status[source][f'extra_{suffix}'][index])
file_path = os.path.join(DATA_DIR, csv_file_status[source][f"extra_{suffix}"][index])
if not os.path.exists(file_path):
return web.json_response({"error": "Extra tags file not found"}, status=404)
@@ -164,7 +176,8 @@ async def get_extra_tags_file(request):
except ValueError:
return web.json_response({"error": "Invalid index format"}, status=400)
@server.PromptServer.instance.routes.post('/autocomplete-plus/csv/force-check-updates')
@server.PromptServer.instance.routes.post("/autocomplete-plus/csv/force-check-updates")
async def force_check_csv_updates(request):
"""
Forces a check for CSV file updates from HuggingFace, ignoring cooldown.
@@ -172,29 +185,29 @@ async def force_check_csv_updates(request):
"""
try:
print("[Autocomplete-Plus] Starting forced check for CSV updates from HuggingFace...")
downloader = dl.Downloader()
downloader.run_check_and_download(force_check=True)
print("[Autocomplete-Plus] Forced check completed successfully.")
# Get the updated last check time
last_check_time = get_last_check_time_from_metadata()
return web.json_response({
"success": True,
"message": "Force check completed successfully",
"last_check_time": last_check_time
})
return web.json_response(
{
"success": True,
"message": "Force check completed successfully",
"last_check_time": last_check_time,
}
)
except Exception as e:
print(f"[Autocomplete-Plus] Error during forced check: {e}")
return web.json_response({
"success": False,
"error": str(e)
}, status=500)
return web.json_response({"success": False, "error": str(e)}, status=500)
@server.PromptServer.instance.routes.get('/autocomplete-plus/csv/last-check-time')
@server.PromptServer.instance.routes.get("/autocomplete-plus/csv/last-check-time")
async def get_last_check_time(_request):
"""
Returns the last remote check timestamp from csv_meta.json.
@@ -202,26 +215,15 @@ async def get_last_check_time(_request):
"""
try:
if not os.path.exists(dl.CSV_META_FILE):
return web.json_response({
"last_check_time": None,
"message": "csv_meta.json file not found"
})
return web.json_response({"last_check_time": None, "message": "csv_meta.json file not found"})
last_check_time = get_last_check_time_from_metadata()
if last_check_time is not None:
return web.json_response({
"last_check_time": last_check_time
})
return web.json_response({"last_check_time": last_check_time})
else:
return web.json_response({
"last_check_time": None,
"message": "No datasets found in metadata"
})
return web.json_response({"last_check_time": None, "message": "No datasets found in metadata"})
except (IOError, json.JSONDecodeError) as e:
print(f"[Autocomplete-Plus] Error reading csv_meta.json: {e}")
return web.json_response({
"last_check_time": None,
"error": str(e)
}, status=500)
return web.json_response({"last_check_time": None, "error": str(e)}, status=500)
+54 -26
View File
@@ -20,7 +20,8 @@ CSV_META_FILE = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", C
DEFAULT_CSV_METADATA = {
"version": 1,
"hf_datasets" : [
"check_updates_on_startup": True,
"hf_datasets": [
{
"hf_dataset_id": "newtextdoc1111/danbooru-tag-csv",
"last_remote_check_timestamp": None,
@@ -34,12 +35,15 @@ DEFAULT_CSV_METADATA = {
"file_name": "danbooru_tags_cooccurrence.csv",
"last_download": None,
"last_modified_on_hf": None,
}
]
},
],
}
]
],
}
# --- HuggingFace Constants ---
HUGGINGFACE_URL = "https://huggingface.co"
# --- Helper Functions ---
def get_file_path(file_name: str) -> str:
@@ -82,12 +86,14 @@ class Downloader:
return default_metadata
try:
with open(CSV_META_FILE, 'r', encoding='utf-8') as f:
with open(CSV_META_FILE, "r", encoding="utf-8") as f:
metadata = json.load(f)
if not isinstance(metadata, dict) or metadata.get("version") != DEFAULT_CSV_METADATA["version"]:
print(f"[Autocomplete-Plus] Metadata version mismatch. Expected {DEFAULT_CSV_METADATA['version']}, "
f"found {metadata.get('version')}. Using default metadata.")
print(
f"[Autocomplete-Plus] Metadata version mismatch. Expected {DEFAULT_CSV_METADATA['version']}, "
f"found {metadata.get('version')}. Using default metadata."
)
return default_metadata
else:
self.csv_meta_file_exists_at_start = True
@@ -101,7 +107,7 @@ class Downloader:
"""Saves metadata to CSV_META_FILE."""
try:
os.makedirs(os.path.dirname(CSV_META_FILE), exist_ok=True)
with open(CSV_META_FILE, 'w', encoding='utf-8') as f:
with open(CSV_META_FILE, "w", encoding="utf-8") as f:
json.dump(self.metadata, f, indent=2)
except IOError as e:
print(f"[Autocomplete-Plus] Error saving metadata to {CSV_META_FILE}: {e}")
@@ -111,12 +117,15 @@ class Downloader:
Retrieves the Last-Modified header for a file on HuggingFace and returns it as an ISO 8601 string.
hf_filename should be the full filename, e.g., "danbooru_tags.csv".
"""
url = f"https://huggingface.co/datasets/{dataset_repo_id}/resolve/main/{hf_filename}"
url = f"{HUGGINGFACE_URL}/datasets/{dataset_repo_id}/resolve/main/{hf_filename}"
try:
req = urllib.request.Request(url, method='HEAD',
headers={"User-Agent": "ComfyUI-Autocomplete-Plus (Python urllib)"})
req = urllib.request.Request(
url,
method="HEAD",
headers={"User-Agent": "ComfyUI-Autocomplete-Plus (Python urllib)"},
)
with urllib.request.urlopen(req, timeout=10) as response:
last_modified_http = response.getheader('Last-Modified')
last_modified_http = response.getheader("Last-Modified")
if last_modified_http:
dt_object = parsedate_to_datetime(last_modified_http)
if dt_object.tzinfo is None or dt_object.tzinfo.utcoffset(dt_object) is None:
@@ -141,7 +150,7 @@ class Downloader:
"""
Downloads a file synchronously with progress display to a temporary location.
"""
download_url = f"https://huggingface.co/datasets/{hf_dataset_id}/resolve/main/{file_name}"
download_url = f"{HUGGINGFACE_URL}/datasets/{hf_dataset_id}/resolve/main/{file_name}"
final_path = get_file_path(file_name)
temp_path = get_temp_download_path(file_name)
now_utc = datetime.now(timezone.utc).isoformat()
@@ -155,18 +164,28 @@ class Downloader:
if os.path.exists(temp_path):
os.remove(temp_path)
req = urllib.request.Request(download_url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 11.0; Win64)"})
req = urllib.request.Request(
download_url,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 11.0; Win64)"},
)
with urllib.request.urlopen(req) as response:
total_size_str = response.getheader("Content-Length")
total_size = int(total_size_str) if total_size_str else None
chunk_size = 8192
with open(temp_path, "wb") as f_out, \
tqdm(total=total_size, unit='B', unit_scale=True, unit_divisor=1024,
desc=f"[Autocomplete-Plus] Downloading {file_name}", leave=False,
bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
) as pbar:
with (
open(temp_path, "wb") as f_out,
tqdm(
total=total_size,
unit="B",
unit_scale=True,
unit_divisor=1024,
desc=f"[Autocomplete-Plus] Downloading {file_name}",
leave=False,
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]",
) as pbar,
):
while True:
chunk = response.read(chunk_size)
if not chunk:
@@ -197,12 +216,15 @@ class Downloader:
if os.path.exists(final_path):
try:
file_size_at_final = os.path.getsize(final_path)
if file_size_at_final == 0 or \
(total_size and total_size > 0 and file_size_at_final < total_size) or \
(not total_size and downloaded_size > 0 and file_size_at_final < downloaded_size):
if (
file_size_at_final == 0
or (total_size and total_size > 0 and file_size_at_final < total_size)
or (not total_size and downloaded_size > 0 and file_size_at_final < downloaded_size)
):
os.remove(final_path)
print(
f"[Autocomplete-Plus] Removed potentially corrupted file at final destination: {final_path}")
f"[Autocomplete-Plus] Removed potentially corrupted file at final destination: {final_path}"
)
except OSError as rm_e:
print(f"[Autocomplete-Plus] Error removing potentially corrupted file {final_path}: {rm_e}")
@@ -221,7 +243,8 @@ class Downloader:
perform_hf_check = False
except (ValueError, KeyError, TypeError):
print(
"[Autocomplete-Plus] Invalid or missing timestamp for last_remote_check_timestamp. Will perform remote check.")
"[Autocomplete-Plus] Invalid or missing timestamp for last_remote_check_timestamp. Will perform remote check."
)
if perform_hf_check:
huggingface_dataset_id = dataset_meta["hf_dataset_id"]
@@ -246,7 +269,8 @@ class Downloader:
dataset_meta["last_remote_check_timestamp"] = now_utc.isoformat()
else:
print(
"[Autocomplete-Plus] Could not update all remote timestamps from HuggingFace. Will try again later.")
"[Autocomplete-Plus] Could not update all remote timestamps from HuggingFace. Will try again later."
)
def _download_csv_files_if_needed(self, dataset_meta: dict):
"""Downloads CSV files if they are missing, outdated, or previously failed."""
@@ -299,10 +323,14 @@ class Downloader:
"""
Orchestrates the process of checking for updates and downloading CSV files.
This is the main entry point for the downloader logic.
Args:
force_check: If True, forces a check of HuggingFace regardless of the last check timestamp.
"""
# If check_updates_on_startup is False and force_check is not set, skip the check
if not (self.metadata.get("check_updates_on_startup", True) or force_check):
print('[Autocomplete-Plus] "check_updates_on_startup" is disabled. Skipping CSV update check and download.')
return
now_utc = datetime.now(timezone.utc)
+10 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-autocomplete-plus"
description = "Autocomplete and Related Tag display for ComfyUI"
version = "1.1.0"
version = "1.2.1"
license = {file = "LICENSE"}
dependencies = ["",]
@@ -9,7 +9,16 @@ dependencies = ["",]
Repository = "https://github.com/newtextdoc1111/ComfyUI-Autocomplete-Plus"
# Used by Comfy Registry https://comfyregistry.org
[dependency-groups]
dev = [
"ruff>=0.12.2",
]
[tool.comfy]
PublisherId = "newtextdoc1111"
DisplayName = "ComfyUI-Autocomplete-Plus"
Icon = ""
[tool.ruff]
# Allow lines to be as long as 120.
line-length = 120
+6 -3
View File
@@ -5,9 +5,10 @@ import {
getEnabledTagSourceInPriorityOrder
} from './data.js';
import {
formatCountHumanReadable,
isLongText,
hiraToKata,
kataToHira,
formatCountHumanReadable,
isContainsLetterOrNumber,
normalizeTagToInsert,
normalizeTagToSearch,
@@ -82,8 +83,10 @@ function searchCompletionCandidates(textareaElement) {
const ESCAPE_SEQUENCE = ["#", "/"]; // If the first string is that character, autocomplete will not be displayed.
const partialTag = getCurrentPartialTag(textareaElement);
if (!partialTag || partialTag.length <= 0 || ESCAPE_SEQUENCE.some(seq => partialTag.startsWith(seq))) {
return []; // No valid input for autocomplete
if (!partialTag || partialTag.length <= 0 ||
ESCAPE_SEQUENCE.some(seq => partialTag.startsWith(seq)) ||
isLongText(partialTag)) {
return []; // No valid input for autocomplete
}
const exactMatches = [];
+3 -2
View File
@@ -1,4 +1,4 @@
import { settingValues } from "./settings.js";
import { settingValues, updateMaxTagLength } from "./settings.js";
// --- Constants ---
@@ -158,6 +158,8 @@ async function loadTags(csvUrl, siteName) {
// Create a TagData instance instead of a plain object
const tagData = new TagData(tag, aliases, category, count, siteName);
updateMaxTagLength(tag.length);
autoCompleteData[siteName].sortedTags.push(tagData);
} else {
console.warn(`[Autocomplete-Plus] Invalid CSV format in line ${i + 1} of ${csvUrl}: ${line}. Expected ${TAGS_CSV_HEADER_COLUMNS.length} columns, but got ${columns.length}.`);
@@ -326,7 +328,6 @@ export async function initializeData(csvListData, source) {
const startTime = performance.now();
autoCompleteData[source].isInitializing = true;
// console.log("[Autocomplete-Plus] Initializing autocomplete data...");
try {
// Store functions that return Promises (Promise Factories)
+4 -3
View File
@@ -1,13 +1,14 @@
import { TagSource, TagCategory, TagData, autoCompleteData } from './data.js';
import { TagCategory, TagData, TagSource, autoCompleteData } from './data.js';
import { settingValues } from './settings.js';
import {
extractTagsFromTextArea,
findAllTagPositions,
getCurrentTagRange,
getViewportMargin,
isLongText,
isValidTag,
normalizeTagToInsert,
normalizeTagToSearch,
getCurrentTagRange,
} from './utils.js';
// --- RelatedTags Logic ---
@@ -309,7 +310,7 @@ class RelatedTagsUI {
const currentTag = getTagFromCursorPosition(textareaElement);
if (!this.isPinned) {
if (isValidTag(currentTag)) {
if (!isLongText(currentTag) && isValidTag(currentTag)) {
this.currentTag = currentTag
} else {
this.hide();
+12 -1
View File
@@ -17,8 +17,19 @@ export const settingValues = {
// Internal logic settings
_useFallbackAttachmentForEventListener: false, // Fallback to attach event listener when somthing goes wrong
_maxTagLength: 100, // Maximum tag length to prevent performance issues with long text input
// Debugging settings (use internally)
_hideWhenOutofFocus: true, // Hide UI when the input is out of focus
_logprocessingTime: false, // Log processing time for debugging
}
}
/**
* Update the maximum tag length setting value.
* This function ensures that the maximum tag length is always at least as long as the new length provided.
* @param {number} newLength
*/
export function updateMaxTagLength(newLength) {
if(isNaN(newLength)) return;
settingValues._maxTagLength = Math.max(settingValues._maxTagLength, newLength);
}
+11
View File
@@ -1,3 +1,5 @@
import { settingValues } from './settings.js';
// --- Html String constants ---
/**
@@ -44,6 +46,15 @@ const REG_STRIP_TRAILING_PAREN = /(.*)(?<!\\)\)$/s;
const REG_WILDCARD_WEIGHTED_TAG = /(\d+)[_\s]*::(.*?)(?=\||$)/g;
const REG_WILDCARD_SIMPLE_WORD = /[^{}_|]+/g;
/**
* Checks if the input text is longer than the maximum allowed tag length.
* @param {string} text The text to check.
* @return {boolean} True if the text is longer than the maximum tag length, false otherwise.
*/
export function isLongText(text) {
return text?.length > settingValues._maxTagLength;
}
/**
* Converts Hiragana to Katakana.
* @param {string} str Input string.