From b07bd173b40ae982598c60a236104eb438945027 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Thu, 19 Dec 2024 16:17:26 -0300 Subject: [PATCH 1/8] feat: Add Pandas kwargs support for CSV Agent (#5372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (csv.py): Add support for passing Pandas kwargs to the CSV agent for more customization and flexibility. * πŸ“ (csv.py): mark 'is_list' attribute as True for the 'Pandas Kwargs' parameter to indicate it can accept a list of values --- .../langflow/components/langchain_utilities/csv.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/components/langchain_utilities/csv.py b/src/backend/base/langflow/components/langchain_utilities/csv.py index 0bd4b7e6df0..3e1e322410d 100644 --- a/src/backend/base/langflow/components/langchain_utilities/csv.py +++ b/src/backend/base/langflow/components/langchain_utilities/csv.py @@ -3,7 +3,7 @@ from langflow.base.agents.agent import LCAgentComponent from langflow.field_typing import AgentExecutor from langflow.inputs import DropdownInput, FileInput, HandleInput -from langflow.inputs.inputs import MessageTextInput +from langflow.inputs.inputs import DictInput, MessageTextInput from langflow.schema.message import Message from langflow.template.field.base import Output @@ -44,6 +44,13 @@ class CSVAgentComponent(LCAgentComponent): display_name="Text", info="Text to be passed as input and extract info from the CSV File.", ), + DictInput( + name="pandas_kwargs", + display_name="Pandas Kwargs", + info="Pandas Kwargs to be passed to the agent.", + advanced=True, + is_list=True, + ), ] outputs = [ @@ -67,6 +74,7 @@ def build_agent_response(self) -> Message: path=self._path(), agent_type=self.agent_type, handle_parsing_errors=self.handle_parsing_errors, + pandas_kwargs=self.pandas_kwargs, **agent_kwargs, ) @@ -84,6 +92,7 @@ def build_agent(self) -> AgentExecutor: path=self._path(), agent_type=self.agent_type, handle_parsing_errors=self.handle_parsing_errors, + pandas_kwargs=self.pandas_kwargs, **agent_kwargs, ) From 68deca4febe68a89a91e2e8950c9a845b2740551 Mon Sep 17 00:00:00 2001 From: VICTOR CORREA GOMES <112295415+Vigtu@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:19:02 -0300 Subject: [PATCH 2/8] refactor(components): Rename StoreMessage to MessageStore for Better Clarity (#5009) * refactor: Rename store message component to message store * [autofix.ci] apply automated fixes * refactor: Rename store message component to message store * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * fix: restore StoreMessage name for backwards compatibility * fix(core): rename store_message.py for compatibility - Restore original filename to maintain system compatibility - Ensure consistent file naming across codebase * fix(ui): correct output message display name - Update Output display_name from "Stored Messages" to "Stored Message" - Maintain consistency in singular/plural naming conventions * refactor: rename message_store to store_message in helpers module - Updated import statement in __init__.py to reflect the new module name for MessageStoreComponent. - This change improves clarity and consistency in the component naming convention. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida --- src/backend/base/langflow/components/helpers/__init__.py | 4 ++-- .../base/langflow/components/helpers/store_message.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/components/helpers/__init__.py b/src/backend/base/langflow/components/helpers/__init__.py index 7300242624c..176282a9223 100644 --- a/src/backend/base/langflow/components/helpers/__init__.py +++ b/src/backend/base/langflow/components/helpers/__init__.py @@ -3,7 +3,7 @@ from .id_generator import IDGeneratorComponent from .memory import MemoryComponent from .output_parser import OutputParserComponent -from .store_message import StoreMessageComponent +from .store_message import MessageStoreComponent from .structured_output import StructuredOutputComponent __all__ = [ @@ -11,7 +11,7 @@ "CurrentDateComponent", "IDGeneratorComponent", "MemoryComponent", + "MessageStoreComponent", "OutputParserComponent", - "StoreMessageComponent", "StructuredOutputComponent", ] diff --git a/src/backend/base/langflow/components/helpers/store_message.py b/src/backend/base/langflow/components/helpers/store_message.py index 544d2a09f2c..ae779b399ed 100644 --- a/src/backend/base/langflow/components/helpers/store_message.py +++ b/src/backend/base/langflow/components/helpers/store_message.py @@ -7,10 +7,10 @@ from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI -class StoreMessageComponent(Component): - display_name = "Store Message" +class MessageStoreComponent(Component): + display_name = "Message Store" description = "Stores a chat message or text into Langflow tables or an external memory." - icon = "save" + icon = "message-square-text" name = "StoreMessage" inputs = [ @@ -46,7 +46,7 @@ class StoreMessageComponent(Component): ] outputs = [ - Output(display_name="Stored Message", name="stored_message", method="store_message"), + Output(display_name="Stored Messages", name="stored_messages", method="store_message", hidden=True), ] async def store_message(self) -> Message: From 9a8f7215157c102d299deb4b3f45556f1a11e73d Mon Sep 17 00:00:00 2001 From: Raphael Valdetaro <79842132+raphaelchristi@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:29:45 -0300 Subject: [PATCH 3/8] feat: Add OpenRouter component for multi-provider model access (#5271) * feat: add OpenRouter component for multi-provider model access - Add OpenRouter integration with provider-based model selection - Support dynamic model fetching from OpenRouter API - Implement temperature and max tokens controls - Add site URL and app name for rankings - Include model tooltips with context length and descriptions * fix: restore SambaNova component in models __init__.py * refactor: simplify OpenRouter build_model method * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../langflow/components/models/__init__.py | 2 + .../langflow/components/models/openrouter.py | 197 ++++++++++++++++++ .../src/icons/OpenRouter/OpenRouterIcon.jsx | 23 ++ src/frontend/src/icons/OpenRouter/index.tsx | 9 + .../src/icons/OpenRouter/openrouter.svg | 1 + src/frontend/src/utils/styleUtils.ts | 2 + 6 files changed, 234 insertions(+) create mode 100644 src/backend/base/langflow/components/models/openrouter.py create mode 100644 src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx create mode 100644 src/frontend/src/icons/OpenRouter/index.tsx create mode 100644 src/frontend/src/icons/OpenRouter/openrouter.svg diff --git a/src/backend/base/langflow/components/models/__init__.py b/src/backend/base/langflow/components/models/__init__.py index e15ce28fd31..b757bd2c6a4 100644 --- a/src/backend/base/langflow/components/models/__init__.py +++ b/src/backend/base/langflow/components/models/__init__.py @@ -13,6 +13,7 @@ from .nvidia import NVIDIAModelComponent from .ollama import ChatOllamaComponent from .openai import OpenAIModelComponent +from .openrouter import OpenRouterComponent from .perplexity import PerplexityComponent from .sambanova import SambaNovaComponent from .vertexai import ChatVertexAIComponent @@ -33,6 +34,7 @@ "MistralAIModelComponent", "NVIDIAModelComponent", "OpenAIModelComponent", + "OpenRouterComponent", "PerplexityComponent", "QianfanChatEndpointComponent", "SambaNovaComponent", diff --git a/src/backend/base/langflow/components/models/openrouter.py b/src/backend/base/langflow/components/models/openrouter.py new file mode 100644 index 00000000000..45d945b9243 --- /dev/null +++ b/src/backend/base/langflow/components/models/openrouter.py @@ -0,0 +1,197 @@ +from collections import defaultdict +from typing import Any + +import httpx +from langchain_openai import ChatOpenAI +from pydantic.v1 import SecretStr + +from langflow.base.models.model import LCModelComponent +from langflow.field_typing import LanguageModel +from langflow.field_typing.range_spec import RangeSpec +from langflow.inputs import ( + DropdownInput, + IntInput, + SecretStrInput, + SliderInput, + StrInput, +) + + +class OpenRouterComponent(LCModelComponent): + """OpenRouter API component for language models.""" + + display_name = "OpenRouter" + description = ( + "OpenRouter provides unified access to multiple AI models " "from different providers through a single API." + ) + icon = "OpenRouter" + + inputs = [ + *LCModelComponent._base_inputs, + SecretStrInput( + name="api_key", display_name="OpenRouter API Key", required=True, info="Your OpenRouter API key" + ), + StrInput( + name="site_url", + display_name="Site URL", + info="Your site URL for OpenRouter rankings", + advanced=True, + ), + StrInput( + name="app_name", + display_name="App Name", + info="Your app name for OpenRouter rankings", + advanced=True, + ), + DropdownInput( + name="provider", + display_name="Provider", + info="The AI model provider", + options=["Loading providers..."], + value="Loading providers...", + real_time_refresh=True, + ), + DropdownInput( + name="model_name", + display_name="Model", + info="The model to use for chat completion", + options=["Select a provider first"], + value="Select a provider first", + real_time_refresh=True, + ), + SliderInput( + name="temperature", display_name="Temperature", value=0.7, range_spec=RangeSpec(min=0, max=2, step=0.01) + ), + IntInput( + name="max_tokens", + display_name="Max Tokens", + info="Maximum number of tokens to generate", + advanced=True, + ), + ] + + def fetch_models(self) -> dict[str, list]: + """Fetch available models from OpenRouter API and organize them by provider.""" + url = "https://openrouter.ai/api/v1/models" + + try: + with httpx.Client() as client: + response = client.get(url) + response.raise_for_status() + + models_data = response.json().get("data", []) + provider_models = defaultdict(list) + + for model in models_data: + model_id = model.get("id", "") + if "/" in model_id: + provider = model_id.split("/")[0].title() + provider_models[provider].append( + { + "id": model_id, + "name": model.get("name", ""), + "description": model.get("description", ""), + "context_length": model.get("context_length", 0), + } + ) + + return dict(provider_models) + + except httpx.HTTPError as e: + self.log(f"Error fetching models: {e!s}") + return {"Error": [{"id": "error", "name": f"Error fetching models: {e!s}"}]} + + def build_model(self) -> LanguageModel: + """Build and return the OpenRouter language model.""" + model_not_selected = "Please select a model" + api_key_required = "API key is required" + + if not self.model_name or self.model_name == "Select a provider first": + raise ValueError(model_not_selected) + + if not self.api_key: + raise ValueError(api_key_required) + + api_key = SecretStr(self.api_key).get_secret_value() + + # Build base configuration + kwargs: dict[str, Any] = { + "model": self.model_name, + "openai_api_key": api_key, + "openai_api_base": "https://openrouter.ai/api/v1", + "temperature": self.temperature if self.temperature is not None else 0.7, + } + + # Add optional parameters + if self.max_tokens: + kwargs["max_tokens"] = self.max_tokens + + headers = {} + if self.site_url: + headers["HTTP-Referer"] = self.site_url + if self.app_name: + headers["X-Title"] = self.app_name + + if headers: + kwargs["default_headers"] = headers + + try: + return ChatOpenAI(**kwargs) + except (ValueError, httpx.HTTPError) as err: + error_msg = f"Failed to build model: {err!s}" + self.log(error_msg) + raise ValueError(error_msg) from err + + def _get_exception_message(self, e: Exception) -> str | None: + """Get a message from an OpenRouter exception. + + Args: + e (Exception): The exception to get the message from. + + Returns: + str | None: The message from the exception, or None if no specific message can be extracted. + """ + try: + from openai import BadRequestError + + if isinstance(e, BadRequestError): + message = e.body.get("message") + if message: + return message + except ImportError: + pass + return None + + def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict: + """Update build configuration based on field updates.""" + try: + if field_name is None or field_name == "provider": + provider_models = self.fetch_models() + build_config["provider"]["options"] = sorted(provider_models.keys()) + if build_config["provider"]["value"] not in provider_models: + build_config["provider"]["value"] = build_config["provider"]["options"][0] + + if field_name == "provider" and field_value in self.fetch_models(): + provider_models = self.fetch_models() + models = provider_models[field_value] + + build_config["model_name"]["options"] = [model["id"] for model in models] + if models: + build_config["model_name"]["value"] = models[0]["id"] + + tooltips = { + model["id"]: ( + f"{model['name']}\n" f"Context Length: {model['context_length']}\n" f"{model['description']}" + ) + for model in models + } + build_config["model_name"]["tooltips"] = tooltips + + except httpx.HTTPError as e: + self.log(f"Error updating build config: {e!s}") + build_config["provider"]["options"] = ["Error loading providers"] + build_config["provider"]["value"] = "Error loading providers" + build_config["model_name"]["options"] = ["Error loading models"] + build_config["model_name"]["value"] = "Error loading models" + + return build_config diff --git a/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx b/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx new file mode 100644 index 00000000000..e16339149e7 --- /dev/null +++ b/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx @@ -0,0 +1,23 @@ +const SvgOpenRouter = (props) => ( + + + + +); +export default SvgOpenRouter; diff --git a/src/frontend/src/icons/OpenRouter/index.tsx b/src/frontend/src/icons/OpenRouter/index.tsx new file mode 100644 index 00000000000..fc79d74a5dd --- /dev/null +++ b/src/frontend/src/icons/OpenRouter/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import SvgOpenRouter from "./OpenRouterIcon"; + +export const OpenRouterIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/icons/OpenRouter/openrouter.svg b/src/frontend/src/icons/OpenRouter/openrouter.svg new file mode 100644 index 00000000000..1f4b1dad009 --- /dev/null +++ b/src/frontend/src/icons/OpenRouter/openrouter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 88216eedc22..d8d11b34401 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -282,6 +282,7 @@ import { NotionIcon } from "../icons/Notion"; import { NvidiaIcon } from "../icons/Nvidia"; import { OllamaIcon } from "../icons/Ollama"; import { OpenAiIcon } from "../icons/OpenAi"; +import { OpenRouterIcon } from "../icons/OpenRouter"; import { OpenSearch } from "../icons/OpenSearch"; import { PineconeIcon } from "../icons/Pinecone"; import { PostgresIcon } from "../icons/Postgres"; @@ -666,6 +667,7 @@ export const nodeIconsLucide: iconsType = { ChatOpenAI: OpenAiIcon, AzureChatOpenAI: OpenAiIcon, OpenAI: OpenAiIcon, + OpenRouter: OpenRouterIcon, OpenAIEmbeddings: OpenAiIcon, Pinecone: PineconeIcon, Qdrant: QDrantIcon, From 01c1d47ff518bedfe17ed4930e0f1900dfd8f1fe Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Thu, 19 Dec 2024 16:50:05 -0300 Subject: [PATCH 4/8] refactor: Enhance error handling, message editing and prompt display (#5310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * πŸ“ (newChatMessage.tsx): add event listener to handle tab visibility change and update state accordingly πŸ“ (newChatMessage.tsx): remove event listener when component unmounts to prevent memory leaks * ✨ (use-tab-visibility.tsx): introduce custom hook useTabVisibility to track tab visibility changes in the browser πŸ“ (newChatMessage.tsx, newChatView.tsx): import and use useTabVisibility hook to handle tab visibility changes and update chat behavior accordingly * πŸ“ (newChatMessage.tsx): remove duplicate import of useTabVisibility and update import path πŸ“ (newChatView.tsx): remove duplicate import of useTabVisibility and update import path ✨ (use-tab-visibility.tsx): create a new custom hook to track tab visibility changes in the browser * reducing to smaller components * πŸ“ (frontend): remove unused imports and clean up code in various files to improve code readability and maintainability * πŸ“ (editMessage/index.tsx): Rename EditMessage component to MarkdownField for better clarity and consistency πŸ“ (newChatMessage.tsx): Replace EditMessage component with MarkdownField component for rendering chat messages with markdown support πŸ“ (newChatView.tsx): Add conditional rendering to display a message when no chat messages are fetched ✨ (chatViewWrapper/index.tsx): Add messageFetched prop to ChatView component to handle messages fetching status and improve component functionality. * prompt viedw * Refactor chat view component and remove unused prop * [autofix.ci] apply automated fixes --------- Co-authored-by: anovazzi1 Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../sessionSelector/newSessionSelector.tsx | 1 - .../components/contentView/index.tsx | 197 ++++++++ .../components/editMessage/index.tsx | 83 ++++ .../chatMessage/helpers/convert-files.ts | 20 + .../chatView/chatMessage/newChatMessage.tsx | 425 ++---------------- .../components/chatView/newChatView.tsx | 4 +- .../components/chatViewWrapper/index.tsx | 115 +++++ .../components/selectedViewField/index.tsx | 64 +++ .../components/sidebarOpenView/index.tsx | 79 ++++ src/frontend/src/modals/IOModal/newModal.tsx | 251 ++--------- .../modals/IOModal/types/chat-view-wrapper.ts | 23 + .../IOModal/types/selected-view-field.ts | 21 + .../modals/IOModal/types/sidebar-open-view.ts | 10 + 13 files changed, 703 insertions(+), 590 deletions(-) create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/contentView/index.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessage/index.tsx create mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts create mode 100644 src/frontend/src/modals/IOModal/components/chatViewWrapper/index.tsx create mode 100644 src/frontend/src/modals/IOModal/components/selectedViewField/index.tsx create mode 100644 src/frontend/src/modals/IOModal/components/sidebarOpenView/index.tsx create mode 100644 src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts create mode 100644 src/frontend/src/modals/IOModal/types/selected-view-field.ts create mode 100644 src/frontend/src/modals/IOModal/types/sidebar-open-view.ts diff --git a/src/frontend/src/modals/IOModal/components/IOFieldView/components/sessionSelector/newSessionSelector.tsx b/src/frontend/src/modals/IOModal/components/IOFieldView/components/sessionSelector/newSessionSelector.tsx index f6de50d7b90..40fecf1c53a 100644 --- a/src/frontend/src/modals/IOModal/components/IOFieldView/components/sessionSelector/newSessionSelector.tsx +++ b/src/frontend/src/modals/IOModal/components/IOFieldView/components/sessionSelector/newSessionSelector.tsx @@ -1,6 +1,5 @@ import IconComponent from "@/components/common/genericIconComponent"; import ShadTooltip from "@/components/common/shadTooltipComponent"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/contentView/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/contentView/index.tsx new file mode 100644 index 00000000000..c819fd8914c --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/contentView/index.tsx @@ -0,0 +1,197 @@ +import { ForwardedIconComponent } from "@/components/common/genericIconComponent"; +import { TextShimmer } from "@/components/ui/TextShimmer"; +import { cn } from "@/utils/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import CodeTabsComponent from "../../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent"; +import LogoIcon from "../chatLogoIcon"; + +export const ErrorView = ({ + closeChat, + fitViewNode, + chat, + showError, + lastMessage, + blocks, +}: { + blocks: any; + showError: boolean; + lastMessage: boolean; + closeChat?: () => void; + fitViewNode: (id: string) => void; + chat: any; +}) => { + return ( + <> +
+ + {!showError && lastMessage ? ( + + +
+ + Flow running... + +
+
+ ) : ( + + + {blocks.map((block, blockIndex) => ( +
+ {block.contents.map((content, contentIndex) => { + if (content.type === "error") { + return ( +
+
+ + {content.component && ( + <> + + An error occured in the{" "} + { + fitViewNode( + chat.properties?.source?.id ?? "", + ); + closeChat?.(); + }} + > + {content.component} + {" "} + Component, stopping your flow. See below for + more details. + + + )} +
+
+

+ Error details: +

+ {content.field && ( +

Field: {content.field}

+ )} + {content.reason && ( + + ( + + {props.children} + + ), + p({ node, ...props }) { + return ( + + {props.children} + + ); + }, + code: ({ + node, + inline, + className, + children, + ...props + }) => { + let content = children as string; + if ( + Array.isArray(children) && + children.length === 1 && + typeof children[0] === "string" + ) { + content = children[0] as string; + } + if (typeof content === "string") { + if (content.length) { + if (content[0] === "▍") { + return ( + + ); + } + } + + const match = /language-(\w+)/.exec( + className || "", + ); + + return !inline ? ( + + ) : ( + + {content} + + ); + } + }, + }} + > + {content.reason} + + + )} + {content.solution && ( +
+

+ Steps to fix: +

+
    +
  1. Check the component settings
  2. +
  3. Ensure all required fields are filled
  4. +
  5. Re-run your flow
  6. +
+
+ )} +
+
+ ); + } + return null; + })} +
+ ))} +
+ )} +
+
+ + ); +}; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessage/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessage/index.tsx new file mode 100644 index 00000000000..6dff003cac5 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/editMessage/index.tsx @@ -0,0 +1,83 @@ +import { cn } from "@/utils/utils"; + +import { EMPTY_OUTPUT_SEND_MESSAGE } from "@/constants/constants"; +import Markdown from "react-markdown"; +import rehypeMathjax from "rehype-mathjax"; +import remarkGfm from "remark-gfm"; +import CodeTabsComponent from "../../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent"; +import EditMessageField from "../editMessageField"; + +type MarkdownFieldProps = { + chat: any; + isEmpty: boolean; + chatMessage: string; + editedFlag: React.ReactNode; +}; + +export const MarkdownField = ({ + chat, + isEmpty, + chatMessage, + editedFlag, +}: MarkdownFieldProps) => { + return ( +
+ {props.children}; + }, + ol({ node, ...props }) { + return
    {props.children}
; + }, + ul({ node, ...props }) { + return
    {props.children}
; + }, + pre({ node, ...props }) { + return <>{props.children}; + }, + code: ({ node, inline, className, children, ...props }) => { + let content = children as string; + if ( + Array.isArray(children) && + children.length === 1 && + typeof children[0] === "string" + ) { + content = children[0] as string; + } + if (typeof content === "string") { + if (content.length) { + if (content[0] === "▍") { + return ; + } + } + + const match = /language-(\w+)/.exec(className || ""); + + return !inline ? ( + + ) : ( + + {content} + + ); + } + }, + }} + > + {isEmpty && !chat.stream_url ? EMPTY_OUTPUT_SEND_MESSAGE : chatMessage} +
+ {editedFlag} +
+ ); +}; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts new file mode 100644 index 00000000000..a9933b2dd35 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts @@ -0,0 +1,20 @@ +export const convertFiles = ( + files: + | ( + | string + | { + path: string; + type: string; + name: string; + } + )[] + | undefined, +) => { + if (!files) return []; + return files.map((file) => { + if (typeof file === "string") { + return file; + } + return file.path; + }); +}; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx index 67deec4cd3d..fffe7bc6e64 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatMessage/newChatMessage.tsx @@ -1,6 +1,5 @@ import { ProfileIcon } from "@/components/core/appHeaderComponent/components/ProfileIcon"; import { ContentBlockDisplay } from "@/components/core/chatComponents/ContentBlockDisplay"; -import { TextShimmer } from "@/components/ui/TextShimmer"; import { useUpdateMessage } from "@/controllers/API/queries/messages"; import { CustomProfileIcon } from "@/customization/components/custom-profile-icon"; import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags"; @@ -8,11 +7,7 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowStore from "@/stores/flowStore"; import { useUtilityStore } from "@/stores/utilityStore"; import Convert from "ansi-to-html"; -import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useRef, useState } from "react"; -import Markdown from "react-markdown"; -import rehypeMathjax from "rehype-mathjax"; -import remarkGfm from "remark-gfm"; import Robot from "../../../../../assets/robot.png"; import IconComponent, { ForwardedIconComponent, @@ -27,10 +22,12 @@ import useTabVisibility from "../../../../../shared/hooks/use-tab-visibility"; import useAlertStore from "../../../../../stores/alertStore"; import { chatMessagePropsType } from "../../../../../types/components"; import { cn } from "../../../../../utils/utils"; -import LogoIcon from "./components/chatLogoIcon"; +import { ErrorView } from "./components/contentView"; +import { MarkdownField } from "./components/editMessage"; import { EditMessageButton } from "./components/editMessageButton/newMessageOptions"; import EditMessageField from "./components/editMessageField/newEditMessageField"; import FileCardWrapper from "./components/fileCardWrapper"; +import { convertFiles } from "./helpers/convert-files"; export default function ChatMessage({ chat, @@ -63,6 +60,7 @@ export default function ChatMessage({ const chatMessageString = chat.message ? chat.message.toString() : ""; setChatMessage(chatMessageString); }, [chat]); + const playgroundScrollBehaves = useUtilityStore( (state) => state.playgroundScrollBehaves, ); @@ -167,27 +165,6 @@ export default function ChatMessage({ const isEmpty = decodedMessage?.trim() === ""; const { mutate: updateMessageMutation } = useUpdateMessage(); - const convertFiles = ( - files: - | ( - | string - | { - path: string; - type: string; - name: string; - } - )[] - | undefined, - ) => { - if (!files) return []; - return files.map((file) => { - if (typeof file === "string") { - return file; - } - return file.path; - }); - }; - const handleEditMessage = (message: string) => { updateMessageMutation( { @@ -252,176 +229,14 @@ export default function ChatMessage({ const blocks = chat.content_blocks ?? []; return ( -
- - {!showError && lastMessage ? ( - - -
- - Flow running... - -
-
- ) : ( - - - {blocks.map((block, blockIndex) => ( -
- {block.contents.map((content, contentIndex) => { - if (content.type === "error") { - return ( -
-
- - {content.component && ( - <> - - An error occured in the{" "} - { - fitViewNode( - chat.properties?.source?.id ?? "", - ); - closeChat?.(); - }} - > - {content.component} - {" "} - Component, stopping your flow. See below for - more details. - - - )} -
-
-

- Error details: -

- {content.field && ( -

Field: {content.field}

- )} - {content.reason && ( - - ( - - {props.children} - - ), - p({ node, ...props }) { - return ( - - {props.children} - - ); - }, - code: ({ - node, - inline, - className, - children, - ...props - }) => { - let content = children as string; - if ( - Array.isArray(children) && - children.length === 1 && - typeof children[0] === "string" - ) { - content = children[0] as string; - } - if (typeof content === "string") { - if (content.length) { - if (content[0] === "▍") { - return ( - - ); - } - } - - const match = /language-(\w+)/.exec( - className || "", - ); - - return !inline ? ( - - ) : ( - - {content} - - ); - } - }, - }} - > - {content.reason} - - - )} - {content.solution && ( -
-

- Steps to fix: -

-
    -
  1. Check the component settings
  2. -
  3. Ensure all required fields are filled
  4. -
  5. Re-run your flow
  6. -
-
- )} -
-
- ); - } - return null; - })} -
- ))} -
- )} -
-
+ ); } @@ -568,100 +383,12 @@ export default function ChatMessage({ onCancel={() => setEditMessage(false)} /> ) : ( - <> -
- - {props.children} - - ); - }, - ol({ node, ...props }) { - return ( -
    - {props.children} -
- ); - }, - ul({ node, ...props }) { - return ( -
    - {props.children} -
- ); - }, - pre({ node, ...props }) { - return <>{props.children}; - }, - code: ({ - node, - inline, - className, - children, - ...props - }) => { - let content = children as string; - if ( - Array.isArray(children) && - children.length === 1 && - typeof children[0] === "string" - ) { - content = children[0] as string; - } - if (typeof content === "string") { - if (content.length) { - if (content[0] === "▍") { - return ( - - ); - } - } - - const match = /language-(\w+)/.exec( - className || "", - ); - - return !inline ? ( - - ) : ( - - {content} - - ); - } - }, - }} - > - {isEmpty && !chat.stream_url - ? EMPTY_OUTPUT_SEND_MESSAGE - : chatMessage} -
- {editedFlag} -
- + )} )} @@ -672,95 +399,37 @@ export default function ChatMessage({ ) : (
- {template ? ( - <> - - - {promptOpen - ? template?.split("\n")?.map((line, index) => { - const regex = /{([^}]+)}/g; - let match; - let parts: Array = []; - let lastIndex = 0; - while ((match = regex.exec(line)) !== null) { - // Push text up to the match - if (match.index !== lastIndex) { - parts.push( - line.substring(lastIndex, match.index), - ); - } - // Push div with matched text - if (chat.message[match[1]]) { - parts.push( - - {chat.message[match[1]]} - , - ); - } - - // Update last index - lastIndex = regex.lastIndex; - } - // Push text after the last match - if (lastIndex !== line.length) { - parts.push(line.substring(lastIndex)); - } - return

{parts}

; - }) - : isEmpty - ? EMPTY_INPUT_SEND_MESSAGE - : chatMessage} -
- - ) : ( -
- {editMessage ? ( - { - handleEditMessage(message); - }} - onCancel={() => setEditMessage(false)} - /> - ) : ( - <> -
- {isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage} - {editedFlag} -
- - )} - {chat.files && ( -
- {chat.files?.map((file, index) => { - return ; - })} + onCancel={() => setEditMessage(false)} + /> + ) : ( + <> +
+ {isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage} + {editedFlag}
- )} -
- )} + + )} + {chat.files && ( +
+ {chat.files?.map((file, index) => { + return ; + })} +
+ )} +
)} diff --git a/src/frontend/src/modals/IOModal/components/chatView/newChatView.tsx b/src/frontend/src/modals/IOModal/components/chatView/newChatView.tsx index 3d26268db22..ab7c49bb6d0 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/newChatView.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/newChatView.tsx @@ -91,8 +91,8 @@ export default function ChatView({ if (messages.length === 0 && !lockChat && chatInputNode) { setChatValue(chatInputNode.data.node.template["input_value"].value ?? ""); - } else if (isTabHidden) { - setChatValue(""); + } else { + isTabHidden ? setChatValue("") : null; } setChatHistory(finalChatHistory); diff --git a/src/frontend/src/modals/IOModal/components/chatViewWrapper/index.tsx b/src/frontend/src/modals/IOModal/components/chatViewWrapper/index.tsx new file mode 100644 index 00000000000..fa141dbc14e --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatViewWrapper/index.tsx @@ -0,0 +1,115 @@ +import ShadTooltip from "@/components/common/shadTooltipComponent"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/utils/utils"; +import IconComponent from "../../../../components/common/genericIconComponent"; +import { ChatViewWrapperProps } from "../../types/chat-view-wrapper"; +import ChatView from "../chatView/newChatView"; + +export const ChatViewWrapper = ({ + selectedViewField, + visibleSession, + sessions, + sidebarOpen, + currentFlowId, + setSidebarOpen, + isPlayground, + setvisibleSession, + setSelectedViewField, + messagesFetched, + sessionId, + sendMessage, + chatValue, + setChatValue, + lockChat, + setLockChat, + canvasOpen, + setOpen, +}: ChatViewWrapperProps) => { + return ( +
+
+ {visibleSession && sessions.length > 0 && sidebarOpen && ( +
+ {visibleSession === currentFlowId + ? "Default Session" + : `${visibleSession}`} +
+ )} +
+
+ +
Playground
+
+
+
+ + + + {!isPlayground && } +
+
+
+ {messagesFetched && ( + { + setOpen(false); + } + } + /> + )} +
+
+ ); +}; diff --git a/src/frontend/src/modals/IOModal/components/selectedViewField/index.tsx b/src/frontend/src/modals/IOModal/components/selectedViewField/index.tsx new file mode 100644 index 00000000000..b1c6c0c9af1 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/selectedViewField/index.tsx @@ -0,0 +1,64 @@ +import { InputOutput } from "@/constants/enums"; +import { cn } from "@/utils/utils"; +import IconComponent from "../../../../components/common/genericIconComponent"; +import { SelectedViewFieldProps } from "../../types/selected-view-field"; +import IOFieldView from "../IOFieldView"; +import SessionView from "../SessionView"; + +export const SelectedViewField = ({ + selectedViewField, + setSelectedViewField, + haveChat, + inputs, + outputs, + sessions, + currentFlowId, + nodes, +}: SelectedViewFieldProps) => { + return ( + <> +
+
+ {haveChat && ( + + )} + { + nodes.find((node) => node.id === selectedViewField?.id)?.data.node + .display_name + } +
+
+ {inputs.some((input) => input.id === selectedViewField?.id) && ( + + )} + {outputs.some((output) => output.id === selectedViewField?.id) && ( + + )} + {sessions.some((session) => session === selectedViewField?.id) && ( + + )} +
+
+ + ); +}; diff --git a/src/frontend/src/modals/IOModal/components/sidebarOpenView/index.tsx b/src/frontend/src/modals/IOModal/components/sidebarOpenView/index.tsx new file mode 100644 index 00000000000..7ae8e4749ce --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/sidebarOpenView/index.tsx @@ -0,0 +1,79 @@ +import ShadTooltip from "@/components/common/shadTooltipComponent"; +import { Button } from "@/components/ui/button"; +import IconComponent from "../../../../components/common/genericIconComponent"; +import { SidebarOpenViewProps } from "../../types/sidebar-open-view"; +import SessionSelector from "../IOFieldView/components/sessionSelector/newSessionSelector"; + +export const SidebarOpenView = ({ + sessions, + setSelectedViewField, + setvisibleSession, + handleDeleteSession, + visibleSession, + selectedViewField, +}: SidebarOpenViewProps) => { + return ( + <> +
+
+
+
+ +
Chat
+
+ +
+ +
+
+
+
+
+ {sessions.map((session, index) => ( + { + handleDeleteSession(session); + if (selectedViewField?.id === session) { + setSelectedViewField(undefined); + } + }} + updateVisibleSession={(session) => { + setvisibleSession(session); + }} + toggleVisibility={() => { + setvisibleSession(session); + }} + isVisible={visibleSession === session} + inspectSession={(session) => { + setSelectedViewField({ + id: session, + type: "Session", + }); + }} + /> + ))} +
+
+ + ); +}; diff --git a/src/frontend/src/modals/IOModal/newModal.tsx b/src/frontend/src/modals/IOModal/newModal.tsx index e5ceabe39cb..69bb54ffda6 100644 --- a/src/frontend/src/modals/IOModal/newModal.tsx +++ b/src/frontend/src/modals/IOModal/newModal.tsx @@ -8,7 +8,6 @@ import { useCallback, useEffect, useState } from "react"; import IconComponent from "../../components/common/genericIconComponent"; import ShadTooltip from "../../components/common/shadTooltipComponent"; import { Button } from "../../components/ui/button"; -import { InputOutput } from "../../constants/enums"; import useAlertStore from "../../stores/alertStore"; import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; @@ -16,10 +15,10 @@ import { useMessagesStore } from "../../stores/messagesStore"; import { IOModalPropsType } from "../../types/components"; import { cn } from "../../utils/utils"; import BaseModal from "../baseModal"; -import IOFieldView from "./components/IOFieldView"; -import SessionSelector from "./components/IOFieldView/components/sessionSelector/newSessionSelector"; -import SessionView from "./components/SessionView"; import ChatView from "./components/chatView/newChatView"; +import { ChatViewWrapper } from "./components/chatViewWrapper"; +import { SelectedViewField } from "./components/selectedViewField"; +import { SidebarOpenView } from "./components/sidebarOpenView"; export default function IOModal({ children, @@ -292,217 +291,51 @@ export default function IOModal({ )} {sidebarOpen && ( -
-
-
-
- -
Chat
-
- -
- -
-
-
-
-
- {sessions.map((session, index) => ( - { - handleDeleteSession(session); - if (selectedViewField?.id === session) { - setSelectedViewField(undefined); - } - }} - updateVisibleSession={(session) => { - setvisibleSession(session); - }} - toggleVisibility={() => { - setvisibleSession(session); - }} - isVisible={visibleSession === session} - inspectSession={(session) => { - setSelectedViewField({ - id: session, - type: "Session", - }); - }} - /> - ))} -
-
+ )}
{selectedViewField && ( -
-
- {haveChat && ( - - )} - { - nodes.find((node) => node.id === selectedViewField.id) - ?.data.node.display_name - } -
-
- {inputs.some( - (input) => input.id === selectedViewField.id, - ) && ( - - )} - {outputs.some( - (output) => output.id === selectedViewField.id, - ) && ( - - )} - {sessions.some( - (session) => session === selectedViewField.id, - ) && ( - - )} -
-
+ )} -
-
- {visibleSession && sessions.length > 0 && sidebarOpen && ( -
- {visibleSession === currentFlowId - ? "Default Session" - : `${visibleSession}`} -
- )} -
-
- -
Playground
-
-
-
- - - - {!isPlayground && } -
-
- {haveChat ? ( -
- {messagesFetched && ( - { - setOpen(false); - } - } - /> - )} -
- ) : ( - - Select an IO component to view - - )} -
+
)} diff --git a/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts b/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts new file mode 100644 index 00000000000..020877612c6 --- /dev/null +++ b/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts @@ -0,0 +1,23 @@ +export type ChatViewWrapperProps = { + selectedViewField: { type: string; id: string } | undefined; + visibleSession: string | undefined; + sessions: string[]; + sidebarOpen: boolean; + currentFlowId: string; + setSidebarOpen: (open: boolean) => void; + isPlayground: boolean | undefined; + setvisibleSession: (session: string | undefined) => void; + setSelectedViewField: ( + field: { type: string; id: string } | undefined, + ) => void; + haveChat: { type: string; id: string; displayName: string } | undefined; + messagesFetched: boolean; + sessionId: string; + sendMessage: (options: { repeat: number; files?: string[] }) => Promise; + chatValue: string; + setChatValue: (value: string) => void; + lockChat: boolean; + setLockChat: (locked: boolean) => void; + canvasOpen: boolean | undefined; + setOpen: (open: boolean) => void; +}; diff --git a/src/frontend/src/modals/IOModal/types/selected-view-field.ts b/src/frontend/src/modals/IOModal/types/selected-view-field.ts new file mode 100644 index 00000000000..b828e6f1a81 --- /dev/null +++ b/src/frontend/src/modals/IOModal/types/selected-view-field.ts @@ -0,0 +1,21 @@ +import { Node } from "reactflow"; +export type SelectedViewFieldProps = { + selectedViewField: { type: string; id: string } | undefined; + setSelectedViewField: ( + field: { type: string; id: string } | undefined, + ) => void; + haveChat: { type: string; id: string; displayName: string } | undefined; + inputs: Array<{ + type: string; + id: string; + displayName: string; + }>; + outputs: Array<{ + type: string; + id: string; + displayName: string; + }>; + sessions: string[]; + currentFlowId: string; + nodes: Node[]; +}; diff --git a/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts b/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts new file mode 100644 index 00000000000..047c6e4317b --- /dev/null +++ b/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts @@ -0,0 +1,10 @@ +export type SidebarOpenViewProps = { + sessions: string[]; + setSelectedViewField: ( + field: { type: string; id: string } | undefined, + ) => void; + setvisibleSession: (session: string | undefined) => void; + handleDeleteSession: (session: string) => void; + visibleSession: string | undefined; + selectedViewField: { type: string; id: string } | undefined; +}; From fb9825ee65275215a38c55f650f91b968a7ba832 Mon Sep 17 00:00:00 2001 From: Abhishek Patil <83769052+abhishekpatil4@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:12:29 +0530 Subject: [PATCH 5/8] fix: Resolve Auth field issue in Composio component and bump Composio (#5357) * fix: replace single quotes with double quotes * feat: update Composio component fields --- pyproject.toml | 3 ++- .../components/composio/composio_api.py | 20 ++++++++++------ uv.lock | 24 ++++++++++--------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aaa47ed6ae5..bddfdb15d55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,8 @@ dependencies = [ "yfinance==0.2.50", "wolframalpha==5.1.3", "astra-assistants[tools]~=2.2.6", - "composio-langchain==0.5.42", + "composio-langchain==0.6.3", + "composio-core==0.6.3", "spider-client==0.1.24", "nltk==3.9.1", "lark==1.2.2", diff --git a/src/backend/base/langflow/components/composio/composio_api.py b/src/backend/base/langflow/components/composio/composio_api.py index cd9a26a5bad..6c8d3760aed 100644 --- a/src/backend/base/langflow/components/composio/composio_api.py +++ b/src/backend/base/langflow/components/composio/composio_api.py @@ -8,7 +8,6 @@ from composio_langchain import Action, App, ComposioToolSet from langchain_core.tools import Tool from loguru import logger -from typing_extensions import override # Local imports from langflow.base.langchain_utilities.model import LCToolComponent @@ -30,16 +29,17 @@ class ComposioAPIComponent(LCToolComponent): name="api_key", display_name="Composio API Key", required=True, - # refresh_button=True, info="Refer to https://docs.composio.dev/faq/api_key/api_key", + real_time_refresh=True, ), DropdownInput( name="app_names", display_name="App Name", - options=list(App.__annotations__), + options=[str(app).replace("App.", "") for app in App.all()], value="", info="The app name to use. Please refresh after selecting app name", refresh_button=True, + required=True, ), # Authentication-related inputs (initially hidden) SecretStrInput( @@ -49,6 +49,7 @@ class ComposioAPIComponent(LCToolComponent): dynamic=True, show=False, info="Credentials for app authentication (API Key, Password, etc)", + load_from_db=False, ), MessageTextInput( name="username", @@ -220,19 +221,24 @@ def _get_normalized_app_name(self) -> str: """ return self.app_names.replace(" βœ…", "").replace("_connected", "") - @override - def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: + def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: # noqa: ARG002 # First, ensure all dynamic fields are hidden by default dynamic_fields = ["app_credentials", "username", "auth_link", "auth_status", "action_names"] for field in dynamic_fields: if field in build_config: if build_config[field]["value"] is None or build_config[field]["value"] == "": build_config[field]["show"] = False - build_config[field]["advanced"] = True # Hide from main view + build_config[field]["advanced"] = True + build_config[field]["load_from_db"] = False else: build_config[field]["show"] = True build_config[field]["advanced"] = False + if field_name == "app_names" and (not hasattr(self, "app_names") or not self.app_names): + build_config["auth_status"]["show"] = True + build_config["auth_status"]["value"] = "Please select an app first" + return build_config + if field_name == "app_names" and hasattr(self, "api_key") and self.api_key != "": # app_name = self._get_normalized_app_name() app_name = self.app_names @@ -285,7 +291,7 @@ def update_build_config(self, build_config: dict, field_value: Any, field_name: # Update action names if connected if build_config["auth_status"]["value"] == "βœ…": - all_action_names = list(Action.__annotations__) + all_action_names = [str(action).replace("Action.", "") for action in Action.all()] app_action_names = [ action_name for action_name in all_action_names diff --git a/uv.lock b/uv.lock index a70c89951cc..54fbc904bf6 100644 --- a/uv.lock +++ b/uv.lock @@ -1157,7 +1157,7 @@ wheels = [ [[package]] name = "composio-core" -version = "0.5.42" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1177,14 +1177,14 @@ dependencies = [ { name = "sentry-sdk" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/b9/a66a7646564f91937e2ac9a72c12308a37aac76dea88ea1d727a061bf153/composio_core-0.5.42.tar.gz", hash = "sha256:b38ea94e25807a6da813efb2c53ea8e01f71e54f0275ecfa55225887b36b6537", size = 295930 } +sdist = { url = "https://files.pythonhosted.org/packages/5f/21/730e8e305489d88f49d44d25630779fa79601d4add08ec00cb3c576f30fb/composio_core-0.6.3.tar.gz", hash = "sha256:13098b20d8832e74453ca194889305c935432156fc07be91dfddf76561ad591b", size = 305588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/7a/45acb7b6bcca477712f89bd43888393f365dca0270062e97389e3653aec8/composio_core-0.5.42-py3-none-any.whl", hash = "sha256:351ddee8ac8203ea8039098c8afce7c43b1cf4a97eccbb2046c489a3a53afa54", size = 457643 }, + { url = "https://files.pythonhosted.org/packages/3f/33/e696eccb062d24182740b3203b53114aa633c9d8ded5a5799f5ff6ef5808/composio_core-0.6.3-py3-none-any.whl", hash = "sha256:981a9856781b791242f947a9685a18974d8a012ac7fab2c09438e1b19610d6a2", size = 468385 }, ] [[package]] name = "composio-langchain" -version = "0.5.42" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "composio-core" }, @@ -1193,9 +1193,9 @@ dependencies = [ { name = "langchainhub" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/3e/fa76297c590405d17ca8183396a51ae461845f82e232e64f68f383e2f804/composio_langchain-0.5.42.tar.gz", hash = "sha256:ed95acce4603099eccc022cd42f75df876dc566ecd0ed509bb33d18d0c541358", size = 4281 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/cc/c6b48d16081c8a90f6e73e339b2cac26d8bae875622b853442bbd3cab525/composio_langchain-0.6.3.tar.gz", hash = "sha256:2036f94bfe60974b31f2be0bfdb33dd75a1d43435f275141219b3376587bf49d", size = 4252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/a4/4d65985c49db8d828ef39843003c443919fa9a285d3244dba3aaedbd4fd1/composio_langchain-0.5.42-py3-none-any.whl", hash = "sha256:ddbffa94c5ad7105bdc877e0103a5633cca8c736a7b6853230078e143e718700", size = 4701 }, + { url = "https://files.pythonhosted.org/packages/ad/01/cad5fd344f79b3c86e0baa7092d16dff8b38ea83640bcfc8350139d4b6aa/composio_langchain-0.6.3-py3-none-any.whl", hash = "sha256:0e749a1603dc0562293412d0a6429f88b75152b01a313cca859732070d762a6b", size = 4676 }, ] [[package]] @@ -3933,6 +3933,7 @@ dependencies = [ { name = "boto3" }, { name = "certifi" }, { name = "chromadb" }, + { name = "composio-core" }, { name = "composio-langchain" }, { name = "crewai" }, { name = "dspy-ai" }, @@ -4031,7 +4032,7 @@ local = [ { name = "sentence-transformers" }, ] -[package.dev-dependencies] +[package.dependency-groups] dev = [ { name = "asgi-lifespan" }, { name = "blockbuster" }, @@ -4087,7 +4088,8 @@ requires-dist = [ { name = "certifi", specifier = ">=2023.11.17,<2025.0.0" }, { name = "chromadb", specifier = "==0.5.23" }, { name = "clickhouse-connect", marker = "extra == 'clickhouse-connect'", specifier = "==0.7.19" }, - { name = "composio-langchain", specifier = "==0.5.42" }, + { name = "composio-core", specifier = "==0.6.3" }, + { name = "composio-langchain", specifier = "==0.6.3" }, { name = "couchbase", marker = "extra == 'couchbase'", specifier = ">=4.2.1" }, { name = "crewai", specifier = "~=0.86.0" }, { name = "ctransformers", marker = "extra == 'local'", specifier = ">=0.2.10" }, @@ -4170,7 +4172,7 @@ requires-dist = [ { name = "zep-python", specifier = "==2.0.2" }, ] -[package.metadata.requires-dev] +[package.metadata.dependency-groups] dev = [ { name = "asgi-lifespan", specifier = ">=2.1.0" }, { name = "blockbuster", specifier = ">=1.5.2,<1.6" }, @@ -4327,7 +4329,7 @@ local = [ { name = "sentence-transformers" }, ] -[package.dev-dependencies] +[package.dependency-groups] dev = [ { name = "asgi-lifespan" }, { name = "pytest-codspeed" }, @@ -4439,7 +4441,7 @@ requires-dist = [ { name = "vulture", marker = "extra == 'dev'", specifier = ">=2.11" }, ] -[package.metadata.requires-dev] +[package.metadata.dependency-groups] dev = [ { name = "asgi-lifespan", specifier = ">=2.1.0" }, { name = "pytest-codspeed", specifier = ">=3.0.0" }, From 68c36c415ec8a524ce4b7f9b13691cdb77f972d8 Mon Sep 17 00:00:00 2001 From: Raphael Valdetaro <79842132+raphaelchristi@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:17:17 -0300 Subject: [PATCH 6/8] feat(component): enhance merge data with standard operations (#5125) * feat(component): enhance merge data with standard operations - Add standard merge operations (concatenate, append, merge, join) - Add operation selection via dropdown - Return DataFrame output type - Implement separate merge strategies * style(component): improve merge data formatting - Add MIN_INPUTS_REQUIRED constant - Use descriptive DataFrame variable names - Move return statement to else block - Use list comprehension for better performance - Fix unused loop variable - Improve overall code formatting * [autofix.ci] apply automated fixes * Update src/backend/base/langflow/components/processing/merge_data.py Co-authored-by: Gabriel Luiz Freitas Almeida * refactor: adjust merge_data operations to use langflow.schema.DataFrame * Update Merge Data component name and description * [autofix.ci] apply automated fixes * refactor: enhance data merging logic in DataMergerComponent - Improved type hinting for combined data structures to enhance code clarity. - Streamlined the concatenation and merging operations to ensure consistent handling of string and object types. - Updated the logic to correctly append values to lists when merging data inputs, improving data integrity in the merging process. * revert merge data changes * add data merger component * refactor: remove DataMergerComponent and clean up imports - Deleted the DataMergerComponent to streamline the processing components. - Updated the __init__.py file to reflect the removal of the DataMergerComponent from the exports. * refactor: enhance MergeDataComponent with new merge operations - Introduced a new enum, MergeOperation, to define various data merging strategies: CONCATENATE, APPEND, MERGE, and JOIN. - Updated the merge_data method to return a DataFrame instead of a list of Data objects, improving data handling. - Enhanced input validation to ensure a minimum number of data inputs are provided. - Streamlined the merging logic to support different operations, improving flexibility and usability of the component. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida --- .../components/processing/merge_data.py | 150 +++++++++--------- 1 file changed, 74 insertions(+), 76 deletions(-) diff --git a/src/backend/base/langflow/components/processing/merge_data.py b/src/backend/base/langflow/components/processing/merge_data.py index 819895ff034..015c6991f14 100644 --- a/src/backend/base/langflow/components/processing/merge_data.py +++ b/src/backend/base/langflow/components/processing/merge_data.py @@ -1,94 +1,92 @@ +from enum import Enum +from typing import cast + from loguru import logger from langflow.custom import Component -from langflow.io import DataInput, Output -from langflow.schema import Data +from langflow.io import DataInput, DropdownInput, Output +from langflow.schema import DataFrame -class MergeDataComponent(Component): - """MergeDataComponent is responsible for combining multiple Data objects into a unified list of Data objects. +class MergeOperation(str, Enum): + CONCATENATE = "concatenate" + APPEND = "append" + MERGE = "merge" + JOIN = "join" - It ensures that all keys across the input Data objects are present in each merged Data object. - Missing keys are filled with empty strings to maintain consistency. - """ +class MergeDataComponent(Component): display_name = "Merge Data" - description = ( - "Combines multiple Data objects into a unified list, ensuring all keys are present in each Data object." - ) + description = "Combines data using merge operations" icon = "merge" + MIN_INPUTS_REQUIRED = 2 + inputs = [ - DataInput( - name="data_inputs", - display_name="Data Inputs", - is_list=True, - info="A list of Data inputs objects to be merged.", + DataInput(name="data_inputs", display_name="Data Inputs", info="Dados para combinar", is_list=True), + DropdownInput( + name="operation", + display_name="Merge Operation", + options=[op.value for op in MergeOperation], + value=MergeOperation.CONCATENATE.value, ), ] - outputs = [ - Output( - display_name="Merged Data", - name="merged_data", - method="merge_data", - ), - ] + outputs = [Output(display_name="DataFrame", name="merged_data", method="merge_data")] - def merge_data(self) -> list[Data]: - """Merges multiple Data objects into a single list of Data objects. - - Ensures that all keys from the input Data objects are present in each merged Data object. - Missing keys are filled with empty strings. - - Returns: - List[Data]: A list of merged Data objects with consistent keys. - """ - logger.info("Initiating the data merging process.") - - data_inputs: list[Data] = self.data_inputs - logger.debug(f"Received {len(data_inputs)} data input(s) for merging.") - - if not data_inputs: - logger.warning("No data inputs provided. Returning an empty list.") - return [] - - # Collect all unique keys from all Data objects - all_keys: set[str] = set() - for idx, data_input in enumerate(data_inputs): - if not isinstance(data_input, Data): - error_message = f"Data input at index {idx} is not of type Data." - logger.error(error_message) - type_error_message = ( - f"All items in data_inputs must be of type Data. Item at index {idx} is {type(data_input)}" - ) - raise TypeError(type_error_message) - all_keys.update(data_input.data.keys()) - logger.debug(f"Collected {len(all_keys)} unique key(s) from input data.") + def merge_data(self) -> DataFrame: + if not self.data_inputs or len(self.data_inputs) < self.MIN_INPUTS_REQUIRED: + empty_dataframe = DataFrame() + self.status = empty_dataframe + return empty_dataframe + operation = MergeOperation(self.operation) try: - # Create new list of Data objects with missing keys filled with empty strings - merged_data_list = [] - for idx, data_input in enumerate(data_inputs): - merged_data_dict = {} - - for key in all_keys: - # Use the existing value if the key exists, otherwise use an empty string - value = data_input.data.get(key, "") - if key not in data_input.data: - log_message = f"Key '{key}' missing in data input at index {idx}. Assigning empty string." - logger.debug(log_message) - merged_data_dict[key] = value - - merged_data = Data( - text_key=data_input.text_key, data=merged_data_dict, default_value=data_input.default_value - ) - merged_data_list.append(merged_data) - logger.debug("Merged Data object created for input at index: " + str(idx)) - - except Exception: - logger.exception("An error occurred during the data merging process.") + merged_dataframe = self._process_operation(operation) + self.status = merged_dataframe + except Exception as e: + logger.error(f"Erro durante operação {operation}: {e!s}") raise - - logger.info("Data merging process completed successfully.") - return merged_data_list + else: + return merged_dataframe + + def _process_operation(self, operation: MergeOperation) -> DataFrame: + if operation == MergeOperation.CONCATENATE: + combined_data: dict[str, str | object] = {} + for data_input in self.data_inputs: + for key, value in data_input.data.items(): + if key in combined_data: + if isinstance(combined_data[key], str) and isinstance(value, str): + combined_data[key] = f"{combined_data[key]}\n{value}" + else: + combined_data[key] = value + else: + combined_data[key] = value + return DataFrame([combined_data]) + + if operation == MergeOperation.APPEND: + rows = [data_input.data for data_input in self.data_inputs] + return DataFrame(rows) + + if operation == MergeOperation.MERGE: + result_data: dict[str, str | list[str] | object] = {} + for data_input in self.data_inputs: + for key, value in data_input.data.items(): + if key in result_data and isinstance(value, str): + if isinstance(result_data[key], list): + cast(list[str], result_data[key]).append(value) + else: + result_data[key] = [result_data[key], value] + else: + result_data[key] = value + return DataFrame([result_data]) + + if operation == MergeOperation.JOIN: + combined_data = {} + for idx, data_input in enumerate(self.data_inputs, 1): + for key, value in data_input.data.items(): + new_key = f"{key}_doc{idx}" if idx > 1 else key + combined_data[new_key] = value + return DataFrame([combined_data]) + + return DataFrame() From 243055e1cdd5b78ebeedce2975a694de366175c9 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 20 Dec 2024 10:28:54 -0300 Subject: [PATCH 7/8] refactor: Simplify agent input text content construction (#5344) * refactor: Simplify agent input text content construction * Change HUMAN to Input --------- Co-authored-by: Gabriel Luiz Freitas Almeida --- src/backend/base/langflow/base/agents/events.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/backend/base/langflow/base/agents/events.py b/src/backend/base/langflow/base/agents/events.py index 26198ff4a01..efda512e789 100644 --- a/src/backend/base/langflow/base/agents/events.py +++ b/src/backend/base/langflow/base/agents/events.py @@ -25,16 +25,8 @@ class InputDict(TypedDict): def _build_agent_input_text_content(agent_input_dict: InputDict) -> str: - chat_history = agent_input_dict.get("chat_history", []) - messages = [ - f"**{message.type.upper()}**: {message.content}" - for message in chat_history - if isinstance(message, BaseMessage) and message.content - ] final_input = agent_input_dict.get("input", "") - if messages and final_input not in messages[-1]: - messages.append(f"**HUMAN**: {final_input}") - return " \n".join(messages) + return f"**Input**: {final_input}" def _calculate_duration(start_time: float) -> int: From 4b0c42e52076fb7cdca084a1ab06d46f87fecff1 Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Fri, 20 Dec 2024 14:45:06 +0100 Subject: [PATCH 8/8] feat: Bump blockbuster version to 1.5.5 (#5382) Bump blockbuster version to 1.5.5 --- pyproject.toml | 2 +- src/backend/tests/conftest.py | 17 ++++++------ uv.lock | 52 +++++++++++++++++------------------ 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bddfdb15d55..b749139091b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,7 +181,7 @@ dev-dependencies = [ "asgi-lifespan>=2.1.0", "pytest-github-actions-annotate-failures>=0.2.0", "pytest-codspeed>=3.0.0", - "blockbuster>=1.5.2,<1.6", + "blockbuster>=1.5.5,<1.6", "types-aiofiles>=24.1.0.20240626", "codeflash>=0.8.4", ] diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index f80410eedfe..b854d682015 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -63,14 +63,15 @@ def blockbuster(request): ]: bb.functions[func].can_block_in("importlib_metadata/__init__.py", "metadata") - # TODO: make set_class_code async - bb.functions["os.stat"].can_block_in("langflow/custom/custom_component/component.py", "set_class_code") - - # TODO: follow discussion in https://github.com/encode/httpx/discussions/3456 - bb.functions["os.stat"].can_block_in("httpx/_client.py", "_init_transport") - - bb.functions["os.stat"].can_block_in("rich/traceback.py", "_render_stack") - bb.functions["os.stat"].can_block_in("langchain_core/_api/internal.py", "is_caller_internal") + ( + bb.functions["os.stat"] + # TODO: make set_class_code async + .can_block_in("langflow/custom/custom_component/component.py", "set_class_code") + # TODO: follow discussion in https://github.com/encode/httpx/discussions/3456 + .can_block_in("httpx/_client.py", "_init_transport") + .can_block_in("rich/traceback.py", "_render_stack") + .can_block_in("langchain_core/_api/internal.py", "is_caller_internal") + ) ( bb.functions["os.path.abspath"] diff --git a/uv.lock b/uv.lock index 54fbc904bf6..2a2bc9acdb8 100644 --- a/uv.lock +++ b/uv.lock @@ -532,7 +532,7 @@ name = "blessed" version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinxed", marker = "platform_system == 'Windows'" }, + { name = "jinxed", marker = "sys_platform == 'win32'" }, { name = "six" }, { name = "wcwidth" }, ] @@ -543,14 +543,14 @@ wheels = [ [[package]] name = "blockbuster" -version = "1.5.2" +version = "1.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "forbiddenfruit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/3a/07f9c267527c561cb0bdca99147d84f7e0c0551b00869bc63a4906280f2b/blockbuster-1.5.2.tar.gz", hash = "sha256:3588dd4d6f18ec1a64e0c9b9fe13eec79e5b16cf4e49504956d8168a33738583", size = 10751 } +sdist = { url = "https://files.pythonhosted.org/packages/44/f8/a91788ed4a26cbabb5d00d2d67ca2ea241ec44faacf4764558ae831f7725/blockbuster-1.5.5.tar.gz", hash = "sha256:4221bd262dbd5cbb9c2793b7dc10b455ee25dfefa3cf5244df350504b6849619", size = 10850 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/8f/7faccc4f7353606606fab3851bf4652f915348f368e71f8d6910474b7f7c/blockbuster-1.5.2-py3-none-any.whl", hash = "sha256:7853b05bf6650c74b2f07324bd56c277f4ef3d7dcc2d4e6b7cf21a758de706bf", size = 8743 }, + { url = "https://files.pythonhosted.org/packages/5a/8f/c1c3025714edd494164370940f922903ecc9802fa5bdd28da115155a4e5e/blockbuster-1.5.5-py3-none-any.whl", hash = "sha256:e2b2a974f3e35b0f456ef0e732131752c37c78de4bb8b0d84fef9b85e72ab966", size = 8765 }, ] [[package]] @@ -962,7 +962,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -3143,7 +3143,7 @@ name = "ipykernel" version = "6.29.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, { name = "ipython" }, @@ -3234,7 +3234,7 @@ name = "jinxed" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ansicon", marker = "platform_system == 'Windows'" }, + { name = "ansicon", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/d0/59b2b80e7a52d255f9e0ad040d2e826342d05580c4b1d7d7747cfb8db731/jinxed-1.3.0.tar.gz", hash = "sha256:1593124b18a41b7a3da3b078471442e51dbad3d77b4d4f2b0c26ab6f7d660dbf", size = 80981 } wheels = [ @@ -4032,7 +4032,7 @@ local = [ { name = "sentence-transformers" }, ] -[package.dependency-groups] +[package.dev-dependencies] dev = [ { name = "asgi-lifespan" }, { name = "blockbuster" }, @@ -4172,10 +4172,10 @@ requires-dist = [ { name = "zep-python", specifier = "==2.0.2" }, ] -[package.metadata.dependency-groups] +[package.metadata.requires-dev] dev = [ { name = "asgi-lifespan", specifier = ">=2.1.0" }, - { name = "blockbuster", specifier = ">=1.5.2,<1.6" }, + { name = "blockbuster", specifier = ">=1.5.5,<1.6" }, { name = "codeflash", specifier = ">=0.8.4" }, { name = "dictdiffer", specifier = ">=0.9.0" }, { name = "httpx", specifier = ">=0.27.0" }, @@ -4329,7 +4329,7 @@ local = [ { name = "sentence-transformers" }, ] -[package.dependency-groups] +[package.dev-dependencies] dev = [ { name = "asgi-lifespan" }, { name = "pytest-codspeed" }, @@ -4441,7 +4441,7 @@ requires-dist = [ { name = "vulture", marker = "extra == 'dev'", specifier = ">=2.11" }, ] -[package.metadata.dependency-groups] +[package.metadata.requires-dev] dev = [ { name = "asgi-lifespan", specifier = ">=2.1.0" }, { name = "pytest-codspeed", specifier = ">=3.0.0" }, @@ -6098,7 +6098,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -8575,19 +8575,19 @@ dependencies = [ { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "sympy" }, - { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ @@ -8628,7 +8628,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [