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
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 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",
label: "What's New?",
collapsed: false,
items: [
"whats-new/a-new-chapter-langflow"
],
items: ["whats-new/a-new-chapter-langflow"],
},
{
type: "category",
@ -17,7 +15,7 @@ module.exports = {
"getting-started/install-langflow",
"getting-started/quickstart",
"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/document-qa",
"starter-projects/memory-chatbot",
"starter-projects/vector-store-rag"
"starter-projects/vector-store-rag",
],
},
{
@ -40,10 +38,12 @@ module.exports = {
"administration/login",
"administration/api",
"administration/cli",
"administration/playground",
"administration/global-env",
"administration/components",
"administration/collection",
"administration/prompt-customization",
"administration/langfuse_integration"
"administration/langfuse_integration",
],
},
{
@ -58,7 +58,7 @@ module.exports = {
"components/helpers",
"components/vector-stores",
"components/embeddings",
"components/custom"
"components/custom",
],
},
{
@ -74,7 +74,7 @@ module.exports = {
"components/retrievers",
"components/text-splitters",
"components/toolkits",
"components/tools"
"components/tools",
],
},
{
@ -88,7 +88,7 @@ module.exports = {
"examples/csv-loader",
"examples/searchapi-tool",
"examples/serp-api-tool",
"examples/python-function"
"examples/python-function",
],
},
{
@ -101,8 +101,8 @@ module.exports = {
"migration/inputs-and-outputs",
"migration/text-and-record",
"migration/compatibility",
"migration/global-variables"
]
"migration/global-variables",
],
},
{
type: "category",
@ -111,16 +111,14 @@ module.exports = {
items: [
"tutorials/chatprompttemplate_guide",
"tutorials/loading_document",
"tutorials/rag-with-astradb"
"tutorials/rag-with-astradb",
],
},
{
type: "category",
label: "Deployment",
collapsed: true,
items: [
"deployment/gcp-deployment"
],
items: ["deployment/gcp-deployment"],
},
{
type: "category",
@ -130,7 +128,7 @@ module.exports = {
"contributing/how-contribute",
"contributing/github-issues",
"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]
name = "langflow"
version = "1.0.0a33"
version = "1.0.0a34"
description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"]
maintainers = [

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,7 +1,5 @@
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.schema.schema import Record

View file

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

View file

@ -3,11 +3,12 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
from langflow.field_typing import Text
class PassComponent(CustomComponent):
display_name = "Pass"
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
field_order = ["ignored_input", "forwarded_input"]
def build_config(self) -> dict:
return {
"ignored_input": {
@ -19,7 +20,7 @@ class PassComponent(CustomComponent):
"display_name": "Input",
"info": "This input is forwarded by the component.",
"input_types": ["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,
message: str = "",
) -> List[Record]:
store_message(
sender=sender,
sender_name=sender_name,

View file

@ -35,7 +35,7 @@ class SubFlowComponent(CustomComponent):
build_config["flow_name"]["options"] = self.get_flow_names()
# Clean up the build config
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]
if field_value is not None and field_name == "flow_name":
try:
@ -85,20 +85,29 @@ class SubFlowComponent(CustomComponent):
"display_name": "Tweaks",
"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
if not messages:
return []
records = []
for message in messages:
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)
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()}
run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
tweaks=tweaks,
@ -112,7 +121,7 @@ class SubFlowComponent(CustomComponent):
if run_output is not None:
for output in run_output.outputs:
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
logger.debug(records)

View file

@ -4,6 +4,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record
from langflow.field_typing import Text
class TextOperatorComponent(CustomComponent):
display_name = "Text Operator"
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": {
"display_name": "Operator",
"info": "The operator to apply for comparing the texts.",
"options": [
"equals",
"not equals",
"contains",
"starts with",
"ends with",
"exists"
],
"options": ["equals", "not equals", "contains", "starts with", "ends with", "exists"],
},
"case_sensitive": {
"display_name": "Case Sensitive",
@ -51,11 +45,8 @@ class TextOperatorComponent(CustomComponent):
case_sensitive: bool = False,
true_output: Optional[Text] = "",
) -> Union[Text, Record]:
if not input_text or not match_text:
raise ValueError(
"Both 'input_text' and 'match_text' must be provided and non-empty."
)
raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
if not case_sensitive:
input_text = input_text.lower()
@ -82,4 +73,4 @@ class TextOperatorComponent(CustomComponent):
self.status = "Comparison failed, stopping execution."
self.stop()
return output_record
return output_record

View file

@ -242,6 +242,7 @@ class Graph:
outputs: list[str],
stream: bool,
session_id: str,
fallback_to_env_vars: bool,
) -> List[Optional["ResultData"]]:
"""
Runs the graph with the given inputs.
@ -289,7 +290,7 @@ class Graph:
start_component_id = next(
(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()
except Exception as exc:
logger.exception(exc)
@ -315,6 +316,7 @@ class Graph:
outputs: Optional[list[str]] = None,
session_id: Optional[str] = None,
stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
Run the graph with the given inputs and return the outputs.
@ -340,6 +342,7 @@ class Graph:
outputs=outputs,
session_id=session_id,
stream=stream,
fallback_to_env_vars=fallback_to_env_vars,
)
try:
@ -362,6 +365,7 @@ class Graph:
outputs: Optional[list[str]] = None,
session_id: Optional[str] = None,
stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
Runs the graph with the given inputs.
@ -403,6 +407,7 @@ class Graph:
outputs=outputs or [],
stream=stream,
session_id=session_id or "",
fallback_to_env_vars=fallback_to_env_vars,
)
run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
logger.debug(f"Run outputs: {run_output_object}")
@ -468,9 +473,9 @@ class Graph:
"""Marks a branch of the graph."""
if visited is None:
visited = set()
visited.add(vertex_id)
if vertex_id in visited:
return
visited.add(vertex_id)
self.mark_vertex(vertex_id, state)
@ -712,6 +717,7 @@ class Graph:
vertex_id: str,
inputs_dict: Optional[Dict[str, str]] = None,
user_id: Optional[str] = None,
fallback_to_env_vars: bool = False,
):
"""
Builds a vertex in the graph.
@ -733,7 +739,7 @@ class Graph:
vertex = self.get_vertex(vertex_id)
try:
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:
params = vertex._built_object_repr()
@ -796,7 +802,7 @@ class Graph:
vertices.append(vertex)
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."""
first_layer = self.sort_vertices(start_component_id=start_component_id)
@ -821,6 +827,7 @@ class Graph:
vertex_id=vertex_id,
user_id=self.user_id,
inputs_dict={},
fallback_to_env_vars=fallback_to_env_vars,
),
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
elif field.get("required"):
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:
val = field.get("value")
if field.get("type") == "code":
@ -390,13 +394,17 @@ class Vertex:
self.params = self._raw_params.copy()
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.
"""
logger.debug(f"Building {self.display_name}")
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._built = True
@ -606,7 +614,7 @@ class Vertex:
if isinstance(self.params[key], list):
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.
"""
@ -615,6 +623,7 @@ class Vertex:
try:
result = await loading.instantiate_class(
user_id=user_id,
fallback_to_env_vars=fallback_to_env_vars,
vertex=self,
)
self._update_built_object_and_artifacts(result)

View file

@ -1,8 +1,8 @@
import inspect
import json
import os
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
import orjson
from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor
@ -35,6 +35,7 @@ if TYPE_CHECKING:
async def instantiate_class(
vertex: "Vertex",
fallback_to_env_vars,
user_id=None,
) -> Any:
"""Instantiate class from module type and key, and params"""
@ -57,7 +58,7 @@ async def instantiate_class(
if not base_type:
raise ValueError("No base type provided for vertex")
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)
return await instantiate_based_on_type(
class_object=class_object,
@ -66,6 +67,7 @@ async def instantiate_class(
params=params,
user_id=user_id,
vertex=vertex,
fallback_to_env_vars=fallback_to_env_vars,
)
@ -93,14 +95,7 @@ def convert_kwargs(params):
return params
async def instantiate_based_on_type(
class_object,
base_type,
node_type,
params,
user_id,
vertex,
):
async def instantiate_based_on_type(class_object, base_type, node_type, params, user_id, vertex, fallback_to_env_vars):
if base_type == "agents":
return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts":
@ -132,33 +127,49 @@ async def instantiate_based_on_type(
elif base_type == "memory":
return instantiate_memory(node_type, class_object, params)
elif 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)
elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params)
else:
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
# and if it is, we will get the value from the custom_component.keys(name)
# and update the params with the value
for field in load_from_db_fields:
if field in params:
try:
key = custom_component.variables(params[field])
params[field] = key if key else params[field]
key = None
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:
logger.error(f"Failed to get value for {field} from custom component. Error: {exc}")
pass
logger.error(f"Failed to get value for {field} from custom component. Setting it to None. Error: {exc}")
params[field] = None
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()
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
custom_component: "CustomComponent" = class_object(
@ -167,7 +178,9 @@ async def instantiate_custom_component(params, user_id, vertex):
vertex=vertex,
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"):
params_copy["retriever"] = params_copy["retriever"].as_retriever()

View file

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

View file

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

View file

@ -82,6 +82,7 @@ def run_flow_from_json(
env_file: Optional[str] = None,
cache: Optional[str] = None,
disable_logs: Optional[bool] = True,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]:
"""
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.
cache (Optional[str], optional): The cache directory to use. Defaults to None.
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:
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,
output_type=output_type,
output_component=output_component,
fallback_to_env_vars=fallback_to_env_vars,
)
return result

View file

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

View file

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

View file

@ -111,6 +111,10 @@ class Settings(BaseSettings):
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
"""Whether to store environment variables as Global Variables in the database."""
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]]
name = "aiohttp"
@ -1137,20 +1137,20 @@ extended-testing = ["faker (>=19.3.1,<20.0.0)", "jinja2 (>=3,<4)", "pandas (>=2.
[[package]]
name = "langchain-text-splitters"
version = "0.0.1"
version = "0.0.2"
description = "LangChain text splitting utilities"
optional = false
python-versions = ">=3.8.1,<4.0"
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_text_splitters-0.0.1-py3-none-any.whl", hash = "sha256:f5b802f873f5ff6a8b9259ff34d53ed989666ef4e1582e6d1adb3b5520e3839a"},
{file = "langchain_text_splitters-0.0.1.tar.gz", hash = "sha256:ac459fa98799f5117ad5425a9330b21961321e30bc19a2a2f9f761ddadd62aa1"},
{file = "langchain_text_splitters-0.0.2-py3-none-any.whl", hash = "sha256:13887f32705862c1e1454213cb7834a63aae57c26fcd80346703a1d09c46168d"},
{file = "langchain_text_splitters-0.0.2.tar.gz", hash = "sha256:ac8927dc0ba08eba702f6961c9ed7df7cead8de19a9f7101ab2b5ea34201b3c1"},
]
[package.dependencies]
langchain-core = ">=0.1.28,<0.2.0"
langchain-core = ">=0.1.28,<0.3"
[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]]
name = "langchainhub"
@ -1169,13 +1169,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]]
name = "langsmith"
version = "0.1.57"
version = "0.1.59"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langsmith-0.1.57-py3-none-any.whl", hash = "sha256:dbd83b0944a2fbea4151f0aa053530d93fcf6784a580621bc60633cb890b57dc"},
{file = "langsmith-0.1.57.tar.gz", hash = "sha256:4682204de19f0218029c2b8445ce2cc3485c8d0df9796b31e2ce4c9051fce365"},
{file = "langsmith-0.1.59-py3-none-any.whl", hash = "sha256:445e3bc1d3baa1e5340cd979907a19483b9763a2ed37b863a01113d406f69345"},
{file = "langsmith-0.1.59.tar.gz", hash = "sha256:e748a89f4dd6aa441349143e49e546c03b5dfb43376a25bfef6a5ca792fe1437"},
]
[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-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_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_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-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_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-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
{file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
@ -1935,13 +1939,13 @@ xmp = ["defusedxml"]
[[package]]
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`."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
{file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[package.extras]
@ -2180,13 +2184,13 @@ cli = ["click (>=5.0)"]
[[package]]
name = "python-engineio"
version = "4.9.0"
version = "4.9.1"
description = "Engine.IO server and client for Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "python-engineio-4.9.0.tar.gz", hash = "sha256:e87459c15638e567711fd156e6f9c4a402668871bed79523f0ecfec744729ec7"},
{file = "python_engineio-4.9.0-py3-none-any.whl", hash = "sha256:979859bff770725b75e60353d7ae53b397e8b517d05ba76733b404a3dcca3e4c"},
{file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"},
{file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"},
]
[package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow-base"
version = "0.0.44"
version = "0.0.45"
description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"]
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 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 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 [errorDownloading, setErrordownloading] = useState(false)
const setErrorList = useAlertStore(state => state.setErrorData);
const [errorDownloading, setErrordownloading] = useState(false);
const setErrorList = useAlertStore((state) => state.setErrorData);
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(() => {
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');
zoomInButton!.addEventListener('click', () => viewer.viewport.zoomBy(1.2));
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 () => {
viewer.destroy();
zoomInButton!.removeEventListener('click', () => viewer.viewport.zoomBy(1.2));
zoomOutButton!.removeEventListener('click', () => viewer.viewport.zoomBy(0.8));
homeButton!.removeEventListener('click', () => viewer.viewport.goHome());
fullPageButton!.removeEventListener('click', () => viewer.setFullScreen(true));
};
}
} catch (error) {
console.error('Error initializing OpenSeadragon:', error);
}
}, [image]);
zoomInButton!.addEventListener("click", () =>
viewer.viewport.zoomBy(1.2)
);
zoomOutButton!.addEventListener("click", () =>
viewer.viewport.zoomBy(0.8)
);
homeButton!.addEventListener("click", () => viewer.viewport.goHome());
fullPageButton!.addEventListener("click", () =>
viewer.setFullScreen(true)
);
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)
});
// Optionally, you can set additional viewer options here
// Cleanup function
return () => {
viewer.destroy();
zoomInButton!.removeEventListener("click", () =>
viewer.viewport.zoomBy(1.2)
);
zoomOutButton!.removeEventListener("click", () =>
viewer.viewport.zoomBy(0.8)
);
homeButton!.removeEventListener("click", () =>
viewer.viewport.goHome()
);
fullPageButton!.removeEventListener("click", () =>
viewer.setFullScreen(true)
);
};
}
} catch (error) {
console.error("Error initializing OpenSeadragon:", error);
}
}, [image]);
return (
image === "" ? (
<div className="w-full h-full bg-muted rounded-md flex align-center justify-center flex-col gap-5 border border-border">
<div className="flex gap-2 align-center justify-center ">
<ForwardedIconComponent
name="Image"
/>
{IMGViewErrorTitle}
</div>
<div className="flex align-center justify-center">
<div className="langflow-chat-desc flex align-center justify-center">
<div className="langflow-chat-desc-span">
{IMGViewErrorMSG}
</div>
</div>
</div>
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);
});
}
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 className="w-full flex align-center justify-center my-2 mb-4">
<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
name="ZoomIn"
className={"text-secondary-foreground w-5 h-5"}
/>
</button>
<div>
<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>
<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={"h-5 w-5 text-secondary-foreground"}
/>
</button>
<div>
<Separator orientation="vertical" />
</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,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion(),
open.length === 0 ? "" : getOpenAccordion()
);
function getOpenAccordion(): string {

View file

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

View file

@ -6,6 +6,7 @@ import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { FlowType } from "../../types/flow";
import { storeComponent } from "../../types/store";
import cloneFLowWithParent, {
getInputsAndOutputs,
@ -22,7 +23,6 @@ import {
CardTitle,
} from "../ui/card";
import Loading from "../ui/loading";
import { FlowType } from "../../types/flow";
export default function CollectionCardComponent({
data,
@ -50,11 +50,11 @@ export default function CollectionCardComponent({
const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false);
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 [downloads_count, setDownloads_count] = useState(
data?.downloads_count ?? 0,
data?.downloads_count ?? 0
);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
@ -65,7 +65,7 @@ export default function CollectionCardComponent({
const [openPlayground, setOpenPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
(state) => state.setCurrentFlowId
);
const [loadingPlayground, setLoadingPlayground] = useState(false);
@ -185,7 +185,7 @@ export default function CollectionCardComponent({
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]",
disabled ? "pointer-events-none opacity-50" : "",
onClick ? "cursor-pointer" : "",
onClick ? "cursor-pointer" : ""
)}
onClick={onClick}
>
@ -198,7 +198,7 @@ export default function CollectionCardComponent({
"flex-shrink-0",
data.is_component
? "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"}
/>
@ -386,7 +386,7 @@ export default function CollectionCardComponent({
name="Trash2"
className={cn(
"h-5 w-5",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
@ -421,7 +421,7 @@ export default function CollectionCardComponent({
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
@ -459,7 +459,7 @@ export default function CollectionCardComponent({
}
className={cn(
loading ? "h-5 w-5 animate-spin" : "h-5 w-5",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</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 ",
!hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground "
: "",
: ""
)}
>
<ForwardedIconComponent
@ -59,14 +59,14 @@ export default function FlowToolbar(): JSX.Element {
"-m-0.5 -ml-1 h-6 w-6",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
: "",
: ""
)}
/>
Share
</button>
</ShareModal>
),
[hasApiKey, validApiKey, currentFlow, hasStore],
[hasApiKey, validApiKey, currentFlow, hasStore]
);
return (
@ -118,7 +118,7 @@ export default function FlowToolbar(): JSX.Element {
<ApiModal flow={currentFlow}>
<div
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

View file

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

View file

@ -55,6 +55,7 @@ export default function CodeTabsComponent({
tweaks,
setActiveTweaks,
activeTweaks,
allowExport = false,
}: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
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={
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
{tweaks && (
<div
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")
Number(activeTab) > 2
? "hidden"
: "relative top-[2.5px] flex gap-2"
}
htmlFor="tweaks-switch"
>
Tweaks
</Label>
</div>
<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
<Switch
style={{
transform: `scaleX(${0.7}) scaleY(${0.7})`,
}}
id="tweaks-switch"
onCheckedChange={setActiveTweaks}
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>
</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 && (
<>
@ -231,7 +236,7 @@ export default function CodeTabsComponent({
<div className="api-modal-according-display">
<div
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) => (
@ -270,8 +275,8 @@ export default function CodeTabsComponent({
.show &&
LANGFLOW_SUPPORTED_TYPES.has(
node.data.node.template[templateField]
.type,
),
.type
)
)
.map((templateField, indx) => {
return (
@ -329,7 +334,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -375,7 +380,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -428,7 +433,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -465,7 +470,7 @@ export default function CodeTabsComponent({
e,
node.data.node.template[
templateField
],
]
);
}}
size="small"
@ -496,7 +501,7 @@ export default function CodeTabsComponent({
].fileTypes
}
onFileChange={(
value: any,
value: any
) => {
node.data.node.template[
templateField
@ -549,7 +554,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -589,7 +594,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
value={
@ -651,7 +656,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -697,7 +702,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -743,7 +748,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -775,8 +780,8 @@ export default function CodeTabsComponent({
].value,
type(
node,
templateField,
),
templateField
)
)
}
duplicateKey={
@ -785,15 +790,15 @@ export default function CodeTabsComponent({
onChange={(target) => {
const valueToNumbers =
convertValuesToNumbers(
target,
target
);
node.data.node!.template[
templateField
].value = valueToNumbers;
setErrorDuplicateKey(
hasDuplicateKeys(
valueToNumbers,
),
valueToNumbers
)
);
setData((old) => {
let newInputList =
@ -810,7 +815,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
isList={
@ -860,7 +865,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,15 +19,15 @@ export default function InputGlobalComponent({
editNode = false,
}: InputGlobalComponentType): JSX.Element {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries,
(state) => state.globalVariablesEntries
);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const unavaliableFields = useGlobalVariablesStore(
(state) => state.unavaliableFields,
(state) => state.unavaliableFields
);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
(state) => state.removeGlobalVariable
);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -129,7 +129,7 @@ export default function InputGlobalComponent({
<ForwardedIconComponent
name="Trash2"
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"
/>

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";
export default function Error(): JSX.Element {
return (
<div className="flex flex-col items-center justify-center h-full w-full bg-muted">
<div className="chat-alert-box">
<span className="flex gap-2">
<IconComponent name="FileX2" />
<span className="langflow-chat-span">{PDFLoadErrorTitle}</span>
</span>
<br />
<div className="langflow-chat-desc">
<span className="langflow-chat-desc-span">
{PDFCheckFlow}{" "}
</span>
</div>
</div>
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="chat-alert-box">
<span className="flex gap-2">
<IconComponent name="FileX2" />
<span className="langflow-chat-span">{PDFLoadErrorTitle}</span>
</span>
<br />
<div className="langflow-chat-desc">
<span className="langflow-chat-desc-span">{PDFCheckFlow} </span>
</div>
);
}
</div>
</div>
);
}

View file

@ -16,7 +16,7 @@ const TableComponent = forwardRef<
<div
className={cn(
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
>
<AgGridReact ref={ref} {...props} />

View file

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

View file

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

View file

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

View file

@ -324,7 +324,7 @@ dark:prose-invert"
</>
) : (
<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={
"chat-message-" + chat.sender_name + "-" + chatMessage
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -37,12 +37,17 @@ const ApiModal = forwardRef(
flow: FlowType;
children: ReactNode;
},
ref,
ref
) => {
const tweak = useTweaksStore((state) => state.tweak);
let tweak = useTweaksStore((state) => state.tweak);
const addTweaks = useTweaksStore((state) => state.setTweak);
const setTweaksList = useTweaksStore((state) => state.setTweaksList);
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 { autoLogin } = useContext(AuthContext);
@ -108,7 +113,7 @@ const ApiModal = forwardRef(
buildTweakObject(
nodeId,
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(
tw: string,
changes: string | string[] | boolean | number | Object[] | Object,
template: TemplateVariableType,
template: TemplateVariableType
) {
changes = getChangesType(changes, template);
@ -162,6 +167,12 @@ const ApiModal = forwardRef(
}
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 curl_code = getCurlCode(flow?.id, autoLogin, cloneTweak);
const pythonCode = getPythonCode(flow?.name, cloneTweak);
@ -201,11 +212,12 @@ const ApiModal = forwardRef(
}}
activeTweaks={activeTweaks}
setActiveTweaks={setActiveTweaks}
allowExport
/>
</BaseModal.Content>
</BaseModal>
);
},
}
);
export default ApiModal;

View file

@ -63,7 +63,7 @@ interface BaseModalProps {
React.ReactElement<ContentProps>,
React.ReactElement<HeaderProps>,
React.ReactElement<TriggerProps>?,
React.ReactElement<FooterProps>?,
React.ReactElement<FooterProps>?
];
open?: boolean;
setOpen?: (open: boolean) => void;
@ -95,16 +95,16 @@ function BaseModal({
type = "dialog",
}: BaseModalProps) {
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(
(child) => (child as React.ReactElement).type === Trigger,
(child) => (child as React.ReactElement).type === Trigger
);
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(
(child) => (child as React.ReactElement).type === Footer,
(child) => (child as React.ReactElement).type === Footer
);
let minWidth: string;

View file

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

View file

@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { NodeToolbar } from "reactflow";
import { GradientGroup } from "../../../../icons/GradientSparkles";
import { validateSelection } from "../../../../utils/reactflowUtils";
import useFlowStore from "../../../../stores/flowStore";
import { validateSelection } from "../../../../utils/reactflowUtils";
export default function SelectionMenu({
onClick,
nodes,
@ -13,7 +13,7 @@ export default function SelectionMenu({
const [disable, setDisable] = useState<boolean>(
lastSelection && edges.length > 0
? validateSelection(lastSelection!, edges).length > 0
: false,
: false
);
const [isOpen, setIsOpen] = useState(false);
const [isTransitioning, setIsTransitioning] = useState(false);
@ -62,7 +62,11 @@ export default function SelectionMenu({
}
>
<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}
disabled={disable}
>

View file

@ -58,7 +58,7 @@ export default function NodeToolbarComponent({
data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int" ||
data.node.template[templateField].type === "dict" ||
data.node.template[templateField].type === "NestedDict"),
data.node.template[templateField].type === "NestedDict")
).length;
const templates = useTypesStore((state) => state.templates);
const hasStore = useStoreStore((state) => state.hasStore);
@ -85,7 +85,7 @@ export default function NodeToolbarComponent({
const [showconfirmShare, setShowconfirmShare] = useState(false);
const [showOverrideModal, setShowOverrideModal] = useState(false);
const [flowComponent, setFlowComponent] = useState<FlowType>(
createFlowComponent(cloneDeep(data), version),
createFlowComponent(cloneDeep(data), version)
);
const openInNewTab = (url) => {
@ -100,7 +100,7 @@ export default function NodeToolbarComponent({
const updateNodeInternals = useUpdateNodeInternals();
const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection,
(state) => state.setLastCopiedSelection
);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -150,7 +150,7 @@ export default function NodeToolbarComponent({
nodes,
edges,
setNodes,
setEdges,
setEdges
);
break;
case "override":
@ -174,7 +174,7 @@ export default function NodeToolbarComponent({
y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position.x,
paneY: nodes.find((node) => node.id === data.id)?.position.y,
},
}
);
break;
case "update":
@ -212,13 +212,13 @@ export default function NodeToolbarComponent({
};
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 handleOnNewValue = (
newValue: string | string[] | boolean | Object[],
newValue: string | string[] | boolean | Object[]
): void => {
if (data.node!.template[name].value !== newValue) {
takeSnapshot();
@ -393,7 +393,7 @@ export default function NodeToolbarComponent({
data-testid="save-button-modal"
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",
hasCode ? " " : " rounded-l-md ",
hasCode ? " " : " rounded-l-md "
)}
onClick={(event) => {
event.preventDefault();
@ -411,7 +411,7 @@ export default function NodeToolbarComponent({
<button
data-testid="duplicate-button-modal"
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) => {
event.preventDefault();
@ -459,7 +459,7 @@ export default function NodeToolbarComponent({
<div
data-testid="more-options-modal"
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

View file

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

View file

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

View file

@ -113,110 +113,109 @@ export default function GeneralPage() {
</div>
<div className="grid gap-6">
<Form.Root
onSubmit={(event) => {
handlePatchGradient();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: gradient
}
<Form.Root
onSubmit={(event) => {
handlePatchGradient();
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: 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) => {
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
className="field-invalid"
match="valueMissing"
>
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
<Form.Message
className="field-invalid"
match="valueMissing"
>
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
)}
</div>
</div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,6 @@ import orjson
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
from sqlmodel import Session, SQLModel, create_engine, select
from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
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.utils import session_getter
from langflow.services.deps import get_db_service
from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool
from typer.testing import CliRunner
@ -26,7 +26,8 @@ if TYPE_CHECKING:
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"
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"

View file

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