Initial commit...

This commit is contained in:
fairy-root
2024-12-27 14:22:25 +01:00
parent 6325f5739f
commit 9150c4b1a4
14 changed files with 419 additions and 0 deletions
+148
View File
@@ -1,2 +1,150 @@
# ComfyUI-GLHF
<<<<<<< HEAD
GLHF is a ComfyUI node that facilitates seamless interaction with the GLHF chat API. Designed to enhance user experience, it supports multiple language models, web search integration, and customizable instructions, making it a powerful extension for AI-driven workflows.
=======
GLHF is a ComfyUI node that facilitates seamless interaction with the GLHF chat API. Designed to enhance user experience, it supports multiple language models, web search integration, and customizable instructions, making it a powerful extension for AI-driven workflows.
---
## Features
### Versatile Model Support
Interact with a range of language models to suit diverse use cases. Configure models via the `config.json` file for flexibility and control over model selection.
### Integrated Web Search
Enhance your prompts with real-time information by enabling web search. The node:
- Retrieves relevant search results.
- Extracts content from URLs included in the prompts.
- Appends this information to the input for more contextual and accurate outputs.
Here are examples of the web search feature in action:
![Web Search Example 1](imgs/web_search_1.png)
![Web Search Example 2](imgs/web_search_2.png)
### Custom Instruction Loading
Customize the behavior of the node by adding `.txt` files to the `custom_instructions` folder. This allows you to tailor the responses based on specific requirements or use cases.
Here are examples of custom instructions:
![Custom Instruction Example 1](imgs/custom_instruction_1.png)
![Custom Instruction Example 2](imgs/custom_instruction_2.png)
### Persistent Chat Context
The node maintains chat history, enabling contextual and coherent multi-turn conversations. You can enable or disable this feature through the `keep_context` option for tailored interactions.
Here are examples of context keeping:
![Context Keeping Example 1](imgs/context_keeping_1.png)
![Context Keeping Example 2](imgs/context_keeping_2.png)
### Uncensored Model
Utilize uncensored models for unrestricted creative applications.
![Uncensored Model Example](imgs/uncensored_model.png)
### Console Logging
Monitor your interactions and debug effortlessly with detailed console logs that display request and response details directly in the ComfyUI console.
---
## Getting Started
### 1. Get your API Key
- Go to [https://glhf.chat/](https://glhf.chat/) and create an account.
- Go to [https://glhf.chat/users/settings/api](https://glhf.chat/users/settings/api) and copy your API Key.
- Paste your API Key in the `config.json` file located in the ComfyUI-GLHF directory.
```json
{
"baseurl": "https://glhf.chat/api/openai/v1",
"api_key": "YOUR_API_KEY",
"models": {
"Llama3.3 70b": "hf:meta-llama/Llama-3.3-70B-Instruct",
"QWQ 32b": "hf:Qwen/QwQ-32B-Preview",
"Llama3.3 70b Uncensored": "hf:huihui-ai/Llama-3.3-70B-Instruct-abliterated"
}
}
```
You can also add more models to the models section of the `config.json` file.
### 2. Install the Node
- Navigate to the `custom_nodes` folder inside your ComfyUI directory.
- Open a command prompt or terminal in the address bar and run the following command:
```bash
git clone https://github.com/fairy-root/ComfyUI-GLHF.git
```
### 3. Install the Requirements
- Navigate to the `ComfyUI\python_embeded` folder.
- Run the following command:
```bash
./python.exe -s -m pip install openai requests bs4 googlesearch-python
```
---
## Node Parameters
The `GLHF Chat` with Advanced Web and Link Search node offers extensive customization through the following parameters:
- **prompt**: The text prompt to send to the GLHF API.
- **seed**: Random seed value for reproducible results.
- **model**: The selected language model from the config.json file.
- **console_log**: Toggle for console logging.
- **enable_web_search**: Enable or disable web search functionality.
- **num_search_results**: Specify the number of search results to include.
- **keep_context**: Enable or disable conversation context maintenance.
- **custom_instruction**: Choose a predefined instruction file to guide model behavior.
## Custom Instructions
Custom instructions can significantly enhance the node’s flexibility. To create a new instruction:
1- Navigate to the `custom_instructions` folder in the ComfyUI-GLHF directory.
2- Create a `.txt` file, using a descriptive name for the instruction.
3- Add the desired instructions to the file.
- Example:
To create an instruction for Python-specific guidance:
1- Name the file python.txt.
2- Populate it with content such as:
```bash
Respond to queries as a Python expert. Provide detailed explanations and examples where applicable.
```
3- The new instruction will appear in the custom_instruction dropdown menu within the node settings after restarting ComfyUI.
---
## Donation
Your support is appreciated:
- USDt (TRC20): `TGCVbSSJbwL5nyXqMuKY839LJ5q5ygn2uS`
- BTC: `13GS1ixn2uQAmFQkte6qA5p1MQtMXre6MT`
- ETH (ERC20): `0xdbc7a7dafbb333773a5866ccf7a74da15ee654cc`
- LTC: `Ldb6SDxUMEdYQQfRhSA3zi4dCUtfUdsPou`
## Author and Contact
- GitHub: [FairyRoot](https://github.com/fairy-root)
- Telegram: [@FairyRoot](https://t.me/FairyRoot)
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request for any improvements or features.
>>>>>>> bfab6cc (Initial release...)
+10
View File
@@ -0,0 +1,10 @@
from .glhf import GlhfChat
NODE_CLASS_MAPPINGS = {
"glhf_chat": GlhfChat
}
NODE_DISPLAY_NAME_MAPPINGS = {
"glhf_chat": "GLHF Chat",
}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+9
View File
@@ -0,0 +1,9 @@
{
"baseurl": "https://glhf.chat/api/openai/v1",
"api_key": "YOUR_API_KEY",
"models": {
"Llama3.3 70b": "hf:meta-llama/Llama-3.3-70B-Instruct",
"QWQ 32b": "hf:Qwen/QwQ-32B-Preview",
"Llama3.3 70b Uncensored": "hf:huihui-ai/Llama-3.3-70B-Instruct-abliterated"
}
}
+1
View File
@@ -0,0 +1 @@
Act as a highly skilled photography prompt generator. When the user provides a subject, object, or general concept for an image, create a detailed and comprehensive photography prompt that can be directly used by an AI image generation model. Focus exclusively on generating the prompt, providing no other commentary or explanation. The generated prompt MUST include rich details about the subject or object, encompassing physical attributes such as precise facial expression (e.g., joyful smile with crinkled eyes, thoughtful gaze, melancholic frown), specific posture (e.g., standing tall with shoulders back, leaning against a wall, curled up on a sofa), distinct gestures (e.g., pointing towards the distance, holding a steaming mug, adjusting glasses), and, where applicable and culturally sensitive, suggestive ethnic group or appearance traits (e.g., a woman with warm brown skin and dark curly hair, an elderly man with weathered features), and meticulous details about their clothing (e.g., a flowing red silk dress with intricate embroidery, a worn leather jacket with patches, a simple white t-shirt and jeans). Furthermore, the prompt MUST specify the setting in vivid detail, including the exact location (e.g., a bustling Tokyo street at night, a serene forest bathed in golden sunlight, a cozy vintage bookstore with overflowing shelves), the time of day (e.g., predawn twilight, midday sun casting harsh shadows, a dimly lit evening), the prevailing weather conditions (e.g., a gentle snowfall blanketing the scene, a dramatic thunderstorm with streaks of lightning, a hazy summer afternoon), and pertinent background and foreground elements that enrich the narrative (e.g., a vintage bicycle leaning against a wall, scattered autumn leaves on a cobblestone path, steam rising from a cup of coffee). If a plot or action is conceivable within the image, the prompt MUST outline it concisely (e.g., a young woman laughing as she chases pigeons in a park, a lone astronaut gazing at a distant planet, a chef expertly flipping a pancake in a busy kitchen). Crucially, the prompt MUST specify the lighting conditions with precision, detailing the type of light (e.g., soft diffused natural light, dramatic Rembrandt lighting, harsh neon light), the direction of the light source (e.g., backlight creating a silhouette, sidelight emphasizing texture, top-down light creating dramatic shadows), and the overall intensity and color temperature of the light (e.g., warm golden hour light, cool blue moonlight). The prompt MUST also dictate the intended artistic style (e.g., a photorealistic photograph, a vibrant oil painting, a detailed pencil sketch), potentially referencing specific photographers or painters if desired (e.g., in the style of Ansel Adams, reminiscent of a Van Gogh painting). The prompt MUST then specify the technical aspects of image capture, including the camera position and angle (e.g., eye-level shot for intimacy, low-angle shot for dramatic effect, bird's-eye view for a sweeping panorama), the type of camera or lens to emulate (e.g., shot on a vintage Hasselblad, captured with a wide-angle lens, using a telephoto lens for compression), and the desired depth of field (e.g., shallow depth of field blurring the background, deep depth of field keeping everything in focus). Additional details to include are specific photographic techniques like long exposure (e.g., long exposure capturing light trails), bokeh (e.g., beautiful bokeh in the background), or motion blur (e.g., intentional motion blur to convey speed). If applicable, specify elements like film stock if aiming for a photographic aesthetic (e.g., shot on Kodak Portra 400 film), or suggest specific lens characteristics or filters. The prompt should also consider the overall mood or atmosphere the image should convey (e.g., a sense of mystery and intrigue, a feeling of tranquility and peace, an atmosphere of vibrant energy). Consider adding details about the color palette to be used (e.g., a muted and desaturated color palette, a vibrant and saturated color scheme, a monochromatic image). Think about including elements that add visual interest and storytelling, such as reflections, shadows, or specific textures. The generated output should be ONLY the prompt string, ready for input into an image generation model.
+21
View File
@@ -0,0 +1,21 @@
Act as an expert Python developer and help to design and create code blocks / modules as per the user specification.
RULES:
- MUST provide clean, production-grade, high quality code.
- ASSUME the user is using python version 3.9+
- USE well-known python design patterns and object-oriented programming approaches
- MUST provide code blocks with proper google style docstrings
- MUST provide code blocks with input and return value type hinting.
- MUST use type hints
- PREFER to use F-string for formatting strings
- PREFER keeping functions Small: Each function should do one thing and do it well.
- USE @property: For getter and setter methods.
- USE List and Dictionary Comprehensions: They are more readable and efficient.
- USE generators for large datasets to save memory.
- USE logging: Replace print statements with logging for better control over output.
- MUST to implement robust error handling when calling external dependencies
- USE dataclasses for storing data
- USE pydantic version 1 for data validation and settings management.
- Ensure the code is presented in code blocks without comments and description.
- An Example use to be presented in if __name__ == "__main__":
- If code to be stored in multiple files, use #!filepath to signal that in the same code block.
+226
View File
@@ -0,0 +1,226 @@
import subprocess
import sys
from jax import config
import openai
import json
import os
from googlesearch import search
import requests
from bs4 import BeautifulSoup
import re
import random
# Get the directory of the current script (glhf.py)
script_directory = os.path.dirname(os.path.abspath(__file__))
custom_instructions_directory = os.path.join(script_directory, 'custom_instructions')
# Construct the full path to config.json
config_path = os.path.join(script_directory, 'config.json')
def install_and_import(package):
try:
__import__(package)
except ImportError:
print(f"Package {package} not found. Installing...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
finally:
globals()[package] = __import__(package)
install_and_import("googlesearch")
install_and_import("requests")
install_and_import("bs4")
def configuration():
api_key = None
base_url = None
models = {}
try:
with open(config_path, 'r') as f:
config_data = json.load(f)
api_key = config_data.get('api_key')
base_url = config_data.get('baseurl')
models = config_data.get('models', {})
models = {key: value for key, value in models.items()}
except FileNotFoundError:
print(f"Error: config.json not found at {config_path}")
except json.JSONDecodeError:
print(f"Error: Could not decode JSON from {config_path}")
return api_key, base_url, models
def load_custom_instructions():
instructions = ["None"] # Add "None" option
if not os.path.exists(custom_instructions_directory):
os.makedirs(custom_instructions_directory)
for filename in os.listdir(custom_instructions_directory):
if filename.endswith(".txt"):
instructions.append(filename[:-4]) # Remove .txt extension
return instructions
api_key, base_url, models_dict = configuration()
models_list = list(models_dict.keys())
default_model = models_list[0] if models_list else None
custom_instructions_list = load_custom_instructions()
def fetch_and_extract_content(url):
try:
response = requests.get(url, timeout=10)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
soup = BeautifulSoup(response.content, 'html.parser')
# Extract main content - you might need to adjust selectors based on common website structures
paragraphs = soup.find_all('p')
text_content = "\n".join([p.text for p in paragraphs])
return text_content.strip()
except requests.exceptions.RequestException as e:
print(f"Error fetching URL {url}: {e}")
return None
except Exception as e:
print(f"Error processing content from {url}: {e}")
return None
class GlhfChat:
chat_history = [] # Class-level chat history
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "Enter your prompt here...", "multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "step": 1}),
},
"optional": {
"model": (models_list, {"default": default_model}),
"Console_log": ("BOOLEAN", {"default": True}),
"enable_web_search": ("BOOLEAN", {"default": False}),
"num_search_results": ("INT", {"default": 5, "min": 1, "max": 10}),
"keep_context": ("BOOLEAN", {"default": True}),
"custom_instruction": (custom_instructions_list, {"default": "None"}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "execute"
CATEGORY = "GLHF"
def execute(self, prompt, seed, model=None, Console_log=False, enable_web_search=False, num_search_results=3, keep_context=True, custom_instruction="None"):
selected_model_value = models_dict.get(model)
if not selected_model_value:
print(f"Error: Model '{model}' not found in config.json. Using default.")
selected_model_value = list(models_dict.values())[0] if models_dict else None
if not selected_model_value:
return ("Error: No valid model found.",)
if Console_log:
print(f"GLHF Chat Request: Model='{model}', Prompt='{prompt}', Seed='{seed}', Web Search Enabled={enable_web_search}, Keep Context={keep_context}, Custom Instruction='{custom_instruction}'")
augmented_prompt = "" # Initialize as empty
if enable_web_search:
# Regex to find URLs in the prompt
urls_in_prompt = re.findall(r'(https?://\S+)', prompt)
try:
search_results_content = []
searched_urls = set() # To avoid processing same URLs multiple times
# Process URLs found in the prompt first
for url in urls_in_prompt:
if url not in searched_urls:
print(f"Fetching content from URL in prompt: {url}")
content = fetch_and_extract_content(url)
if content:
search_results_content.append(f"Source (from prompt): {url}\nContent:\n{content}\n---\n")
else:
search_results_content.append(f"Source (from prompt): {url}\nCould not retrieve content.\n---\n")
searched_urls.add(url)
# Process URLs from Google Search
for i, url in enumerate(search(prompt, num_results=num_search_results)):
if url not in searched_urls:
print(f"Fetching content from search result {i+1}: {url}")
content = fetch_and_extract_content(url)
if content:
search_results_content.append(f"Search Result {i+1}:\nSource: {url}\nContent:\n{content}\n---\n")
else:
search_results_content.append(f"Search Result {i+1}:\nSource: {url}\nCould not retrieve content.\n---\n")
searched_urls.add(url)
if search_results_content:
augmented_prompt = f"Original query: {prompt}\n\nWeb search results and linked content:\n\n{''.join(search_results_content)}\n\nBased on this information, please provide a response."
else:
augmented_prompt = f"Original query: {prompt}\n\nNo relevant web search results or linked content found. Please proceed with the original query."
if Console_log:
print("No relevant web search results or linked content found.")
except Exception as e:
print(f"Web Search Error: {e}")
augmented_prompt = f"Original query: {prompt}\n\nAn error occurred during web search or fetching linked content. Please proceed with the original query."
else:
augmented_prompt = prompt # If web search is not enabled, use the original prompt
response = self._glhf_interaction(base_url, api_key, selected_model_value, augmented_prompt, Console_log, keep_context, custom_instruction)
return response
def _glhf_interaction(self, base_url, api_key, model_value, prompt, Console_log, keep_context, custom_instruction):
try:
client = openai.OpenAI(
api_key=api_key,
base_url=base_url,
)
messages = []
if custom_instruction != "None":
instruction_path = os.path.join(custom_instructions_directory, f"{custom_instruction}.txt")
try:
with open(instruction_path, 'r') as f:
system_instruction = f.read()
messages.append({"role": "system", "content": system_instruction})
except FileNotFoundError:
print(f"Error: Custom instruction file '{instruction_path}' not found.")
except Exception as e:
print(f"Error reading custom instruction file: {e}")
if keep_context and self.chat_history:
messages.extend(self.chat_history)
messages.append({"role": "user", "content": prompt})
completion = client.chat.completions.create(
model=model_value,
messages=messages,
stream=False
)
output_text = completion.choices[0].message.content
if Console_log:
print(f"GLHF Chat Response: {output_text}")
# Update chat history
self.chat_history.append({"role": "user", "content": prompt})
self.chat_history.append({"role": "assistant", "content": output_text})
return (output_text,)
except openai.AuthenticationError as e:
print(f"GLHF Chat Error: Authentication failed - {e}")
return (f"Error: Authentication failed - {e}",)
except openai.APIConnectionError as e:
print(f"GLHF Chat Error: Could not connect to GLHF API - {e}")
return (f"Error: Could not connect to GLHF API - {e}",)
except openai.RateLimitError as e:
print(f"GLHF Chat Error: API request exceeded rate limit - {e}")
return (f"Error: API request exceeded rate limit - {e}",)
except openai.APIStatusError as e:
print(f"GLHF Chat Error: GLHF API returned an error - {e}")
return (f"Error: GLHF API returned an error - {e}",)
except Exception as e:
print(f"GLHF Chat Error: An unexpected error occurred - {e}")
return (f"Error: An unexpected error occurred - {e}",)
# Node export details
NODE_CLASS_MAPPINGS = {
"glhf_chat": GlhfChat
}
NODE_DISPLAY_NAME_MAPPINGS = {
"glhf_chat": "GLHF Chat with Advanced Web and Link Search"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

+4
View File
@@ -0,0 +1,4 @@
openai
googlesearch-python
requests
bs4