merge dev

This commit is contained in:
cristhianzl 2024-05-20 11:09:58 -03:00
commit cf7e664bfd
74 changed files with 15440 additions and 15479 deletions

View file

@ -100,6 +100,12 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
## Deploy on Railway ## Deploy on Railway
Use this template to deploy Langflow 1.0 Preview on Railway:
[![Deploy 1.0 Preview on Railway](https://railway.app/button.svg)](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
Or this one to deploy Langflow 0.6.x:
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/JMXEWp?referralCode=MnPSdg) [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
## Deploy on Render ## Deploy on Render

View file

@ -0,0 +1,45 @@
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";
# Global environment variables
Langflow 1.0 alpha includes the option to add **Global Environment Variables** for your application.
## Add a global variable to a project
In this example, you'll add the `openai_api_key` credential as a global environment variable to the **Basic Prompting** starter project.
For more information on the starter flow, see [Basic prompting](../starter-projects/basic-prompting.mdx).
1. From the Langflow dashboard, click **New Project**.
2. Select **Basic Prompting**.
The **Basic Prompting** flow is created.
3. To create an environment variable for the **OpenAI** component:
1. In the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
2. In the **Variable Name** field, enter `openai_api_key`.
3. In the **Value** field, paste your OpenAI API Key (`sk-...`).
4. For the variable **Type**, select **Credential**.
5. In the **Apply to Fields** field, select **OpenAI API Key** to apply this variable to all fields named **OpenAI API Key**.
6. Click **Save Variable**.
You now have a `openai_api_key` global environment variable for your Langflow project.
<Admonition type="tip">
You can also create global variables in **Settings** > **Variables and
Secrets**.
</Admonition>
<ZoomableImage
alt="Docusaurus themed image"
sources={{
light: "img/global-env.png",
dark: "img/global-env.png",
}}
style={{ width: "40%", margin: "20px auto" }}
/>
4. To view and manage your project's global environment variables, visit **Settings** > **Variables and Secrets**.
For more on variables in HuggingFace Spaces, see [Managing Secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets).

View file

@ -0,0 +1,29 @@
import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import ReactPlayer from "react-player";
import Admonition from "@theme/Admonition";
# Playground
In Langflow 1.0 alpha, the **Playground** replaces the **Interaction Panel**.
The **Playground** provides an interface for interacting with flows without opening them in the flow editor.
It even works for flows hosted on the Langflow store!
As long as you have a flow's environment variables set, you can run it by clicking the **Playground** button.
1. From your **Collections** page, click **Playground** in one of your flows.
The **Playground** window opens.
<ZoomableImage
alt="Docusaurus themed image"
sources={{
light: useBaseUrl("img/playground-chat.png"),
dark: useBaseUrl("img/playground-chat.png"),
}}
style={{ width: "50%", maxWidth: "600px", margin: "0 auto" }}
/>
2. Chat with your bot as you normally would, all without having to open the editor.

View file

@ -4,9 +4,7 @@ module.exports = {
type: "category", type: "category",
label: "What's New?", label: "What's New?",
collapsed: false, collapsed: false,
items: [ items: ["whats-new/a-new-chapter-langflow"],
"whats-new/a-new-chapter-langflow"
],
}, },
{ {
type: "category", type: "category",
@ -17,7 +15,7 @@ module.exports = {
"getting-started/install-langflow", "getting-started/install-langflow",
"getting-started/quickstart", "getting-started/quickstart",
"getting-started/huggingface-spaces", "getting-started/huggingface-spaces",
"getting-started/new-to-llms" "getting-started/new-to-llms",
], ],
}, },
{ {
@ -29,7 +27,7 @@ module.exports = {
"starter-projects/blog-writer", "starter-projects/blog-writer",
"starter-projects/document-qa", "starter-projects/document-qa",
"starter-projects/memory-chatbot", "starter-projects/memory-chatbot",
"starter-projects/vector-store-rag" "starter-projects/vector-store-rag",
], ],
}, },
{ {
@ -40,10 +38,12 @@ module.exports = {
"administration/login", "administration/login",
"administration/api", "administration/api",
"administration/cli", "administration/cli",
"administration/playground",
"administration/global-env",
"administration/components", "administration/components",
"administration/collection", "administration/collection",
"administration/prompt-customization", "administration/prompt-customization",
"administration/langfuse_integration" "administration/langfuse_integration",
], ],
}, },
{ {
@ -58,7 +58,7 @@ module.exports = {
"components/helpers", "components/helpers",
"components/vector-stores", "components/vector-stores",
"components/embeddings", "components/embeddings",
"components/custom" "components/custom",
], ],
}, },
{ {
@ -74,7 +74,7 @@ module.exports = {
"components/retrievers", "components/retrievers",
"components/text-splitters", "components/text-splitters",
"components/toolkits", "components/toolkits",
"components/tools" "components/tools",
], ],
}, },
{ {
@ -88,7 +88,7 @@ module.exports = {
"examples/csv-loader", "examples/csv-loader",
"examples/searchapi-tool", "examples/searchapi-tool",
"examples/serp-api-tool", "examples/serp-api-tool",
"examples/python-function" "examples/python-function",
], ],
}, },
{ {
@ -101,8 +101,8 @@ module.exports = {
"migration/inputs-and-outputs", "migration/inputs-and-outputs",
"migration/text-and-record", "migration/text-and-record",
"migration/compatibility", "migration/compatibility",
"migration/global-variables" "migration/global-variables",
] ],
}, },
{ {
type: "category", type: "category",
@ -111,16 +111,14 @@ module.exports = {
items: [ items: [
"tutorials/chatprompttemplate_guide", "tutorials/chatprompttemplate_guide",
"tutorials/loading_document", "tutorials/loading_document",
"tutorials/rag-with-astradb" "tutorials/rag-with-astradb",
], ],
}, },
{ {
type: "category", type: "category",
label: "Deployment", label: "Deployment",
collapsed: true, collapsed: true,
items: [ items: ["deployment/gcp-deployment"],
"deployment/gcp-deployment"
],
}, },
{ {
type: "category", type: "category",
@ -130,7 +128,7 @@ module.exports = {
"contributing/how-contribute", "contributing/how-contribute",
"contributing/github-issues", "contributing/github-issues",
"contributing/community", "contributing/community",
"contributing/contribute-component" "contributing/contribute-component",
], ],
}, },
], ],

BIN
docs/static/img/global-env.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
docs/static/img/playground-chat.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

750
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "1.0.0a33" version = "1.0.0a34"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -23,43 +23,57 @@ export class Rds extends Construct{
}) })
// DB クラスターのパラメータグループ作成 // DB クラスターのパラメータグループ作成
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{ const clusterParameterGroup = new rds.ParameterGroup(
engine: rds.DatabaseClusterEngine.auroraMysql({ scope,
version: rds.AuroraMysqlEngineVersion.VER_3_02_0 "ClusterParameterGroup",
}), {
description: 'for-langflow', engine: rds.DatabaseClusterEngine.auroraMysql({
}) version: rds.AuroraMysqlEngineVersion.of(
"8.0.mysql_aurora.3.05.2",
"8.0"
),
}),
description: "for-langflow",
}
);
clusterParameterGroup.bindToCluster({}) clusterParameterGroup.bindToCluster({})
// DB インスタンスのパラメタグループ作成 // DB インスタンスのパラメタグループ作成
const instanceParameterGroup = new rds.ParameterGroup(scope, 'InstanceParameterGroup',{ const instanceParameterGroup = new rds.ParameterGroup(
engine: rds.DatabaseClusterEngine.auroraMysql({ scope,
version: rds.AuroraMysqlEngineVersion.VER_3_02_0, "InstanceParameterGroup",
}), {
description: 'for-langflow', engine: rds.DatabaseClusterEngine.auroraMysql({
}) version: rds.AuroraMysqlEngineVersion.of("8.0.mysql_aurora.3.05.2", '8.0'),
}),
description: "for-langflow",
}
);
instanceParameterGroup.bindToInstance({}) instanceParameterGroup.bindToInstance({})
this.rdsCluster = new rds.DatabaseCluster(scope, 'LangflowDbCluster', { this.rdsCluster = new rds.DatabaseCluster(scope, "LangflowDbCluster", {
engine: rds.DatabaseClusterEngine.auroraMysql({ engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_02_0, version: rds.AuroraMysqlEngineVersion.of(
"8.0.mysql_aurora.3.05.2",
"8.0"
),
}), }),
storageEncrypted: true, storageEncrypted: true,
credentials: rdsCredentials, credentials: rdsCredentials,
instanceIdentifierBase: 'langflow-instance', instanceIdentifierBase: "langflow-instance",
vpc:vpc, vpc: vpc,
vpcSubnets:vpc.selectSubnets({ vpcSubnets: vpc.selectSubnets({
subnetGroupName: 'langflow-Isolated', subnetGroupName: "langflow-Isolated",
}), }),
securityGroups:[dbSG], securityGroups: [dbSG],
writer: rds.ClusterInstance.provisioned("WriterInstance", { writer: rds.ClusterInstance.provisioned("WriterInstance", {
instanceType: instanceType, instanceType: instanceType,
enablePerformanceInsights: true, enablePerformanceInsights: true,
parameterGroup:instanceParameterGroup, parameterGroup: instanceParameterGroup,
}), }),
// 2台目以降はreaders:で設定 // 2台目以降はreaders:で設定
parameterGroup: clusterParameterGroup, parameterGroup: clusterParameterGroup,
defaultDatabaseName: 'langflow', defaultDatabaseName: "langflow",
}) });
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -11,21 +11,21 @@
"cdk": "cdk" "cdk": "cdk"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.12",
"@types/node": "20.1.7", "@types/node": "^20.12.12",
"aws-cdk": "^2.86.0", "aws-cdk": "^2.141.0",
"jest": "^29.5.0", "jest": "^29.7.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.2",
"typescript": "~5.1.3" "typescript": "^5.4.5"
}, },
"dependencies": { "dependencies": {
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.49.0", "@aws-solutions-constructs/aws-cloudfront-s3": "^2.57.0",
"aws-cdk-lib": "^2.124.0", "aws-cdk-lib": "^2.141.0",
"cdk-ecr-deployment": "^2.5.30", "cdk-ecr-deployment": "^3.0.55",
"constructs": "^10.0.0", "constructs": "^10.3.0",
"deploy-time-build": "^0.3.12", "deploy-time-build": "^0.3.21",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"
} }
} }

View file

@ -49,7 +49,6 @@ class ChatComponent(CustomComponent):
sender: Optional[str] = None, sender: Optional[str] = None,
sender_name: Optional[str] = None, sender_name: Optional[str] = None,
) -> list[Record]: ) -> list[Record]:
records = store_message( records = store_message(
message, message,
session_id=session_id, session_id=session_id,

View file

@ -1,7 +1,5 @@
from typing import Optional from typing import Optional
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record

View file

@ -1,9 +1,9 @@
from typing import List, Optional
from pydantic.v1 import SecretStr from pydantic.v1 import SecretStr
from langchain_mistralai.embeddings import MistralAIEmbeddings from langchain_mistralai.embeddings import MistralAIEmbeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.field_typing import Embeddings, NestedDict from langflow.field_typing import Embeddings
class MistralAIEmbeddingsComponent(CustomComponent): class MistralAIEmbeddingsComponent(CustomComponent):
display_name = "MistralAI Embeddings" display_name = "MistralAI Embeddings"
@ -37,11 +37,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
"advanced": True, "advanced": True,
"value": 120, "value": 120,
}, },
"endpoint": { "endpoint": {"display_name": "API Endpoint", "advanced": True, "value": "https://api.mistral.ai/v1/"},
"display_name": "API Endpoint",
"advanced": True,
"value": "https://api.mistral.ai/v1/"
}
} }
def build( def build(
@ -51,7 +47,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
max_concurrent_requests: int = 64, max_concurrent_requests: int = 64,
max_retries: int = 5, max_retries: int = 5,
timeout: int = 120, timeout: int = 120,
endpoint: str = "https://api.mistral.ai/v1/" endpoint: str = "https://api.mistral.ai/v1/",
) -> Embeddings: ) -> Embeddings:
if mistral_api_key: if mistral_api_key:
api_key = SecretStr(mistral_api_key) api_key = SecretStr(mistral_api_key)
@ -64,6 +60,5 @@ class MistralAIEmbeddingsComponent(CustomComponent):
endpoint=endpoint, endpoint=endpoint,
max_concurrent_requests=max_concurrent_requests, max_concurrent_requests=max_concurrent_requests,
max_retries=max_retries, max_retries=max_retries,
timeout=timeout timeout=timeout,
) )

View file

@ -3,6 +3,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record from langflow.schema import Record
from langflow.field_typing import Text from langflow.field_typing import Text
class PassComponent(CustomComponent): class PassComponent(CustomComponent):
display_name = "Pass" display_name = "Pass"
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction." description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
@ -19,7 +20,7 @@ class PassComponent(CustomComponent):
"display_name": "Input", "display_name": "Input",
"info": "This input is forwarded by the component.", "info": "This input is forwarded by the component.",
"input_types": ["Text", "Record"], "input_types": ["Text", "Record"],
} },
} }
def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]: def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]:

View file

