feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization
This commit is contained in:
@@ -3,11 +3,16 @@
|
||||
# ComfyUI General API Node Pack
|
||||
*Created by FeiMao-326*
|
||||
|
||||
<<<<<<< HEAD
|
||||
[**English**](#english) | [**中文**](#chinese)
|
||||
=======
|
||||
[**English**](README.md) | [**中文说明**](README_CN.md)
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
<<<<<<< HEAD
|
||||
<a name="english"></a>
|
||||
|
||||
## 🇬🇧 English
|
||||
@@ -23,6 +28,23 @@ This pack contains the following nodes, all available under the **`FeiMao-326`**
|
||||
- Features automatic GPU cleanup for local Ollama.
|
||||
- **[NEW]** Native Google Gemini API support (Chat & Image Generation).
|
||||
- Includes advanced seed control (`fixed`, `increment`, `randomize`).
|
||||
=======
|
||||
|
||||
## 🇬🇧 English
|
||||
|
||||
A suite of powerful and versatile utility nodes for ComfyUI, designed to streamline complex workflows involving Large Language Models and text manipulation. This pack is created by FeiMao-326 and is modernized for 2026 models.
|
||||
|
||||
### ✨ Nodes Included
|
||||
|
||||
This pack contains the following nodes available under the **`FeiMao-326`** category:
|
||||
|
||||
1. **General API Node**: **[Core]** The Ultimate Multimodal LLM Engine.
|
||||
- **2026 SOTA Model Support**: Native adaptation for Gemini 3.1 Pro/Flash, GPT-5.4, Llama 4, and Claude 3.5.
|
||||
- **Smart Task Routing**: Automated redirection for Google Gemini. Use standard names like `gemini-3.1-pro` or `gemini-3.1-pro-image-preview` for chat/vision; it automatically switches to Image Gen mode when the model name includes the `image-` keyword.
|
||||
- **Double VRAM Cleanup**: Specialized for local Ollama. When `cleanup_local_gpu` is enabled, the node triggers a REST API `keep_alive: 0` and a CLI fallback to physically purge model data from VRAM immediately after execution.
|
||||
- **Advanced Multimodal Vision**: Supports concurrent parsing and comparison of 2 or more images.
|
||||
- **Seed Logic**: Includes industrial-grade seed control (`fixed`, `increment`, `randomize`).
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
2. **Text Batch Replace**: A powerful text utility for performing up to 8 find-and-replace operations in a single node.
|
||||
3. **JSON Parser**: Deconstructs complex, nested JSON payloads into multiple separate text outputs with a built-in labeled preview. It deeply searches for user-defined keys, perfect for handling structured prompts.
|
||||
4. **Show Text**: Displays input text directly on the node UI.
|
||||
@@ -30,6 +52,15 @@ This pack contains the following nodes, all available under the **`FeiMao-326`**
|
||||
6. **Text Split By Delimiter**: Splits a text string into a list based on a delimiter.
|
||||
7. **Get List Element**: Retrieves a specific element from a list by index.
|
||||
8. **Text Iterator**: Splits text by delimiter and outputs it as a batch, triggering batch execution for downstream nodes.
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
9. **Markdown Extractor**: Intelligent regex-based extraction of ` ```json ` or code blocks from LLM responses.
|
||||
10. **Text Template (Dynamic)**: **[Recommended]** Dynamic slot template. `{var}` automatically generates input ports. Supports all languages.
|
||||
11. **Prompt Cleaner**: Normalizes prompts by removing duplicates, extra commas, and empty brackets.
|
||||
12. **Text Logic Switch**: Flow control (If-Else) based on content matching (Contains, Regex, Exact, etc.).
|
||||
13. **Regex Extractor Pro**: Advanced pattern capture with capture group support.
|
||||
14. **Dictionary Translator**: Bulk mapping/replacement via JSON or Key=Value dictionaries.
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
### 🔧 Installation
|
||||
|
||||
@@ -71,6 +102,7 @@ This pack contains the following nodes, all available under the **`FeiMao-326`**
|
||||
- **🖼️ Single Image Description**: Connect an image to `image_1`.
|
||||
- **🎬 Dual Image for Video Transitions**: Connect a start frame to `image_1` and an end frame to `image_2`.
|
||||
- **📸 Multi-Image Analysis**: You can connect multiple images (`image_1`, `image_2`, `image_3...etc`) for complex analysis tasks.
|
||||
<<<<<<< HEAD
|
||||
4. **API Connection Examples**:
|
||||
- **Local Ollama**:
|
||||
- `api_baseurl`: `http://127.0.0.1:11434/v1`
|
||||
@@ -245,3 +277,77 @@ This project is licensed under the Apache 2.0 License. See the [LICENSE](LICENSE
|
||||
### 📜 许可证
|
||||
|
||||
本项目采用 Apache 2.0 许可证。详情请参阅 [LICENSE](LICENSE) 和 [NOTICE](NOTICE) 文件。
|
||||
=======
|
||||
|
||||
4. **API Connection Examples**:
|
||||
- **NVIDIA NIM (Official/Local)**:
|
||||
- `api_baseurl`: `https://integrate.api.nvidia.com/v1`
|
||||
- `api_key`: Your NVIDIA API Key (`nvapi-xxxxxxxx`)
|
||||
- `model`: `meta/llama-4-maverick`, `nvidia/nemotron-3-ultra`
|
||||
- **OpenAI GPT-5 (Latest)**:
|
||||
- `api_baseurl`: `https://api.openai.com/v1`
|
||||
- `api_key`: Your OpenAI API key
|
||||
- `model`: `gpt-5.4-thinking` or `o3-pro` (Reasoning model)
|
||||
- **Local Ollama**:
|
||||
- `api_baseurl`: `http://127.0.0.1:11434/v1`
|
||||
- `api_key`: `ollama`
|
||||
- `model`: `gemma4:e4b` (Current default, supports vision analysis)
|
||||
- `cleanup_local_gpu`: Keep it checked (True).
|
||||
- **Google Gemini (Native)**:
|
||||
- `api_baseurl`: `https://generativelanguage.googleapis.com/v1beta/`
|
||||
- `api_key`: Your Google AI Studio Key
|
||||
- `model`: `gemini-3.1-pro` (Chat/Vision) or `gemini-3.1-pro-image-preview` (Image Gen)
|
||||
- > [!TIP]
|
||||
- > **Important**: For **Image Generation**, the model name MUST contain the keyword `image-` (e.g., `gemini-3.1-pro-image-preview`). For **Chat/Reasoning**, use standard names like `gemini-3.1-pro`.
|
||||
|
||||

|
||||
|
||||
#### Text Batch Replace
|
||||
- Perform multiple find-and-replace operations sequentially.
|
||||

|
||||
|
||||
#### JSON Parser
|
||||
- Automatically parses LLM JSON outputs into independent string slots based on defined keys.
|
||||

|
||||
|
||||
#### Simple Text / Show Text
|
||||
- Basic input and visual display modules.
|
||||
|
||||
#### Text Split / Get Element / Iterator
|
||||
- Essential tools for handling lists and batch execution flows.
|
||||

|
||||
|
||||
#### Markdown Extractor
|
||||
- **Problem**: LLMs often reply with chatter like "Sure! Here is your JSON: ```json ... ```".
|
||||
- **Solution**: Automatically identifies and extracts clean content from Markdown code blocks.
|
||||
- **Scenario**: When LLM generates a JSON structure, passing it directly to JsonParser would crash due to Markdown syntax. This node ensures a "smooth" connection.
|
||||

|
||||
|
||||
#### Text Template (Dynamic)
|
||||
- **Concept**: Write a template with variables: `A {subject} in {style} lighting, {mood} background`.
|
||||
- **Magic**: The node instantly creates input slots for `subject`, `style`, and `mood`.
|
||||
- **Note**: In Nodes 2.0 (Vue UI), right-click and "Refresh" the node after modifying variables.
|
||||

|
||||
|
||||
#### Prompt Cleaner
|
||||
- **Function**: Removes duplicate keywords, merges extra commas, and cleans inconsistent spaces.
|
||||
- **Scenario**: Multiple prompt concatenations often result in messy punctuation (e.g., `, ,`). Use this as a "final touch" before KSampler.
|
||||

|
||||
|
||||
#### Text Logic Switch
|
||||
- **Function**: Flow control (If-Else). If input text contains a keyword (or matches regex), output travels to the "True" port, otherwise "False".
|
||||
- **Scenario**: Check if LLM output contains "Error" to stop the workflow or switch to a fallback model.
|
||||
|
||||
#### Regex Extractor Pro
|
||||
- **Function**: Precise data extraction using Capture Groups.
|
||||
- **Scenario**: Extract specific info like HEX color codes (#FFFFFF) or numeric coordinates from a long LLM description.
|
||||
|
||||
#### Dictionary Translator
|
||||
- **Function**: Bulk tag replacement using a mapping table (JSON or Simple Text Pairs).
|
||||
- **Scenario**: Map common Chinese terms to professional SD tags (e.g., replace "Cinematic" with "cinematic lighting, 8k, highly detailed").
|
||||
|
||||
---
|
||||
|
||||
### 📜 License
|
||||
This project is licensed under the Apache 2.0 License.
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
<div align="center">
|
||||
|
||||
# ComfyUI General API Node Pack
|
||||
*本节点包由 FeiMao-326 创作*
|
||||
|
||||
[**English**](README.md) | [**中文**](README_CN.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🇨🇳 中文
|
||||
|
||||
一套为 ComfyUI 设计的、功能强大且用途广泛的实用节点包,旨在简化涉及大语言模型和文本处理的复杂工作流。本节点包由 FeiMao-326 创作。
|
||||
|
||||
### ✨ 包含的节点
|
||||
|
||||
本节点包包含以下节点,您都可以在 **`FeiMao-326`** 分类下找到它们:
|
||||
|
||||
1. **General API Node**: **[核心]** 强大的多模态 AI 交互引擎。
|
||||
- **2026 模型原生适配**: 完美支持 Gemini 3.1 (Pro/Flash)、GPT-5.4、Llama 4 及 Claude 3.5。
|
||||
- **智能任务路由**: 针对 Google Gemini 实现自动重定向。使用标准模型名(如 `gemini-3.1-pro`,`gemini-3.1-pro-image-preview`)进行对话或读图;当检测到模型名包含 `image-` 关键字时,自动切换为生图模式。
|
||||
- **显存双重清理**: 专为本地 Ollama 打造。勾选 `cleanup_local_gpu` 后,节点会通过 REST API 发送 `keep_alive: 0` 并辅助 CLI 命令强制卸载模型,在运行完毕后瞬间物理腾空 GPU 显存。
|
||||
- **多模态视觉解析**: 支持多达 2 张及以上图像的并发解析与对比。
|
||||
- **高级种子控制**: 包含 `固定`, `递增`, `随机` 等工业级种子逻辑。
|
||||
2. **Text Batch Replace**: 一个强大的文本工具,可在单个节点中执行多达8次的查找与替换操作。
|
||||
3. **JSON Parser**: 可将复杂的、深度嵌套的JSON结构,解析为多个独立的文本输出,并自带带标签的预览功能。它会深度搜索用户定义的关键字,非常适合处理结构化提示词。
|
||||
4. **Show Text**: 直接在节点界面上显示输入的文本。
|
||||
5. **Simple Text**: 一个简单的文本输入节点,用于将字符串传递给其他节点。
|
||||
6. **Text Split By Delimiter**: 根据分隔符将文本字符串分割为列表。
|
||||
7. **Get List Element**: 通过索引从列表中检索特定元素。
|
||||
8. **Text Iterator**: 根据分隔符分割文本并作为批次输出,触发下游节点的批次执行。
|
||||
9. **Markdown Extractor**: 智能正则表达式提取,专为抓取 LLM 回复中的 ` ```json ` 或代码块设计。
|
||||
10. **Text Template (Dynamic)**: **[推荐]** 动态插槽模板,支持 `{变量}` 自动生成输入端口,支持中文变量名。
|
||||
11. **Prompt Cleaner**: 提示词清洗,自动去重、规范逗号、清理空括号。
|
||||
12. **Text Logic Switch**: 文本逻辑开关,基于内容匹配实现工作流 If-Else 分流。
|
||||
13. **Regex Extractor Pro**: 高级正则提取,支持捕获组筛选。
|
||||
14. **Dictionary Translator**: 词典批量映射替换,支持 JSON 或 Key=Value 格式。
|
||||
|
||||
### 🔧 安装方法
|
||||
|
||||
1. **克隆仓库**
|
||||
- 打开您的终端。
|
||||
- 导航到您的 ComfyUI `custom_nodes` 文件夹:
|
||||
```bash
|
||||
cd path/to/your/ComfyUI/custom_nodes/
|
||||
```
|
||||
- 克隆此仓库:
|
||||
```bash
|
||||
git clone https://github.com/FeiMao-326/Comfyui-General-API-Node.git
|
||||
```
|
||||
|
||||
2. **安装依赖**
|
||||
- 导航到刚刚克隆下来的节点文件夹:
|
||||
```bash
|
||||
cd Comfyui-General-API-Node
|
||||
```
|
||||
- 安装所需的依赖项:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **重启 ComfyUI**
|
||||
- 完成以上步骤后,请完全重启 ComfyUI。
|
||||
|
||||
### 💡 如何使用
|
||||
|
||||
#### General API Node
|
||||
1. **找到节点**: 在 ComfyUI 中,您可以通过右键菜单 -> `Add Node` -> `FeiMao-326` -> `FeiMao-326 General API Node` 找到它。
|
||||
|
||||

|
||||
|
||||
2. **设置种子控制**: 若要启用自动种子变更(例如 `randomize`),请将节点的 `seed` **输出**端口连接回它自身的 `seed` **输入**端口。这个“循环”连接会在每次运行后自动更新种子值。
|
||||
|
||||
3. **使用场景**:
|
||||
- **📝 纯文本生成**: 将 `image_1` 和 `image_2` 保持断开。
|
||||
- **🖼️ 单图描述**: 连接一张图片到 `image_1` 接口。
|
||||
- **🎬 双图视频转场**: 连接**起始帧**到 `image_1`,连接**结束帧**到 `image_2`。
|
||||
- **📸 多图分析**: 您最多可以连接多张图片 (`image_1`, `image_2`, `image_3...等`) 进行复杂的分析任务。
|
||||
4. **API 连接示例**:
|
||||
- **NVIDIA NIM (英伟达官方/私有化部署)**:
|
||||
- `api_baseurl`: `https://integrate.api.nvidia.com/v1` (或私有 NIM 地址)
|
||||
- `api_key`: 填入您的 NVIDIA API 密钥 (`nvapi-xxxxxxxx`)
|
||||
- `model`: `meta/llama-4-maverick` 或 `nvidia/nemotron-3-ultra`
|
||||
- **OpenAI GPT-5 (最新版)**:
|
||||
- `api_baseurl`: `https://api.openai.com/v1`
|
||||
- `api_key`: 填入您的 OpenAI 密钥
|
||||
- `model`: `gpt-5.4-thinking` 或 `o3-pro` (旗舰推理模型)
|
||||
- **本地 Ollama**:
|
||||
- `api_baseurl`: `http://127.0.0.1:11434/v1`
|
||||
- `api_key`: `ollama`
|
||||
- `model`: `gemma4:e4b` (当前默认模型,支持图像分析)
|
||||
- `cleanup_local_gpu`: 保持勾选 (True)。
|
||||
- **外部 API (以 OpenAI 为例)**:
|
||||
- `api_baseurl`: `https://api.openai.com/v1`
|
||||
- `api_key`: 填入您的 OpenAI 密钥 (`sk-xxxxxxxx`)
|
||||
- `model`: `gpt-4o`
|
||||
- **Google Gemini (原生)**:
|
||||
- `api_baseurl`: `https://generativelanguage.googleapis.com/v1beta/`
|
||||
- `api_key`: 填入您的 Google AI Studio 密钥
|
||||
- `model`: `gemini-3.1-pro` (对话/读图) 或 `gemini-3.1-pro-image-preview` (生图)
|
||||
- > [!TIP]
|
||||
- > **重要提示**: 若要使用 Gemini 的 **生图功能**,模型名称必须包含 `image-` 关键词(例如 `gemini-3.1-pro-image-preview`)。单纯的对话或读图分析请使用 `gemini-3.1-pro`。
|
||||
下面是一个完整的双图转场任务的示例工作流:
|
||||
|
||||

|
||||
|
||||
#### Text Batch Replace
|
||||
- 在 `text` 字段中输入任意文本。
|
||||
- 填写 `find_x` 和 `replace_x` 字段以执行顺序替换。
|
||||

|
||||
|
||||
#### JSON Parser
|
||||
- 将您的复杂JSON粘贴到 `json_payload` 字段中。
|
||||
- 在 `keys_to_extract` 字段中,输入您想提取的关键字,用逗号分隔(例如 `shot, subject, audio`)。
|
||||
- 节点会在JSON的任何位置找到这些关键字,将其合并后的值输出到对应的 `output_x` 端口,并在节点内显示预览。
|
||||

|
||||
|
||||
#### Show Text
|
||||
- 将任何字符串输出连接到 `text` 输入。
|
||||
- 文本将直接显示在节点上。
|
||||
|
||||
#### Simple Text
|
||||
- 在文本框中输入您的文本。
|
||||
- 将 `text` 输出连接到任何需要字符串输入的节点。
|
||||
|
||||
#### Text Split By Delimiter
|
||||
- 输入文本和分隔符(默认为逗号)。
|
||||
- 输出一个 `LIST` 类型,可与 `Get List Element` 配合使用。
|
||||

|
||||
|
||||
#### Get List Element
|
||||
- 连接 `LIST` 输入。
|
||||
- 指定索引(从0开始)以检索特定字符串。
|
||||
|
||||
#### Text Iterator
|
||||
- 输入文本和分隔符。
|
||||
- 将每个分割项作为单独 monocle 批次项输出。
|
||||
- 用于迭代提示词列表或参数列表。
|
||||

|
||||
|
||||
#### Markdown Extractor
|
||||
- LLM 经常会话多,输出类似 ```json ... ``` 这种带格式的内容。
|
||||
- 核心功能:自动识别并提取 Markdown 代码块中的纯净内容(如 JSON, Python, CSV)。
|
||||
- 实用场景:LLM 生成了 JSON 结构,你的 JsonParser,多出的 ```json 字符会导致报错。有了这个节点,对接会变得“丝滑”。
|
||||

|
||||
|
||||
#### Text Template
|
||||
- 核心功能:允许用户写一段带变量的模板,如 A {subject} in {style} lighting, {mood} background。节点会自动产生 subject、style、mood 三个输入插槽。
|
||||
- **注意**: 在 Nodes 2.0 (Vue UI) 中,修改变量后建议右键点击节点并选择“刷新”。
|
||||

|
||||
|
||||
#### Prompt Cleaner
|
||||
- 核心功能:自动移除重复的关键词,合并多余的逗号,清理不规范的空格。
|
||||
- 实用场景:多节点的 Prompt 拼接经常会出现 , , 这种多余标点,这个节点可以作为发送给 KSampler 之前的“最后润色”。
|
||||

|
||||
|
||||
#### Text Logic Switch
|
||||
- 核心功能:输入文本 A,如果其中包含某个关键词(或满足正则匹配),就把文本传给“真”输出口,否则传给“假”输出口。
|
||||
- 实用场景:判断 LLM 的输出是否包含“错误”字样,如果有,就自动停止生图流,或者切换到另一个修正模型。
|
||||
#### Regex Extractor Pro
|
||||
- 核心功能:通过正则捕获组提取特定信息。比如从一堆描述中提取出所有括号里的十六进制颜色代码(#FFFFFF)。
|
||||
- 实用场景:从长篇大论的 LLM 描述中,精准抓取颜色、光影强度、材质名称等参数,传给下方的 ControlNet 权重。
|
||||
|
||||
#### Dictionary Translator
|
||||
- 核心功能:支持用户输入一个映射表(JSON 或简单的文本对),一键将 Prompt 中的关键词批量替换。
|
||||
- 实用场景:把中文通俗词批量映射成专业的生图词库(例如:把“电影感”自动替换成 cinematic lighting, 8k, highly detailed 这一长串)。
|
||||
|
||||
### 📜 许可证
|
||||
|
||||
本项目采用 Apache 2.0 许可证。详情请参阅 [LICENSE](LICENSE) 和 [NOTICE](NOTICE) 文件。
|
||||
@@ -12,7 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
<<<<<<< HEAD
|
||||
class ShowTextNode:
|
||||
=======
|
||||
class FeiMao_326_ShowTextNode:
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
<<<<<<< HEAD
|
||||
class SimpleTextNode:
|
||||
=======
|
||||
class FeiMao_326_SimpleTextNode:
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
|
||||
+38
@@ -22,6 +22,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
<<<<<<< HEAD
|
||||
__version__ = "1.0.5"
|
||||
|
||||
from .general_api_node import FeiMao_326_GeneralAPINode
|
||||
@@ -29,6 +30,15 @@ from .text_utils_node import FeiMao_326_TextBatchReplace
|
||||
from .json_parser_node import FeiMao_326_JsonParser
|
||||
from .ShowTextNode import ShowTextNode
|
||||
from .SimpleTextNode import SimpleTextNode
|
||||
=======
|
||||
__version__ = "1.0.6"
|
||||
|
||||
from .general_api_node import FeiMao_326_GeneralAPINode
|
||||
from .text_utils_node import FeiMao_326_TextBatchReplace, FeiMao_326_MarkdownExtractor, FeiMao_326_TextTemplate, FeiMao_326_PromptCleaner, FeiMao_326_TextLogicSwitch, FeiMao_326_RegexExtractor, FeiMao_326_DictionaryReplace
|
||||
from .json_parser_node import FeiMao_326_JsonParser
|
||||
from .ShowTextNode import FeiMao_326_ShowTextNode
|
||||
from .SimpleTextNode import FeiMao_326_SimpleTextNode
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
from .list_utils_node import FeiMao_326_TextSplitByDelimiter, FeiMao_326_GetListElement, FeiMao_326_TextIterator
|
||||
|
||||
|
||||
@@ -36,22 +46,50 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FeiMao_326_GeneralAPINode": FeiMao_326_GeneralAPINode,
|
||||
"FeiMao_326_TextBatchReplace": FeiMao_326_TextBatchReplace,
|
||||
"FeiMao_326_JsonParser": FeiMao_326_JsonParser,
|
||||
<<<<<<< HEAD
|
||||
"ShowTextNode": ShowTextNode,
|
||||
"SimpleTextNode": SimpleTextNode,
|
||||
"FeiMao_326_TextSplitByDelimiter": FeiMao_326_TextSplitByDelimiter,
|
||||
"FeiMao_326_GetListElement": FeiMao_326_GetListElement,
|
||||
"FeiMao_326_TextIterator": FeiMao_326_TextIterator,
|
||||
=======
|
||||
"FeiMao_326_ShowTextNode": FeiMao_326_ShowTextNode,
|
||||
"FeiMao_326_SimpleTextNode": FeiMao_326_SimpleTextNode,
|
||||
"FeiMao_326_TextSplitByDelimiter": FeiMao_326_TextSplitByDelimiter,
|
||||
"FeiMao_326_GetListElement": FeiMao_326_GetListElement,
|
||||
"FeiMao_326_TextIterator": FeiMao_326_TextIterator,
|
||||
"FeiMao_326_MarkdownExtractor": FeiMao_326_MarkdownExtractor,
|
||||
"FeiMao_326_TextTemplate": FeiMao_326_TextTemplate,
|
||||
"FeiMao_326_PromptCleaner": FeiMao_326_PromptCleaner,
|
||||
"FeiMao_326_TextLogicSwitch": FeiMao_326_TextLogicSwitch,
|
||||
"FeiMao_326_RegexExtractor": FeiMao_326_RegexExtractor,
|
||||
"FeiMao_326_DictionaryReplace": FeiMao_326_DictionaryReplace,
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FeiMao_326_GeneralAPINode": f"General API Node v{__version__} (FeiMao-326)",
|
||||
"FeiMao_326_TextBatchReplace": f"Text Batch Replace (FeiMao-326)",
|
||||
"FeiMao_326_JsonParser": f"JSON Parser (FeiMao-326)",
|
||||
<<<<<<< HEAD
|
||||
"ShowTextNode": "Show Text (FeiMao-326)",
|
||||
"SimpleTextNode": "Simple Text (FeiMao-326)",
|
||||
"FeiMao_326_TextSplitByDelimiter": "Text Split By Delimiter (Array) (FeiMao-326)",
|
||||
"FeiMao_326_GetListElement": "Get List Element (List) (FeiMao-326)",
|
||||
"FeiMao_326_TextIterator": "Text Iterator (FeiMao-326)",
|
||||
=======
|
||||
"FeiMao_326_ShowTextNode": "Show Text (FeiMao-326)",
|
||||
"FeiMao_326_SimpleTextNode": "Simple Text (FeiMao-326)",
|
||||
"FeiMao_326_TextSplitByDelimiter": "Text Split By Delimiter (Array) (FeiMao-326)",
|
||||
"FeiMao_326_GetListElement": "Get List Element (List) (FeiMao-326)",
|
||||
"FeiMao_326_TextIterator": "Text Iterator (FeiMao-326)",
|
||||
"FeiMao_326_MarkdownExtractor": "Markdown Extractor (FeiMao-326)",
|
||||
"FeiMao_326_TextTemplate": "Text Template (FeiMao-326)",
|
||||
"FeiMao_326_PromptCleaner": "Prompt Cleaner (FeiMao-326)",
|
||||
"FeiMao_326_TextLogicSwitch": "Text Logic Switch (FeiMao-326)",
|
||||
"FeiMao_326_RegexExtractor": "Regex Extractor Pro (FeiMao-326)",
|
||||
"FeiMao_326_DictionaryReplace": "Dictionary Translator (FeiMao-326)",
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
@@ -51,13 +51,21 @@ class FeiMao_326_GeneralAPINode:
|
||||
"required": {
|
||||
"api_baseurl": ("STRING", {"multiline": False, "default": "http://127.0.0.1:11434/v1"}),
|
||||
"api_key": ("STRING", {"multiline": False, "default": "ollama"}),
|
||||
<<<<<<< HEAD
|
||||
"model": ("STRING", {"multiline": False, "default": "gemma3:4b"}),
|
||||
=======
|
||||
"model": ("STRING", {"multiline": False, "default": "gemma4:e4b"}),
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
"role": ("STRING", {"multiline": True, "default": "You are a helpful assistant. Follow the user's instructions exactly. Output only the requested content without conversational filler, markdown formatting, or explanations."}),
|
||||
"prompt": ("STRING", {"multiline": True, "default": "Describe this image in detail."}),
|
||||
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": s.MAX_SEED}),
|
||||
"temperature": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 2.0, "step": 0.01}),
|
||||
<<<<<<< HEAD
|
||||
"max_tokens": ("INT", {"default": 4096, "min": 64, "max": 16384, "step": 64}),
|
||||
=======
|
||||
"max_tokens": ("INT", {"default": 4096, "min": 64, "max": 131072, "step": 512}),
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
"control_after_generate": (["fixed", "increment", "decrement", "randomize"],),
|
||||
"cleanup_local_gpu": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
@@ -114,6 +122,7 @@ class FeiMao_326_GeneralAPINode:
|
||||
"""Returns a 1x1x3 black image tensor"""
|
||||
return torch.zeros((1, 1, 1, 3), dtype=torch.float32)
|
||||
|
||||
<<<<<<< HEAD
|
||||
def cleanup_with_ollama_cli(self, model):
|
||||
command = ['ollama', 'stop', model]
|
||||
print(f"🔵 [FeiMao-326 API Node] Attempting to unload local model via CLI: {' '.join(command)}")
|
||||
@@ -136,6 +145,60 @@ class FeiMao_326_GeneralAPINode:
|
||||
print("❌ [FeiMao-326 API Node] Error: 'ollama' command not found. Please ensure Ollama is installed correctly.")
|
||||
except Exception as e:
|
||||
print(f"⚠️ [FeiMao-326 API Node] An error occurred while executing the CLI command: {str(e)}")
|
||||
=======
|
||||
def cleanup_ollama_model(self, api_baseurl, model):
|
||||
"""
|
||||
Unloads an Ollama model from memory.
|
||||
1. Primary attempt: Native REST API with keep_alive: 0 (No environment variables required).
|
||||
2. Fallback: CLI 'ollama stop' command (Depends on system PATH).
|
||||
"""
|
||||
model_unloaded = False
|
||||
|
||||
# --- 1. PRIMARY: REST API UNLOAD ---
|
||||
try:
|
||||
# Construct Ollama native generate URL (usually /v1 -> /api/generate)
|
||||
# Example: http://127.0.0.1:11434/v1 -> http://127.0.0.1:11434/api/generate
|
||||
if "/v1" in api_baseurl:
|
||||
unload_url = api_baseurl.replace("/v1", "/api/generate").rstrip("/")
|
||||
else:
|
||||
# If it's a custom URL, try to guess the root
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(api_baseurl)
|
||||
unload_url = f"{parsed.scheme}://{parsed.netloc}/api/generate"
|
||||
|
||||
print(f"🔵 [FeiMao-326 API Node] Attempting to unload model '{model}' via REST API...")
|
||||
payload = {
|
||||
"model": model,
|
||||
"keep_alive": 0 # Unload immediately
|
||||
}
|
||||
response = requests.post(unload_url, json=payload, timeout=5)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"✅ [FeiMao-326 API Node] Model '{model}' successfully unloaded via API.")
|
||||
model_unloaded = True
|
||||
else:
|
||||
print(f"⚠️ [FeiMao-326 API Node] API unload status: {response.status_code}. Falling back to CLI.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ [FeiMao-326 API Node] REST API unload failed ({type(e).__name__}). Falling back to CLI.")
|
||||
|
||||
# --- 2. FALLBACK: CLI COMMAND ---
|
||||
if not model_unloaded:
|
||||
command = ['ollama', 'stop', model]
|
||||
print(f"🔵 [FeiMao-326 API Node] Attempting to unload via CLI: {' '.join(command)}")
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, timeout=10, check=False)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"✅ [FeiMao-326 API Node] Model '{model}' successfully stopped via CLI.")
|
||||
else:
|
||||
stderr_output = result.stderr.decode(locale.getpreferredencoding(), errors='replace')
|
||||
print(f"❌ [FeiMao-326 API Node] CLI stop failed: {stderr_output.strip()}")
|
||||
except FileNotFoundError:
|
||||
print("❌ [FeiMao-326 API Node] Error: 'ollama' command not found. Environment variable may not be configured.")
|
||||
except Exception as e:
|
||||
print(f"⚠️ [FeiMao-326 API Node] CLI stop error: {str(e)}")
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
def execute(self, api_baseurl, api_key, model, role, prompt, seed, temperature, max_tokens, control_after_generate,
|
||||
cleanup_local_gpu=True, image_1=None, image_2=None, image_3=None, **kwargs):
|
||||
@@ -143,6 +206,14 @@ class FeiMao_326_GeneralAPINode:
|
||||
current_seed = self._normalize_seed(seed)
|
||||
generated_image_tensor = self.get_empty_image() # Default empty image
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
# --- 0. AUTO-MAPPING DEPRECATED MODELS ---
|
||||
if "gemini-3-pro-preview" in model.lower():
|
||||
print(f"⚠️ [FeiMao_326 API Node] Model 'gemini-3-pro-preview' is deprecated. Auto-redirecting to 'gemini-3.1-pro'.")
|
||||
model = model.replace("gemini-3-pro-preview", "gemini-3.1-pro")
|
||||
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
# Enforce silent role if empty or default
|
||||
if not role or not role.strip():
|
||||
role = "You are a helpful assistant. Follow the user's instructions exactly. Output only the requested content without conversational filler."
|
||||
@@ -367,7 +438,11 @@ class FeiMao_326_GeneralAPINode:
|
||||
finally:
|
||||
is_local_ollama = "127.0.0.1" in api_baseurl or "localhost" in api_baseurl
|
||||
if cleanup_local_gpu and is_local_ollama:
|
||||
<<<<<<< HEAD
|
||||
self.cleanup_with_ollama_cli(model)
|
||||
=======
|
||||
self.cleanup_ollama_model(api_baseurl, model)
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
if TORCH_AVAILABLE and torch.cuda.is_available():
|
||||
print(f"🔵 [FeiMao-326 API Node] Clearing PyTorch GPU cache...")
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
@@ -37,6 +37,10 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
this.onResize?.(this.size);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
app.canvas.setDirty?.(true);
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
}
|
||||
|
||||
if (maxExistingIndex > targetCount) {
|
||||
@@ -48,6 +52,10 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
this.onResize?.(this.size);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
app.canvas.setDirty?.(true);
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -88,6 +96,10 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
this.onResize?.(this.size);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
app.canvas.setDirty?.(true);
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
}
|
||||
|
||||
if (maxExistingIndex > finalTargetCount) {
|
||||
@@ -102,5 +114,51 @@ app.registerExtension({
|
||||
}
|
||||
};
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
// --- 3. Text Template (Variable-based Dynamic Inputs) ---
|
||||
if (nodeData.name === "FeiMao_326_TextTemplate") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
const templateWidget = this.widgets.find(w => w.name === "template");
|
||||
|
||||
const updateInputs = () => {
|
||||
const text = templateWidget.value || "";
|
||||
// Match all {var_name} (Support Unicode/Chinese)
|
||||
const matches = text.match(/\{([^{}\s]+)\}/g) || [];
|
||||
const vars = [...new Set(matches.map(m => m.slice(1, -1)))];
|
||||
|
||||
// Add new inputs
|
||||
vars.forEach(v => {
|
||||
if (!this.inputs || !this.inputs.find(i => i.name === v)) {
|
||||
this.addInput(v, "*"); // Universal type
|
||||
}
|
||||
});
|
||||
|
||||
// Remove outdated inputs
|
||||
if (this.inputs) {
|
||||
for (let i = this.inputs.length - 1; i >= 0; i--) {
|
||||
const inputName = this.inputs[i].name;
|
||||
if (!vars.includes(inputName)) {
|
||||
this.removeInput(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.onResize?.(this.size);
|
||||
app.canvas.setDirty?.(true);
|
||||
};
|
||||
|
||||
// Watch for widget changes
|
||||
templateWidget.callback = updateInputs;
|
||||
|
||||
// Initial sync
|
||||
setTimeout(updateInputs, 100);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,11 +4,33 @@ import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
app.registerExtension({
|
||||
name: "FeiMao-326.ShowText",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
<<<<<<< HEAD
|
||||
if (nodeData.name === "ShowTextNode") {
|
||||
=======
|
||||
// --- GLOBAL VISUAL UNIFICATION (AESTHETICS) ---
|
||||
// Force all sockets in this node pack to be circles (0 = LITEGRAPH.CIRCLE_SHAPE)
|
||||
if (nodeData.name && nodeData.name.startsWith("FeiMao_326_")) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
if (this.inputs) {
|
||||
this.inputs.forEach(input => { input.shape = 0; });
|
||||
}
|
||||
if (this.outputs) {
|
||||
this.outputs.forEach(output => { output.shape = 0; });
|
||||
}
|
||||
return r;
|
||||
};
|
||||
}
|
||||
|
||||
// --- 1. ShowTextNode Specific Logic ---
|
||||
if (nodeData.name === "FeiMao_326_ShowTextNode") {
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex((w) => w.name === "text");
|
||||
if (pos !== -1) {
|
||||
@@ -19,16 +41,58 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
=======
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
let text = message.text;
|
||||
if (Array.isArray(text)) {
|
||||
text = text.join("\n");
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
w.inputEl.readOnly = true;
|
||||
w.inputEl.style.opacity = 0.6;
|
||||
w.value = text;
|
||||
|
||||
this.onResize?.(this.size);
|
||||
=======
|
||||
|
||||
// --- NODES 2.0 ROBUST RENDERING ---
|
||||
// Rename widget to avoid conflict with 'text' input slot
|
||||
let w = this.widgets?.find((w) => w.name === "display_text");
|
||||
|
||||
if (!w) {
|
||||
// Create if not exists
|
||||
w = ComfyWidgets["STRING"](this, "display_text", ["STRING", { multiline: true }], app).widget;
|
||||
w.inputEl.readOnly = true;
|
||||
w.inputEl.style.opacity = 0.8;
|
||||
w.inputEl.style.pointerEvents = "auto"; // FORCE interactions for scroll
|
||||
|
||||
// Prevent these values from being saved into the workflow JSON
|
||||
w.serializeValue = () => undefined;
|
||||
|
||||
w.inputEl.addEventListener("wheel", (e) => {
|
||||
// Just stop propagation to prevent canvas zoom
|
||||
// Let the browser handle internal scroll when focused
|
||||
e.stopPropagation();
|
||||
}, { passive: false });
|
||||
|
||||
w.inputEl.addEventListener("mousedown", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
w.inputEl.addEventListener("keydown", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
w.value = text;
|
||||
if (w.inputEl) {
|
||||
w.inputEl.value = text;
|
||||
}
|
||||
|
||||
this.onResize?.(this.size);
|
||||
app.canvas.setDirty?.(true);
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
[project]
|
||||
name = "general-api-node"
|
||||
description = "A suite of powerful and versatile utility nodes for ComfyUI, designed to streamline complex workflows involving Large Language Models and text manipulation."
|
||||
<<<<<<< HEAD
|
||||
version = "1.0.5"
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
=======
|
||||
version = "1.0.6"
|
||||
license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
keywords = ["comfyui", "api", "llm", "gemini", "vision", "gpt-5", "ollama"]
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
requires-python = ">=3.9"
|
||||
|
||||
dependencies = [
|
||||
@@ -14,8 +21,16 @@ dependencies = [
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/FeiMao-326/Comfyui-General-API-Node"
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
Documentation = "https://github.com/FeiMao-326/Comfyui-General-API-Node/blob/main/README.md"
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "feimao"
|
||||
DisplayName = "Comfyui-General-API-Node"
|
||||
Icon = ""
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
requires-comfyui = ">=0.2.0"
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
+254
-1
@@ -49,4 +49,257 @@ class FeiMao_326_TextBatchReplace:
|
||||
if find_s:
|
||||
modified_text = modified_text.replace(find_s, replace_s)
|
||||
|
||||
return (modified_text,)
|
||||
<<<<<<< HEAD
|
||||
return (modified_text,)
|
||||
=======
|
||||
return (modified_text,)
|
||||
|
||||
class FeiMao_326_MarkdownExtractor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"remove_artifacts": ("BOOLEAN", {"default": True}),
|
||||
"first_block_only": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, text, remove_artifacts, first_block_only):
|
||||
import re
|
||||
# Find all ```blocks```
|
||||
pattern = re.compile(r'```(?:\w+)?\n?(.*?)```', re.DOTALL)
|
||||
matches = pattern.findall(text)
|
||||
|
||||
if not matches:
|
||||
result = text
|
||||
else:
|
||||
if first_block_only:
|
||||
result = matches[0].strip()
|
||||
else:
|
||||
result = "\n\n".join([m.strip() for m in matches])
|
||||
|
||||
if remove_artifacts:
|
||||
# Strip common markdown signs if they are lingering
|
||||
result = re.sub(r'(\*\*|__)', '', result)
|
||||
result = re.sub(r'^#+\s+', '', result, flags=re.MULTILINE)
|
||||
|
||||
return (result,)
|
||||
|
||||
class FeiMao_326_TextTemplate:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"template": ("STRING", {"multiline": True, "placeholder": "Example: A {subject} in {style} style."}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, template, **kwargs):
|
||||
# Result string will be formatted using dynamic inputs
|
||||
# ComfyUI passing dynamic inputs via **kwargs
|
||||
import re
|
||||
result = template
|
||||
|
||||
# We find all matches to avoid KeyErrors
|
||||
# Support for all languages (Unicode) in variables
|
||||
found_vars = re.findall(r'\{([^{}\s]+)\}', template)
|
||||
|
||||
for var in found_vars:
|
||||
if var in kwargs:
|
||||
val = str(kwargs[var])
|
||||
result = result.replace(f"{{{var}}}", val)
|
||||
else:
|
||||
# Keep as is or replace with empty if not connected?
|
||||
# Better keep as is to allow nested templates or partial fills
|
||||
pass
|
||||
|
||||
return (result,)
|
||||
|
||||
class FeiMao_326_PromptCleaner:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"remove_duplicate_tags": ("BOOLEAN", {"default": True}),
|
||||
"clean_brackets": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, text, remove_duplicate_tags, clean_brackets):
|
||||
import re
|
||||
# 1. Normalize commas and spaces
|
||||
cleaned = text.replace("\n", ", ")
|
||||
cleaned = re.sub(r',\s*,', ',', cleaned) # Multi commas
|
||||
|
||||
# 2. Split by comma
|
||||
tags = [t.strip() for t in cleaned.split(",")]
|
||||
|
||||
# 3. Remove duplicates while keeping order
|
||||
if remove_duplicate_tags:
|
||||
seen = set()
|
||||
new_tags = []
|
||||
for t in tags:
|
||||
if t and t.lower() not in seen:
|
||||
new_tags.append(t)
|
||||
seen.add(t.lower())
|
||||
tags = new_tags
|
||||
else:
|
||||
tags = [t for t in tags if t]
|
||||
|
||||
result = ", ".join(tags)
|
||||
|
||||
if clean_brackets:
|
||||
# Remove empty brackets like (), ( ), [], [ ]
|
||||
result = re.sub(r'\(\s*\)', '', result)
|
||||
result = re.sub(r'\[\s*\]', '', result)
|
||||
# Remove trailing/leading commas again after bracket cleanup
|
||||
result = re.sub(r',\s*,', ',', result)
|
||||
|
||||
return (result.strip(", "),)
|
||||
|
||||
class FeiMao_326_TextLogicSwitch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"pattern": ("STRING", {"default": ""}),
|
||||
"mode": (["contains", "not_contains", "regex", "exactly", "starts_with", "ends_with"], {"default": "contains"}),
|
||||
"on_true": ("STRING", {"multiline": True, "default": "True"}),
|
||||
"on_false": ("STRING", {"multiline": True, "default": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, text, pattern, mode, on_true, on_false):
|
||||
import re
|
||||
is_true = False
|
||||
|
||||
if mode == "contains":
|
||||
is_true = pattern in text
|
||||
elif mode == "not_contains":
|
||||
is_true = pattern not in text
|
||||
elif mode == "exactly":
|
||||
is_true = text == pattern
|
||||
elif mode == "starts_with":
|
||||
is_true = text.startswith(pattern)
|
||||
elif mode == "ends_with":
|
||||
is_true = text.endswith(pattern)
|
||||
elif mode == "regex":
|
||||
try:
|
||||
is_true = bool(re.search(pattern, text))
|
||||
except:
|
||||
is_true = False
|
||||
|
||||
return (on_true if is_true else on_false,)
|
||||
|
||||
class FeiMao_326_RegexExtractor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"regex": ("STRING", {"default": r"(\d+)"}),
|
||||
"group_index": ("INT", {"default": 1, "min": 0, "max": 99}),
|
||||
"all_matches": ("BOOLEAN", {"default": False}),
|
||||
"delimiter": ("STRING", {"default": ", "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, text, regex, group_index, all_matches, delimiter):
|
||||
import re
|
||||
try:
|
||||
pattern = re.compile(regex)
|
||||
if all_matches:
|
||||
matches = pattern.finditer(text)
|
||||
results = []
|
||||
for m in matches:
|
||||
try:
|
||||
results.append(m.group(group_index))
|
||||
except IndexError:
|
||||
pass
|
||||
return (delimiter.join(results),)
|
||||
else:
|
||||
match = pattern.search(text)
|
||||
if match:
|
||||
try:
|
||||
return (match.group(group_index),)
|
||||
except IndexError:
|
||||
return ("",)
|
||||
return ("",)
|
||||
except Exception as e:
|
||||
return (f"Regex Error: {str(e)}",)
|
||||
|
||||
class FeiMao_326_DictionaryReplace:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"dictionary_data": ("STRING", {"multiline": True, "placeholder": "key=value or JSON {\"key\": \"value\"}"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "FeiMao-326"
|
||||
|
||||
def execute(self, text, dictionary_data):
|
||||
import json
|
||||
|
||||
mapping = {}
|
||||
# 1. Try JSON
|
||||
try:
|
||||
mapping = json.loads(dictionary_data)
|
||||
except:
|
||||
# 2. Try Key=Value lines
|
||||
lines = dictionary_data.strip().split("\n")
|
||||
for line in lines:
|
||||
if "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
mapping[k.strip()] = v.strip()
|
||||
elif ":" in line:
|
||||
k, v = line.split(":", 1)
|
||||
mapping[k.strip()] = v.strip()
|
||||
|
||||
if not mapping:
|
||||
return (text,)
|
||||
|
||||
result = text
|
||||
# To avoid partial replacements causing issues,
|
||||
# we sort keys by length (longest first)
|
||||
sorted_keys = sorted(mapping.keys(), key=len, reverse=True)
|
||||
|
||||
for k in sorted_keys:
|
||||
if k:
|
||||
result = result.replace(k, mapping[k])
|
||||
|
||||
return (result,)
|
||||
>>>>>>> e9ec5ee (feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user