diff --git a/src/backend/base/langflow/components/astra_assistants/create_assistant.py b/src/backend/base/langflow/components/astra_assistants/create_assistant.py index 37725a93f..03b877437 100644 --- a/src/backend/base/langflow/components/astra_assistants/create_assistant.py +++ b/src/backend/base/langflow/components/astra_assistants/create_assistant.py @@ -1,50 +1,55 @@ -from typing import Optional - from astra_assistants import patch # type: ignore from openai import OpenAI - -from langflow.custom import CustomComponent +from langflow.custom import Component +from langflow.inputs import StrInput, MultilineInput +from langflow.template import Output +from langflow.schema.message import Message -class AssistantsCreateAssistant(CustomComponent): +class AssistantsCreateAssistant(Component): + icon = "bot" display_name = "Create Assistant" description = "Creates an Assistant and returns it's id" - def build_config(self): - return { - "name": { - "display_name": "Assistant Name", - "advanced": False, - "info": "Name for the assistant being created", - }, - "instructions": { - "display_name": "Instructions", - "info": "Instructions for the assistant, think of these as the system prompt.", - "advanced": False, - }, - "model": { - "display_name": "Model name", - "advanced": False, - "info": ( - "Model for the assistant.\n\n" - "Environment variables for provider credentials can be set with the Dotenv Component.\n\n" - "Models are supported via LiteLLM, see (https://docs.litellm.ai/docs/providers) for supported model names and env vars." - ), - }, - "env_set": { - "display_name": "Environment Set", - "advanced": False, - "info": "Dummy input to allow chaining with Dotenv Component.", - }, - } + inputs = [ + StrInput( + name="assistant_name", + display_name="Assistant Name", + info="Name for the assistant being created", + ), + StrInput( + name="instructions", + display_name="Instructions", + info="Instructions for the assistant, think of these as the system prompt.", + ), + StrInput( + name="model", + display_name="Model name", + info=( + "Model for the assistant.\n\n" + "Environment variables for provider credentials can be set with the Dotenv Component.\n\n" + "Models are supported via LiteLLM, see (https://docs.litellm.ai/docs/providers) for supported model names and env vars." + ), + # refresh_model=True + ), + MultilineInput( + name="env_set", + display_name="Environment Set", + info="Dummy input to allow chaining with Dotenv Component.", + ), + ] - def build(self, name: str, instructions: str, model: str, env_set: Optional[str] = None) -> str: - if env_set is None: - raise Exception("Environment variables not set") + outputs = [ + Output(display_name="Assistant ID", name="assistant_id", method="process_inputs"), + ] + + def process_inputs(self) -> Message: + print(f"env_set is {self.env_set}") client = patch(OpenAI()) assistant = client.beta.assistants.create( - name=name, - instructions=instructions, - model=model, + name=self.assistant_name, + instructions=self.instructions, + model=self.model, ) - return assistant.id + message = Message(text=assistant.id) + return message diff --git a/src/backend/base/langflow/components/astra_assistants/create_thread.py b/src/backend/base/langflow/components/astra_assistants/create_thread.py index 3d68e6e3e..d225a1c28 100644 --- a/src/backend/base/langflow/components/astra_assistants/create_thread.py +++ b/src/backend/base/langflow/components/astra_assistants/create_thread.py @@ -1,28 +1,32 @@ -from typing import Optional - from astra_assistants import patch # type: ignore +from langflow.custom import Component from openai import OpenAI - -from langflow.custom import CustomComponent +from langflow.inputs import MultilineInput +from langflow.schema.message import Message +from langflow.template import Output -class AssistantsCreateThread(CustomComponent): +class AssistantsCreateThread(Component): display_name = "Create Assistant Thread" description = "Creates a thread and returns the thread id" - def build_config(self): - return { - "env_set": { - "display_name": "Environment Set", - "advanced": False, - "info": "Dummy input to allow chaining with Dotenv Component.", - }, - } + inputs = [ + MultilineInput( + name="env_set", + display_name="Environment Set", + info="Dummy input to allow chaining with Dotenv Component.", + ), + ] - def build(self, env_set: Optional[str] = None) -> str: + outputs = [ + Output(display_name="Thread ID", name="thread_id", method="process_inputs"), + ] + + def process_inputs(self) -> Message: client = patch(OpenAI()) thread = client.beta.threads.create() thread_id = thread.id - return thread_id + message = Message(text=thread_id) + return message diff --git a/src/backend/base/langflow/components/astra_assistants/dotenv.py b/src/backend/base/langflow/components/astra_assistants/dotenv.py index d9fc27a85..7df4c2915 100644 --- a/src/backend/base/langflow/components/astra_assistants/dotenv.py +++ b/src/backend/base/langflow/components/astra_assistants/dotenv.py @@ -1,30 +1,32 @@ import io - from dotenv import load_dotenv - -from langflow.custom import CustomComponent +from langflow.custom import Component +from langflow.inputs import MultilineSecretInput +from langflow.schema.message import Message +from langflow.template import Output -class Dotenv(CustomComponent): +class Dotenv(Component): display_name = "Dotenv" description = "Load .env file into env vars" - def build_config(self): - return { - "dotenv_file_content": { - "display_name": "Dotenv file content", - "advanced": False, - "info": ( - "Paste the content of your .env file directly\n\n" - "Since contents are sensitive, using a Global variable set as 'password' is recommended" - ), - }, - } + inputs = [ + MultilineSecretInput( + name="dotenv_file_content", + display_name="Dotenv file content", + info="Paste the content of your .env file directly, since contents are sensitive, using a Global variable set as 'password' is recommended", + ) + ] - def build(self, dotenv_file_content: str) -> str: - try: - fake_file = io.StringIO(dotenv_file_content) - result = load_dotenv(stream=fake_file, override=True) - return "Loaded .env" if result else "No variables found in .env" - except Exception as e: - raise e + outputs = [ + Output(display_name="env_set", name="env_set", method="process_inputs"), + ] + + def process_inputs(self) -> Message: + fake_file = io.StringIO(self.dotenv_file_content) + result = load_dotenv(stream=fake_file, override=True) + + message = Message(text="No variables found in .env") + if result: + message = Message(text="Loaded .env") + return message diff --git a/src/backend/base/langflow/components/astra_assistants/get_assistant.py b/src/backend/base/langflow/components/astra_assistants/get_assistant.py index 0d2d193da..8c0412fe4 100644 --- a/src/backend/base/langflow/components/astra_assistants/get_assistant.py +++ b/src/backend/base/langflow/components/astra_assistants/get_assistant.py @@ -1,30 +1,37 @@ -from typing import Optional from astra_assistants import patch # type: ignore +from langflow.custom import Component from openai import OpenAI -from langflow.custom import CustomComponent +from langflow.inputs import StrInput, MultilineInput +from langflow.schema.message import Message +from langflow.template import Output -class AssistantsGetAssistantName(CustomComponent): +class AssistantsGetAssistantName(Component): + client = patch(OpenAI()) display_name = "Get Assistant name" description = "Assistant by id" - def build_config(self): - return { - "assistant_id": { - "display_name": "Assistant ID", - "advanced": False, - }, - "env_set": { - "display_name": "Environment Set", - "advanced": False, - "info": "Dummy input to allow chaining with Dotenv Component.", - }, - } + inputs = [ + StrInput( + name="assistant_id", + display_name="Assistant ID", + info="ID of the assistant", + ), + MultilineInput( + name="env_set", + display_name="Environment Set", + info="Dummy input to allow chaining with Dotenv Component.", + ), + ] - def build(self, assistant_id: str, env_set: Optional[str] = None) -> str: - client = patch(OpenAI()) - assistant = client.beta.assistants.retrieve( - assistant_id=assistant_id, + outputs = [ + Output(display_name="Assistant Name", name="assistant_name", method="process_inputs"), + ] + + def process_inputs(self) -> Message: + assistant = self.client.beta.assistants.retrieve( + assistant_id=self.assistant_id, ) - return assistant.name + message = Message(text=assistant.name) + return message diff --git a/src/backend/base/langflow/components/astra_assistants/getenvvar.py b/src/backend/base/langflow/components/astra_assistants/getenvvar.py index 164117c15..c46085936 100644 --- a/src/backend/base/langflow/components/astra_assistants/getenvvar.py +++ b/src/backend/base/langflow/components/astra_assistants/getenvvar.py @@ -1,14 +1,30 @@ import os -from langflow.custom import CustomComponent +from langflow.custom import Component +from langflow.inputs import StrInput +from langflow.schema.message import Message +from langflow.template import Output -class GetEnvVar(CustomComponent): +class GetEnvVar(Component): display_name = "Get env var" description = "Get env var" icon = "custom_components" - def build_config(self): - return {"env_var_name": {"display_name": "Env var name"}} + inputs = [ + StrInput( + name="env_var_name", + display_name="Env var name", + info="Name of the environment variable to get", + ) + ] - def build(self, env_var_name: str) -> str: - return os.environ[env_var_name] + outputs = [ + Output(display_name="Env var value", name="env_var_value", method="process_inputs"), + ] + + def process_inputs(self) -> Message: + if self.env_var_name not in os.environ: + raise Exception(f"Environment variable {self.env_var_name} not set") + else: + message = Message(text=os.environ[self.env_var_name]) + return message diff --git a/src/backend/base/langflow/components/astra_assistants/list_assistants.py b/src/backend/base/langflow/components/astra_assistants/list_assistants.py index f0cb5ba5f..c9e57a9e0 100644 --- a/src/backend/base/langflow/components/astra_assistants/list_assistants.py +++ b/src/backend/base/langflow/components/astra_assistants/list_assistants.py @@ -1,20 +1,24 @@ -from typing import List - from astra_assistants import patch # type: ignore +from langflow.template.field.base import Output +from langflow.custom import Component from openai import OpenAI - -from langflow.custom import CustomComponent +from langflow.schema.message import Message -class AssistantsListAssistants(CustomComponent): +class AssistantsListAssistants(Component): + client = patch(OpenAI()) display_name = "List Assistants" description = "Returns a list of assistant id's" - def build_config(self): - return {} + outputs = [ + Output(display_name="Assistants", name="assistants", method="process_inputs"), + ] - def build(self) -> List[str]: - client = patch(OpenAI()) - assistants = client.beta.assistants.list() + def process_inputs(self) -> Message: + assistants = self.client.beta.assistants.list() id_list = [assistant.id for assistant in assistants] - return id_list + message = Message( + # get text from list + text="\n".join(id_list) + ) + return message diff --git a/src/backend/base/langflow/components/astra_assistants/run.py b/src/backend/base/langflow/components/astra_assistants/run.py index d50246312..079dc179c 100644 --- a/src/backend/base/langflow/components/astra_assistants/run.py +++ b/src/backend/base/langflow/components/astra_assistants/run.py @@ -1,67 +1,94 @@ -from typing import Optional from astra_assistants import patch # type: ignore +from typing import Any, Optional + +from langflow.custom import Component from openai import OpenAI from openai.lib.streaming import AssistantEventHandler - -from langflow.custom import CustomComponent +from langflow.inputs import MultilineInput +from langflow.schema import dotdict +from langflow.schema.message import Message +from langflow.template import Output -class AssistantsRun(CustomComponent): +class AssistantsRun(Component): + client = patch(OpenAI()) + display_name = "Run Assistant" description = "Executes an Assistant Run against a thread" - def build_config(self): - return { - "assistant_id": { - "display_name": "Assistant ID", - "advanced": False, - "info": ( - "The ID of the assistant to run. \n\n" - "Can be retrieved using the List Assistants component or created with the Create Assistant component." - ), - }, - "user_message": { - "display_name": "User Message", - "info": "User message to pass to the run.", - "advanced": False, - }, - "thread_id": { - "display_name": "Thread ID", - "advanced": False, - "info": "Thread ID to use with the run. If not provided, a new thread will be created.", - }, - "env_set": { - "display_name": "Environment Set", - "advanced": False, - "info": "Dummy input to allow chaining with Dotenv Component.", - }, - } + def update_build_config( + self, + build_config: dotdict, + field_value: Any, + field_name: Optional[str] = None, + ): + if field_name == "thread_id": + if field_value is None: + thread = self.client.beta.threads.create() + self.thread_id = thread.id + field_value + build_config["thread_id"] = field_value - def build( - self, assistant_id: str, user_message: str, thread_id: Optional[str] = None, env_set: Optional[str] = None - ) -> str: - text = "" - client = patch(OpenAI()) + inputs = [ + MultilineInput( + name="assistant_id", + display_name="Assistant ID", + info=( + "The ID of the assistant to run. \n\n" + "Can be retrieved using the List Assistants component or created with the Create Assistant component." + ), + ), + MultilineInput( + name="user_message", + display_name="User Message", + info="User message to pass to the run.", + ), + MultilineInput( + name="thread_id", + display_name="Thread ID", + required=False, + info="Thread ID to use with the run. If not provided, a new thread will be created.", + ), + MultilineInput( + name="env_set", + display_name="Environment Set", + info="Dummy input to allow chaining with Dotenv Component.", + ), + ] - if thread_id is None: - thread = client.beta.threads.create() - thread_id = thread.id + outputs = [Output(display_name="Assistant Response", name="assistant_response", method="process_inputs")] - # add the user message - client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_message) + def process_inputs(self) -> Message: + try: + text = "" - class EventHandler(AssistantEventHandler): - def __init__(self): - super().__init__() + if self.thread_id is None: + thread = self.client.beta.threads.create() + self.thread_id = thread.id - event_handler = EventHandler() - with client.beta.threads.runs.create_and_stream( - thread_id=thread_id, - assistant_id=assistant_id, - event_handler=event_handler, - ) as stream: - # return stream.text_deltas - for part in stream.text_deltas: - text += part - print(part) - return text + # add the user message + self.client.beta.threads.messages.create(thread_id=self.thread_id, role="user", content=self.user_message) + + class EventHandler(AssistantEventHandler): + def __init__(self): + super().__init__() + + def on_exception(self, exception: Exception) -> None: + print(f"Exception: {exception}") + raise exception + + event_handler = EventHandler() + with self.client.beta.threads.runs.create_and_stream( + thread_id=self.thread_id, + assistant_id=self.assistant_id, + event_handler=event_handler, + ) as stream: + # return stream.text_deltas + for part in stream.text_deltas: + text += part + print(part) + message = Message(text=text) + return message + except Exception as e: + print(e) + raise Exception(f"Error running assistant: {e}") diff --git a/src/backend/base/langflow/inputs/__init__.py b/src/backend/base/langflow/inputs/__init__.py index 01283ad46..0afcacb06 100644 --- a/src/backend/base/langflow/inputs/__init__.py +++ b/src/backend/base/langflow/inputs/__init__.py @@ -11,6 +11,7 @@ from .inputs import ( MessageInput, MessageTextInput, MultilineInput, + MultilineSecretInput, NestedDictInput, PromptInput, SecretStrInput, @@ -30,6 +31,7 @@ __all__ = [ "IntInput", "MessageInput", "MultilineInput", + "MultilineSecretInput", "NestedDictInput", "PromptInput", "SecretStrInput", diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index bd81fc3c4..aa328d493 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -199,6 +199,20 @@ class MultilineInput(MessageTextInput, MultilineMixin, InputTraceMixin): multiline: CoalesceBool = True +class MultilineSecretInput(MessageTextInput, MultilineMixin, InputTraceMixin): + """ + Represents a multiline input field. + + Attributes: + field_type (Optional[SerializableFieldTypes]): The type of the field. Defaults to FieldTypes.TEXT. + multiline (CoalesceBool): Indicates whether the input field should support multiple lines. Defaults to True. + """ + + field_type: Optional[SerializableFieldTypes] = FieldTypes.PASSWORD + multiline: CoalesceBool = True + password: CoalesceBool = Field(default=True) + + class SecretStrInput(BaseInputMixin, DatabaseLoadMixin): """ Represents a field with password field type. @@ -354,6 +368,7 @@ InputTypes = Union[ HandleInput, IntInput, MultilineInput, + MultilineSecretInput, NestedDictInput, PromptInput, SecretStrInput, diff --git a/src/backend/base/langflow/io/__init__.py b/src/backend/base/langflow/io/__init__.py index 007281904..063f030ff 100644 --- a/src/backend/base/langflow/io/__init__.py +++ b/src/backend/base/langflow/io/__init__.py @@ -11,6 +11,7 @@ from langflow.inputs import ( MessageInput, MessageTextInput, MultilineInput, + MultilineSecretInput, NestedDictInput, PromptInput, SecretStrInput, @@ -31,6 +32,7 @@ __all__ = [ "IntInput", "MessageInput", "MultilineInput", + "MultilineSecretInput", "NestedDictInput", "PromptInput", "SecretStrInput", diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 231dc6f2f..0b5e8a287 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -1078,6 +1078,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/frontend/src/components/parameterRenderComponent/component/strRenderComponent/index.tsx b/src/frontend/src/components/parameterRenderComponent/component/strRenderComponent/index.tsx index 1c8dad612..677428ef3 100644 --- a/src/frontend/src/components/parameterRenderComponent/component/strRenderComponent/index.tsx +++ b/src/frontend/src/components/parameterRenderComponent/component/strRenderComponent/index.tsx @@ -39,6 +39,15 @@ export function StrRenderComponent({ /> ) : templateData.multiline ? ( { + if (templateData.password !== undefined) { + handleOnNewValue( + { password: !templateData.password }, + { skipSnapshot: true }, + ); + } + }} id={`textarea_${id}`} disabled={disabled} editNode={editNode} diff --git a/src/frontend/src/components/textAreaComponent/index.tsx b/src/frontend/src/components/textAreaComponent/index.tsx index 253e3eff9..b89437c40 100644 --- a/src/frontend/src/components/textAreaComponent/index.tsx +++ b/src/frontend/src/components/textAreaComponent/index.tsx @@ -1,4 +1,5 @@ -import { useEffect } from "react"; +import { classNames } from "@/utils/utils"; +import { useEffect, useState } from "react"; import { EDIT_TEXT_MODAL_TITLE } from "../../constants/constants"; import { TypeModal } from "../../constants/enums"; import GenericModal from "../../modals/genericModal"; @@ -14,6 +15,8 @@ export default function TextAreaComponent({ disabled, editNode = false, id = "", + password, + updateVisibility, }: TextAreaComponentType): JSX.Element { // Clear text area useEffect(() => { @@ -23,22 +26,29 @@ export default function TextAreaComponent({ }, [disabled]); return ( -
+
- - { - onChange(event.target.value); - }} - /> - + { + onChange(event.target.value); + }} + /> - {!editNode ? ( -
- -
- ) : ( - - )} +
+ {password !== undefined && ( + + )}
); diff --git a/src/frontend/src/components/ui/textarea.tsx b/src/frontend/src/components/ui/textarea.tsx index 10fcbcf56..21b5c9e19 100644 --- a/src/frontend/src/components/ui/textarea.tsx +++ b/src/frontend/src/components/ui/textarea.tsx @@ -2,22 +2,31 @@ import * as React from "react"; import { cn } from "../../utils/utils"; export interface TextareaProps - extends React.TextareaHTMLAttributes {} + extends React.TextareaHTMLAttributes { + password?: boolean; + editNode?: boolean; +} const Textarea = React.forwardRef( - ({ className, ...props }, ref) => { + ({ className, password, editNode, ...props }, ref) => { return ( -