feat: Release v1.0.6 with 6 new advanced text nodes and Nodes 2.0 (Vue UI) optimization

This commit is contained in:
FeiMao-326
2026-04-05 03:12:14 +08:00
parent cf5bec6121
commit 58bae48398
14 changed files with 794 additions and 2 deletions
+106
View File
@@ -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`.
![General API Node Workflow Example](./assets/workflow_example.png)
#### Text Batch Replace
- Perform multiple find-and-replace operations sequentially.
![Text Batch Replace Interface](./assets/text_batch_replace.png)
#### JSON Parser
- Automatically parses LLM JSON outputs into independent string slots based on defined keys.
![JSON Parser Interface](./assets/json_parser.png)
#### Simple Text / Show Text
- Basic input and visual display modules.
#### Text Split / Get Element / Iterator
- Essential tools for handling lists and batch execution flows.
![Text Iterator Interface](./assets/Text_Iterator.png)
#### 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.
![Markdown Extractor](./assets/Markdown_Extractor.png)
#### 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.
![Text Template](./assets/Text_Template.png)
#### 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.
![Prompt Cleaner](./assets/Prompt_Cleaner.png)
#### 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
View File
@@ -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` 找到它。
![FeiMao-326 节点界面](./assets/node_interface.png)
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`。
下面是一个完整的双图转场任务的示例工作流:
![General API Node 工作流示例](./assets/workflow_example.png)
#### Text Batch Replace
- 在 `text` 字段中输入任意文本。
- 填写 `find_x` 和 `replace_x` 字段以执行顺序替换。
![Text Batch Replace 界面](./assets/text_batch_replace.png)
#### JSON Parser
- 将您的复杂JSON粘贴到 `json_payload` 字段中。
- 在 `keys_to_extract` 字段中,输入您想提取的关键字,用逗号分隔(例如 `shot, subject, audio`)。
- 节点会在JSON的任何位置找到这些关键字,将其合并后的值输出到对应的 `output_x` 端口,并在节点内显示预览。
![JSON Parser 界面](./assets/json_parser.png)
#### Show Text
- 将任何字符串输出连接到 `text` 输入。
- 文本将直接显示在节点上。
#### Simple Text
- 在文本框中输入您的文本。
- 将 `text` 输出连接到任何需要字符串输入的节点。
#### Text Split By Delimiter
- 输入文本和分隔符(默认为逗号)。
- 输出一个 `LIST` 类型,可与 `Get List Element` 配合使用。
![Text Split By Delimiter](./assets/text_split.png)
#### Get List Element
- 连接 `LIST` 输入。
- 指定索引(从0开始)以检索特定字符串。
#### Text Iterator
- 输入文本和分隔符。
- 将每个分割项作为单独 monocle 批次项输出。
- 用于迭代提示词列表或参数列表。
![Text Iterator 界面](./assets/Text_Iterator.png)
#### Markdown Extractor
- LLM 经常会话多,输出类似 ```json ... ``` 这种带格式的内容。
- 核心功能:自动识别并提取 Markdown 代码块中的纯净内容(如 JSON, Python, CSV)。
- 实用场景:LLM 生成了 JSON 结构,你的 JsonParser,多出的 ```json 字符会导致报错。有了这个节点,对接会变得“丝滑”。
![Markdown Extractor](./assets/Markdown_Extractor.png)
#### Text Template
- 核心功能:允许用户写一段带变量的模板,如 A {subject} in {style} lighting, {mood} background。节点会自动产生 subject、style、mood 三个输入插槽。
- **注意**: 在 Nodes 2.0 (Vue UI) 中,修改变量后建议右键点击节点并选择“刷新”。
![Text Template](./assets/Text_Template.png)
#### Prompt Cleaner
- 核心功能:自动移除重复的关键词,合并多余的逗号,清理不规范的空格。
- 实用场景:多节点的 Prompt 拼接经常会出现 , , 这种多余标点,这个节点可以作为发送给 KSampler 之前的“最后润色”。
![Prompt Cleaner](./assets/Prompt_Cleaner.png)
#### 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) 文件。
+4
View File
@@ -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 {
+4
View File
@@ -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
View File
@@ -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

+75
View File
@@ -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()
+58
View File
@@ -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)
},
});
+64
View File
@@ -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)
};
}
},
+15
View File
@@ -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
View File
@@ -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