initial commit

This commit is contained in:
CC-BryanOttho
2024-02-28 00:31:39 +01:00
parent 80e4b2edaf
commit 5e685905d0
7 changed files with 351 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
# Virtual Environment
venv/
env/
ENV/
# IDE
.vscode/
# Compiled files
*.pyc
# Logs and databases
*.log
*.sqlite3
# Dependency directories
lib/
lib64/
+63
View File
@@ -1 +1,64 @@
# ComfyUI_API_Manager
```
# Custom Workflow Nodes for API Integration
This package provides three custom nodes designed to streamline workflows involving API requests, dynamic text manipulation based on API responses, and image posting to APIs. These nodes are particularly useful for automating interactions with APIs, enhancing text-based workflows with dynamic data, and facilitating image uploads.
## Installation
To use these custom nodes, clone this repository into your `custom_nodes` directory. No external dependencies are required.
```bash
git clone https://github.com/CC-BryanOttho/ComfyUI_API_Manager.git custom_nodes
```
## Usage
### API Request Node
This node performs API requests and processes the responses:
- **auth_url**: Specify the authentication endpoint of the API.
- **auth_body_text**: Enter the payload for the API authentication request.
- **array_path**: Navigate through the response object to locate the desired object or array, especially useful if it's nested.
1. Configure the `auth_url` and `auth_body_text` to authenticate with your target API.
2. Use the `array_path` to specify the path to the data of interest in the API response.
### Text Prompt Combiner Node
Utilizes API response data for dynamic text manipulation:
- Dynamically replace text in a template using `$attributeName` syntax to insert values from the API response.
- **id_field_name**: Specify the attribute name whose value is returned as a string, aiding in identifying or referencing objects.
1. Insert API response data into a text box, using `$attribute1.attribute2` to reference nested attributes.
2. The `id_field_name` can be used to extract specific values from the response for further use.
### Image Post Node
Facilitates the posting of images to an API:
- Requires an image, an object ID as a string, and an API key for authentication.
- **api_url**: The endpoint for image posting, with `$id` used in the URL to dynamically insert the ID.
1. Provide the necessary image, API object ID, and API key.
2. Configure the `api_url`, ensuring to include `$id` where the object ID should be inserted in the URL.
## Example Workflow
Imagine a workflow where you need to fetch data from an API, use part of that data to generate a text prompt, and then post an image related to that prompt to another API endpoint:
1. **API Request Node** fetches the desired data.
2. **Text Prompt Combiner Node** generates a dynamic text prompt based on the fetched data.
3. **Image Post Node** posts an image using details from the previous nodes.
## Additional Information
These nodes are designed to be flexible and can be adjusted or extended based on specific workflow requirements. For more complex API interactions or additional dynamic capabilities, consider customizing these nodes further.
## Contributing
Contributions are welcome! If you have improvements or bug fixes, please submit a pull request or open an issue.
```
+20
View File
@@ -0,0 +1,20 @@
from .api_request import APIRequestNode
from .image_post_node import PostImageToAPI
from .text_prompt_combiner_node import TextPromptCombinerNode
# Define the mappings for your custom nodes
NODE_CLASS_MAPPINGS = {
"APIRequestNode": APIRequestNode,
"PostImageToAPI": PostImageToAPI,
"TextPromptCombinerNode": TextPromptCombinerNode,
}
# Define human-readable names for your nodes (optional)
NODE_DISPLAY_NAME_MAPPINGS = {
"APIRequestNode": "API Request Node",
"PostImageToAPI": "Image Post Node",
"TextPromptCombinerNode": "Text Prompt Combiner Node",
}
# Export the mappings for use by ComfyUI
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+105
View File
@@ -0,0 +1,105 @@
import datetime
import requests
import json
class APIRequestNode:
"""
A custom node for making API requests and processing responses.
Attributes and class methods are defined to match the framework's expectations,
similar to the provided Example node structure.
"""
@classmethod
def INPUT_TYPES(cls):
"""
Define input parameters for the APIRequestNode.
"""
return {
"required": {
"api_url": ("STRING", {"default": ""}),
"auth_url": ("STRING", {"default": ""}),
"token_attribute_name": ("STRING", {"default": ""}),
"auth_body_text": ("STRING", {"multiline": True}),
"array_path": ("STRING", {"default": ""}),
"iteration_index": ("INT", {
"default": 0,
"min": 0,
"max": 9999, # Adjust based on expected maximum array length
"step": 1,
"display": "number" # Allow users to input or select the index number
})
}
}
RETURN_TYPES = ("JSON", "INT", "STRING") # Output types: JSON response
RETURN_NAMES = ("API_RESPONSE", "LENGTH", "API_KEY")
FUNCTION = "execute" # The entry-point method for this node
CATEGORY = "API Manager" # Category under which the node will appear in the UI
def __init__(self):
pass
def authenticate(self, auth_url, auth_body, token_attribute_name):
"""
Authenticate and retrieve a bearer token using the provided authentication URL and body.
"""
try:
response = requests.post(auth_url, json=auth_body)
response.raise_for_status()
token = response.json().get(token_attribute_name)
print(f"APIRequestNode: Obtained token")
return token
except requests.exceptions.RequestException as e:
print(f"APIRequestNode: Error obtaining bearer token - {e}")
return None
def execute(self, api_url, auth_url, token_attribute_name, auth_body_text, array_path, iteration_index):
"""
The main logic for making an API request, processing the response,
and potentially queuing image generation requests or other post-processing steps.
"""
auth_body = None
if auth_body_text:
try:
auth_body = json.loads("{" + auth_body_text + "}")
except json.JSONDecodeError as e:
print(f"Error parsing JSON input: {e}")
return None, None, None
token = self.authenticate(auth_url, auth_body, token_attribute_name) if auth_url else None
headers = {'Authorization': f'Bearer {token}'} if token else {}
response_data, error = self.make_api_request(api_url, headers)
if error:
return {}, None, ""
array_data = self.extract_array_data(response_data, array_path) if array_path else response_data
selected_item = array_data[iteration_index] if isinstance(array_data, list) else array_data
return selected_item, len(array_data), f'Bearer {token}'
def make_api_request(self, api_url, headers, params={}):
"""
Make the actual API request and return the response.
"""
try:
response = requests.get(api_url, headers=headers, params=params)
response.raise_for_status()
return response.json(), None
except requests.exceptions.RequestException as e:
print(f"Error making API request: {e}")
return None, e
def extract_array_data(self, response_data, array_path):
# Navigate through the JSON structure to extract the specified array
target_data = response_data
for key in array_path.split('.'):
target_data = target_data.get(key, [])
return target_data if isinstance(target_data, list) else []
NODE_CLASS_MAPPINGS = {
"APIRequestNode": APIRequestNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"APIRequestNode": "API Request Node"
}
+50
View File
@@ -0,0 +1,50 @@
import json
import requests
from io import BytesIO
import numpy as np
from PIL import Image
class PostImageToAPI:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"api_url": ("STRING", {"default": ""}),
"api_object_id": ("STRING", {"forceInput": True}),
"api_key": ("STRING", {"forceInput": True})
}
}
RETURN_TYPES = ()
FUNCTION = "post_images"
CATEGORY = "API Manager"
OUTPUT_NODE = True
def post_images(self, images, api_url, api_object_id, api_key=""):
api_url = api_url.replace("$id", api_object_id)
headers = {'Authorization': api_key} if api_key else {}
results = []
for (batch_number, image_tensor) in enumerate(images):
image_np = 255. * image_tensor.cpu().numpy()
image = Image.fromarray(np.clip(image_np, 0, 255).astype(np.uint8))
buffer = BytesIO()
image.save(buffer, format="PNG")
buffer.seek(0)
# Specify file name and MIME type
files = {'file': ('image.png', buffer, 'image/png')}
response = requests.post(api_url, headers=headers, files=files)
if response.status_code == 200:
results.append(response.json())
else:
print(f"Error posting image {batch_number}: {response.text}")
print(f"PostImageToAPI: Posted image to {api_url}\nResponse: {results}")
return {"api_responses": results}
+43
View File
@@ -0,0 +1,43 @@
class JSONArrayIteratorNode:
"""
A custom node for iterating through a JSON array and outputting specific objects
based on a selection number provided via the node interface.
"""
@classmethod
def INPUT_TYPES(cls):
"""
Define input parameters for the JSONArrayIteratorNode.
"""
return {
"required": {
"json_array": ("JSON", {"default": "[]"}),
"selection_number": ("INT", {
"default": 0,
"min": 0,
"max": 9999, # Adjust based on expected maximum array length
"step": 1,
"display": "number" # Allow users to input or select the index number
}),
},
}
RETURN_TYPES = ("JSON",) # Output type: the selected JSON object from the array
FUNCTION = "iterate" # The entry-point method for this node
CATEGORY = "Data Processing" # Category under which the node will appear in the UI
def __init__(self):
pass
def iterate(self, json_array, selection_number):
print(f"JSONArrayIteratorNode: Iterating with selection_number={selection_number}")
print (f"JSONArrayIteratorNode: Iterating with json_array={json_array}")
# Check if the selection number is within the bounds of the json_array
if isinstance(json_array, list) and 0 <= selection_number < len(json_array):
selected_object = json_array[selection_number]
else:
print(f"Selection number {selection_number} is out of bounds for the array length {len(json_array)}.")
selected_object = {} # Return an empty object or handle as needed
return selected_object
+46
View File
@@ -0,0 +1,46 @@
class TextPromptCombinerNode:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text_prompt_template": ("STRING", {"multiline": True}),
"api_response": ("JSON", {"default": {}}),
"id_field_name": ("STRING", {"default": ""}),
"clip": ("CLIP", )
},
}
RETURN_TYPES = ("CONDITIONING", "STRING") # Combined Text Prompt, UUID, Auth Token
RETURN_NAMES = ("CONDITIONING", "ID")
FUNCTION = "execute"
CATEGORY = "API Manager"
def execute(self, text_prompt_template, api_response, id_field_name, clip):
print("TextPromptCombinerNode: Starting to combine text prompt...")
# Initialize the combined text prompt with the template
combined_text_prompt = text_prompt_template
if isinstance(api_response, dict):
# Iterate through each key in the api_response
for key, value in api_response.items():
# Define the placeholder pattern with the $ prefix
placeholder = f"${key}"
# Replace each occurrence of the placeholder in the template with its corresponding value
combined_text_prompt = combined_text_prompt.replace(placeholder, str(value))
# Extract the ID using the id_field_name, if provided and api_response is a dict
extracted_id = api_response.get(id_field_name, "") if isinstance(api_response, dict) else ""
print(f"Combined Text Prompt: {combined_text_prompt}")
print(f"Extracted ID: {extracted_id}")
# Now encode the combined text using CLIP, similar to CLIPTextEncode class
tokens = clip.tokenize(combined_text_prompt)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
# Return the conditioning and the extracted ID
return ([[cond, {"pooled_output": pooled}]], extracted_id)