@ -32,7 +32,6 @@ class StoreMessageComponent(CustomComponent):
session_id: Optional[str] = None, session_id: Optional[str] = None,
message: str = "", message: str = "",
) -> List[Record]: ) -> List[Record]:
store_message( store_message(
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,

View file

@ -35,7 +35,7 @@ class SubFlowComponent(CustomComponent):
build_config["flow_name"]["options"] = self.get_flow_names() build_config["flow_name"]["options"] = self.get_flow_names()
# Clean up the build config # Clean up the build config
for key in list(build_config.keys()): for key in list(build_config.keys()):
if key not in self.field_order + ["code", "_type"]: if key not in self.field_order + ["code", "_type", "get_final_results_only"]:
del build_config[key] del build_config[key]
if field_value is not None and field_name == "flow_name": if field_value is not None and field_name == "flow_name":
try: try:
@ -85,20 +85,29 @@ class SubFlowComponent(CustomComponent):
"display_name": "Tweaks", "display_name": "Tweaks",
"info": "Tweaks to apply to the flow.", "info": "Tweaks to apply to the flow.",
}, },
"get_final_results_only": {
"display_name": "Get Final Results Only",
"info": "If False, the output will contain all outputs from the flow.",
"advanced": True,
},
} }
def build_records_from_result_data(self, result_data: ResultData) -> List[Record]: def build_records_from_result_data(self, result_data: ResultData, get_final_results_only: bool) -> List[Record]:
messages = result_data.messages messages = result_data.messages
if not messages: if not messages:
return [] return []
records = [] records = []
for message in messages: for message in messages:
message_dict = message if isinstance(message, dict) else message.model_dump() message_dict = message if isinstance(message, dict) else message.model_dump()
record = Record(data={"result": result_data.model_dump(), "message": message_dict.get("message", "")}) if get_final_results_only:
result_data_dict = result_data.model_dump()
results = result_data_dict.get("results", {})
inner_result = results.get("result", {})
record = Record(data={"result": inner_result, "message": message_dict}, text_key="result")
records.append(record) records.append(record)
return records return records
async def build(self, flow_name: str, **kwargs) -> List[Record]: async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Record]:
tweaks = {key: {"input_value": value} for key, value in kwargs.items()} tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
run_outputs: List[Optional[RunOutputs]] = await self.run_flow( run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
tweaks=tweaks, tweaks=tweaks,
@ -112,7 +121,7 @@ class SubFlowComponent(CustomComponent):
if run_output is not None: if run_output is not None:
for output in run_output.outputs: for output in run_output.outputs:
if output: if output:
records.extend(self.build_records_from_result_data(output)) records.extend(self.build_records_from_result_data(output, get_final_results_only))
self.status = records self.status = records
logger.debug(records) logger.debug(records)

View file

@ -4,6 +4,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record from langflow.schema import Record
from langflow.field_typing import Text from langflow.field_typing import Text
class TextOperatorComponent(CustomComponent): class TextOperatorComponent(CustomComponent):
display_name = "Text Operator" display_name = "Text Operator"
description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity." description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity."
@ -21,14 +22,7 @@ class TextOperatorComponent(CustomComponent):
"operator": { "operator": {
"display_name": "Operator", "display_name": "Operator",
"info": "The operator to apply for comparing the texts.", "info": "The operator to apply for comparing the texts.",
"options": [ "options": ["equals", "not equals", "contains", "starts with", "ends with", "exists"],
"equals",
"not equals",
"contains",
"starts with",
"ends with",
"exists"
],
}, },
"case_sensitive": { "case_sensitive": {
"display_name": "Case Sensitive", "display_name": "Case Sensitive",
@ -51,11 +45,8 @@ class TextOperatorComponent(CustomComponent):
case_sensitive: bool = False, case_sensitive: bool = False,
true_output: Optional[Text] = "", true_output: Optional[Text] = "",
) -> Union[Text, Record]: ) -> Union[Text, Record]:
if not input_text or not match_text: if not input_text or not match_text:
raise ValueError( raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
"Both 'input_text' and 'match_text' must be provided and non-empty."
)
if not case_sensitive: if not case_sensitive:
input_text = input_text.lower() input_text = input_text.lower()

View file

@ -242,6 +242,7 @@ class Graph:
outputs: list[str], outputs: list[str],
stream: bool, stream: bool,
session_id: str, session_id: str,
fallback_to_env_vars: bool,
) -> List[Optional["ResultData"]]: ) -> List[Optional["ResultData"]]:
""" """
Runs the graph with the given inputs. Runs the graph with the given inputs.
@ -289,7 +290,7 @@ class Graph:
start_component_id = next( start_component_id = next(
(vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None (vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None
) )
await self.process(start_component_id=start_component_id) await self.process(start_component_id=start_component_id, fallback_to_env_vars=fallback_to_env_vars)
self.increment_run_count() self.increment_run_count()
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
@ -315,6 +316,7 @@ class Graph:
outputs: Optional[list[str]] = None, outputs: Optional[list[str]] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
stream: bool = False, stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
Run the graph with the given inputs and return the outputs. Run the graph with the given inputs and return the outputs.
@ -340,6 +342,7 @@ class Graph:
outputs=outputs, outputs=outputs,
session_id=session_id, session_id=session_id,
stream=stream, stream=stream,
fallback_to_env_vars=fallback_to_env_vars,
) )
try: try:
@ -362,6 +365,7 @@ class Graph:
outputs: Optional[list[str]] = None, outputs: Optional[list[str]] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
stream: bool = False, stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
Runs the graph with the given inputs. Runs the graph with the given inputs.
@ -403,6 +407,7 @@ class Graph:
outputs=outputs or [], outputs=outputs or [],
stream=stream, stream=stream,
session_id=session_id or "", session_id=session_id or "",
fallback_to_env_vars=fallback_to_env_vars,
) )
run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs) run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
logger.debug(f"Run outputs: {run_output_object}") logger.debug(f"Run outputs: {run_output_object}")
@ -468,9 +473,9 @@ class Graph:
"""Marks a branch of the graph.""" """Marks a branch of the graph."""
if visited is None: if visited is None:
visited = set() visited = set()
visited.add(vertex_id)
if vertex_id in visited: if vertex_id in visited:
return return
visited.add(vertex_id)
self.mark_vertex(vertex_id, state) self.mark_vertex(vertex_id, state)
@ -712,6 +717,7 @@ class Graph:
vertex_id: str, vertex_id: str,
inputs_dict: Optional[Dict[str, str]] = None, inputs_dict: Optional[Dict[str, str]] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
fallback_to_env_vars: bool = False,
): ):
""" """
Builds a vertex in the graph. Builds a vertex in the graph.
@ -733,7 +739,7 @@ class Graph:
vertex = self.get_vertex(vertex_id) vertex = self.get_vertex(vertex_id)
try: try:
if not vertex.frozen or not vertex._built: if not vertex.frozen or not vertex._built:
await vertex.build(user_id=user_id, inputs=inputs_dict) await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars)
if vertex.result is not None: if vertex.result is not None:
params = vertex._built_object_repr() params = vertex._built_object_repr()
@ -796,7 +802,7 @@ class Graph:
vertices.append(vertex) vertices.append(vertex)
return vertices return vertices
async def process(self, start_component_id: Optional[str] = None) -> "Graph": async def process(self, fallback_to_env_vars: bool, start_component_id: Optional[str] = None) -> "Graph":
"""Processes the graph with vertices in each layer run in parallel.""" """Processes the graph with vertices in each layer run in parallel."""
first_layer = self.sort_vertices(start_component_id=start_component_id) first_layer = self.sort_vertices(start_component_id=start_component_id)
@ -821,6 +827,7 @@ class Graph:
vertex_id=vertex_id, vertex_id=vertex_id,
user_id=self.user_id, user_id=self.user_id,
inputs_dict={}, inputs_dict={},
fallback_to_env_vars=fallback_to_env_vars,
), ),
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}", name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
) )

View file

@ -315,7 +315,11 @@ class Vertex:
params[field_name] = full_path params[field_name] = full_path
elif field.get("required"): elif field.get("required"):
field_display_name = field.get("display_name") field_display_name = field.get("display_name")
raise ValueError(f"File path not found for {field_display_name} in component {self.display_name}") logger.warning(
f"File path not found for {field_display_name} in component {self.display_name}. Setting to None."
)
params[field_name] = None
elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None: elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None:
val = field.get("value") val = field.get("value")
if field.get("type") == "code": if field.get("type") == "code":
@ -390,13 +394,17 @@ class Vertex:
self.params = self._raw_params.copy() self.params = self._raw_params.copy()
self.updated_raw_params = True self.updated_raw_params = True
async def _build(self, user_id=None): async def _build(
self,
fallback_to_env_vars,
user_id=None,
):
""" """
Initiate the build process. Initiate the build process.
""" """
logger.debug(f"Building {self.display_name}") logger.debug(f"Building {self.display_name}")
await self._build_each_vertex_in_params_dict(user_id) await self._build_each_vertex_in_params_dict(user_id)
await self._get_and_instantiate_class(user_id) await self._get_and_instantiate_class(user_id, fallback_to_env_vars)
self._validate_built_object() self._validate_built_object()
self._built = True self._built = True
@ -606,7 +614,7 @@ class Vertex:
if isinstance(self.params[key], list): if isinstance(self.params[key], list):
self.params[key].extend(result) self.params[key].extend(result)
async def _get_and_instantiate_class(self, user_id=None): async def _get_and_instantiate_class(self, user_id=None, fallback_to_env_vars=False):
""" """
Gets the class from a dictionary and instantiates it with the params. Gets the class from a dictionary and instantiates it with the params.
""" """
@ -615,6 +623,7 @@ class Vertex:
try: try:
result = await loading.instantiate_class( result = await loading.instantiate_class(
user_id=user_id, user_id=user_id,
fallback_to_env_vars=fallback_to_env_vars,
vertex=self, vertex=self,
) )
self._update_built_object_and_artifacts(result) self._update_built_object_and_artifacts(result)

View file

@ -1,8 +1,8 @@
import inspect import inspect
import json import json
import os
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
import orjson import orjson
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
@ -35,6 +35,7 @@ if TYPE_CHECKING:
async def instantiate_class( async def instantiate_class(
vertex: "Vertex", vertex: "Vertex",
fallback_to_env_vars,
user_id=None, user_id=None,
) -> Any: ) -> Any:
"""Instantiate class from module type and key, and params""" """Instantiate class from module type and key, and params"""
@ -57,7 +58,7 @@ async def instantiate_class(
if not base_type: if not base_type:
raise ValueError("No base type provided for vertex") raise ValueError("No base type provided for vertex")
if base_type == "custom_components": if base_type == "custom_components":
return await instantiate_custom_component(params, user_id, vertex) return await instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars=fallback_to_env_vars)
class_object = import_by_type(_type=base_type, name=vertex_type) class_object = import_by_type(_type=base_type, name=vertex_type)
return await instantiate_based_on_type( return await instantiate_based_on_type(
class_object=class_object, class_object=class_object,
@ -66,6 +67,7 @@ async def instantiate_class(
params=params, params=params,
user_id=user_id, user_id=user_id,
vertex=vertex, vertex=vertex,
fallback_to_env_vars=fallback_to_env_vars,
) )
@ -93,14 +95,7 @@ def convert_kwargs(params):
return params return params
async def instantiate_based_on_type( async def instantiate_based_on_type(class_object, base_type, node_type, params, user_id, vertex, fallback_to_env_vars):
class_object,
base_type,
node_type,
params,
user_id,
vertex,
):
if base_type == "agents": if base_type == "agents":
return instantiate_agent(node_type, class_object, params) return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts": elif base_type == "prompts":
@ -132,33 +127,49 @@ async def instantiate_based_on_type(
elif base_type == "memory": elif base_type == "memory":
return instantiate_memory(node_type, class_object, params) return instantiate_memory(node_type, class_object, params)
elif base_type == "custom_components": elif base_type == "custom_components":
return await instantiate_custom_component( return await instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars=fallback_to_env_vars)
params,
user_id,
vertex,
)
elif base_type == "wrappers": elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params) return instantiate_wrapper(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
def update_params_with_load_from_db_fields(custom_component: "CustomComponent", params, load_from_db_fields): def update_params_with_load_from_db_fields(
custom_component: "CustomComponent", params, load_from_db_fields, fallback_to_env_vars=False
):
# For each field in load_from_db_fields, we will check if it's in the params # For each field in load_from_db_fields, we will check if it's in the params
# and if it is, we will get the value from the custom_component.keys(name) # and if it is, we will get the value from the custom_component.keys(name)
# and update the params with the value # and update the params with the value
for field in load_from_db_fields: for field in load_from_db_fields:
if field in params: if field in params:
try: try:
key = custom_component.variables(params[field]) key = None
params[field] = key if key else params[field] try:
key = custom_component.variables(params[field])
except ValueError as e:
# check if "User id is not set" is in the error message
if "User id is not set" in str(e) and not fallback_to_env_vars:
raise e
logger.debug(str(e))
if fallback_to_env_vars and key is None:
var = os.getenv(params[field])
if var is None:
raise ValueError(f"Environment variable {params[field]} is not set.")
key = var
logger.info(f"Using environment variable {params[field]} for {field}")
if key is None:
logger.warning(f"Could not get value for {field}. Setting it to None.")
params[field] = key
except Exception as exc: except Exception as exc:
logger.error(f"Failed to get value for {field} from custom component. Error: {exc}") logger.error(f"Failed to get value for {field} from custom component. Setting it to None. Error: {exc}")
pass
params[field] = None
return params return params
async def instantiate_custom_component(params, user_id, vertex): async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars: bool = False):
params_copy = params.copy() params_copy = params.copy()
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code")) class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
custom_component: "CustomComponent" = class_object( custom_component: "CustomComponent" = class_object(
@ -167,7 +178,9 @@ async def instantiate_custom_component(params, user_id, vertex):
vertex=vertex, vertex=vertex,
selected_output_type=vertex.selected_output_type, selected_output_type=vertex.selected_output_type,
) )
params_copy = update_params_with_load_from_db_fields(custom_component, params_copy, vertex.load_from_db_fields) params_copy = update_params_with_load_from_db_fields(
custom_component, params_copy, vertex.load_from_db_fields, fallback_to_env_vars
)
if "retriever" in params_copy and hasattr(params_copy["retriever"], "as_retriever"): if "retriever" in params_copy and hasattr(params_copy["retriever"], "as_retriever"):
params_copy["retriever"] = params_copy["retriever"].as_retriever() params_copy["retriever"] = params_copy["retriever"].as_retriever()

View file

