diff --git a/README.md b/README.md index 736d848..3504e37 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,16 @@ This repo creates a series of nodes that enable you to utilize the [Griptape Python Framework](https://github.com/griptape-ai/griptape/) with [ComfyUI](https://github.com/comfyanonymous/ComfyUI), integrating LLMs (Large Language Models) and AI into your workflow. -This example creates two [Griptape Agents](https://docs.griptape.ai/stable/griptape-framework/structures/agents/) - a Writer and an Editor. The writer has a [WebScraper](https://docs.griptape.ai/stable/griptape-tools/official-tools/web-scraper/) tool and gets a description of a website. It then generates blog topic ideas and passes them to_ the Editor Agent. The editor chooses the top 3 topics, and generates outlines and next actions for the next two. A third Agent generates thumbnail images for each blog post using a [Prompt Image Generation Task](https://docs.griptape.ai/stable/griptape-framework/structures/tasks/#prompt-image-generation-task) based on the outlines. +### Instructions and tutorials -![Writer and Editor agents](docs/images/writere_editor_agents.png) +Watch the trailer and all the instructional videos on our [YouTube Playlist](https://www.youtube.com/playlist?list=PLZRzNKLLiEyeK9VN-i53sUU1v5vBLl-nG). + +[![Watch the Trailer on YouTube](docs/images/playlist.png)](https://www.youtube.com/playlist?list=PLZRzNKLLiEyeK9VN-i53sUU1v5vBLl-nG) The repo currently has a subset of Griptape nodes, with more to come soon. Current nodes can: * Create [Agents](https://docs.griptape.ai/stable/griptape-framework/structures/agents/) using these models: - * Local - via Ollama + * Local - via **Ollama** and **LM Studio** * Llama 3 * Mistral * etc.. @@ -58,10 +60,16 @@ In this example, we're using three `Image Description` nodes to describe the giv 4. Griptape Tools - Featuring Task Memory and Off-Prompt: https://youtu.be/TvEbv0vTZ5Q 5. Griptape Rulesets, and Image Creation: https://youtu.be/DK16ouQ_vSs 6. Image Generation with multiple drivers: https://youtu.be/Y4vxJmAZcho +7. Image Description, Parallel Image Description: https://youtu.be/OgYKIluSWWs?si=JUNxhvGohYM0YQaK +8. Audio Transcription: https://youtu.be/P4GVmm122B0?si=24b9c4v1LWk_n80T +9. Using Ollama as a Configuration Driver: https://youtu.be/jIq_TL5xmX0?si=ilfomN6Ka1G4hIEp +10. Combining Rulesets: https://youtu.be/3LDdiLwexp8?si=Oeb6ApEUTqIz6J6O +11. Integrating Text: https://youtu.be/_TVr2zZORnA?si=c6tN4pCEE3Qp0sBI +12. New Nodes & Quality of life improvements: https://youtu.be/M2YBxCfyPVo?si=pj3AFAhl2Tjpd_hw +13. Merge Input Data: https://youtu.be/wQ9lKaGWmZo?si=FgboU5iUg82pXRkC ## Installation - #### 1. ComfyUI Install [ComfyUI](https://github.com/comfyanonymous/ComfyUI) using the [instructions](https://github.com/comfyanonymous/ComfyUI?tab=readme-ov-file#installing) for your particular operating system. diff --git a/docs/images/playlist.png b/docs/images/playlist.png new file mode 100644 index 0000000..d14c667 Binary files /dev/null and b/docs/images/playlist.png differ diff --git a/js/gtUINodes.js b/js/gtUINodes.js index 0e25482..005a83d 100644 --- a/js/gtUINodes.js +++ b/js/gtUINodes.js @@ -600,6 +600,96 @@ app.registerExtension({ this?.graph?.setDirtyCanvas(true, true); }; }; + + // Combine: Merge Texts + if ( nodeData.name === "Griptape Combine: Merge Texts" || + nodeData.name === "Griptape Combine: Merge Inputs" || + nodeData.name === "Griptape Combine: Rules List" || + nodeData.name === "Griptape Combine: Tool List") { + + // Set the base name of the input node + var input_name = "input_"; + + switch (nodeData.name) { + case 'Griptape Combine: Rules List': + input_name = "rules_"; + break; + case 'Griptape Combine: Tool List': + input_name = "tool_"; + break; + } + + const onConnectionsChange = nodeType.prototype.onConnectionsChange; + nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) { + if(!link_info) + return; + + if(type==1) { + const node = app.graph.getNodeById(link_info.origin_id); + let origin_type = node.outputs[link_info.origin_slot].type; + + if(origin_type == '*') { + this.disconnectInput(link_info.target_slot); + } + + for(let i in this.inputs) { + let input_i = this.inputs[i]; + for(let i in this.inputs) { + let input_i = this.inputs[i]; + if(input_i.name != 'select' && input_i.name != 'sel_mode') + input_i.type = origin_type; + } + + this.outputs[0].type = origin_type; + this.outputs[0].label = origin_type; + this.outputs[0].name = origin_type; + } + } + + let select_slot = this.inputs.find(x => x.name == "select"); + let mode_slot = this.inputs.find(x => x.name == "sel_mode"); + + let converted_count = 0; + converted_count += select_slot?1:0; + converted_count += mode_slot?1:0; + + if (!connected && (this.inputs.length > 1+converted_count)) { + const stackTrace = new Error().stack; + + if( + !stackTrace.includes('LGraphNode.prototype.connect') && // for touch device + !stackTrace.includes('LGraphNode.connect') && // for mouse device + !stackTrace.includes('loadGraphData') && + this.inputs[index].name != 'select') { + this.removeInput(index); + } + } + + let slot_i = 1; + for (let i = 0; i < this.inputs.length; i++) { + let input_i = this.inputs[i]; + if(input_i.name != 'select'&& input_i.name != 'sel_mode') { + input_i.name = `${input_name}${slot_i}` + slot_i++; + } + } + + let last_slot = this.inputs[this.inputs.length - 1]; + if ( + (last_slot.name == 'select' && last_slot.name != 'sel_mode' && this.inputs[this.inputs.length - 2].link != undefined) + || (last_slot.name != 'select' && last_slot.name != 'sel_mode' && last_slot.link != undefined)) { + this.addInput(`${input_name}${slot_i}`, this.outputs[0].type); + } + + if(this.widgets) { + this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length; + this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max); + if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0) + this.widgets[0].value = 1; + } + } + + } } }); diff --git a/nodes/agent/base_agent.py b/nodes/agent/base_agent.py index a160b9f..34c9416 100644 --- a/nodes/agent/base_agent.py +++ b/nodes/agent/base_agent.py @@ -16,7 +16,6 @@ class BaseAgent: def __init__(self): self.default_prompt = default_prompt self.agent = gtComfyAgent() - # self.agent.set_default_config() @classmethod def INPUT_TYPES(s): @@ -41,13 +40,12 @@ class BaseAgent: "STRING", { "forceInput": True, + "dynamicPrompts": True, }, ), "STRING": ( "STRING", - { - "multiline": True, - }, + {"multiline": True, "dynamicPrompts": True}, ), }, } @@ -115,6 +113,7 @@ class BaseAgent: self.agent.tools = tools if len(rulesets) > 0: self.agent.rulesets = rulesets + print(self.agent.rulesets) # Warn for models model, simple_model = self.agent.model_check() diff --git a/nodes/base_loader.py b/nodes/base_loader.py index bf987d3..35eb8cc 100644 --- a/nodes/base_loader.py +++ b/nodes/base_loader.py @@ -1,74 +1,29 @@ -from griptape.structures import Agent -from griptape.tasks import PromptTask +from griptape.artifacts import BaseArtifact -class gtUIBaseTask: +class gtUIBaseLoader: def __init__(self): pass - DESCRIPTION = "Run a PromptTask." + DESCRIPTION = "Loader" @classmethod def INPUT_TYPES(s): return { - "required": { - "STRING": ( - "STRING", - { - "multiline": True, - }, - ), - }, - "optional": { - "input_string": ( - "STRING", - { - "forceInput": True, - }, - ), - "agent": ("AGENT",), - }, + "required": {}, + "optional": {}, } - RETURN_TYPES = ( - "STRING", - "AGENT", - ) - RETURN_NAMES = ( - "OUTPUT", - "AGENT", - ) + RETURN_TYPES = ("ARTIFACT",) + RETURN_NAMES = ("OUTPUT",) FUNCTION = "run" OUTPUT_NODE = True CATEGORY = "Griptape/Agent Tasks" - def get_prompt_text(self, STRING, input_string): - # Get the prompt text - if not input_string: - prompt_text = STRING - else: - prompt_text = STRING + "\n\n" + input_string - - return prompt_text - def run( self, - STRING, - input_string=None, - agent=None, ): - if not agent: - agent = Agent() - - prompt_text = self.get_prompt_text(STRING, input_string) - try: - agent.add_task(PromptTask(prompt_text)) - except Exception as e: - print(e) - result = agent.run() - return ( - result.output_task.output.value, - agent, - ) + artifact = BaseArtifact() + return (artifact,) diff --git a/nodes/base_task.py b/nodes/base_task.py index 3e6e140..5f83e32 100644 --- a/nodes/base_task.py +++ b/nodes/base_task.py @@ -15,17 +15,13 @@ class gtUIBaseTask: "required": { "STRING": ( "STRING", - { - "multiline": True, - }, + {"multiline": True, "dynamicPrompts": True}, ), }, "optional": { "input_string": ( "STRING", - { - "forceInput": True, - }, + {"forceInput": True, "dynamicPrompts": True}, ), "agent": ("AGENT",), }, diff --git a/nodes/combine_nodes.py b/nodes/combine_nodes.py index ca60fb7..8a6193d 100644 --- a/nodes/combine_nodes.py +++ b/nodes/combine_nodes.py @@ -17,26 +17,6 @@ class MergeTexts: "STRING", {"multiline": False, "default": "", "forceInput": True}, ), - "input_2": ( - "STRING", - {"multiline": False, "default": "", "forceInput": True}, - ), - "input_3": ( - "STRING", - {"multiline": False, "default": "", "forceInput": True}, - ), - "input_4": ( - "STRING", - {"multiline": False, "default": "", "forceInput": True}, - ), - "input_5": ( - "STRING", - {"multiline": False, "default": "", "forceInput": True}, - ), - "input_6": ( - "STRING", - {"multiline": False, "default": "", "forceInput": True}, - ), }, } @@ -48,23 +28,15 @@ class MergeTexts: def create( self, - input_1="", - input_2="", - input_3="", - input_4="", - input_5="", - input_6="", + **kwargs, ): - # Join strings by newline - concatenated_string = "\n\n".join( - [ - string - for string in [input_1, input_2, input_3, input_4, input_5, input_6] - if not string == "" - ], - ) + merged_text = "" - return (concatenated_string,) + inputs = [value for value in kwargs.values()] + + for input in inputs: + merged_text += "\n\n" + input + return (merged_text,) class gtUIMergeInputs: @@ -83,11 +55,6 @@ class gtUIMergeInputs: "required": {}, "optional": { "input_1": ("*",), - "input_2": ("*",), - "input_3": ("*",), - "input_4": ("*",), - "input_5": ("*",), - "input_6": ("*",), }, } @@ -99,30 +66,13 @@ class gtUIMergeInputs: def create( self, - input_1="", - input_2="", - input_3="", - input_4="", - input_5="", - input_6="", + **kwargs, ): - # Join strings by newline - concatenated_string = "\n\n".join( - [ - string - for string in [ - str(input_1), - str(input_2), - str(input_3), - str(input_4), - str(input_5), - str(input_6), - ] - if not string == "" - ], - ) - - return (concatenated_string,) + concatenated_value = "" + inputs = [value for value in kwargs.values()] + for input in inputs: + concatenated_value += "\n\n" + str(input) + return (concatenated_value,) class RulesList: @@ -140,11 +90,6 @@ class RulesList: return { "optional": { "rules_1": ("RULESET",), - "rules_2": ("RULESET",), - "rules_3": ("RULESET",), - "rules_4": ("RULESET",), - "rules_5": ("RULESET",), - "rules_6": ("RULESET",), } } @@ -154,27 +99,15 @@ class RulesList: CATEGORY = "Griptape/Agent Rules" - def create( - self, - rules_1=None, - rules_2=None, - rules_3=None, - rules_4=None, - rules_5=None, - rules_6=None, - ): - rule_list = [ - rule[0] - for rule in [ - rules_1, - rules_2, - rules_3, - rules_4, - rules_5, - rules_6, - ] - if rule is not None - ] + def create(self, **kwargs): + # Clear the rule_list + rule_list = [] + + rules = [value for value in kwargs.values()] + if len(rules) > 0: + for rule in rules: + rule_list.append(rule[0]) + # rule_list = [rule[0] for rule in [kwargs.values()] if rule is not None] return (rule_list,) @@ -193,11 +126,6 @@ class ToolList: return { "optional": { "tool_1": ("TOOL_LIST",), - "tool_2": ("TOOL_LIST",), - "tool_3": ("TOOL_LIST",), - "tool_4": ("TOOL_LIST",), - "tool_5": ("TOOL_LIST",), - "tool_6": ("TOOL_LIST",), } } @@ -207,25 +135,11 @@ class ToolList: CATEGORY = "Griptape/Agent Tools" - def create( - self, - tool_1=None, - tool_2=None, - tool_3=None, - tool_4=None, - tool_5=None, - tool_6=None, - ): - tool_list = [ - tool[0] - for tool in [ - tool_1, - tool_2, - tool_3, - tool_4, - tool_5, - tool_6, - ] - if tool is not None - ] + def create(self, **kwargs): + tool_list = [] + + tools = [value for value in kwargs.values()] + if len(tools) > 0: + for tool in tools: + tool_list.append(tool[0]) return (tool_list,) diff --git a/nodes/tasks.py b/nodes/tasks.py index 4684d12..50dbc3a 100644 --- a/nodes/tasks.py +++ b/nodes/tasks.py @@ -64,6 +64,7 @@ class gtUICsvExtractionTask(gtUIBaseTask): "STRING", { "multiline": False, + "dynamicPrompts": True, "default": "Column 1, Column 2, Column 3", }, ), @@ -115,6 +116,7 @@ class gtUIJsonExtractionTask(gtUIBaseTask): "STRING", { "multiline": True, + "dynamicPrompts": True, "default": default_schema, }, ), diff --git a/nodes/text_nodes.py b/nodes/text_nodes.py index fced6e4..a32cd1e 100644 --- a/nodes/text_nodes.py +++ b/nodes/text_nodes.py @@ -12,7 +12,9 @@ class gtUIInputStringNode: def INPUT_TYPES(s): return { "required": {}, - "optional": {"STRING": ("STRING", {"multiline": True})}, + "optional": { + "STRING": ("STRING", {"multiline": True, "dynamicPrompts": True}) + }, } RETURN_TYPES = ("STRING",)