diff --git a/__init__.py b/__init__.py index 58650ef..261b8cd 100644 --- a/__init__.py +++ b/__init__.py @@ -18,7 +18,7 @@ if jmanager.on_startup(False): else: jmanager.log_events("config.json was not updated") -__version__ ="1.21.12" +__version__ ="1.21.13" print('Plush - Version:', __version__) diff --git a/api_requests.py b/api_requests.py index e7030c9..fbf6060 100644 --- a/api_requests.py +++ b/api_requests.py @@ -270,6 +270,12 @@ class oai_web_request(Request): response = None CGPT_response = "" + self.cFig.lm_url = url + if not self.cFig.is_lm_server_up: + self.j_mngr.log_events("Local or remote server is not responding, may be unable to send data.", + TroubleSgltn.Severity.WARNING, + True) + #if there's an image here if image and request_type == self.mode.OSSIMPLE: self.j_mngr.log_events("The AI Service using 'Simplfied Data' can't process an image. The image will be disregarded in generated output.", @@ -404,6 +410,12 @@ class ooba_web_request(Request): url = self.utils.validate_and_correct_url(url) #validate v1/chat/completions path + self.cFig.lm_url = url + if not self.cFig.is_lm_server_up: + self.j_mngr.log_events("Local server is not responding, may be unable to send data.", + TroubleSgltn.Severity.WARNING, + True) + #image code is here, but right now none of the tested LLM front ends can handle them #when using an http POST if image: @@ -656,6 +668,7 @@ class dall_e_request(Request): for _ in range(batch_size): try: + response = client.images.generate( model = GPTmodel, prompt = prompt, @@ -664,7 +677,7 @@ class dall_e_request(Request): style = style, n=1, response_format = "b64_json", - ) + ) # Get the revised_prompt if response and not 'error' in response: @@ -716,6 +729,21 @@ class dall_e_request(Request): is_trouble=True) self.trbl.pop_header() return(batched_images, revised_prompt) + + def modify_image(self, client, model, image_bytes, prompt, image_size): + """This is an unused stub to be used if Dall-e-3 ever implements image to image edits""" + image_bytes.seek(0) # Ensure the buffer is at the beginning + + response = client.images.edit( + model=model, + image=image_bytes, + prompt=prompt, + n=1, + size=image_size, + response_format = "b64_json" + ) + + return response class request_context: def __init__(self)-> None: diff --git a/fetch_models.py b/fetch_models.py index 324ca66..3607521 100644 --- a/fetch_models.py +++ b/fetch_models.py @@ -2,7 +2,9 @@ from abc import ABC, abstractmethod from enum import Enum from .mng_json import json_manager, TroubleSgltn #add . +from .utils import CommUtils import openai +import json from groq import Groq class RequestMode(Enum): @@ -14,11 +16,13 @@ class RequestMode(Enum): GEMINI = 6 OSSIMPLE = 7 LMSTUDIO = 8 + OLLAMA = 9 class ModelFetchStrategy(ABC): def __init__(self)->None: self.j_mngr = json_manager() + self.utils = ModelUtils() @abstractmethod def fetch_models(self, api_obj, key): @@ -71,8 +75,6 @@ class FetchGeminiModels(ModelFetchStrategy): return packaged_models - - class FetchByMethod(ModelFetchStrategy): def fetch_models(self, api_obj, key:str): @@ -87,6 +89,43 @@ class FetchByMethod(ModelFetchStrategy): True) return None return model_list + +class FetchOllama(ModelFetchStrategy): + + def __init__(self)->None: + super().__init__() # Ensures common setup from Request + self.comm = CommUtils() + + def fetch_models(self, api_obj, key): + """Parameters are ignored in this method and class as Ollama is a local app that has no + imported api object and doesn't require a key. Ollama is unique among local apps + in that it requires a model name be passed in the request.""" + + url = self.utils.url_file("urls.json", "ollama_url") + t_response = self.comm.is_lm_server_up(url,1,2) + if t_response: + response = self.comm.get_data(url, retries=2) + else: + response = None + + model_list = [] + if response is None: + return ModelsContainer(model_list) + + try: + data = response.json() + except json.JSONDecodeError as e: + self.j_mngr.log_events(f"Failed to decode Ollama models JSON file: {e}", + TroubleSgltn.Severity.WARNING, + True) + return ModelsContainer(model_list) + + for model in data.get('models', []): + model_list.append(model.get('name')) + + return ModelsContainer(model_list) + + class FetchModels: def __init__(self): @@ -94,7 +133,7 @@ class FetchModels: self.strategy = None self.api_obj = None - def fetch_models(self, request_type:RequestMode, key: str): + def fetch_models(self, request_type:RequestMode, key: str=""): if request_type == RequestMode.OPENAI: self.api_obj = openai @@ -111,6 +150,9 @@ class FetchModels: elif request_type == RequestMode.GEMINI: model_names = ['gemini-1.0-pro', 'gemini-1.0-pro-001', 'gemini-1.0-pro-latest', 'gemini-1.0-pro-vision-latest', 'gemini-1.5-pro-latest', 'gemini-pro', 'gemini-pro-vision'] return ModelsContainer(model_names) + + elif request_type == RequestMode.OLLAMA: + self.strategy = FetchOllama() if self.strategy and self.api_obj: return self.strategy.fetch_models(self.api_obj, key) @@ -144,6 +186,13 @@ class ModelUtils: return prepped_models + def url_file(self, file_name:str, field_name:str) -> str: + url_file_name = self.j_mngr.append_filename_to_path(self.j_mngr.script_dir, file_name) + url_data = self.j_mngr.load_json(url_file_name) + if url_data: + return url_data.get(field_name,'') + return '' + #Create container for models that are generated in non-standard formats class Model: def __init__(self, model_id): @@ -160,7 +209,11 @@ class ModelContainer: def get_models(self, sort_it:bool=True, with_none:bool=True, filter_str:str="",): models = ['none'] if with_none else [] - models.extend(model for model in self._models if filter_str.lower() in model.lower()) + + if filter_str: + models.extend(model for model in self._models if filter_str.lower() in model.lower()) + else: + models = self._models if sort_it: models.sort() diff --git a/help.json b/help.json index 1fef201..ee65e30 100644 --- a/help.json +++ b/help.json @@ -2,6 +2,6 @@ "sp_help": "\u2022 Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n\n****************\n\n\n\u2726 AI_Selection [input connection]: Attach the Plush 'AI_Chooser' Node to this input so you can select the AI_Service and model you want to use. As of v1.21.11 ChatGPT, Anthropic & Groq services and models are available. \n\n\u2726 creative_latitude: Higher numbers give the model more freedom to interpret your prompt or image. Lower numbers constrain the model to stick closely to your input.\n\n\u2726 tokens: A limit on how many tokens are made available for ChatGPT to use, it doesn't have to use them all.\n\n\u2726 style: Choose the art style you want to base your prompt on. If this list is too long, type a few characters of the style you're looking for and the list will dynamically filter.\n\n\u2726 artist: Will produce a 'style of' phrase listing the number of artists you indicate. They will be artists that work in the chosen style. Choose 0 if you don't want this.\n\n\u2726 prompt_style: 'Narrative' is long form grammatically correct creative writing, This is the preferred form for Dall-e. 'Tags' is a terse, stripped down list of visual attributes without grammatical phrasing, This is the preferred form for SD and Midjourney.\n\n\u2726 max_elements: A limit on the number of distinct descriptions of visual elements in the prompt. Smaller numbers makes a shorter prompt.\n\n\u2726 style_info: Set to True if you want background information about the art style you chose.", "wrangler_help": "\u2022 Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n\u2022 Exif Wrangler will extract Exif and/or AI generation workflow metadata from .jpg (.jpeg) and .png images. .jpg photographs can be queried for their camera settings. ComfyUI's .png files will yield certain values from their workflow including the prompt, seed etc. Images from other AI generators may or may not yield data depending on where they store their metadata. For instance Auto 1111 .jpg's will yield their workflow information that's stored in their Exif comment.\n\n**************\n \n\u2726 write_to_file: Whether or not to save the meta data file you see in the output to a .txt file in the: '.../ComfyUI/output/PlushFiles' directory.\n\n\u2726 file_prefix: The prefix for the file name of the saved file, this will be appended to a date/time value to make the file unique. The file will have a .txt extension: e.g., 'MyFileName_ew_20240204_193224.txt'\n\n\u2726 Min_Prompt_len: A filter value for prompts: Exif Wrangler has to distinguish between actual prompts and other long strings in the ComfyUI embeded meta data. Every Note, every text display box, and even some text that's hidden in nodes is included in the JSON that holds this information. This field allows you to set a minimum length for strings to be displayed to help filter out shorter unwanted text strings.\n\n\u2726 Alpha_Char_Pct: Another prompt filter that works by only allowing text strings that have a percentage of alpha ASCII characters (Aa - Zz plus comma) equal to or higher than this setting. Increasing the percentage screens out strings that have lots of bytes, symbols and numbers. If you use a lot of weightings or Lora values in your prompts that introduce angle brackets, parentheses, brackets and colons, you may have to lower this percentage to see your prompt. \n\n\u2726 Prompt_Filter_Term: Enter a single term or short phrase here. A particular prompt string will only be included in Possible Prompts if it contains an exact match for this term. This can be used in a couple of ways: \n 1) If you know there's a term you always or frequently use in the prompts, or if you remember part of a particular image prompt's wording, you can add it here before you click the Queue button. \n 2) If, after clicking Queue, a lot of Possible Prompt candidates clutter your output. Find the one you know is the actual prompt, find a unique word or phrase in it e.g.: 'regal'. Enter that word or phrase as a filter term and run Wrangler again. You'll get back an uncluttered response to save as a file.\n\n***************\n\n\u2726 troubleshooting output: Hook this output up to a text display node to see any INFO/WARNING/ERROR data that's generated during this node's run. ", "dalle_help": "\u2022 Use 'Show Text|pysssss' nodes for displaying text output from Plush nodes. Plush outputs text as UTF-8 Unicode, which Show Text can display correctly.\n\n\u2022 Dall-e Image will produce an image .PNG from a text prompt using the Dall-e 3 model from OpenAI. It requires a OpenAI API key.\n\n**************\n\n\u2726 GPTmodel: The Dall-e model that will generate the image file. Currently this is limited to Dall-e 3.\n\n\u2726 prompt: The text prompt for the image you want to produce. Be aware that OpenAI will generate their own prompt from your prompt and pass that to the image model.\n\n\u2726 image_size: Choose a square, portrait or landscape image. The image size format is: Width, Height. The 1792 image sizes cost slightly more tokens.\n\n\u2726 image_quality: Self explanatory, you can experiment to see if you think there's a noticable difference. The standard quality image costs a few less tokens than hd.\n\n\u2726 style: Vivid produces a little more contrast and more saturated colors. The choice depends on what type of image you're trying to produce.\n\n\u2726 batch_size: The number of images you want to produce in one run. The vast majority of the times batches run without incident, but you should be aware that sending image requests to the Dall-e server is not as reliable as running images locally in SD. If the server gets overtaxed, or hiccups you may not get back all the images you requested. This Dall-e node will handle OpenAI server errors gracefully and allow your batch to continue to completion, but sometimes you may get back fewer images than you requested. If you keep the 'troubleshooting' output connected it will report any errors and let you know how many images were processed vs how many you requested.\n\n\u2726 seed: This works just like a seed in a KSampler except that it doesn't affect a latent or the image. It's simply there for you to set to: 'randomize' or 'increment' if you want Dall-e to run with every Queue, or to 'fixed' if you only want Dall-e to run once per prompt or setting. The Dall_e API doesn't actually pass seed values. This can also be controlled by the 'Global Seed' from the Inspire Pack. \n\n***************\n\n\u2726 troubleshooting output: Hook this output up to a text display node to see any INFO/WARNING/ERROR data that's generated during this node's run.\n\n\u2726 Dalle_e_prompt: The prompt that Dall-e 3 generates from your prompt. This is the prompt that actually gets passed to the image model. Hook up a text display node to see it.", - "adv_prompt_help": "\u2022 Advanced Prompt Enhancer uses AI Models to generate text output from any combination of: Instruction, Example(s), Image and Prompt you provide. No API key is needed for Open source Models. This node can use various remote models, ChatGPT, Groq and Anthropic Claude if you have an API key and have stored it in an environment variable (see ReadMe file). With or without a key it can also connect to various local apps and models e.g.: LM Studio, Oobabooga, Koboldcpp, etc.\n\n\u2022 image input: Advanced Prompt Enhancer can send image data (in the form of a b64 image file) to AI vision capable models. If you're sending an image to an AI model be sure both the model and the app or remote service have vision capabilities and can handle image files.\n\n\u2022 API Keys: API keys need to be kept in environment variables. The Environment Variable names that Advanced Prompt Enhancer looks for are: \u2726ChatGPT: OPENAI_API_KEY or OAI_KEY; \u2726Groq: GROQ_API_KEY; \u2726Anthropic: ANTHROPIC_API_KEY. Find instructions on how to create the Enviroment Variable here: https://github.com/glibsonoran/Plush-for-ComfyUI?tab=readme-ov-file#requirements . \n\n**************\n\n\u2022 AI_service: This indicates the type of AI service and connection you're going to send your data to. If you're using a local AI app you'll need to provide a valid URL in the LLM_URL field near the bottom of the node. If you're using 'Oobabooga API' make sure you read the LLM_URL help below. 'OpenAI compatible http POST' uses a web POST action rather than the OpenAI API Object to communicate with the local or remote AI server, typically this requires a 'v1/chat/completions' path in the URL. 'http POST Simplified Data' also uses a web POST action and presents a simplified data structure. Try this if the other AI service methods don't work, it will also require a: 'v1/chat/completions' path. \n\n\u2022 GPTmodel: This field only applies when the LLM field is set to 'ChatGPT'. Select the specific OpenAI ChatGPT model you want to use. If you're inputting an image, make sure the model you choose is vision capable.\n\n\u2022 Groq_model: This only applies when you select 'Groq' in the AI_service field. Choose the Groq model you want to use. \n\n\u2022 Anthropic_model: This only applies when you select 'Anthropic' from the AI_service field. Choose the Anthopic model you want to use. \n\n\u2022 optional_local_model: If you're running a local AI/LLM front end that requires a model name be passed to it (e.g. Ollama), type the name of the model you want to use in this field. \n\n\u2022 creative_latitude: (Temperature) This will set how strictly the LLM adheres to common word relationships and how closely it will follow your instruction and prompt. Setting this value higher allows more creative freedom in interpreting your input and generating its ouptput.\n\n\u2022 tokens: The maximum number of tokens that the LLM can use in processing your prompt and return text. This is not the number of tokens it 'will' use, it's the number available that it 'can' use.\n\n\u2022 seed: This is a pseudo or mock seed, it has no effect on the text generated, and it's not passed to the LLM. It's used here solely to control when the node will run. It works the same as a KSampler, set it to 'fixed' if you want the node to run only once each time you change your inputs, set it to random or increment/decrement if you want it run with each Queue.\n\n\u2022 example_delimiter: You can provide multiple examples to the LLM of how you want the generated output to look (writing style, formatting, use of language, etc). Providing multiple examples for a given instruction is a type of 'Few Shot Learning', which can be effective with some LLM's. This field indicates how the node will distinguish each separate example. You can choose to separate your examples with a pipe '|' character, two newlines (i.e.: carriage returns) or two colons '::', these are called delimiters. You can add other newlines or spaces between examples as you need to for readability as long as these delimiters are somewhere between the separate examples (the extra lines and spaces will be stripped out before being sent to the LLM). \n\n\u2022 LLM_URL: When using an LLM other than ChatGPT, Anthropic or Groq you'll need to provide a URL in this field. Typically the AI application you're using (e.g. LM Studio, Oobabooga), will indicate the URL to use after you startup its server. It may be in the terminal output or in the UI. Some local AI apps will specify that a particular URL is OpenAI compatible, if so this is the one you want to use. Typically the URLs for local apps have this general format: http://localhost:5001/v1 where '5001' is the port and 'localhost' is interchangable with '127.0.0.1'. If you're using the Oobabooga API or 'OpenAI compatible http POST' selection your url will need to have /chat/completions appended as part of the url: http://127.0.0.1:5000/v1/chat/completions\n\n**************\n\n\u2022 Use the troubleshooting output if you have issues with model connections, or if you want to see exactly which model was used to produce your output (some ChatGPT model names are actually only pointers to the latest specific model in that category) and how many tokens were used.", + "adv_prompt_help": "\u2022 Advanced Prompt Enhancer (APE) uses AI Models to generate text output from any combination of: Instruction, Example(s), Image and Prompt you provide. No API key is needed for Open source Models. This node can use various remote models, ChatGPT, Groq and Anthropic Claude if you have an API key and have stored it in an environment variable (see ReadMe file). With or without a key it can also connect to various local apps and models e.g.: LM Studio, Oobabooga, Koboldcpp, etc.\n\n\u2022 image input: Advanced Prompt Enhancer can send image data (in the form of a b64 image file) to AI vision capable models. If you're sending an image to an AI model be sure both the model and the app or remote service have vision capabilities and can handle image files.\n\n\u2022 API Keys: API keys need to be kept in environment variables. The Environment Variable names that Advanced Prompt Enhancer looks for are: \u2726ChatGPT: OPENAI_API_KEY or OAI_KEY; \u2726Groq: GROQ_API_KEY; \u2726Anthropic: ANTHROPIC_API_KEY. Find instructions on how to create the Enviroment Variable here: https://github.com/glibsonoran/Plush-for-ComfyUI?tab=readme-ov-file#requirements . \n\n**************\n\n\u2022 AI_service: This indicates the type of AI service and connection you're going to send your data to. If you're using a local AI app you'll need to provide a valid URL in the LLM_URL field near the bottom of the node. If you're using 'Oobabooga API' make sure you read the LLM_URL help below. 'OpenAI compatible http POST' uses a web POST action rather than the OpenAI API Object to communicate with the local or remote AI server, typically this requires a 'v1/chat/completions' path in the URL. 'http POST Simplified Data' also uses a web POST action and presents a simplified data structure. Try this if the other AI service methods don't work, it will also require a: 'v1/chat/completions' path. \n\n\u2022 GPTmodel: This field only applies when the LLM field is set to 'ChatGPT'. Select the specific OpenAI ChatGPT model you want to use. If you're inputting an image, make sure the model you choose is vision capable.\n\n\u2022 Groq_model: This only applies when you select 'Groq' in the AI_service field. Choose the Groq model you want to use. \n\n\u2022 Anthropic_model: This only applies when you select 'Anthropic' from the AI_service field. Choose the Anthopic model you want to use. \n\n\u2022 Ollama_model: This will display the model(s) currently loaded in the Ollama front end. In order for models to show up in the drop down Ollama will have to be running with the models you intend to use loaded *before* starting ComfyUI. Note that APE looks for the standard url: http://localhost:11434/api/tags when retrieving the model names. If you've setup Ollama with another url (e.g. different port), you'll need to modify the 'urls.json' file. \n\n\u2022 creative_latitude: (Temperature) This will set how strictly the LLM adheres to common word relationships and how closely it will follow your instruction and prompt. Setting this value higher allows more creative freedom in interpreting your input and generating its ouptput.\n\n\u2022 tokens: The maximum number of tokens that the LLM can use in processing your prompt and return text. This is not the number of tokens it 'will' use, it's the number available that it 'can' use.\n\n\u2022 seed: This is a pseudo or mock seed, it has no effect on the text generated, and it's not passed to the LLM. It's used here solely to control when the node will run. It works the same as a KSampler, set it to 'fixed' if you want the node to run only once each time you change your inputs, set it to random or increment/decrement if you want it run with each Queue.\n\n\u2022 example_delimiter: You can provide multiple examples to the LLM of how you want the generated output to look (writing style, formatting, use of language, etc). Providing multiple examples for a given instruction is a type of 'Few Shot Learning', which can be effective with some LLM's. This field indicates how the node will distinguish each separate example. You can choose to separate your examples with a pipe '|' character, two newlines (i.e.: carriage returns) or two colons '::', these are called delimiters. You can add other newlines or spaces between examples as you need to for readability as long as these delimiters are somewhere between the separate examples (the extra lines and spaces will be stripped out before being sent to the LLM). \n\n\u2022 LLM_URL: When using an LLM other than ChatGPT, Anthropic or Groq you'll need to provide a URL in this field. Typically the AI application you're using (e.g. LM Studio, Oobabooga), will indicate the URL to use after you startup its server. It may be in the terminal output or in the UI. Some local AI apps will specify that a particular URL is OpenAI compatible, if so this is the one you want to use. Typically the URLs for local apps have this general format: http://localhost:5001/v1 where '5001' is the port and 'localhost' is interchangable with '127.0.0.1'. If you're using the Oobabooga API or 'OpenAI compatible http POST' selection your url will need to have /chat/completions appended as part of the url: http://127.0.0.1:5000/v1/chat/completions\n\n**************\n\n\u2022 Use the troubleshooting output if you have issues with model connections, or if you want to see exactly which model was used to produce your output (some ChatGPT model names are actually only pointers to the latest specific model in that category) and how many tokens were used.", "tagger_help": "\u2022 Tagger adds tags to the beginning, middle or end of a text block. Tagger can be used whenever you want to add text that needs to appear exactly as written. \n\n**************\n\n\u2022 Beginning_tags: The text (tags) you want to appear at the very beginning of the input text block. It will preface all other text in the block. \n\n\u2022 Middle_tags: The text (tags) you want to appear in the middle of the text block. These tags will always appear immediately after a comma or period. \n\n\u2022 Prefer_middle_tag_after_period: You can indicate a preference for the tags to follow a period by clicking this button. Otherwise the tags may follow a period or a comma whichever is closest to the middle of the text. \n\n\u2022 End_tags: Tags that will be appended to the end of the input text block.\n\n\u2022 Examples: Beginning_tags: '[An Abstract Painting:| Digital Art:]', Middle_tags: '(Big Black Hat:1.4)', End_tags: 'In the style of Piet Mondrian' " } \ No newline at end of file diff --git a/style_prompt.py b/style_prompt.py index 8d94f91..5b483c3 100644 --- a/style_prompt.py +++ b/style_prompt.py @@ -48,6 +48,7 @@ class cFigSingleton: cls._groq_models = None cls._claude_models = None cls._gemini_models = None + cls._ollama_models = None cls._written_url = "" cls.j_mngr = json_manager() cls._model_fetch = FetchModels() @@ -137,7 +138,7 @@ class cFigSingleton: self._groq_models = self._model_fetch.fetch_models(RequestMode.GROQ, self._groq_key) self._claude_models = self._model_fetch.fetch_models(RequestMode.CLAUDE, self._claude_key) self._gemini_models = self._model_fetch.fetch_models(RequestMode.GEMINI, self._gemini_key) - + self._ollama_models = self._model_fetch.fetch_models(RequestMode.OLLAMA, "") def get_chat_models(self, sort_it:bool=False, filter_str:str="")->list: return self._model_prep.prep_models_list(self._fig_gpt_models, sort_it, filter_str) @@ -149,7 +150,10 @@ class cFigSingleton: return self._model_prep.prep_models_list(self._claude_models, sort_it, filter_str) def get_gemini_models(self, sort_it:bool=False, filter_str:str="")->list: - return self._model_prep.prep_models_list(self._gemini_models, sort_it, filter_str) + return self._model_prep.prep_models_list(self._gemini_models, sort_it, filter_str) + + def get_ollama_models(self, sort_it:bool=False, filter_str:str="")->list: + return self._model_prep.prep_models_list(self._ollama_models, sort_it, filter_str) def _set_llm_client(self, url:str, request_type:RequestMode=RequestMode.OPENSOURCE)-> bool: @@ -228,6 +232,7 @@ class cFigSingleton: #self._lm_models = [] if url: # If the new URL is not empty, update the client self._set_llm_client(url, self._lm_request_mode) + def is_lm_server_up(self): #should be util in api_requests.py @@ -243,6 +248,7 @@ class cFigSingleton: True) return True else: + self.write_url(self._lm_url) #Save url to a text file self.j_mngr.log_events(f"Server returned response code: {response.status_code}", TroubleSgltn.Severity.INFO, True) @@ -647,7 +653,7 @@ class AdvPromptEnhancer: if connection_type == "Anthropic": return Anthropic_model - return local_model + return local_model @classmethod @@ -662,7 +668,7 @@ class AdvPromptEnhancer: "ChatGPT_model": (cFig.get_chat_models(True,'gpt'), {"default": ""}), "Groq_model": (cFig.get_groq_models(True), {"default": ""}), "Anthropic_model": (cFig.get_claude_models(True), {"default": ""}), - "optional_local_model": ("STRING",{"default": "None"}), + "Ollama_model": (cFig.get_ollama_models(True), {"default": ""}), "creative_latitude" : ("FLOAT", {"max": 1.901, "min": 0.1, "step": 0.1, "display": "number", "round": 0.1, "default": 0.7}), "tokens" : ("INT", {"max": 8000, "min": 20, "step": 10, "default": 500, "display": "number"}), "seed": ("INT", {"default": 9, "min": 0, "max": 0xffffffffffffffff}), @@ -692,7 +698,7 @@ class AdvPromptEnhancer: CATEGORY = "Plush/Prompt" - def gogo(self, AI_service, ChatGPT_model, Groq_model, Anthropic_model, optional_local_model, creative_latitude, tokens, seed, examples_delimiter, + def gogo(self, AI_service, ChatGPT_model, Groq_model, Anthropic_model, Ollama_model, creative_latitude, tokens, seed, examples_delimiter, LLM_URL:str="", Instruction:str="", Prompt:str = "", Examples:str ="",image=None, unique_id=None): if unique_id: @@ -710,10 +716,10 @@ class AdvPromptEnhancer: LLM_URL = Enhancer.undefined_to_none(LLM_URL) image = Enhancer.undefined_to_none(image) - remote_model = self.get_model(ChatGPT_model, Groq_model, Anthropic_model, optional_local_model, AI_service) + remote_model = self.get_model(ChatGPT_model, Groq_model, Anthropic_model, Ollama_model, AI_service) - if remote_model == "None": - self.j_mngr.log_events("No model selected. If you're using a Local application it will most likely use the loaded model.", + if remote_model == "none": + self.j_mngr.log_events("No model selected. If you're using a local desktop application, most will just use the loaded model.", TroubleSgltn.Severity.INFO, True) @@ -937,6 +943,29 @@ class DalleImage: return base64_image + @staticmethod + def tensor_to_bytes(tensor: torch.Tensor) -> BytesIO: + """ + Converts a PyTorch tensor to a bytes object. + + Args: + tensor (torch.Tensor): The image tensor to convert. + + Returns: + BytesIO: BytesIO object containing the image data. + """ + # Convert tensor to PIL Image + if tensor.ndim == 4: + tensor = tensor.squeeze(0) # Remove batch dimension if present + pil_image = Image.fromarray((tensor.numpy() * 255).astype('uint8')) + + # Save PIL Image to a buffer + buffer = BytesIO() + pil_image.save(buffer, format="PNG") # Can change to JPEG if preferred + buffer.seek(0) + + return buffer + @classmethod def INPUT_TYPES(cls): #dall-e-2 API requires differnt input parameters as compared to dall-e-3, at this point I'll just use dall-e-3 @@ -974,6 +1003,7 @@ class DalleImage: else: self.trbl.reset('Dall-e Image Node') + _help = self.help_data.dalle_help self.ctx.request = rqst.dall_e_request() kwargs = { "model": GPTmodel, @@ -981,7 +1011,7 @@ class DalleImage: "image_size": image_size, "image_quality": image_quality, "style": style, - "batch_size": batch_size + "batch_size": batch_size, } batched_images, revised_prompt = self.ctx.execute_request(**kwargs) @@ -1305,3 +1335,4 @@ NODE_DISPLAY_NAME_MAPPINGS = { "DalleImage": "OAI Dall_e Image", "ImageInfoExtractor": "Exif Wrangler" } + diff --git a/urls.json b/urls.json new file mode 100644 index 0000000..717c0b9 --- /dev/null +++ b/urls.json @@ -0,0 +1,3 @@ +{ +"ollama_url": "http://localhost:11434/api/tags" +} \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..359e247 --- /dev/null +++ b/utils.py @@ -0,0 +1,59 @@ +import requests +from requests.adapters import HTTPAdapter, Retry +from .mng_json import json_manager, TroubleSgltn + +class CommUtils: + def __init__(self)->None: + self.j_mngr = json_manager() + + def is_lm_server_up(self, endpoint:str, comm_retries:int=2, timeout:int=4): #should be util in api_requests.py + session = requests.Session() + retries = Retry(total=comm_retries, backoff_factor=0, status_forcelist=[500, 502, 503, 504]) + session.mount('http://', HTTPAdapter(max_retries=retries)) + try: + response = session.head(endpoint, timeout=timeout) # Use HEAD to minimize data transfer + if 200 <= response.status_code <= 300: + self.write_url(endpoint) #Save url to a text file + self.j_mngr.log_events(f"Local LLM Server is running with status code: {response.status_code}", + TroubleSgltn.Severity.INFO, + True) + return True + else: + self.j_mngr.log_events(f"Server returned response code: {response.status_code}", + TroubleSgltn.Severity.INFO, + True) + return True + + except requests.RequestException as e: + self.j_mngr.log_events(f"Local LLM Server is not running: {e}", + TroubleSgltn.Severity.WARNING, + True) + return False + + def get_data(self, endpoint:str="", timeout:int=8, retries:int=1, data_type:str="" )-> requests.Response | None: + session = requests.Session() + gretries = Retry(total=retries, backoff_factor=0, status_forcelist=[500, 502, 503, 504]) + session.mount('http://', HTTPAdapter(max_retries=gretries)) + stat_code = 0 + try: + response = session.get(endpoint, timeout=timeout) + stat_code = response.status_code + response.raise_for_status() # Raises an HTTPError if the response status code indicates an error + return response + + except requests.RequestException as e: + self.j_mngr.log_events(f"Unable to fetch data for: {data_type}. Server returned code: {stat_code}. Error: {e} ", + TroubleSgltn.Severity.WARNING, + True) + return None + + def write_url(self, url:str) -> bool: + # Save the current open source url for startup retrieval of models + + url_file = self.j_mngr.append_filename_to_path(self.j_mngr.script_dir, 'OpenSourceURL.txt') + url_result = self.j_mngr.write_string_to_file(url, url_file) + self._written_url = url + self.j_mngr.log_events("Open source LLM URL saved to file.", + TroubleSgltn.Severity.INFO, + True) + return url_result