@ -5,7 +5,6 @@ import orjson
from langchain_community.vectorstores import ( from langchain_community.vectorstores import (
FAISS, FAISS,
Chroma, Chroma,
ElasticsearchStore,
MongoDBAtlasVectorSearch, MongoDBAtlasVectorSearch,
Pinecone, Pinecone,
Qdrant, Qdrant,

View file

@ -108,7 +108,6 @@ def store_message(
sender: Optional[str] = None, sender: Optional[str] = None,
sender_name: Optional[str] = None, sender_name: Optional[str] = None,
) -> list[Record]: ) -> list[Record]:
if not message: if not message:
warnings.warn("No message provided.") warnings.warn("No message provided.")
return [] return []

View file

@ -82,6 +82,7 @@ def run_flow_from_json(
env_file: Optional[str] = None, env_file: Optional[str] = None,
cache: Optional[str] = None, cache: Optional[str] = None,
disable_logs: Optional[bool] = True, disable_logs: Optional[bool] = True,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
Run a flow from a JSON file or dictionary. Run a flow from a JSON file or dictionary.
@ -98,6 +99,7 @@ def run_flow_from_json(
env_file (Optional[str], optional): The environment file to load. Defaults to None. env_file (Optional[str], optional): The environment file to load. Defaults to None.
cache (Optional[str], optional): The cache directory to use. Defaults to None. cache (Optional[str], optional): The cache directory to use. Defaults to None.
disable_logs (Optional[bool], optional): Whether to disable logs. Defaults to True. disable_logs (Optional[bool], optional): Whether to disable logs. Defaults to True.
fallback_to_env_vars (bool, optional): Whether Global Variables should fallback to environment variables if not found. Defaults to False.
Returns: Returns:
List[RunOutputs]: A list of RunOutputs objects representing the results of running the flow. List[RunOutputs]: A list of RunOutputs objects representing the results of running the flow.
@ -127,5 +129,6 @@ def run_flow_from_json(
input_type=input_type, input_type=input_type,
output_type=output_type, output_type=output_type,
output_component=output_component, output_component=output_component,
fallback_to_env_vars=fallback_to_env_vars,
) )
return result return result

View file

@ -175,6 +175,7 @@ def run_graph(
input_value: str, input_value: str,
input_type: str, input_type: str,
output_type: str, output_type: str,
fallback_to_env_vars: bool = False,
output_component: Optional[str] = None, output_component: Optional[str] = None,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
@ -218,6 +219,7 @@ def run_graph(
outputs or [], outputs or [],
stream=False, stream=False,
session_id="", session_id="",
fallback_to_env_vars=fallback_to_env_vars,
) )
return run_outputs return run_outputs

View file

@ -4,7 +4,6 @@ from typing import Literal, Optional, cast
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from pydantic import BaseModel, model_validator from pydantic import BaseModel, model_validator
from langchain_core.messages import HumanMessage, AIMessage
class Record(BaseModel): class Record(BaseModel):

View file

@ -111,6 +111,10 @@ class Settings(BaseSettings):
CELERY_ENABLED: bool = False CELERY_ENABLED: bool = False
fallback_to_env_var: bool = True
"""If set to True, Global Variables set in the UI will fallback to a environment variable
with the same name in case Langflow fails to retrieve the variable value."""
store_environment_variables: bool = True store_environment_variables: bool = True
"""Whether to store environment variables as Global Variables in the database.""" """Whether to store environment variables as Global Variables in the database."""
variables_to_get_from_environment: list[str] = VARIABLES_TO_GET_FROM_ENVIRONMENT variables_to_get_from_environment: list[str] = VARIABLES_TO_GET_FROM_ENVIRONMENT

View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -1137,20 +1137,20 @@ extended-testing = ["faker (>=19.3.1,<20.0.0)", "jinja2 (>=3,<4)", "pandas (>=2.
[[package]] [[package]]
name = "langchain-text-splitters" name = "langchain-text-splitters"
version = "0.0.1" version = "0.0.2"
description = "LangChain text splitting utilities" description = "LangChain text splitting utilities"
optional = false optional = false
python-versions = ">=3.8.1,<4.0" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langchain_text_splitters-0.0.1-py3-none-any.whl", hash = "sha256:f5b802f873f5ff6a8b9259ff34d53ed989666ef4e1582e6d1adb3b5520e3839a"}, {file = "langchain_text_splitters-0.0.2-py3-none-any.whl", hash = "sha256:13887f32705862c1e1454213cb7834a63aae57c26fcd80346703a1d09c46168d"},
{file = "langchain_text_splitters-0.0.1.tar.gz", hash = "sha256:ac459fa98799f5117ad5425a9330b21961321e30bc19a2a2f9f761ddadd62aa1"}, {file = "langchain_text_splitters-0.0.2.tar.gz", hash = "sha256:ac8927dc0ba08eba702f6961c9ed7df7cead8de19a9f7101ab2b5ea34201b3c1"},
] ]
[package.dependencies] [package.dependencies]
langchain-core = ">=0.1.28,<0.2.0" langchain-core = ">=0.1.28,<0.3"
[package.extras] [package.extras]
extended-testing = ["lxml (>=5.1.0,<6.0.0)"] extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
[[package]] [[package]]
name = "langchainhub" name = "langchainhub"
@ -1169,13 +1169,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.1.57" version = "0.1.59"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langsmith-0.1.57-py3-none-any.whl", hash = "sha256:dbd83b0944a2fbea4151f0aa053530d93fcf6784a580621bc60633cb890b57dc"}, {file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"},
{file = "langsmith-0.1.57.tar.gz", hash = "sha256:4682204de19f0218029c2b8445ce2cc3485c8d0df9796b31e2ce4c9051fce365"}, {file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"},
] ]
[package.dependencies] [package.dependencies]
@ -1282,9 +1282,13 @@ files = [
{file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"},
{file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"},
{file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
{file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
@ -1935,13 +1939,13 @@ xmp = ["defusedxml"]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.2.1" version = "4.2.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
] ]
[package.extras] [package.extras]
@ -2180,13 +2184,13 @@ cli = ["click (>=5.0)"]
[[package]] [[package]]
name = "python-engineio" name = "python-engineio"
version = "4.9.0" version = "4.9.1"
description = "Engine.IO server and client for Python" description = "Engine.IO server and client for Python"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "python-engineio-4.9.0.tar.gz", hash = "sha256:e87459c15638e567711fd156e6f9c4a402668871bed79523f0ecfec744729ec7"}, {file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"},
{file = "python_engineio-4.9.0-py3-none-any.whl", hash = "sha256:979859bff770725b75e60353d7ae53b397e8b517d05ba76733b404a3dcca3e4c"}, {file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow-base" name = "langflow-base"
version = "0.0.44" version = "0.0.45"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

File diff suppressed because it is too large Load diff

View file

@ -1,141 +1,162 @@
import { saveAs } from "file-saver";
import OpenSeadragon from "openseadragon";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import ForwardedIconComponent from "../genericIconComponent";
import useFlowStore from "../../stores/flowStore";
import OpenSeadragon from 'openseadragon';
import { Separator } from "../ui/separator";
import { saveAs } from 'file-saver'
import useAlertStore from "../../stores/alertStore";
import { IMGViewErrorMSG, IMGViewErrorTitle } from "../../constants/constants"; import { IMGViewErrorMSG, IMGViewErrorTitle } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
export default function ImageViewer({image }) { export default function ImageViewer({ image }) {
const viewerRef = useRef(null); const viewerRef = useRef(null);
const [errorDownloading, setErrordownloading] = useState(false) const [errorDownloading, setErrordownloading] = useState(false);
const setErrorList = useAlertStore(state => state.setErrorData); const setErrorList = useAlertStore((state) => state.setErrorData);
const [initialMsg, setInicialMsg] = useState("Please build your flow"); const [initialMsg, setInicialMsg] = useState("Please build your flow");
useEffect(() => {
try {
if (viewerRef.current) {
// Initialize OpenSeadragon viewer
const viewer = OpenSeadragon({
element: viewerRef.current,
prefixUrl:
"https://cdnjs.cloudflare.com/ajax/libs/openseadragon/2.4.2/images/", // Optional: Set the path to OpenSeadragon images
tileSources: { type: "image", url: image },
defaultZoomLevel: 1,
maxZoomPixelRatio: 4,
showNavigationControl: false,
});
const zoomInButton = document.getElementById("zoom-in-button");
const zoomOutButton = document.getElementById("zoom-out-button");
const homeButton = document.getElementById("home-button");
const fullPageButton = document.getElementById("full-page-button");
useEffect(() => { zoomInButton!.addEventListener("click", () =>
try { viewer.viewport.zoomBy(1.2)
if (viewerRef.current) { );
// Initialize OpenSeadragon viewer zoomOutButton!.addEventListener("click", () =>
const viewer = OpenSeadragon({ viewer.viewport.zoomBy(0.8)
element: viewerRef.current, );
prefixUrl: 'https://cdnjs.cloudflare.com/ajax/libs/openseadragon/2.4.2/images/', // Optional: Set the path to OpenSeadragon images homeButton!.addEventListener("click", () => viewer.viewport.goHome());
tileSources: {type: 'image', url: image}, fullPageButton!.addEventListener("click", () =>
defaultZoomLevel: 1, viewer.setFullScreen(true)
maxZoomPixelRatio: 4, );
showNavigationControl: false,
});
const zoomInButton = document.getElementById('zoom-in-button');
const zoomOutButton = document.getElementById('zoom-out-button');
const homeButton = document.getElementById('home-button');
const fullPageButton = document.getElementById('full-page-button');
zoomInButton!.addEventListener('click', () => viewer.viewport.zoomBy(1.2)); // Optionally, you can set additional viewer options here
zoomOutButton!.addEventListener('click', () => viewer.viewport.zoomBy(0.8));
homeButton!.addEventListener('click', () => viewer.viewport.goHome());
fullPageButton!.addEventListener('click', () => viewer.setFullScreen(true));
// Optionally, you can set additional viewer options here // Cleanup function
return () => {
// Cleanup function viewer.destroy();
return () => { zoomInButton!.removeEventListener("click", () =>
viewer.destroy(); viewer.viewport.zoomBy(1.2)
zoomInButton!.removeEventListener('click', () => viewer.viewport.zoomBy(1.2)); );
zoomOutButton!.removeEventListener('click', () => viewer.viewport.zoomBy(0.8)); zoomOutButton!.removeEventListener("click", () =>
homeButton!.removeEventListener('click', () => viewer.viewport.goHome()); viewer.viewport.zoomBy(0.8)
fullPageButton!.removeEventListener('click', () => viewer.setFullScreen(true)); );
}; homeButton!.removeEventListener("click", () =>
} viewer.viewport.goHome()
} catch (error) { );
console.error('Error initializing OpenSeadragon:', error); fullPageButton!.removeEventListener("click", () =>
} viewer.setFullScreen(true)
}, [image]); );
};
function download() {
const imageUrl = image;
// Fetch the image data
fetch(imageUrl)
.then(response => response.blob())
.then(blob => {
// Save the image using FileSaver.js
saveAs(blob, 'image.jpg');
})
.catch(error => {
setErrorList({title: "There was an error downloading your image"})
console.error('Error downloading image:', error)
});
} }
} catch (error) {
console.error("Error initializing OpenSeadragon:", error);
}
}, [image]);
return ( function download() {
image === "" ? ( const imageUrl = image;
<div className="w-full h-full bg-muted rounded-md flex align-center justify-center flex-col gap-5 border border-border"> // Fetch the image data
<div className="flex gap-2 align-center justify-center "> fetch(imageUrl)
<ForwardedIconComponent .then((response) => response.blob())
name="Image" .then((blob) => {
/> // Save the image using FileSaver.js
{IMGViewErrorTitle} saveAs(blob, "image.jpg");
</div> })
<div className="flex align-center justify-center"> .catch((error) => {
<div className="langflow-chat-desc flex align-center justify-center"> setErrorList({ title: "There was an error downloading your image" });
<div className="langflow-chat-desc-span"> console.error("Error downloading image:", error);
{IMGViewErrorMSG} });
</div> }
</div>
</div> return image === "" ? (
<div className="align-center flex h-full w-full flex-col justify-center gap-5 rounded-md border border-border bg-muted">
<div className="align-center flex justify-center gap-2 ">
<ForwardedIconComponent name="Image" />
{IMGViewErrorTitle}
</div>
<div className="align-center flex justify-center">
<div className="langflow-chat-desc align-center flex justify-center">
<div className="langflow-chat-desc-span">{IMGViewErrorMSG}</div>
</div>
</div>
</div>
) : (
<>
<div className="align-center my-2 mb-4 flex w-full justify-center">
<div className="shadow-round-btn-shadow hover:shadow-round-btn-shadow flex w-[50%] items-center justify-center rounded-sm border bg-muted shadow-md transition-all">
<button
id="zoom-in-button"
className="relative inline-flex w-full w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"
>
<ForwardedIconComponent
name="ZoomIn"
className={"h-5 w-5 text-secondary-foreground"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div> </div>
) : ( <button
<> id="zoom-out-button"
<div className="w-full flex align-center justify-center my-2 mb-4"> className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"
<div className="shadow-round-btn-shadow hover:shadow-round-btn-shadow flex items-center justify-center rounded-sm border bg-muted shadow-md transition-all w-[50%]"> >
<button id="zoom-in-button" className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all w-full transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"> <ForwardedIconComponent
<ForwardedIconComponent name="ZoomOut"
name="ZoomIn" className={"h-5 w-5 text-secondary-foreground"}
className={"text-secondary-foreground w-5 h-5"} />
/> </button>
</button> <div>
<div> <Separator orientation="vertical" />
<Separator orientation="vertical" />
</div>
<button id="zoom-out-button" className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="ZoomOut"
className={"text-secondary-foreground w-5 h-5"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div>
<button id="home-button" className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="RotateCcw"
className={"text-secondary-foreground w-5 h-5"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div>
<button id="full-page-button" className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="Maximize2"
className={"text-secondary-foreground w-5 h-5"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div>
<button onClick={download} className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="ArrowDownToLine"
className={"text-secondary-foreground w-5 h-5"}
/>
</button>
</div>
</div> </div>
<div id="canvas" ref={viewerRef} className={`w-full h-[90%] `} /> <button
</> id="home-button"
) className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"
); >
<ForwardedIconComponent
name="RotateCcw"
className={"h-5 w-5 text-secondary-foreground"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div>
<button
id="full-page-button"
className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"
>
<ForwardedIconComponent
name="Maximize2"
className={"h-5 w-5 text-secondary-foreground"}
/>
</button>
<div>
<Separator orientation="vertical" />
</div>
<button
onClick={download}
className="relative inline-flex w-full items-center justify-center px-3 py-3 text-sm font-semibold transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover"
>
<ForwardedIconComponent
name="ArrowDownToLine"
className={"h-5 w-5 text-secondary-foreground"}
/>
</button>
</div>
</div>
<div id="canvas" ref={viewerRef} className={`h-[90%] w-full `} />
</>
);
} }

View file

@ -15,7 +15,7 @@ export default function AccordionComponent({
sideBar, sideBar,
}: AccordionComponentType): JSX.Element { }: AccordionComponentType): JSX.Element {
const [value, setValue] = useState( const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion(), open.length === 0 ? "" : getOpenAccordion()
); );
function getOpenAccordion(): string { function getOpenAccordion(): string {

View file

@ -24,19 +24,19 @@ export default function AddNewVariableButton({ children }): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields); const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set( const unavaliableFields = new Set(
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)), Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields))
); );
const availableFields = () => { const availableFields = () => {
const fields = Array.from(componentFields).filter( const fields = Array.from(componentFields).filter(
(field) => !unavaliableFields.has(field), (field) => !unavaliableFields.has(field)
); );
return sortByName(fields); return sortByName(fields);
}; };
const addGlobalVariable = useGlobalVariablesStore( const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable, (state) => state.addGlobalVariable
); );
function handleSaveVariable() { function handleSaveVariable() {

View file

@ -6,6 +6,7 @@ import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore"; import { useStoreStore } from "../../stores/storeStore";
import { FlowType } from "../../types/flow";
import { storeComponent } from "../../types/store"; import { storeComponent } from "../../types/store";
import cloneFLowWithParent, { import cloneFLowWithParent, {
getInputsAndOutputs, getInputsAndOutputs,
@ -22,7 +23,6 @@ import {
CardTitle, CardTitle,
} from "../ui/card"; } from "../ui/card";
import Loading from "../ui/loading"; import Loading from "../ui/loading";
import { FlowType } from "../../types/flow";
export default function CollectionCardComponent({ export default function CollectionCardComponent({
data, data,
@ -50,11 +50,11 @@ export default function CollectionCardComponent({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false); const [loadingLike, setLoadingLike] = useState(false);
const [liked_by_user, setLiked_by_user] = useState( const [liked_by_user, setLiked_by_user] = useState(
data?.liked_by_user ?? false, data?.liked_by_user ?? false
); );
const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0); const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0);
const [downloads_count, setDownloads_count] = useState( const [downloads_count, setDownloads_count] = useState(
data?.downloads_count ?? 0, data?.downloads_count ?? 0
); );
const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
@ -65,7 +65,7 @@ export default function CollectionCardComponent({
const [openPlayground, setOpenPlayground] = useState(false); const [openPlayground, setOpenPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId, (state) => state.setCurrentFlowId
); );
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false);
@ -185,7 +185,7 @@ export default function CollectionCardComponent({
className={cn( className={cn(
"group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]", "group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
disabled ? "pointer-events-none opacity-50" : "", disabled ? "pointer-events-none opacity-50" : "",
onClick ? "cursor-pointer" : "", onClick ? "cursor-pointer" : ""
)} )}
onClick={onClick} onClick={onClick}
> >
@ -198,7 +198,7 @@ export default function CollectionCardComponent({
"flex-shrink-0", "flex-shrink-0",
data.is_component data.is_component
? "mx-0.5 h-6 w-6 text-component-icon" ? "mx-0.5 h-6 w-6 text-component-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon", : "h-7 w-7 flex-shrink-0 text-flow-icon"
)} )}
name={data.is_component ? "ToyBrick" : "Group"} name={data.is_component ? "ToyBrick" : "Group"}
/> />
@ -386,7 +386,7 @@ export default function CollectionCardComponent({
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5", "h-5 w-5",
!authorized ? " text-ring" : "", !authorized ? " text-ring" : ""
)} )}
/> />
</Button> </Button>
@ -421,7 +421,7 @@ export default function CollectionCardComponent({
liked_by_user liked_by_user
? "fill-destructive stroke-destructive" ? "fill-destructive stroke-destructive"
: "", : "",
!authorized ? " text-ring" : "", !authorized ? " text-ring" : ""
)} )}
/> />
</Button> </Button>
@ -459,7 +459,7 @@ export default function CollectionCardComponent({
} }
className={cn( className={cn(
loading ? "h-5 w-5 animate-spin" : "h-5 w-5", loading ? "h-5 w-5 animate-spin" : "h-5 w-5",
!authorized ? " text-ring" : "", !authorized ? " text-ring" : ""
)} )}
/> />
</Button> </Button>

View file

@ -50,7 +50,7 @@ export default function FlowToolbar(): JSX.Element {
"relative inline-flex h-full w-full items-center justify-center gap-[4px] bg-muted px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-background hover:bg-hover ", "relative inline-flex h-full w-full items-center justify-center gap-[4px] bg-muted px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-background hover:bg-hover ",
!hasApiKey || !validApiKey || !hasStore !hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground " ? " button-disable text-muted-foreground "
: "", : ""
)} )}
> >
<ForwardedIconComponent <ForwardedIconComponent
@ -59,14 +59,14 @@ export default function FlowToolbar(): JSX.Element {
"-m-0.5 -ml-1 h-6 w-6", "-m-0.5 -ml-1 h-6 w-6",
!hasApiKey || !validApiKey || !hasStore !hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable" ? "extra-side-bar-save-disable"
: "", : ""
)} )}
/> />
Share Share
</button> </button>
</ShareModal> </ShareModal>
), ),
[hasApiKey, validApiKey, currentFlow, hasStore], [hasApiKey, validApiKey, currentFlow, hasStore]
); );
return ( return (
@ -118,7 +118,7 @@ export default function FlowToolbar(): JSX.Element {
<ApiModal flow={currentFlow}> <ApiModal flow={currentFlow}>
<div <div
className={classNames( className={classNames(
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover", "relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover"
)} )}
> >
<ForwardedIconComponent <ForwardedIconComponent

View file

@ -18,7 +18,7 @@ export default function CodeAreaComponent({
setOpen, setOpen,
}: CodeAreaComponentType) { }: CodeAreaComponentType) {
const [myValue, setMyValue] = useState( const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value), typeof value == "string" ? value : JSON.stringify(value)
); );
useEffect(() => { useEffect(() => {
if (disabled && myValue !== "") { if (disabled && myValue !== "") {

View file

@ -55,6 +55,7 @@ export default function CodeTabsComponent({
tweaks, tweaks,
setActiveTweaks, setActiveTweaks,
activeTweaks, activeTweaks,
allowExport = false,
}: codeTabsPropsType) { }: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false); const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
@ -146,39 +147,43 @@ export default function CodeTabsComponent({
)} )}
<div className="float-right mx-1 mb-1 mt-2 flex gap-2"> <div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<div {tweaks && (
className={ <div
Number(activeTab) > 2
? "hidden"
: "relative top-[2.5px] flex gap-2"
}
>
<Switch
style={{
transform: `scaleX(${0.7}) scaleY(${0.7})`,
}}
id="tweaks-switch"
onCheckedChange={setActiveTweaks}
autoFocus={false}
/>
<Label
className={ className={
"relative right-1 top-[4px] text-xs font-medium text-gray-500 dark:text-gray-300 " + Number(activeTab) > 2
(activeTweaks ? "hidden"
? "font-bold text-black dark:text-white" : "relative top-[2.5px] flex gap-2"
: "font-medium")
} }
htmlFor="tweaks-switch"
> >
Tweaks <Switch
</Label> style={{
</div> transform: `scaleX(${0.7}) scaleY(${0.7})`,
<ExportModal> }}
<div className="flex cursor-pointer items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"> id="tweaks-switch"
<IconComponent name="FileDown" className="h-4 w-4" /> onCheckedChange={setActiveTweaks}
Export Flow autoFocus={false}
/>
<Label
className={
"relative right-1 top-[4px] text-xs font-medium text-gray-500 dark:text-gray-300 " +
(activeTweaks
? "font-bold text-black dark:text-white"
: "font-medium")
}
htmlFor="tweaks-switch"
>
Tweaks
</Label>
</div> </div>
</ExportModal> )}
{allowExport && (
<ExportModal>
<div className="flex cursor-pointer items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300">
<IconComponent name="FileDown" className="h-4 w-4" />
Export Flow
</div>
</ExportModal>
)}
{Number(activeTab) < 4 && ( {Number(activeTab) < 4 && (
<> <>
@ -231,7 +236,7 @@ export default function CodeTabsComponent({
<div className="api-modal-according-display"> <div className="api-modal-according-display">
<div <div
className={classNames( className={classNames(
"h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll", "h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll"
)} )}
> >
{data?.map((node: any, i) => ( {data?.map((node: any, i) => (
@ -270,8 +275,8 @@ export default function CodeTabsComponent({
.show && .show &&
LANGFLOW_SUPPORTED_TYPES.has( LANGFLOW_SUPPORTED_TYPES.has(
node.data.node.template[templateField] node.data.node.template[templateField]
.type, .type
), )
) )
.map((templateField, indx) => { .map((templateField, indx) => {
return ( return (
@ -329,7 +334,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -375,7 +380,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -428,7 +433,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -465,7 +470,7 @@ export default function CodeTabsComponent({
e, e,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
size="small" size="small"
@ -496,7 +501,7 @@ export default function CodeTabsComponent({
].fileTypes ].fileTypes
} }
onFileChange={( onFileChange={(
value: any, value: any
) => { ) => {
node.data.node.template[ node.data.node.template[
templateField templateField
@ -549,7 +554,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -589,7 +594,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
value={ value={
@ -651,7 +656,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -697,7 +702,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -743,7 +748,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />
@ -775,8 +780,8 @@ export default function CodeTabsComponent({
].value, ].value,
type( type(
node, node,
templateField, templateField
), )
) )
} }
duplicateKey={ duplicateKey={
@ -785,15 +790,15 @@ export default function CodeTabsComponent({
onChange={(target) => { onChange={(target) => {
const valueToNumbers = const valueToNumbers =
convertValuesToNumbers( convertValuesToNumbers(
target, target
); );
node.data.node!.template[ node.data.node!.template[
templateField templateField
].value = valueToNumbers; ].value = valueToNumbers;
setErrorDuplicateKey( setErrorDuplicateKey(
hasDuplicateKeys( hasDuplicateKeys(
valueToNumbers, valueToNumbers
), )
); );
setData((old) => { setData((old) => {
let newInputList = let newInputList =
@ -810,7 +815,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
isList={ isList={
@ -860,7 +865,7 @@ export default function CodeTabsComponent({
target, target,
node.data.node.template[ node.data.node.template[
templateField templateField
], ]
); );
}} }}
/> />

View file

@ -2,7 +2,6 @@ export const convertCSVToData = (csvFile, csvSeparator: string) => {
const lines = csvFile.data.trim().split("\n"); const lines = csvFile.data.trim().split("\n");
const headers = lines[0].trim().split(csvSeparator); const headers = lines[0].trim().split(csvSeparator);
const initialRowData: any = []; const initialRowData: any = [];
const initialColDefs = headers.map((header) => ({ const initialColDefs = headers.map((header) => ({
field: header.trim(), field: header.trim(),

View file

@ -69,7 +69,7 @@ function CsvOutputComponent({
if (file) { if (file) {
const { rowData: data, colDefs: columns } = convertCSVToData( const { rowData: data, colDefs: columns } = convertCSVToData(
file, file,
separator, separator
); );
setRowData(data); setRowData(data);
setColDefs(columns); setColDefs(columns);
@ -114,14 +114,14 @@ function CsvOutputComponent({
(params: any) => { (params: any) => {
updateRowHeight(params); updateRowHeight(params);
}, },
[updateRowHeight], [updateRowHeight]
); );
const onGridSizeChanged = useCallback( const onGridSizeChanged = useCallback(
(params: any) => { (params: any) => {
updateRowHeight(params); updateRowHeight(params);
}, },
[updateRowHeight], [updateRowHeight]
); );
return ( return (

View file

@ -78,6 +78,8 @@ export default function Dropdown({
</PopoverTrigger> </PopoverTrigger>
)} )}
<PopoverContentDropdown <PopoverContentDropdown
side="bottom"
avoidCollisions={!!children}
className="nocopy nowheel nopan nodelete nodrag noundo p-0" className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={ style={
children children

View file

@ -76,7 +76,7 @@ export default function InputComponent({
editNode ? " input-edit-node " : "", editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "", password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "", password && !editNode ? "pr-10" : "",
className!, className!
)} )}
placeholder={password && editNode ? "Key" : placeholder} placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => { onChange={(e) => {
@ -104,9 +104,9 @@ export default function InputComponent({
(selectedOption !== "" || !onChange) && setSelectedOption (selectedOption !== "" || !onChange) && setSelectedOption
? selectedOption ? selectedOption
: (selectedOptions?.length !== 0 || !onChange) && : (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions setSelectedOptions
? selectedOptions?.join(", ") ? selectedOptions?.join(", ")
: value : value
} }
autoFocus={autoFocus} autoFocus={autoFocus}
disabled={disabled} disabled={disabled}
@ -134,7 +134,7 @@ export default function InputComponent({
? "pr-8" ? "pr-8"
: "", : "",
className!, className!
)} )}
placeholder={password && editNode ? "Key" : placeholder} placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => { onChange={(e) => {
@ -169,6 +169,7 @@ export default function InputComponent({
className="nocopy nopan nodelete nodrag noundo p-0" className="nocopy nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }} style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom" side="bottom"
avoidCollisions={false}
align="center" align="center"
> >
<Command <Command
@ -194,15 +195,15 @@ export default function InputComponent({
setSelectedOption( setSelectedOption(
currentValue === selectedOption currentValue === selectedOption
? "" ? ""
: currentValue, : currentValue
); );
setSelectedOptions && setSelectedOptions &&
setSelectedOptions( setSelectedOptions(
selectedOptions?.includes(currentValue) selectedOptions?.includes(currentValue)
? selectedOptions.filter( ? selectedOptions.filter(
(item) => item !== currentValue, (item) => item !== currentValue
) )
: [...selectedOptions, currentValue], : [...selectedOptions, currentValue]
); );
!setSelectedOptions && setShowOptions(false); !setSelectedOptions && setShowOptions(false);
}} }}
@ -215,7 +216,7 @@ export default function InputComponent({
selectedOption === option || selectedOption === option ||
selectedOptions?.includes(option) selectedOptions?.includes(option)
? "opacity-100" ? "opacity-100"
: "opacity-0", : "opacity-0"
)} )}
> >
<div className="absolute opacity-100 transition-all group-hover:opacity-0"> <div className="absolute opacity-100 transition-all group-hover:opacity-0">
@ -254,7 +255,7 @@ export default function InputComponent({
((selectedOptions?.length !== 0 || !onChange) && ((selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions) setSelectedOptions)
? "" ? ""
: "hidden", : "hidden"
)} )}
onClick={ onClick={
((selectedOption !== "" || !onChange) && setSelectedOption) || ((selectedOption !== "" || !onChange) && setSelectedOption) ||
@ -275,7 +276,7 @@ export default function InputComponent({
<span <span
className={cn( className={cn(
password && selectedOption === "" ? "right-8" : "right-0", password && selectedOption === "" ? "right-8" : "right-0",
"absolute inset-y-0 flex items-center pr-2.5", "absolute inset-y-0 flex items-center pr-2.5"
)} )}
> >
<button <button
@ -286,7 +287,7 @@ export default function InputComponent({
selectedOption !== "" selectedOption !== ""
? "text-medium-indigo" ? "text-medium-indigo"
: "text-muted-foreground", : "text-muted-foreground",
"hover:text-accent-foreground", "hover:text-accent-foreground"
)} )}
> >
<ForwardedIconComponent <ForwardedIconComponent
@ -306,7 +307,7 @@ export default function InputComponent({
"mb-px", "mb-px",
editNode editNode
? "input-component-true-button" ? "input-component-true-button"
: "input-component-false-button", : "input-component-false-button"
)} )}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
@ -323,7 +324,7 @@ export default function InputComponent({
className={classNames( className={classNames(
editNode editNode
? "input-component-true-svg" ? "input-component-true-svg"
: "input-component-false-svg", : "input-component-false-svg"
)} )}
> >
<path <path
@ -342,7 +343,7 @@ export default function InputComponent({
className={classNames( className={classNames(
editNode editNode
? "input-component-true-svg" ? "input-component-true-svg"
: "input-component-false-svg", : "input-component-false-svg"
)} )}
> >
<path <path

View file

@ -106,8 +106,8 @@ export default function InputFileComponent({
editNode editNode
? "input-edit-node input-dialog text-muted-foreground" ? "input-edit-node input-dialog text-muted-foreground"
: disabled : disabled
? "input-disable input-dialog primary-input" ? "input-disable input-dialog primary-input"
: "input-dialog primary-input text-muted-foreground" : "input-dialog primary-input text-muted-foreground"
} }
> >
{myValue !== "" ? myValue : "No file"} {myValue !== "" ? myValue : "No file"}

View file

@ -19,15 +19,15 @@ export default function InputGlobalComponent({
editNode = false, editNode = false,
}: InputGlobalComponentType): JSX.Element { }: InputGlobalComponentType): JSX.Element {
const globalVariablesEntries = useGlobalVariablesStore( const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries, (state) => state.globalVariablesEntries
); );
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const unavaliableFields = useGlobalVariablesStore( const unavaliableFields = useGlobalVariablesStore(
(state) => state.unavaliableFields, (state) => state.unavaliableFields
); );
const removeGlobalVariable = useGlobalVariablesStore( const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable, (state) => state.removeGlobalVariable
); );
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
@ -129,7 +129,7 @@ export default function InputGlobalComponent({
<ForwardedIconComponent <ForwardedIconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100", "h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100"
)} )}
aria-hidden="true" aria-hidden="true"
/> />

View file

@ -1,23 +1,19 @@
import { CHAT_FIRST_INITIAL_TEXT, CHAT_SECOND_INITIAL_TEXT, PDFCheckFlow, PDFLoadErrorTitle } from "../../../constants/constants"; import { PDFCheckFlow, PDFLoadErrorTitle } from "../../../constants/constants";
import IconComponent from "../../genericIconComponent"; import IconComponent from "../../genericIconComponent";
export default function Error(): JSX.Element { export default function Error(): JSX.Element {
return ( return (
<div className="flex flex-col items-center justify-center h-full w-full bg-muted"> <div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="chat-alert-box"> <div className="chat-alert-box">
<span className="flex gap-2"> <span className="flex gap-2">
<IconComponent name="FileX2" /> <IconComponent name="FileX2" />
<span className="langflow-chat-span">{PDFLoadErrorTitle}</span> <span className="langflow-chat-span">{PDFLoadErrorTitle}</span>
</span> </span>
<br /> <br />
<div className="langflow-chat-desc"> <div className="langflow-chat-desc">
<span className="langflow-chat-desc-span"> <span className="langflow-chat-desc-span">{PDFCheckFlow} </span>
{PDFCheckFlow}{" "}
</span>
</div>
</div>
</div> </div>
); </div>
</div>
);
} }

View file

@ -16,7 +16,7 @@ const TableComponent = forwardRef<
<div <div
className={cn( className={cn(
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz", dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col", "ag-theme-shadcn flex h-full flex-col"
)} // applying the grid theme )} // applying the grid theme
> >
<AgGridReact ref={ref} {...props} /> <AgGridReact ref={ref} {...props} />

View file

@ -1,5 +1,5 @@
import { AxiosRequestConfig, AxiosResponse } from "axios"; import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Edge, ReactFlowJsonObject,Node } from "reactflow"; import { Edge, Node, ReactFlowJsonObject } from "reactflow";
import { BASE_URL_API } from "../../constants/constants"; import { BASE_URL_API } from "../../constants/constants";
import { api } from "../../controllers/API/api"; import { api } from "../../controllers/API/api";
import { import {
@ -929,25 +929,29 @@ export async function getVerticesOrder(
flowId: string, flowId: string,
startNodeId?: string | null, startNodeId?: string | null,
stopNodeId?: string | null, stopNodeId?: string | null,
nodes?:Node[], nodes?: Node[],
Edges?:Edge[] Edges?: Edge[]
): Promise<AxiosResponse<VerticesOrderTypeAPI>> { ): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
// nodeId is optional and is a query parameter // nodeId is optional and is a query parameter
// if nodeId is not provided, the API will return all vertices // if nodeId is not provided, the API will return all vertices
const config:AxiosRequestConfig<any> = {}; const config: AxiosRequestConfig<any> = {};
if (stopNodeId) { if (stopNodeId) {
config["params"] = { stop_component_id: stopNodeId }; config["params"] = { stop_component_id: stopNodeId };
} else if (startNodeId) { } else if (startNodeId) {
config["params"] = { start_component_id: startNodeId }; config["params"] = { start_component_id: startNodeId };
} }
const data = { const data = {
data:{} data: {},
};
if (nodes && Edges) {
data["data"]["nodes"] = nodes;
data["data"]["edges"] = Edges;
} }
if(nodes && Edges){ return await api.post(
data["data"]["nodes"] = nodes `${BASE_URL_API}build/${flowId}/vertices`,
data["data"]["edges"] = Edges data,
} config
return await api.post(`${BASE_URL_API}build/${flowId}/vertices`,data, config); );
} }
export async function postBuildVertex( export async function postBuildVertex(

View file

@ -89,7 +89,7 @@ export default function ParameterComponent({
setNode, setNode,
renderTooltips, renderTooltips,
isLoading, isLoading,
setIsLoading, setIsLoading
); );
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass( const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
@ -98,7 +98,7 @@ export default function ParameterComponent({
takeSnapshot, takeSnapshot,
setNode, setNode,
updateNodeInternals, updateNodeInternals,
renderTooltips, renderTooltips
); );
const { handleRefreshButtonPress: handleRefreshButtonPressHook } = const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
@ -107,7 +107,7 @@ export default function ParameterComponent({
let disabled = let disabled =
edges.some( edges.some(
(edge) => (edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id)
) ?? false; ) ?? false;
const handleRefreshButtonPress = async (name, data) => { const handleRefreshButtonPress = async (name, data) => {
@ -120,12 +120,12 @@ export default function ParameterComponent({
handleUpdateValues, handleUpdateValues,
setNode, setNode,
renderTooltips, renderTooltips,
setIsLoading, setIsLoading
); );
const handleOnNewValue = async ( const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[], newValue: string | string[] | boolean | Object[],
skipSnapshot: boolean | undefined = false, skipSnapshot: boolean | undefined = false
): Promise<void> => { ): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot); handleOnNewValueHook(newValue, skipSnapshot);
}; };
@ -209,7 +209,7 @@ export default function ParameterComponent({
className={classNames( className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ", left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background", "h-3 w-3 rounded-full border-2 bg-background",
!showNode ? "mt-0" : "", !showNode ? "mt-0" : ""
)} )}
style={{ style={{
borderColor: color ?? nodeColors.unknown, borderColor: color ?? nodeColors.unknown,
@ -298,7 +298,7 @@ export default function ParameterComponent({
} }
className={classNames( className={classNames(
left ? "-ml-0.5" : "-mr-0.5", left ? "-ml-0.5" : "-mr-0.5",
"h-3 w-3 rounded-full border-2 bg-background", "h-3 w-3 rounded-full border-2 bg-background"
)} )}
style={{ borderColor: color ?? nodeColors.unknown }} style={{ borderColor: color ?? nodeColors.unknown }}
onClick={() => setFilterEdge(groupedEdge.current)} onClick={() => setFilterEdge(groupedEdge.current)}

View file

@ -303,7 +303,7 @@ export default function GenericNode({
) => { ) => {
let isInvalid = validationStatus && !validationStatus.valid; let isInvalid = validationStatus && !validationStatus.valid;
if (buildStatus === BuildStatus.INACTIVE && isInvalid) { if (buildStatus === BuildStatus.INACTIVE) {
// INACTIVE should have its own class // INACTIVE should have its own class
return "inactive-status"; return "inactive-status";
} }
@ -329,6 +329,7 @@ export default function GenericNode({
buildStatus, buildStatus,
validationStatus validationStatus
); );
const baseBorderClass = getBaseBorderClass(selected); const baseBorderClass = getBaseBorderClass(selected);
const nodeSizeClass = getNodeSizeClass(showNode); const nodeSizeClass = getNodeSizeClass(showNode);
return classNames( return classNames(
@ -383,7 +384,6 @@ export default function GenericNode({
isOutdated, isOutdated,
selected, selected,
]); ]);
return ( return (
<> <>
{memoizedNodeToolbarComponent} {memoizedNodeToolbarComponent}

View file

@ -324,7 +324,7 @@ dark:prose-invert"
</> </>
) : ( ) : (
<span <span
className="prose text-primary word-break-break-word dark:prose-invert" className="prose text-primary word-break-break-word dark:prose-invert"
data-testid={ data-testid={
"chat-message-" + chat.sender_name + "-" + chatMessage "chat-message-" + chat.sender_name + "-" + chatMessage
} }

View file

@ -34,25 +34,25 @@ export default function IOModal({
}: IOModalPropsType): JSX.Element { }: IOModalPropsType): JSX.Element {
const allNodes = useFlowStore((state) => state.nodes); const allNodes = useFlowStore((state) => state.nodes);
const inputs = useFlowStore((state) => state.inputs).filter( const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput", (input) => input.type !== "ChatInput"
); );
const chatInput = useFlowStore((state) => state.inputs).find( const chatInput = useFlowStore((state) => state.inputs).find(
(input) => input.type === "ChatInput", (input) => input.type === "ChatInput"
); );
const outputs = useFlowStore((state) => state.outputs).filter( const outputs = useFlowStore((state) => state.outputs).filter(
(output) => output.type !== "ChatOutput", (output) => output.type !== "ChatOutput"
); );
const chatOutput = useFlowStore((state) => state.outputs).find( const chatOutput = useFlowStore((state) => state.outputs).find(
(output) => output.type === "ChatOutput", (output) => output.type === "ChatOutput"
); );
const nodes = useFlowStore((state) => state.nodes).filter( const nodes = useFlowStore((state) => state.nodes).filter(
(node) => (node) =>
inputs.some((input) => input.id === node.id) || inputs.some((input) => input.id === node.id) ||
outputs.some((output) => output.id === node.id), outputs.some((output) => output.id === node.id)
); );
const haveChat = chatInput || chatOutput; const haveChat = chatInput || chatOutput;
const [selectedTab, setSelectedTab] = useState( const [selectedTab, setSelectedTab] = useState(
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0, inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0
); );
function startView() { function startView() {
@ -140,7 +140,7 @@ export default function IOModal({
{selectedTab !== 0 && ( {selectedTab !== 0 && (
<div <div
className={cn( className={cn(
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300", "mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300"
)} )}
> >
<Tabs <Tabs
@ -173,11 +173,11 @@ export default function IOModal({
</div> </div>
{nodes {nodes
.filter((node) => .filter((node) =>
inputs.some((input) => input.id === node.id), inputs.some((input) => input.id === node.id)
) )
.map((node, index) => { .map((node, index) => {
const input = inputs.find( const input = inputs.find(
(input) => input.id === node.id, (input) => input.id === node.id
)!; )!;
return ( return (
<div <div
@ -241,11 +241,11 @@ export default function IOModal({
</div> </div>
{nodes {nodes
.filter((node) => .filter((node) =>
outputs.some((output) => output.id === node.id), outputs.some((output) => output.id === node.id)
) )
.map((node, index) => { .map((node, index) => {
const output = outputs.find( const output = outputs.find(
(output) => output.id === node.id, (output) => output.id === node.id
)!; )!;
return ( return (
<div <div
@ -308,7 +308,7 @@ export default function IOModal({
<div <div
className={cn( className={cn(
"flex h-full w-full flex-col items-start gap-4 pt-4", "flex h-full w-full flex-col items-start gap-4 pt-4",
!selectedViewField ? "hidden" : "", !selectedViewField ? "hidden" : ""
)} )}
> >
<div className="font-xl flex items-center justify-center gap-3 font-semibold"> <div className="font-xl flex items-center justify-center gap-3 font-semibold">
@ -327,7 +327,7 @@ export default function IOModal({
</div> </div>
<div className="h-full w-full"> <div className="h-full w-full">
{inputs.some( {inputs.some(
(input) => input.id === selectedViewField.id, (input) => input.id === selectedViewField.id
) ? ( ) ? (
<IOFieldView <IOFieldView
type={InputOutput.INPUT} type={InputOutput.INPUT}
@ -349,7 +349,7 @@ export default function IOModal({
<div <div
className={cn( className={cn(
"flex h-full w-full", "flex h-full w-full",
selectedViewField ? "hidden" : "", selectedViewField ? "hidden" : ""
)} )}
> >
{haveChat ? ( {haveChat ? (
@ -384,7 +384,7 @@ export default function IOModal({
"h-4 w-4", "h-4 w-4",
isBuilding isBuilding
? "animate-spin" ? "animate-spin"
: "fill-current text-medium-indigo", : "fill-current text-medium-indigo"
)} )}
/> />
Run Flow Run Flow

View file

@ -6,7 +6,7 @@ export const checkCanBuildTweakObject = (element, templateField) => {
templateField.charAt(0) !== "_" && templateField.charAt(0) !== "_" &&
element.data.node.template[templateField].show && element.data.node.template[templateField].show &&
LANGFLOW_SUPPORTED_TYPES.has( LANGFLOW_SUPPORTED_TYPES.has(
element.data.node.template[templateField].type, element.data.node.template[templateField].type
) && ) &&
templateField !== "code" templateField !== "code"
); );

View file

@ -3,7 +3,7 @@ import { convertArrayToObj } from "../../../utils/reactflowUtils";
export const getChangesType = ( export const getChangesType = (
changes: string | string[] | boolean | number | Object[] | Object, changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType, template: TemplateVariableType
) => { ) => {
if (typeof changes === "string" && template.type === "float") { if (typeof changes === "string" && template.type === "float") {
changes = parseFloat(changes); changes = parseFloat(changes);

View file

@ -13,8 +13,8 @@ export const getNodesWithDefaultValue = (flow) => {
templateField.charAt(0) !== "_" && templateField.charAt(0) !== "_" &&
node.data.node.template[templateField].show && node.data.node.template[templateField].show &&
LANGFLOW_SUPPORTED_TYPES.has( LANGFLOW_SUPPORTED_TYPES.has(
node.data.node.template[templateField].type, node.data.node.template[templateField].type
), )
) )
.map((n, i) => { .map((n, i) => {
arrNodesWithValues.push(node["id"]); arrNodesWithValues.push(node["id"]);

View file

@ -5,7 +5,7 @@ export const getValue = (
value: string, value: string,
node: NodeType, node: NodeType,
template: TemplateVariableType, template: TemplateVariableType,
tweak: Object[], tweak: Object[]
) => { ) => {
let returnValue = value ?? ""; let returnValue = value ?? "";

View file

@ -37,12 +37,17 @@ const ApiModal = forwardRef(
flow: FlowType; flow: FlowType;
children: ReactNode; children: ReactNode;
}, },
ref, ref
) => { ) => {
const tweak = useTweaksStore((state) => state.tweak); let tweak = useTweaksStore((state) => state.tweak);
const addTweaks = useTweaksStore((state) => state.setTweak); const addTweaks = useTweaksStore((state) => state.setTweak);
const setTweaksList = useTweaksStore((state) => state.setTweaksList); const setTweaksList = useTweaksStore((state) => state.setTweaksList);
const tweaksList = useTweaksStore((state) => state.tweaksList); const tweaksList = useTweaksStore((state) => state.tweaksList);
// ! Workdaround to fix the issue with the tweak object being an array
// TODO: Fix the issue with the tweak object being an array
if (Array.isArray(tweak) && tweak.length === 1) {
tweak = tweak[0];
}
const [activeTweaks, setActiveTweaks] = useState(false); const [activeTweaks, setActiveTweaks] = useState(false);
const { autoLogin } = useContext(AuthContext); const { autoLogin } = useContext(AuthContext);
@ -108,7 +113,7 @@ const ApiModal = forwardRef(
buildTweakObject( buildTweakObject(
nodeId, nodeId,
element.data.node.template[templateField].value, element.data.node.template[templateField].value,
element.data.node.template[templateField], element.data.node.template[templateField]
); );
} }
}); });
@ -125,7 +130,7 @@ const ApiModal = forwardRef(
async function buildTweakObject( async function buildTweakObject(
tw: string, tw: string,
changes: string | string[] | boolean | number | Object[] | Object, changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType, template: TemplateVariableType
) { ) {
changes = getChangesType(changes, template); changes = getChangesType(changes, template);
@ -162,6 +167,12 @@ const ApiModal = forwardRef(
} }
const addCodes = (cloneTweak) => { const addCodes = (cloneTweak) => {
// if cloneTweak is an array and it's lenght is 1, then it's a single tweak
// so just get the first element
if (Array.isArray(cloneTweak) && cloneTweak.length === 1) {
cloneTweak = cloneTweak[0];
}
const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, cloneTweak); const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, cloneTweak);
const curl_code = getCurlCode(flow?.id, autoLogin, cloneTweak); const curl_code = getCurlCode(flow?.id, autoLogin, cloneTweak);
const pythonCode = getPythonCode(flow?.name, cloneTweak); const pythonCode = getPythonCode(flow?.name, cloneTweak);
@ -201,11 +212,12 @@ const ApiModal = forwardRef(
}} }}
activeTweaks={activeTweaks} activeTweaks={activeTweaks}
setActiveTweaks={setActiveTweaks} setActiveTweaks={setActiveTweaks}
allowExport
/> />
</BaseModal.Content> </BaseModal.Content>
</BaseModal> </BaseModal>
); );
}, }
); );
export default ApiModal; export default ApiModal;

View file

@ -63,7 +63,7 @@ interface BaseModalProps {
React.ReactElement<ContentProps>, React.ReactElement<ContentProps>,
React.ReactElement<HeaderProps>, React.ReactElement<HeaderProps>,
React.ReactElement<TriggerProps>?, React.ReactElement<TriggerProps>?,
React.ReactElement<FooterProps>?, React.ReactElement<FooterProps>?
]; ];
open?: boolean; open?: boolean;
setOpen?: (open: boolean) => void; setOpen?: (open: boolean) => void;
@ -95,16 +95,16 @@ function BaseModal({
type = "dialog", type = "dialog",
}: BaseModalProps) { }: BaseModalProps) {
const headerChild = React.Children.toArray(children).find( const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header, (child) => (child as React.ReactElement).type === Header
); );
const triggerChild = React.Children.toArray(children).find( const triggerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Trigger, (child) => (child as React.ReactElement).type === Trigger
); );
const ContentChild = React.Children.toArray(children).find( const ContentChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Content, (child) => (child as React.ReactElement).type === Content
); );
const ContentFooter = React.Children.toArray(children).find( const ContentFooter = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Footer, (child) => (child as React.ReactElement).type === Footer
); );
let minWidth: string; let minWidth: string;

View file

@ -51,19 +51,19 @@ export default function Page({
}): JSX.Element { }): JSX.Element {
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const autoSaveCurrentFlow = useFlowsManagerStore( const autoSaveCurrentFlow = useFlowsManagerStore(
(state) => state.autoSaveCurrentFlow, (state) => state.autoSaveCurrentFlow
); );
const types = useTypesStore((state) => state.types); const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates); const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge); const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const reactFlowWrapper = useRef<HTMLDivElement>(null); const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [showCanvas, setSHowCanvas] = useState( const [showCanvas, setSHowCanvas] = useState(
Object.keys(templates).length > 0 && Object.keys(types).length > 0, Object.keys(templates).length > 0 && Object.keys(types).length > 0
); );
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const setReactFlowInstance = useFlowStore( const setReactFlowInstance = useFlowStore(
(state) => state.setReactFlowInstance, (state) => state.setReactFlowInstance
); );
const nodes = useFlowStore((state) => state.nodes); const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges); const edges = useFlowStore((state) => state.edges);
@ -80,10 +80,10 @@ export default function Page({
const paste = useFlowStore((state) => state.paste); const paste = useFlowStore((state) => state.paste);
const resetFlow = useFlowStore((state) => state.resetFlow); const resetFlow = useFlowStore((state) => state.resetFlow);
const lastCopiedSelection = useFlowStore( const lastCopiedSelection = useFlowStore(
(state) => state.lastCopiedSelection, (state) => state.lastCopiedSelection
); );
const setLastCopiedSelection = useFlowStore( const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection, (state) => state.setLastCopiedSelection
); );
const onConnect = useFlowStore((state) => state.onConnect); const onConnect = useFlowStore((state) => state.onConnect);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
@ -106,7 +106,7 @@ export default function Page({
clonedSelection!, clonedSelection!,
clonedNodes, clonedNodes,
clonedEdges, clonedEdges,
getRandomName(), getRandomName()
); );
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId); const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
const newEdges = reconnectEdges(newGroupNode, removedEdges); const newEdges = reconnectEdges(newGroupNode, removedEdges);
@ -114,8 +114,8 @@ export default function Page({
...clonedNodes.filter( ...clonedNodes.filter(
(oldNodes) => (oldNodes) =>
!clonedSelection?.nodes.some( !clonedSelection?.nodes.some(
(selectionNode) => selectionNode.id === oldNodes.id, (selectionNode) => selectionNode.id === oldNodes.id
), )
), ),
newGroupNode, newGroupNode,
]); ]);
@ -125,8 +125,8 @@ export default function Page({
!clonedSelection!.nodes.some( !clonedSelection!.nodes.some(
(selectionNode) => (selectionNode) =>
selectionNode.id === oldEdge.target || selectionNode.id === oldEdge.target ||
selectionNode.id === oldEdge.source, selectionNode.id === oldEdge.source
), )
), ),
...newEdges, ...newEdges,
]); ]);
@ -179,7 +179,7 @@ export default function Page({
{ {
x: position.current.x, x: position.current.x,
y: position.current.y, y: position.current.y,
}, }
); );
} }
if (!isWrappedWithClass(event, "noundo")) { if (!isWrappedWithClass(event, "noundo")) {
@ -275,7 +275,7 @@ export default function Page({
useEffect(() => { useEffect(() => {
setSHowCanvas( setSHowCanvas(
Object.keys(templates).length > 0 && Object.keys(types).length > 0, Object.keys(templates).length > 0 && Object.keys(types).length > 0
); );
}, [templates, types]); }, [templates, types]);
@ -284,7 +284,7 @@ export default function Page({
takeSnapshot(); takeSnapshot();
onConnect(params); onConnect(params);
}, },
[takeSnapshot, onConnect], [takeSnapshot, onConnect]
); );
const onNodeDragStart: NodeDragHandler = useCallback(() => { const onNodeDragStart: NodeDragHandler = useCallback(() => {
@ -325,7 +325,7 @@ export default function Page({
// Extract the data from the drag event and parse it as a JSON object // Extract the data from the drag event and parse it as a JSON object
const data: { type: string; node?: APIClassType } = JSON.parse( const data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("nodedata"), event.dataTransfer.getData("nodedata")
); );
const newId = getNodeId(data.type); const newId = getNodeId(data.type);
@ -341,7 +341,7 @@ export default function Page({
}; };
paste( paste(
{ nodes: [newNode], edges: [] }, { nodes: [newNode], edges: [] },
{ x: event.clientX, y: event.clientY }, { x: event.clientX, y: event.clientY }
); );
} else if (event.dataTransfer.types.some((types) => types === "Files")) { } else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot(); takeSnapshot();
@ -370,7 +370,7 @@ export default function Page({
} }
}, },
// Specify dependencies for useCallback // Specify dependencies for useCallback
[getNodeId, setNodes, takeSnapshot, paste], [getNodeId, setNodes, takeSnapshot, paste]
); );
const onEdgeUpdateStart = useCallback(() => { const onEdgeUpdateStart = useCallback(() => {
@ -386,7 +386,7 @@ export default function Page({
setEdges((els) => updateEdge(oldEdge, newConnection, els)); setEdges((els) => updateEdge(oldEdge, newConnection, els));
} }
}, },
[setEdges], [setEdges]
); );
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => { const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
@ -419,7 +419,7 @@ export default function Page({
(flow: OnSelectionChangeParams): void => { (flow: OnSelectionChangeParams): void => {
setLastSelection(flow); setLastSelection(flow);
}, },
[], []
); );
const onPaneClick = useCallback((flow) => { const onPaneClick = useCallback((flow) => {

View file

@ -1,8 +1,8 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { NodeToolbar } from "reactflow"; import { NodeToolbar } from "reactflow";
import { GradientGroup } from "../../../../icons/GradientSparkles"; import { GradientGroup } from "../../../../icons/GradientSparkles";
import { validateSelection } from "../../../../utils/reactflowUtils";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import { validateSelection } from "../../../../utils/reactflowUtils";
export default function SelectionMenu({ export default function SelectionMenu({
onClick, onClick,
nodes, nodes,
@ -13,7 +13,7 @@ export default function SelectionMenu({
const [disable, setDisable] = useState<boolean>( const [disable, setDisable] = useState<boolean>(
lastSelection && edges.length > 0 lastSelection && edges.length > 0
? validateSelection(lastSelection!, edges).length > 0 ? validateSelection(lastSelection!, edges).length > 0
: false, : false
); );
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false); const [isTransitioning, setIsTransitioning] = useState(false);
@ -62,7 +62,11 @@ export default function SelectionMenu({
} }
> >
<button <button
className={`${disable ? "flex h-full w-full cursor-not-allowed items-center justify-between text-sm text-muted-foreground" : "flex h-full w-full items-center justify-between text-sm hover:text-indigo-500"}`} className={`${
disable
? "flex h-full w-full cursor-not-allowed items-center justify-between text-sm text-muted-foreground"
: "flex h-full w-full items-center justify-between text-sm hover:text-indigo-500"
}`}
onClick={onClick} onClick={onClick}
disabled={disable} disabled={disable}
> >

View file

@ -58,7 +58,7 @@ export default function NodeToolbarComponent({
data.node.template[templateField].type === "Any" || data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int" || data.node.template[templateField].type === "int" ||
data.node.template[templateField].type === "dict" || data.node.template[templateField].type === "dict" ||
data.node.template[templateField].type === "NestedDict"), data.node.template[templateField].type === "NestedDict")
).length; ).length;
const templates = useTypesStore((state) => state.templates); const templates = useTypesStore((state) => state.templates);
const hasStore = useStoreStore((state) => state.hasStore); const hasStore = useStoreStore((state) => state.hasStore);
@ -85,7 +85,7 @@ export default function NodeToolbarComponent({
const [showconfirmShare, setShowconfirmShare] = useState(false); const [showconfirmShare, setShowconfirmShare] = useState(false);
const [showOverrideModal, setShowOverrideModal] = useState(false); const [showOverrideModal, setShowOverrideModal] = useState(false);
const [flowComponent, setFlowComponent] = useState<FlowType>( const [flowComponent, setFlowComponent] = useState<FlowType>(
createFlowComponent(cloneDeep(data), version), createFlowComponent(cloneDeep(data), version)
); );
const openInNewTab = (url) => { const openInNewTab = (url) => {
@ -100,7 +100,7 @@ export default function NodeToolbarComponent({
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const setLastCopiedSelection = useFlowStore( const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection, (state) => state.setLastCopiedSelection
); );
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -150,7 +150,7 @@ export default function NodeToolbarComponent({
nodes, nodes,
edges, edges,
setNodes, setNodes,
setEdges, setEdges
); );
break; break;
case "override": case "override":
@ -174,7 +174,7 @@ export default function NodeToolbarComponent({
y: 10, y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position.x, paneX: nodes.find((node) => node.id === data.id)?.position.x,
paneY: nodes.find((node) => node.id === data.id)?.position.y, paneY: nodes.find((node) => node.id === data.id)?.position.y,
}, }
); );
break; break;
case "update": case "update":
@ -212,13 +212,13 @@ export default function NodeToolbarComponent({
}; };
const isSaved = flows.some((flow) => const isSaved = flows.some((flow) =>
Object.values(flow).includes(data.node?.display_name!), Object.values(flow).includes(data.node?.display_name!)
); );
const setNode = useFlowStore((state) => state.setNode); const setNode = useFlowStore((state) => state.setNode);
const handleOnNewValue = ( const handleOnNewValue = (
newValue: string | string[] | boolean | Object[], newValue: string | string[] | boolean | Object[]
): void => { ): void => {
if (data.node!.template[name].value !== newValue) { if (data.node!.template[name].value !== newValue) {
takeSnapshot(); takeSnapshot();
@ -393,7 +393,7 @@ export default function NodeToolbarComponent({
data-testid="save-button-modal" data-testid="save-button-modal"
className={classNames( className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", "relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
hasCode ? " " : " rounded-l-md ", hasCode ? " " : " rounded-l-md "
)} )}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
@ -411,7 +411,7 @@ export default function NodeToolbarComponent({
<button <button
data-testid="duplicate-button-modal" data-testid="duplicate-button-modal"
className={classNames( className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", "relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)} )}
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
@ -459,7 +459,7 @@ export default function NodeToolbarComponent({
<div <div
data-testid="more-options-modal" data-testid="more-options-modal"
className={classNames( className={classNames(
"relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", "relative -ml-px inline-flex h-8 w-[31px] items-center rounded-r-md bg-background text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)} )}
> >
<IconComponent <IconComponent

View file

@ -3,10 +3,10 @@ import { useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent"; import FlowToolbar from "../../components/chatComponent";
import Header from "../../components/headerComponent"; import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent"; import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent"; import ExtraSidebar from "./components/extraSidebarComponent";
import useFlowStore from "../../stores/flowStore";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element { export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(

View file

@ -1,64 +1,69 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { getComponent } from "../../controllers/API";
import cloneFLowWithParent from "../../utils/storeUtils";
import LoadingComponent from "../../components/loadingComponent"; import LoadingComponent from "../../components/loadingComponent";
import useFlowStore from "../../stores/flowStore"; import { getComponent } from "../../controllers/API";
import IOModal from "../../modals/IOModal"; import IOModal from "../../modals/IOModal";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import cloneFLowWithParent from "../../utils/storeUtils";
export default function PlaygroundPage() { export default function PlaygroundPage() {
const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const getFlowById = useFlowsManagerStore((state) => state.getFlowById); const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); const setCurrentFlowId = useFlowsManagerStore(
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); (state) => state.setCurrentFlowId
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); );
const setNodes = useFlowStore((state) => state.setNodes); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const setEdges = useFlowStore((state) => state.setEdges); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool); const setNodes = useFlowStore((state) => state.setNodes);
const setEdges = useFlowStore((state) => state.setEdges);
const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
const { id } = useParams(); const { id } = useParams();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
async function getFlowData() { async function getFlowData() {
const res = await getComponent(id!); const res = await getComponent(id!);
const newFlow = cloneFLowWithParent(res, res.id, false, true); const newFlow = cloneFLowWithParent(res, res.id, false, true);
return newFlow; return newFlow;
}
// Set flow tab id
useEffect(() => {
console.log("id", id);
if (getFlowById(id!)) {
setCurrentFlowId(id!);
} else {
getFlowData().then((flow) => {
setCurrentFlow(flow);
});
} }
}, [id]);
// Set flow tab id useEffect(() => {
useEffect(() => { if (currentFlow) {
console.log("id", id); setNodes(currentFlow?.data?.nodes ?? [], true);
if (getFlowById(id!)) { setEdges(currentFlow?.data?.edges ?? [], true);
setCurrentFlowId(id!); cleanFlowPool();
} setLoading(false);
else { }
getFlowData().then((flow) => { return () => {
setCurrentFlow(flow); setNodes([], true);
}); setEdges([], true);
} cleanFlowPool();
}, [id]); };
}, [currentFlow]);
useEffect(() => { return (
if (currentFlow) { <div className="flex h-full w-full flex-col items-center justify-center align-middle">
setNodes(currentFlow?.data?.nodes ?? [], true); {loading ? (
setEdges(currentFlow?.data?.edges ?? [], true); <div>
cleanFlowPool(); <LoadingComponent remSize={24}></LoadingComponent>
setLoading(false);
}
return () => {
setNodes([], true);
setEdges([], true);
cleanFlowPool();
};
}, [currentFlow]);
return (
<div className="w-full h-full flex flex-col align-middle items-center justify-center">
{loading ? <div><LoadingComponent remSize={24}></LoadingComponent></div> :
<IOModal open={true}setOpen={()=>{}} isPlayground>
<></>
</IOModal>}
</div> </div>
) ) : (
<IOModal open={true} setOpen={() => {}} isPlayground>
<></>
</IOModal>
)}
</div>
);
} }

View file

@ -113,110 +113,109 @@ export default function GeneralPage() {
</div> </div>
<div className="grid gap-6"> <div className="grid gap-6">
<Form.Root
<Form.Root onSubmit={(event) => {
onSubmit={(event) => { handlePatchGradient();
handlePatchGradient(); event.preventDefault();
event.preventDefault(); }}
}} >
> <Card x-chunk="dashboard-04-chunk-1">
<Card x-chunk="dashboard-04-chunk-1"> <CardHeader>
<CardHeader> <CardTitle>Profile Gradient</CardTitle>
<CardTitle>Profile Gradient</CardTitle> <CardDescription>
<CardDescription> Choose the gradient that appears as your profile picture.
Choose the gradient that appears as your profile picture. </CardDescription>
</CardDescription> </CardHeader>
</CardHeader> <CardContent>
<CardContent> <div className="py-2">
<div className="py-2"> <GradientChooserComponent
<GradientChooserComponent value={
value={ gradient == ""
gradient == "" ? userData?.profile_image ??
? userData?.profile_image ?? gradients[
gradients[ parseInt(userData?.id ?? "", 30) % gradients.length
parseInt(userData?.id ?? "", 30) % ]
gradients.length : gradient
] }
: gradient onChange={(value) => {
} handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
{!autoLogin && (
<Form.Root
onSubmit={(event) => {
handlePatchPassword();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Type your new password and confirm it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full gap-4">
<Form.Field name="password" className="w-full">
<InputComponent
id="pasword"
onChange={(value) => { onChange={(value) => {
handleInput({ target: { name: "gradient", value } }); handleInput({ target: { name: "password", value } });
}} }}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message
match="valueMissing"
className="field-invalid"
>
Please enter your password
</Form.Message>
</Form.Field>
<Form.Field name="cnfPassword" className="w-full">
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({
target: { name: "cnfPassword", value },
});
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/> />
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>{!autoLogin && (
<Form.Root
onSubmit={(event) => {
handlePatchPassword();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Type your new password and confirm it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full gap-4">
<Form.Field name="password" className="w-full">
<InputComponent
id="pasword"
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message
match="valueMissing"
className="field-invalid"
>
Please enter your password
</Form.Message>
</Form.Field>
<Form.Field name="cnfPassword" className="w-full">
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({
target: { name: "cnfPassword", value },
});
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/>
<Form.Message <Form.Message
className="field-invalid" className="field-invalid"
match="valueMissing" match="valueMissing"
> >
Please confirm your password Please confirm your password
</Form.Message> </Form.Message>
</Form.Field> </Form.Field>
</div> </div>
</CardContent> </CardContent>
<CardFooter className="border-t px-6 py-4"> <CardFooter className="border-t px-6 py-4">
<Form.Submit asChild> <Form.Submit asChild>
<Button type="submit">Save</Button> <Button type="submit">Save</Button>
</Form.Submit> </Form.Submit>
</CardFooter> </CardFooter>
</Card> </Card>
</Form.Root> </Form.Root>
)} )}
</div> </div>
</div> </div>

View file

@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils";
export default function GlobalVariablesPage() { export default function GlobalVariablesPage() {
const globalVariablesEntries = useGlobalVariablesStore( const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries, (state) => state.globalVariablesEntries
); );
const removeGlobalVariable = useGlobalVariablesStore( const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable, (state) => state.removeGlobalVariable
); );
const globalVariables = useGlobalVariablesStore( const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables, (state) => state.globalVariables
); );
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
@ -156,7 +156,7 @@ export default function GlobalVariablesPage() {
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5 text-destructive group-disabled:text-primary", "h-5 w-5 text-destructive group-disabled:text-primary"
)} )}
/> />
</Button> </Button>
@ -176,7 +176,7 @@ export default function GlobalVariablesPage() {
overlayNoRowsTemplate="No data available" overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => { onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows( setSelectedRows(
event.api.getSelectedRows().map((row) => row.name), event.api.getSelectedRows().map((row) => row.name)
); );
}} }}
rowSelection="multiple" rowSelection="multiple"

View file

@ -44,7 +44,6 @@ import { getInputsAndOutputs } from "../utils/storeUtils";
import useAlertStore from "./alertStore"; import useAlertStore from "./alertStore";
import { useDarkStore } from "./darkStore"; import { useDarkStore } from "./darkStore";
import useFlowsManagerStore from "./flowsManagerStore"; import useFlowsManagerStore from "./flowsManagerStore";
import FlowPage from "../pages/FlowPage";
// this is our useStore hook that we can use in our components to get parts of the store and call actions // this is our useStore hook that we can use in our components to get parts of the store and call actions
const useFlowStore = create<FlowStoreType>((set, get) => ({ const useFlowStore = create<FlowStoreType>((set, get) => ({
@ -80,7 +79,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
updateFlowPool: ( updateFlowPool: (
nodeId: string, nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType, data: FlowPoolObjectType | ChatOutputType | chatInputType,
buildId?: string, buildId?: string
) => { ) => {
let newFlowPool = cloneDeep({ ...get().flowPool }); let newFlowPool = cloneDeep({ ...get().flowPool });
if (!newFlowPool[nodeId]) { if (!newFlowPool[nodeId]) {
@ -171,7 +170,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
flowsManager.autoSaveCurrentFlow( flowsManager.autoSaveCurrentFlow(
newChange, newChange,
newEdges, newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
} }
}, },
@ -187,7 +186,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
flowsManager.autoSaveCurrentFlow( flowsManager.autoSaveCurrentFlow(
get().nodes, get().nodes,
newChange, newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
} }
}, },
@ -205,7 +204,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
return newChange; return newChange;
} }
return node; return node;
}), })
); );
}, },
getNode: (id: string) => { getNode: (id: string) => {
@ -216,8 +215,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().nodes.filter((node) => get().nodes.filter((node) =>
typeof nodeId === "string" typeof nodeId === "string"
? node.id !== nodeId ? node.id !== nodeId
: !nodeId.includes(node.id), : !nodeId.includes(node.id)
), )
); );
}, },
deleteEdge: (edgeId) => { deleteEdge: (edgeId) => {
@ -225,8 +224,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().edges.filter((edge) => get().edges.filter((edge) =>
typeof edgeId === "string" typeof edgeId === "string"
? edge.id !== edgeId ? edge.id !== edgeId
: !edgeId.includes(edge.id), : !edgeId.includes(edge.id)
), )
); );
}, },
paste: (selection, position) => { paste: (selection, position) => {
@ -292,7 +291,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
let source = idsMap[edge.source]; let source = idsMap[edge.source];
let target = idsMap[edge.target]; let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse( const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!, edge.sourceHandle!
); );
let sourceHandle = scapedJSONStringfy({ let sourceHandle = scapedJSONStringfy({
...sourceHandleObject, ...sourceHandleObject,
@ -302,7 +301,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
edge.data.sourceHandle = sourceHandleObject; edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!, edge.targetHandle!
); );
let targetHandle = scapedJSONStringfy({ let targetHandle = scapedJSONStringfy({
...targetHandleObject, ...targetHandleObject,
@ -323,7 +322,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
className: "stroke-gray-900 ", className: "stroke-gray-900 ",
selected: false, selected: false,
}, },
newEdges.map((edge) => ({ ...edge, selected: false })), newEdges.map((edge) => ({ ...edge, selected: false }))
); );
}); });
get().setEdges(newEdges); get().setEdges(newEdges);
@ -342,10 +341,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
const newNodes = get().nodes.filter( const newNodes = get().nodes.filter(
(node) => !nodesIdsSelected.includes(node.id), (node) => !nodesIdsSelected.includes(node.id)
); );
const newEdges = get().edges.filter( const newEdges = get().edges.filter(
(edge) => !edgesIdsSelected.includes(edge.id), (edge) => !edgesIdsSelected.includes(edge.id)
); );
set({ nodes: newNodes, edges: newEdges }); set({ nodes: newNodes, edges: newEdges });
@ -403,7 +402,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
style: { stroke: "#555" }, style: { stroke: "#555" },
className: "stroke-foreground stroke-connection", className: "stroke-foreground stroke-connection",
}, },
oldEdges, oldEdges
); );
return newEdges; return newEdges;
@ -413,7 +412,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
.autoSaveCurrentFlow( .autoSaveCurrentFlow(
get().nodes, get().nodes,
newEdges, newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
}, },
unselectAll: () => { unselectAll: () => {
@ -444,7 +443,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function validateSubgraph(nodes: string[]) { function validateSubgraph(nodes: string[]) {
const errorsObjs = validateNodes( const errorsObjs = validateNodes(
get().nodes.filter((node) => nodes.includes(node.id)), get().nodes.filter((node) => nodes.includes(node.id)),
get().edges, get().edges
); );
const errors = errorsObjs.map((obj) => obj.errors).flat(); const errors = errorsObjs.map((obj) => obj.errors).flat();
@ -463,10 +462,15 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function handleBuildUpdate( function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI, vertexBuildData: VertexBuildTypeAPI,
status: BuildStatus, status: BuildStatus,
runId: string, runId: string
) { ) {
console.log("handleBuildUpdate", vertexBuildData, status, runId);
if (vertexBuildData && vertexBuildData.inactivated_vertices) { if (vertexBuildData && vertexBuildData.inactivated_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices); get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
get().updateBuildStatus(
vertexBuildData.inactivated_vertices,
BuildStatus.INACTIVE
);
} }
if (vertexBuildData.next_vertices_ids) { if (vertexBuildData.next_vertices_ids) {
@ -481,11 +485,14 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// next_vertices_ids should be next_vertices_ids without the inactivated vertices // next_vertices_ids should be next_vertices_ids without the inactivated vertices
const next_vertices_ids = vertexBuildData.next_vertices_ids.filter( const next_vertices_ids = vertexBuildData.next_vertices_ids.filter(
(id) => !vertexBuildData.inactivated_vertices?.includes(id), (id) => !vertexBuildData.inactivated_vertices?.includes(id)
);
const top_level_vertices = vertexBuildData.top_level_vertices.filter(
(vertex) => !vertexBuildData.inactivated_vertices?.includes(vertex.id)
); );
const nextVertices: VertexLayerElementType[] = zip( const nextVertices: VertexLayerElementType[] = zip(
next_vertices_ids, next_vertices_ids,
vertexBuildData.top_level_vertices, top_level_vertices
).map(([id, reference]) => ({ id: id!, reference })); ).map(([id, reference]) => ({ id: id!, reference }));
const newLayers = [ const newLayers = [
@ -502,15 +509,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
runId: runId, runId: runId,
verticesToRun: get().verticesBuild!.verticesToRun, verticesToRun: get().verticesBuild!.verticesToRun,
}); });
get().updateBuildStatus( get().updateBuildStatus(top_level_vertices, BuildStatus.TO_BUILD);
vertexBuildData.top_level_vertices,
BuildStatus.TO_BUILD,
);
} }
get().addDataToFlowPool( get().addDataToFlowPool(
{ ...vertexBuildData, buildId: runId }, { ...vertexBuildData, buildId: runId },
vertexBuildData.id, vertexBuildData.id
); );
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status); useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
@ -519,7 +523,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
const newFlowBuildStatus = { ...get().flowBuildStatus }; const newFlowBuildStatus = { ...get().flowBuildStatus };
// filter out the vertices that are not status // filter out the vertices that are not status
const verticesToUpdate = verticesIds?.filter( const verticesToUpdate = verticesIds?.filter(
(id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT, (id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT
); );
if (verticesToUpdate) { if (verticesToUpdate) {
@ -584,7 +588,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
verticesLayers: VertexLayerElementType[][]; verticesLayers: VertexLayerElementType[][];
runId: string; runId: string;
verticesToRun: string[]; verticesToRun: string[];
} | null, } | null
) => { ) => {
set({ verticesBuild: vertices }); set({ verticesBuild: vertices });
}, },
@ -609,7 +613,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// that are going to be built // that are going to be built
verticesIds: get().verticesBuild!.verticesIds.filter( verticesIds: get().verticesBuild!.verticesIds.filter(
// keep the vertices that are not in the list of vertices to remove // keep the vertices that are not in the list of vertices to remove
(vertex) => !vertices.includes(vertex), (vertex) => !vertices.includes(vertex)
), ),
}, },
}); });

View file

@ -52,7 +52,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
currentFlow: flow, currentFlow: flow,
currentFlowId: flow.id, currentFlowId: flow.id,
})); }));
}, },
getFlowById: (id: string) => { getFlowById: (id: string) => {
return get().flows.find((flow) => flow.id === id); return get().flows.find((flow) => flow.id === id);

View file

@ -512,7 +512,7 @@ export type nodeToolbarPropsType = {
updateNodeCode?: ( updateNodeCode?: (
newNodeClass: APIClassType, newNodeClass: APIClassType,
code: string, code: string,
name: string, name: string
) => void; ) => void;
setShowState: (show: boolean | SetStateAction<boolean>) => void; setShowState: (show: boolean | SetStateAction<boolean>) => void;
isOutdated?: boolean; isOutdated?: boolean;
@ -562,7 +562,7 @@ export type chatMessagePropsType = {
updateChat: ( updateChat: (
chat: ChatMessageType, chat: ChatMessageType,
message: string, message: string,
stream_url?: string, stream_url?: string
) => void; ) => void;
}; };
@ -654,16 +654,17 @@ export type codeTabsPropsType = {
value: string, value: string,
node: NodeType, node: NodeType,
template: TemplateVariableType, template: TemplateVariableType,
tweak: tweakType, tweak: tweakType
) => string; ) => string;
buildTweakObject?: ( buildTweakObject?: (
tw: string, tw: string,
changes: string | string[] | boolean | number | Object[] | Object, changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType, template: TemplateVariableType
) => Promise<string | void>; ) => Promise<string | void>;
}; };
activeTweaks?: boolean; activeTweaks?: boolean;
setActiveTweaks?: (value: boolean) => void; setActiveTweaks?: (value: boolean) => void;
allowExport?: boolean;
}; };
export type crashComponentPropsType = { export type crashComponentPropsType = {

View file

@ -76,8 +76,14 @@ export type FlowStoreType = {
edges: Edge[]; edges: Edge[];
onNodesChange: OnNodesChange; onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange; onEdgesChange: OnEdgesChange;
setNodes: (update: Node[] | ((oldState: Node[]) => Node[]),skipSave?:boolean) => void; setNodes: (
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[]),skipSave?:boolean) => void; update: Node[] | ((oldState: Node[]) => Node[]),
skipSave?: boolean
) => void;
setEdges: (
update: Edge[] | ((oldState: Edge[]) => Edge[]),
skipSave?: boolean
) => void;
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
getNode: (id: string) => Node | undefined; getNode: (id: string) => Node | undefined;
deleteNode: (nodeId: string | Array<string>) => void; deleteNode: (nodeId: string | Array<string>) => void;

View file

@ -25,7 +25,7 @@ export type GlobalVariablesStore = {
) => void; ) => void;
removeGlobalVariable: (name: string) => Promise<void>; removeGlobalVariable: (name: string) => Promise<void>;
getVariableId: (name: string) => string | undefined; getVariableId: (name: string) => string | undefined;
unavaliableFields: {[name: string]: string}; unavaliableFields: { [name: string]: string };
setUnavaliableFields: (fields: {[name: string]: string}) => void; setUnavaliableFields: (fields: { [name: string]: string }) => void;
removeUnavaliableField: (field: string) => void; removeUnavaliableField: (field: string) => void;
}; };

View file

@ -21,7 +21,7 @@ export const handleUpdateValues = async (name: string, data: NodeDataType) => {
code, code,
template, template,
name, name,
data.node?.template[name]?.value, data.node?.template[name]?.value
); );
if (res.status === 200 && data.node?.template) { if (res.status === 200 && data.node?.template) {
return res.data.template; return res.data.template;
@ -34,5 +34,5 @@ export const handleUpdateValues = async (name: string, data: NodeDataType) => {
export const debouncedHandleUpdateValues = debounce( export const debouncedHandleUpdateValues = debounce(
handleUpdateValues, handleUpdateValues,
SAVE_DEBOUNCE_TIME, SAVE_DEBOUNCE_TIME
); );

View file

@ -102,18 +102,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
export function isValidConnection( export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection, { source, target, sourceHandle, targetHandle }: Connection,
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[]
) { ) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!); const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!); const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
if ( if (
targetHandleObject.inputTypes?.some( targetHandleObject.inputTypes?.some(
(n) => n === sourceHandleObject.dataType, (n) => n === sourceHandleObject.dataType
) || ) ||
sourceHandleObject.baseClasses.some( sourceHandleObject.baseClasses.some(
(t) => (t) =>
targetHandleObject.inputTypes?.some((n) => n === t) || targetHandleObject.inputTypes?.some((n) => n === t) ||
t === targetHandleObject.type, t === targetHandleObject.type
) )
) { ) {
let targetNode = nodes.find((node) => node.id === target!)?.data?.node; let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
@ -146,7 +146,7 @@ export function removeApiKeys(flow: FlowType): FlowType {
export function updateTemplate( export function updateTemplate(
reference: APITemplateType, reference: APITemplateType,
objectToUpdate: APITemplateType, objectToUpdate: APITemplateType
): APITemplateType { ): APITemplateType {
let clonedObject: APITemplateType = cloneDeep(reference); let clonedObject: APITemplateType = cloneDeep(reference);
@ -206,7 +206,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
export function updateIds( export function updateIds(
{ edges, nodes }: { edges: Edge[]; nodes: Node[] }, { edges, nodes }: { edges: Edge[]; nodes: Node[] },
selection?: { edges: Edge[]; nodes: Node[] }, selection?: { edges: Edge[]; nodes: Node[] }
) { ) {
let idsMap = {}; let idsMap = {};
const selectionIds = selection?.nodes.map((n) => n.id); const selectionIds = selection?.nodes.map((n) => n.id);
@ -234,7 +234,7 @@ export function updateIds(
edge.source = idsMap[edge.source]; edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target]; edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse( const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!, edge.sourceHandle!
); );
edge.sourceHandle = scapedJSONStringfy({ edge.sourceHandle = scapedJSONStringfy({
...sourceHandleObject, ...sourceHandleObject,
@ -244,7 +244,7 @@ export function updateIds(
edge.data.sourceHandle.id = edge.source; edge.data.sourceHandle.id = edge.source;
} }
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!, edge.targetHandle!
); );
edge.targetHandle = scapedJSONStringfy({ edge.targetHandle = scapedJSONStringfy({
...targetHandleObject, ...targetHandleObject,
@ -290,11 +290,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName === (scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
t && t &&
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id === (scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id, node.id
) )
) { ) {
errors.push( errors.push(
`${displayName || type} is missing ${getFieldTitle(template, t)}.`, `${displayName || type} is missing ${getFieldTitle(template, t)}.`
); );
} else if ( } else if (
template[t].type === "dict" && template[t].type === "dict" &&
@ -308,15 +308,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
errors.push( errors.push(
`${displayName || type} (${getFieldTitle( `${displayName || type} (${getFieldTitle(
template, template,
t, t
)}) contains duplicate keys with the same values.`, )}) contains duplicate keys with the same values.`
); );
if (hasEmptyKey(template[t].value)) if (hasEmptyKey(template[t].value))
errors.push( errors.push(
`${displayName || type} (${getFieldTitle( `${displayName || type} (${getFieldTitle(
template, template,
t, t
)}) field must not be empty.`, )}) field must not be empty.`
); );
} }
return errors; return errors;
@ -325,7 +325,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
export function validateNodes( export function validateNodes(
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[]
): // this returns an array of tuples with the node id and the errors ): // this returns an array of tuples with the node id and the errors
Array<{ id: string; errors: Array<string> }> { Array<{ id: string; errors: Array<string> }> {
if (nodes.length === 0) { if (nodes.length === 0) {
@ -346,7 +346,7 @@ export function updateEdges(edges: Edge[]) {
if (edges) if (edges)
edges.forEach((edge) => { edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!, edge.targetHandle!
); );
edge.className = "stroke-gray-900 stroke-connection"; edge.className = "stroke-gray-900 stroke-connection";
}); });
@ -413,7 +413,7 @@ export function handleKeyDown(
| React.KeyboardEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>
| React.KeyboardEvent<HTMLTextAreaElement>, | React.KeyboardEvent<HTMLTextAreaElement>,
inputValue: string | string[] | null, inputValue: string | string[] | null,
block: string, block: string
) { ) {
//condition to fix bug control+backspace on Windows/Linux //condition to fix bug control+backspace on Windows/Linux
if ( if (
@ -438,7 +438,7 @@ export function handleKeyDown(
} }
export function handleOnlyIntegerInput( export function handleOnlyIntegerInput(
event: React.KeyboardEvent<HTMLInputElement>, event: React.KeyboardEvent<HTMLInputElement>
) { ) {
if ( if (
event.key === "." || event.key === "." ||
@ -454,7 +454,7 @@ export function handleOnlyIntegerInput(
export function getConnectedNodes( export function getConnectedNodes(
edge: Edge, edge: Edge,
nodes: Array<NodeType>, nodes: Array<NodeType>
): Array<NodeType> { ): Array<NodeType> {
const sourceId = edge.source; const sourceId = edge.source;
const targetId = edge.target; const targetId = edge.target;
@ -555,7 +555,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
!edge.sourceHandle || !edge.sourceHandle ||
!edge.targetHandle || !edge.targetHandle ||
!edge.sourceHandle.includes("{") || !edge.sourceHandle.includes("{") ||
!edge.targetHandle.includes("{"), !edge.targetHandle.includes("{")
); );
} }
@ -578,7 +578,7 @@ export function customStringify(obj: any): string {
const keys = Object.keys(obj).sort(); const keys = Object.keys(obj).sort();
const keyValuePairs = keys.map( const keyValuePairs = keys.map(
(key) => `"${key}":${customStringify(obj[key])}`, (key) => `"${key}":${customStringify(obj[key])}`
); );
return `{${keyValuePairs.join(",")}}`; return `{${keyValuePairs.join(",")}}`;
} }
@ -607,7 +607,7 @@ export function getHandleId(
source: string, source: string,
sourceHandle: string, sourceHandle: string,
target: string, target: string,
targetHandle: string, targetHandle: string
) { ) {
return ( return (
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
@ -618,7 +618,7 @@ export function generateFlow(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
name: string, name: string
): generateFlowType { ): generateFlowType {
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } }; const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
@ -627,7 +627,7 @@ export function generateFlow(
newFlowData.edges = selection.edges.filter( newFlowData.edges = selection.edges.filter(
(edge) => (edge) =>
selection.nodes.some((node) => node.id === edge.target) && selection.nodes.some((node) => node.id === edge.target) &&
selection.nodes.some((node) => node.id === edge.source), selection.nodes.some((node) => node.id === edge.source)
); );
newFlowData.nodes = selection.nodes; newFlowData.nodes = selection.nodes;
@ -648,7 +648,7 @@ export function generateFlow(
(edge) => (edge) =>
(selection.nodes.some((node) => node.id === edge.target) || (selection.nodes.some((node) => node.id === edge.target) ||
selection.nodes.some((node) => node.id === edge.source)) && selection.nodes.some((node) => node.id === edge.source)) &&
newFlowData.edges.every((e) => e.id !== edge.id), newFlowData.edges.every((e) => e.id !== edge.id)
), ),
}; };
} }
@ -659,13 +659,13 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
const { nodes, edges } = groupNode.data.node!.flow!.data!; const { nodes, edges } = groupNode.data.node!.flow!.data!;
const lastNode = findLastNode(groupNode.data.node!.flow!.data!); const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
newEdges = newEdges.filter( newEdges = newEdges.filter(
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id), (e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id)
); );
newEdges.forEach((edge) => { newEdges.forEach((edge) => {
if (lastNode && edge.source === lastNode.id) { if (lastNode && edge.source === lastNode.id) {
edge.source = groupNode.id; edge.source = groupNode.id;
let newSourceHandle: sourceHandleType = scapeJSONParse( let newSourceHandle: sourceHandleType = scapeJSONParse(
edge.sourceHandle!, edge.sourceHandle!
); );
newSourceHandle.id = groupNode.id; newSourceHandle.id = groupNode.id;
edge.sourceHandle = scapedJSONStringfy(newSourceHandle); edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
@ -692,7 +692,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
export function filterFlow( export function filterFlow(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) { ) {
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node))); setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge))); setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
@ -730,7 +730,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
export function concatFlows( export function concatFlows(
flow: FlowType, flow: FlowType,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) { ) {
const { nodes, edges } = flow.data!; const { nodes, edges } = flow.data!;
setNodes((old) => [...old, ...nodes]); setNodes((old) => [...old, ...nodes]);
@ -739,43 +739,45 @@ export function concatFlows(
export function validateSelection( export function validateSelection(
selection: OnSelectionChangeParams, selection: OnSelectionChangeParams,
edges: Edge[], edges: Edge[]
): Array<string> { ): Array<string> {
const clonedSelection = cloneDeep(selection);
const clonedEdges = cloneDeep(edges);
//add edges to selection if selection mode selected only nodes //add edges to selection if selection mode selected only nodes
if (selection.edges.length === 0) { if (clonedSelection.edges.length === 0) {
selection.edges = edges; clonedSelection.edges = clonedEdges;
} }
// get only edges that are connected to the nodes in the selection // get only edges that are connected to the nodes in the selection
// first creates a set of all the nodes ids // first creates a set of all the nodes ids
let nodesSet = new Set(selection.nodes.map((n) => n.id)); let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
// then filter the edges that are connected to the nodes in the set // then filter the edges that are connected to the nodes in the set
let connectedEdges = selection.edges.filter( let connectedEdges = clonedSelection.edges.filter(
(e) => nodesSet.has(e.source) && nodesSet.has(e.target), (e) => nodesSet.has(e.source) && nodesSet.has(e.target)
); );
// add the edges to the selection // add the edges to the selection
selection.edges = connectedEdges; clonedSelection.edges = connectedEdges;
let errorsArray: Array<string> = []; let errorsArray: Array<string> = [];
// check if there is more than one node // check if there is more than one node
if (selection.nodes.length < 2) { if (clonedSelection.nodes.length < 2) {
errorsArray.push("Please select more than one node"); errorsArray.push("Please select more than one node");
} }
if ( if (
selection.nodes.some( clonedSelection.nodes.some(
(node) => (node) =>
isInputNode(node.data as NodeDataType) || isInputNode(node.data as NodeDataType) ||
isOutputNode(node.data as NodeDataType), isOutputNode(node.data as NodeDataType)
) )
) { ) {
errorsArray.push( errorsArray.push(
"Please select only nodes that are not input or output nodes", "Please select only nodes that are not input or output nodes"
); );
} }
//check if there are two or more nodes with free outputs //check if there are two or more nodes with free outputs
if ( if (
selection.nodes.filter( clonedSelection.nodes.filter(
(n) => !selection.edges.some((e) => e.source === n.id), (n) => !clonedSelection.edges.some((e) => e.source === n.id)
).length > 1 ).length > 1
) { ) {
errorsArray.push("Please select only one node with free outputs"); errorsArray.push("Please select only one node with free outputs");
@ -783,10 +785,10 @@ export function validateSelection(
// check if there is any node that does not have any connection // check if there is any node that does not have any connection
if ( if (
selection.nodes.some( clonedSelection.nodes.some(
(node) => (node) =>
!selection.edges.some((edge) => edge.target === node.id) && !clonedSelection.edges.some((edge) => edge.target === node.id) &&
!selection.edges.some((edge) => edge.source === node.id), !clonedSelection.edges.some((edge) => edge.source === node.id)
) )
) { ) {
errorsArray.push("Please select only nodes that are connected"); errorsArray.push("Please select only nodes that are connected");
@ -843,8 +845,8 @@ export function mergeNodeTemplates({
nodeTemplate[key].display_name nodeTemplate[key].display_name
? nodeTemplate[key].display_name ? nodeTemplate[key].display_name
: nodeTemplate[key].name : nodeTemplate[key].name
? toTitleCase(nodeTemplate[key].name) ? toTitleCase(nodeTemplate[key].name)
: toTitleCase(key); : toTitleCase(key);
} }
} }
}); });
@ -855,7 +857,7 @@ function isHandleConnected(
edges: Edge[], edges: Edge[],
key: string, key: string,
field: TemplateVariableType, field: TemplateVariableType,
nodeId: string, nodeId: string
) { ) {
/* /*
this function receives a flow and a handleId and check if there is a connection with this handle this function receives a flow and a handleId and check if there is a connection with this handle
@ -871,7 +873,7 @@ function isHandleConnected(
id: nodeId, id: nodeId,
proxy: { id: field.proxy!.id, field: field.proxy!.field }, proxy: { id: field.proxy!.id, field: field.proxy!.field },
inputTypes: field.input_types, inputTypes: field.input_types,
} as targetHandleType), } as targetHandleType)
) )
) { ) {
return true; return true;
@ -886,7 +888,7 @@ function isHandleConnected(
fieldName: key, fieldName: key,
id: nodeId, id: nodeId,
inputTypes: field.input_types, inputTypes: field.input_types,
} as targetHandleType), } as targetHandleType)
) )
) { ) {
return true; return true;
@ -909,7 +911,7 @@ export function generateNodeTemplate(Flow: FlowType) {
export function generateNodeFromFlow( export function generateNodeFromFlow(
flow: FlowType, flow: FlowType,
getNodeId: (type: string) => string, getNodeId: (type: string) => string
): NodeType { ): NodeType {
const { nodes } = flow.data!; const { nodes } = flow.data!;
const outputNode = cloneDeep(findLastNode(flow.data!)); const outputNode = cloneDeep(findLastNode(flow.data!));
@ -940,7 +942,7 @@ export function generateNodeFromFlow(
export function connectedInputNodesOnHandle( export function connectedInputNodesOnHandle(
nodeId: string, nodeId: string,
handleId: string, handleId: string,
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }, { nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
) { ) {
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> = const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
[]; [];
@ -977,7 +979,7 @@ export function connectedInputNodesOnHandle(
export function updateProxyIdsOnTemplate( export function updateProxyIdsOnTemplate(
template: APITemplateType, template: APITemplateType,
idsMap: { [key: string]: string }, idsMap: { [key: string]: string }
) { ) {
Object.keys(template).forEach((key) => { Object.keys(template).forEach((key) => {
if (template[key].proxy && idsMap[template[key].proxy!.id]) { if (template[key].proxy && idsMap[template[key].proxy!.id]) {
@ -988,7 +990,7 @@ export function updateProxyIdsOnTemplate(
export function updateEdgesIds( export function updateEdgesIds(
edges: Edge[], edges: Edge[],
idsMap: { [key: string]: string }, idsMap: { [key: string]: string }
) { ) {
edges.forEach((edge) => { edges.forEach((edge) => {
let targetHandle: targetHandleType = edge.data.targetHandle; let targetHandle: targetHandleType = edge.data.targetHandle;
@ -1020,7 +1022,7 @@ export function expandGroupNode(
nodes: Node[], nodes: Node[],
edges: Edge[], edges: Edge[],
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void, setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void, setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
) { ) {
const idsMap = updateIds(flow!.data!); const idsMap = updateIds(flow!.data!);
updateProxyIdsOnTemplate(template, idsMap); updateProxyIdsOnTemplate(template, idsMap);
@ -1063,7 +1065,7 @@ export function expandGroupNode(
const lastNode = cloneDeep(findLastNode(flow!.data!)); const lastNode = cloneDeep(findLastNode(flow!.data!));
newEdge.source = lastNode!.id; newEdge.source = lastNode!.id;
let newSourceHandle: sourceHandleType = scapeJSONParse( let newSourceHandle: sourceHandleType = scapeJSONParse(
newEdge.sourceHandle!, newEdge.sourceHandle!
); );
newSourceHandle.id = lastNode!.id; newSourceHandle.id = lastNode!.id;
newEdge.data.sourceHandle = newSourceHandle; newEdge.data.sourceHandle = newSourceHandle;
@ -1120,7 +1122,7 @@ export function expandGroupNode(
export function getGroupStatus( export function getGroupStatus(
flow: FlowType, flow: FlowType,
ssData: { [key: string]: { valid: boolean; params: string } }, ssData: { [key: string]: { valid: boolean; params: string } }
) { ) {
let status = { valid: true, params: SUCCESS_BUILD }; let status = { valid: true, params: SUCCESS_BUILD };
const { nodes } = flow.data!; const { nodes } = flow.data!;
@ -1139,7 +1141,7 @@ export function getGroupStatus(
export function createFlowComponent( export function createFlowComponent(
nodeData: NodeDataType, nodeData: NodeDataType,
version: string, version: string
): FlowType { ): FlowType {
const flowNode: FlowType = { const flowNode: FlowType = {
data: { data: {
@ -1175,7 +1177,7 @@ export function downloadNode(NodeFLow: FlowType) {
export function updateComponentNameAndType( export function updateComponentNameAndType(
data: any, data: any,
component: NodeDataType, component: NodeDataType
) {} ) {}
export function removeFileNameFromComponents(flow: FlowType) { export function removeFileNameFromComponents(flow: FlowType) {
@ -1249,7 +1251,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) {
export function downloadFlow( export function downloadFlow(
flow: FlowType, flow: FlowType,
flowName: string, flowName: string,
flowDescription?: string, flowDescription?: string
) { ) {
let clonedFlow = cloneDeep(flow); let clonedFlow = cloneDeep(flow);
removeFileNameFromComponents(clonedFlow); removeFileNameFromComponents(clonedFlow);
@ -1259,7 +1261,7 @@ export function downloadFlow(
...clonedFlow, ...clonedFlow,
name: flowName, name: flowName,
description: flowDescription, description: flowDescription,
}), })
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
@ -1274,7 +1276,7 @@ export function downloadFlow(
export function downloadFlows() { export function downloadFlows() {
downloadFlowsFromDatabase().then((flows) => { downloadFlowsFromDatabase().then((flows) => {
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flows), JSON.stringify(flows)
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
@ -1289,7 +1291,7 @@ export function downloadFlows() {
export const createNewFlow = ( export const createNewFlow = (
flowData: ReactFlowJsonObject, flowData: ReactFlowJsonObject,
flow: FlowType, flow: FlowType
) => { ) => {
return { return {
description: flow?.description ?? getRandomDescription(), description: flow?.description ?? getRandomDescription(),

View file

@ -7,15 +7,14 @@ export default function cloneFLowWithParent(
flow: FlowType, flow: FlowType,
parent: string, parent: string,
is_component: boolean, is_component: boolean,
keepId=false keepId = false
) { ) {
let childFLow = cloneDeep(flow); let childFLow = cloneDeep(flow);
childFLow.parent = parent; childFLow.parent = parent;
if(!keepId){ if (!keepId) {
childFLow.id = ""; childFLow.id = "";
} } else {
else{ childFLow.id = uniqueId() + "-" + childFLow.id;
childFLow.id = uniqueId()+"-"+childFLow.id;
} }
childFLow.is_component = is_component; childFLow.is_component = is_component;
return childFLow; return childFLow;

View file

@ -60,7 +60,7 @@ export function normalCaseToSnakeCase(str: string): string {
export function toTitleCase( export function toTitleCase(
str: string | undefined, str: string | undefined,
isNodeField?: boolean, isNodeField?: boolean
): string { ): string {
if (!str) return ""; if (!str) return "";
let result = str let result = str
@ -69,7 +69,7 @@ export function toTitleCase(
if (isNodeField) return word; if (isNodeField) return word;
if (index === 0) { if (index === 0) {
return checkUpperWords( return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase(), word[0].toUpperCase() + word.slice(1).toLowerCase()
); );
} }
return checkUpperWords(word.toLowerCase()); return checkUpperWords(word.toLowerCase());
@ -82,7 +82,7 @@ export function toTitleCase(
if (isNodeField) return word; if (isNodeField) return word;
if (index === 0) { if (index === 0) {
return checkUpperWords( return checkUpperWords(
word[0].toUpperCase() + word.slice(1).toLowerCase(), word[0].toUpperCase() + word.slice(1).toLowerCase()
); );
} }
return checkUpperWords(word.toLowerCase()); return checkUpperWords(word.toLowerCase());
@ -122,7 +122,7 @@ export function groupByFamily(
data: APIDataType, data: APIDataType,
baseClasses: string, baseClasses: string,
left: boolean, left: boolean,
flow?: NodeType[], flow?: NodeType[]
): groupedObjType[] { ): groupedObjType[] {
const baseClassesSet = new Set(baseClasses.split("\n")); const baseClassesSet = new Set(baseClasses.split("\n"));
let arrOfPossibleInputs: Array<{ let arrOfPossibleInputs: Array<{
@ -148,7 +148,7 @@ export function groupByFamily(
baseClassesSet.has(template.type)) || baseClassesSet.has(template.type)) ||
(template.input_types && (template.input_types &&
template.input_types.some((inputType) => template.input_types.some((inputType) =>
baseClassesSet.has(inputType), baseClassesSet.has(inputType)
))) )))
); );
}; };
@ -168,7 +168,7 @@ export function groupByFamily(
hasBaseClassInBaseClasses: hasBaseClassInBaseClasses:
foundNode?.hasBaseClassInBaseClasses || foundNode?.hasBaseClassInBaseClasses ||
nodeData.node!.base_classes.some((baseClass) => nodeData.node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass), baseClassesSet.has(baseClass)
), //seta como anterior ou verifica se o node tem base class ), //seta como anterior ou verifica se o node tem base class
displayName: nodeData.node?.display_name, displayName: nodeData.node?.display_name,
}); });
@ -185,10 +185,10 @@ export function groupByFamily(
if (!foundNode) { if (!foundNode) {
foundNode = { foundNode = {
hasBaseClassInTemplate: Object.values(node!.template).some( hasBaseClassInTemplate: Object.values(node!.template).some(
checkBaseClass, checkBaseClass
), ),
hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) => hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass), baseClassesSet.has(baseClass)
), ),
displayName: node?.display_name, displayName: node?.display_name,
}; };
@ -245,7 +245,7 @@ export function getRandomDescription(): string {
export function getRandomName( export function getRandomName(
retry: number = 0, retry: number = 0,
noSpace: boolean = false, noSpace: boolean = false,
maxRetries: number = 3, maxRetries: number = 3
): string { ): string {
const left: string[] = ADJECTIVES; const left: string[] = ADJECTIVES;
const right: string[] = NOUNS; const right: string[] = NOUNS;
@ -324,7 +324,7 @@ export function getChatInputField(flowState?: FlowState) {
export function getPythonApiCode( export function getPythonApiCode(
flowId: string, flowId: string,
isAuth: boolean, isAuth: boolean,
tweaksBuildedObject, tweaksBuildedObject
): string { ): string {
return `import requests return `import requests
from typing import Optional from typing import Optional
@ -381,7 +381,7 @@ print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
export function getCurlCode( export function getCurlCode(
flowId: string, flowId: string,
isAuth: boolean, isAuth: boolean,
tweaksBuildedObject, tweaksBuildedObject
): string { ): string {
return `curl -X POST \\ return `curl -X POST \\
${window.location.protocol}//${ ${window.location.protocol}//${
@ -426,6 +426,7 @@ TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
result = run_flow_from_json(flow="${flowName}.json", result = run_flow_from_json(flow="${flowName}.json",
input_value="message", input_value="message",
fallback_to_env_vars=True, # False by default
tweaks=TWEAKS)`; tweaks=TWEAKS)`;
} }
@ -437,7 +438,7 @@ result = run_flow_from_json(flow="${flowName}.json",
export function getWidgetCode( export function getWidgetCode(
flowId: string, flowId: string,
flowName: string, flowName: string,
isAuth: boolean, isAuth: boolean
): string { ): string {
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script> return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
@ -562,7 +563,7 @@ export function checkLocalStorageKey(key: string): boolean {
export function IncrementObjectKey( export function IncrementObjectKey(
object: object, object: object,
key: string, key: string
): { newKey: string; increment: number } { ): { newKey: string; increment: number } {
let count = 1; let count = 1;
const type = removeCountFromString(key); const type = removeCountFromString(key);
@ -634,7 +635,7 @@ export function getSetFromObject(obj: object, key?: string): Set<string> {
export function getFieldTitle( export function getFieldTitle(
template: APITemplateType, template: APITemplateType,
templateField: string, templateField: string
): string { ): string {
return template[templateField].display_name return template[templateField].display_name
? template[templateField].display_name! ? template[templateField].display_name!

View file

@ -10,7 +10,6 @@ import orjson
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from httpx import AsyncClient from httpx import AsyncClient
from sqlmodel import Session, SQLModel, create_engine, select
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_password_hash from langflow.services.auth.utils import get_password_hash
@ -19,6 +18,7 @@ from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.user.model import User, UserCreate from langflow.services.database.models.user.model import User, UserCreate
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service from langflow.services.deps import get_db_service
from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool from sqlmodel.pool import StaticPool
from typer.testing import CliRunner from typer.testing import CliRunner
@ -26,7 +26,8 @@ if TYPE_CHECKING:
from langflow.services.database.service import DatabaseService from langflow.services.database.service import DatabaseService
def pytest_configure(): def pytest_configure(config):
config.addinivalue_line("markers", "noclient: don't create a client for this test")
data_path = Path(__file__).parent.absolute() / "data" data_path = Path(__file__).parent.absolute() / "data"
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json" pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"

View file

@ -407,7 +407,7 @@ def test_update_source_handle():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_pickle_graph(json_vector_store): async def test_pickle_graph():
starter_projects = load_starter_projects() starter_projects = load_starter_projects()
data = starter_projects[0][1]["data"] data = starter_projects[0][1]["data"]
graph = Graph.from_payload(data) graph = Graph.from_payload(data)