Merge dev into refactor/utils
This commit is contained in:
commit
92c953cdcd
76 changed files with 2648 additions and 2456 deletions
14
.env.example
14
.env.example
|
|
@ -4,6 +4,19 @@
|
|||
# Do not commit .env file to git
|
||||
# Do not change .env.example file
|
||||
|
||||
# Config directory
|
||||
# Directory where files, logs and database will be stored
|
||||
# Example: LANGFLOW_CONFIG_DIR=~/.langflow
|
||||
LANGFLOW_CONFIG_DIR=
|
||||
|
||||
# Save database in the config directory
|
||||
# Values: true, false
|
||||
# If false, the database will be saved in Langflow's root directory
|
||||
# This means that the database will be deleted when Langflow is uninstalled
|
||||
# and that the database will not be shared between different virtual environments
|
||||
# Example: LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
|
||||
LANGFLOW_SAVE_DB_IN_CONFIG_DIR=
|
||||
|
||||
# Database URL
|
||||
# Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow
|
||||
# SQLite example:
|
||||
|
|
@ -56,7 +69,6 @@ LANGFLOW_REMOVE_API_KEYS=
|
|||
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
|
||||
LANGFLOW_CACHE_TYPE=
|
||||
|
||||
|
||||
# Set AUTO_LOGIN to false if you want to disable auto login
|
||||
# and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
|
||||
# must be set if AUTO_LOGIN is set to false
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
[](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
|
||||
|
||||
Or this one to deploy Langflow 0.6.x:
|
||||
|
||||
[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
|
||||
|
||||
## Deploy on Render
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# PYTHON-BASE
|
||||
# Sets up all our shared environment variables
|
||||
################################
|
||||
FROM python:3.10-slim as python-base
|
||||
FROM python:3.12-slim as python-base
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
|
|
@ -47,7 +47,7 @@ ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
|||
# Used to build deps + create our virtual environment
|
||||
################################
|
||||
FROM python-base as builder-base
|
||||
RUN
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# deps for installing poetry
|
||||
|
|
@ -55,7 +55,12 @@ RUN apt-get update \
|
|||
# deps for building python deps
|
||||
build-essential \
|
||||
# npm
|
||||
npm
|
||||
npm \
|
||||
# gcc
|
||||
gcc \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
|
||||
# Now we need to copy the entire project into the image
|
||||
|
|
@ -70,15 +75,12 @@ RUN --mount=type=cache,target=/root/.cache \
|
|||
RUN python -m pip install requests && cd ./scripts && python update_dependencies.py
|
||||
RUN $POETRY_HOME/bin/poetry lock
|
||||
RUN $POETRY_HOME/bin/poetry build
|
||||
# Final stage for the application
|
||||
FROM python-base as final
|
||||
|
||||
# Copy virtual environment and built .tar.gz from builder base
|
||||
RUN useradd -m -u 1000 user
|
||||
COPY --from=builder-base /app/dist/*.tar.gz ./
|
||||
# Install the package from the .tar.gz
|
||||
RUN python -m pip install *.tar.gz --user
|
||||
RUN python -m pip install /app/dist/*.tar.gz --user
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["python", "-m", "langflow", "run"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
# PYTHON-BASE
|
||||
# Sets up all our shared environment variables
|
||||
################################
|
||||
FROM python:3.10-slim as python-base
|
||||
FROM python:3.12-slim as python-base
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
|
|
@ -55,6 +55,8 @@ RUN apt-get update \
|
|||
build-essential \
|
||||
# npm
|
||||
npm \
|
||||
# gcc
|
||||
gcc \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
@ -78,15 +80,11 @@ RUN cp -r src/frontend/build src/backend/base/langflow/frontend
|
|||
RUN rm -rf src/backend/base/dist
|
||||
RUN cd src/backend/base && $POETRY_HOME/bin/poetry build --format sdist
|
||||
|
||||
# Final stage for the application
|
||||
FROM python-base as final
|
||||
|
||||
# Copy virtual environment and built .tar.gz from builder base
|
||||
RUN useradd -m -u 1000 user
|
||||
COPY --from=builder-base /app/src/backend/base/dist/*.tar.gz ./
|
||||
# Install the package from the .tar.gz
|
||||
RUN pip install *.tar.gz --user
|
||||
RUN python -m pip install /app/dist/*.tar.gz --user
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["python", "-m", "langflow", "run"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
45
docs/docs/administration/global-env.mdx
Normal file
45
docs/docs/administration/global-env.mdx
Normal 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).
|
||||
29
docs/docs/administration/playground.mdx
Normal file
29
docs/docs/administration/playground.mdx
Normal 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.
|
||||
|
|
@ -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
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
BIN
docs/static/img/playground-chat.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
750
poetry.lock
generated
750
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -1,65 +1,85 @@
|
|||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2'
|
||||
import { Construct } from "constructs";
|
||||
import * as ec2 from "aws-cdk-lib/aws-ec2";
|
||||
import * as rds from "aws-cdk-lib/aws-rds";
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as cdk from "aws-cdk-lib";
|
||||
|
||||
interface RdsProps {
|
||||
vpc: ec2.Vpc
|
||||
dbSG:ec2.SecurityGroup
|
||||
vpc: ec2.Vpc;
|
||||
dbSG: ec2.SecurityGroup;
|
||||
}
|
||||
|
||||
export class Rds extends Construct{
|
||||
readonly rdsCluster: rds.DatabaseCluster
|
||||
export class Rds extends Construct {
|
||||
readonly rdsCluster: rds.DatabaseCluster;
|
||||
|
||||
constructor(scope: Construct, id:string, props: RdsProps){
|
||||
constructor(scope: Construct, id: string, props: RdsProps) {
|
||||
super(scope, id);
|
||||
|
||||
const {vpc, dbSG} = props
|
||||
const instanceType = ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM)
|
||||
const { vpc, dbSG } = props;
|
||||
const instanceType = ec2.InstanceType.of(
|
||||
ec2.InstanceClass.BURSTABLE4_GRAVITON,
|
||||
ec2.InstanceSize.MEDIUM,
|
||||
);
|
||||
|
||||
// RDSのパスワードを自動生成してSecrets Managerに格納
|
||||
const rdsCredentials = rds.Credentials.fromGeneratedSecret('db_user',{
|
||||
secretName: 'langflow-DbSecret',
|
||||
})
|
||||
|
||||
const rdsCredentials = rds.Credentials.fromGeneratedSecret("db_user", {
|
||||
secretName: "langflow-DbSecret",
|
||||
});
|
||||
|
||||
// DB クラスターのパラメータグループ作成
|
||||
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0
|
||||
}),
|
||||
description: 'for-langflow',
|
||||
})
|
||||
clusterParameterGroup.bindToCluster({})
|
||||
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',
|
||||
})
|
||||
instanceParameterGroup.bindToInstance({})
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1085
scripts/aws/package-lock.json
generated
1085
scripts/aws/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ apt -y upgrade
|
|||
|
||||
# Install Python 3 pip, Langflow, and Nginx
|
||||
apt -y install python3-pip
|
||||
pip install langflow
|
||||
langflow --host 0.0.0.0 --port 7860
|
||||
pip3 install pip -U
|
||||
apt -y update
|
||||
pip3 install langflow
|
||||
langflow run --host 0.0.0.0 --port 7860
|
||||
EOF
|
||||
)
|
||||
|
||||
|
|
|
|||
1
src/backend/.gitignore
vendored
1
src/backend/.gitignore
vendored
|
|
@ -131,3 +131,4 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
*.db
|
||||
|
|
@ -2,9 +2,9 @@ import platform
|
|||
import socket
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import warnings
|
||||
|
||||
import click
|
||||
import httpx
|
||||
|
|
@ -443,6 +443,41 @@ def superuser(
|
|||
typer.echo("Superuser creation failed.")
|
||||
|
||||
|
||||
# command to copy the langflow database from the cache to the current directory
|
||||
# because now the database is stored per installation
|
||||
@app.command()
|
||||
def copy_db():
|
||||
"""
|
||||
Copy the database files to the current directory.
|
||||
|
||||
This function copies the 'langflow.db' and 'langflow-pre.db' files from the cache directory to the current directory.
|
||||
If the files exist in the cache directory, they will be copied to the same directory as this script (__main__.py).
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
import shutil
|
||||
|
||||
from platformdirs import user_cache_dir
|
||||
|
||||
cache_dir = Path(user_cache_dir("langflow"))
|
||||
db_path = cache_dir / "langflow.db"
|
||||
pre_db_path = cache_dir / "langflow-pre.db"
|
||||
# It should be copied to the current directory
|
||||
# this file is __main__.py and it should be in the same directory as the database
|
||||
destination_folder = Path(__file__).parent
|
||||
if db_path.exists():
|
||||
shutil.copy(db_path, destination_folder)
|
||||
typer.echo(f"Database copied to {destination_folder}")
|
||||
else:
|
||||
typer.echo("Database not found in the cache directory.")
|
||||
if pre_db_path.exists():
|
||||
shutil.copy(pre_db_path, destination_folder)
|
||||
typer.echo(f"Pre-release database copied to {destination_folder}")
|
||||
else:
|
||||
typer.echo("Pre-release database not found in the cache directory.")
|
||||
|
||||
|
||||
@app.command()
|
||||
def migration(
|
||||
test: bool = typer.Option(True, help="Run migrations in test mode."),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class StoreMessageComponent(CustomComponent):
|
|||
session_id: Optional[str] = None,
|
||||
message: str = "",
|
||||
) -> List[Record]:
|
||||
|
||||
store_message(
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)}",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import orjson
|
|||
from langchain_community.vectorstores import (
|
||||
FAISS,
|
||||
Chroma,
|
||||
ElasticsearchStore,
|
||||
MongoDBAtlasVectorSearch,
|
||||
Pinecone,
|
||||
Qdrant,
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ from typing import Any, List, Optional, Tuple, Type
|
|||
|
||||
import orjson
|
||||
import yaml
|
||||
from langflow.services.settings.constants import VARIABLES_TO_GET_FROM_ENVIRONMENT
|
||||
from loguru import logger
|
||||
from pydantic import field_validator, validator
|
||||
from pydantic import field_validator
|
||||
from pydantic.fields import FieldInfo
|
||||
from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
|
||||
|
||||
from langflow.services.settings.constants import VARIABLES_TO_GET_FROM_ENVIRONMENT
|
||||
|
||||
# BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
|
||||
BASE_COMPONENTS_PATH = str(Path(__file__).parent.parent.parent / "components")
|
||||
|
||||
|
|
@ -76,6 +75,10 @@ class Settings(BaseSettings):
|
|||
|
||||
# Define the default LANGFLOW_DIR
|
||||
CONFIG_DIR: Optional[str] = None
|
||||
# Define if langflow db should be saved in config dir or
|
||||
# in the langflow directory
|
||||
SAVE_DB_IN_CONFIG_DIR: bool = False
|
||||
"""Define if langflow database should be saved in LANGFLOW_CONFIG_DIR or in the langflow directory (i.e. in the package directory)."""
|
||||
|
||||
DEV: bool = False
|
||||
DATABASE_URL: Optional[str] = None
|
||||
|
|
@ -108,12 +111,16 @@ 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
|
||||
"""List of environment variables to get from the environment and store in the database."""
|
||||
|
||||
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
|
||||
@field_validator("CONFIG_DIR", mode="before")
|
||||
def set_langflow_dir(cls, value):
|
||||
if not value:
|
||||
from platformdirs import user_cache_dir
|
||||
|
|
@ -136,8 +143,8 @@ class Settings(BaseSettings):
|
|||
|
||||
return str(value)
|
||||
|
||||
@validator("DATABASE_URL", pre=True)
|
||||
def set_database_url(cls, value, values):
|
||||
@field_validator("DATABASE_URL", mode="before")
|
||||
def set_database_url(cls, value, info):
|
||||
if not value:
|
||||
logger.debug("No database_url provided, trying LANGFLOW_DATABASE_URL env variable")
|
||||
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
||||
|
|
@ -148,29 +155,36 @@ class Settings(BaseSettings):
|
|||
# Originally, we used sqlite:///./langflow.db
|
||||
# so we need to migrate to the new format
|
||||
# if there is a database in that location
|
||||
if not values["CONFIG_DIR"]:
|
||||
if not info.data["CONFIG_DIR"]:
|
||||
raise ValueError("CONFIG_DIR not set, please set it or provide a DATABASE_URL")
|
||||
from langflow.version import is_pre_release # type: ignore
|
||||
|
||||
if info.data["SAVE_DB_IN_CONFIG_DIR"]:
|
||||
database_dir = info.data["CONFIG_DIR"]
|
||||
logger.debug(f"Saving database to CONFIG_DIR: {database_dir}")
|
||||
else:
|
||||
database_dir = Path(__file__).parent.parent.parent.resolve()
|
||||
logger.debug(f"Saving database to langflow directory: {database_dir}")
|
||||
|
||||
pre_db_file_name = "langflow-pre.db"
|
||||
db_file_name = "langflow.db"
|
||||
new_pre_path = f"{values['CONFIG_DIR']}/{pre_db_file_name}"
|
||||
new_path = f"{values['CONFIG_DIR']}/{db_file_name}"
|
||||
new_pre_path = f"{database_dir}/{pre_db_file_name}"
|
||||
new_path = f"{database_dir}/{db_file_name}"
|
||||
final_path = None
|
||||
if is_pre_release:
|
||||
if Path(new_pre_path).exists():
|
||||
final_path = new_pre_path
|
||||
elif Path(new_path).exists():
|
||||
elif Path(new_path).exists() and info.data["SAVE_DB_IN_CONFIG_DIR"]:
|
||||
# We need to copy the current db to the new location
|
||||
logger.debug("Copying existing database to new location")
|
||||
copy2(new_path, new_pre_path)
|
||||
logger.debug(f"Copied existing database to {new_pre_path}")
|
||||
elif Path(f"./{db_file_name}").exists():
|
||||
elif Path(f"./{db_file_name}").exists() and info.data["SAVE_DB_IN_CONFIG_DIR"]:
|
||||
logger.debug("Copying existing database to new location")
|
||||
copy2(f"./{db_file_name}", new_pre_path)
|
||||
logger.debug(f"Copied existing database to {new_pre_path}")
|
||||
else:
|
||||
logger.debug(f"Database already exists at {new_pre_path}, using it")
|
||||
logger.debug(f"Creating new database at {new_pre_path}")
|
||||
final_path = new_pre_path
|
||||
else:
|
||||
if Path(new_path).exists():
|
||||
|
|
@ -311,3 +325,6 @@ def load_settings_from_yaml(file_path: str) -> Settings:
|
|||
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
|
||||
|
||||
return Settings(**settings_dict)
|
||||
return Settings(**settings_dict)
|
||||
return Settings(**settings_dict)
|
||||
return Settings(**settings_dict)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import os
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.settings.auth import AuthSettings
|
||||
from langflow.services.settings.base import Settings
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class SettingsService(Service):
|
||||
|
|
@ -31,7 +30,7 @@ class SettingsService(Service):
|
|||
|
||||
for key in settings_dict:
|
||||
if key not in Settings.model_fields.keys():
|
||||
raise KeyError(f"Key {key} not found in settings")
|
||||
logger.warning(f"Key {key} not found in settings")
|
||||
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
|
||||
|
||||
settings = Settings(**settings_dict)
|
||||
|
|
|
|||
36
src/backend/base/poetry.lock
generated
36
src/backend/base/poetry.lock
generated
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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 `} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import {
|
|||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
|
||||
import ExportModal from "../../modals/exportModal";
|
||||
import { Case } from "../../shared/components/caseComponent";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { codeTabsPropsType } from "../../types/components";
|
||||
|
|
@ -43,7 +45,6 @@ import KeypairListComponent from "../keypairListComponent";
|
|||
import ShadTooltip from "../shadTooltipComponent";
|
||||
import { Label } from "../ui/label";
|
||||
import { Switch } from "../ui/switch";
|
||||
import ExportModal from "../../modals/exportModal";
|
||||
|
||||
export default function CodeTabsComponent({
|
||||
flow,
|
||||
|
|
@ -54,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);
|
||||
|
|
@ -87,6 +89,10 @@ export default function CodeTabsComponent({
|
|||
});
|
||||
};
|
||||
|
||||
const type = (node, templateParam) => {
|
||||
return node.data.node.template[templateParam].type;
|
||||
};
|
||||
|
||||
const downloadAsFile = () => {
|
||||
const fileExtension = tabs[activeTab].language || ".txt";
|
||||
const suggestedFileName = `${"generated-code."}${fileExtension}`;
|
||||
|
|
@ -141,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 && (
|
||||
<>
|
||||
|
|
@ -276,107 +286,71 @@ export default function CodeTabsComponent({
|
|||
</TableCell>
|
||||
<TableCell className="p-0 text-xs text-foreground">
|
||||
<div className="m-auto w-[250px]">
|
||||
{node.data.node.template[
|
||||
templateField
|
||||
].type === "str" &&
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].options ? (
|
||||
<div className="mx-auto">
|
||||
{node.data.node.template[
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"str" &&
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
]?.list ? (
|
||||
<InputListComponent
|
||||
componentName={
|
||||
templateField
|
||||
}
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? [""]
|
||||
: node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks?.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : node.data.node.template[
|
||||
].options
|
||||
}
|
||||
>
|
||||
<Case
|
||||
condition={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].multiline ? (
|
||||
<div>
|
||||
<TextAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
!node.data.node
|
||||
.template[
|
||||
]?.list
|
||||
}
|
||||
>
|
||||
<InputListComponent
|
||||
componentName={templateField}
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? [""]
|
||||
: node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node
|
||||
.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks?.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node
|
||||
.template[
|
||||
templateField
|
||||
],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<InputComponent
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
password={
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks?.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].password ?? false
|
||||
}
|
||||
],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].multiline
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<TextAreaComponent
|
||||
disabled={false}
|
||||
editNode={true}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
|
|
@ -410,11 +384,68 @@ export default function CodeTabsComponent({
|
|||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "bool" ? (
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].multiline &&
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].list
|
||||
}
|
||||
>
|
||||
<InputComponent
|
||||
editNode={true}
|
||||
disabled={false}
|
||||
password={
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].password ?? false
|
||||
}
|
||||
value={
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value === ""
|
||||
? ""
|
||||
: node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
}
|
||||
onChange={(target) => {
|
||||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks?.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
templateField
|
||||
],
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Case>
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"bool"
|
||||
}
|
||||
>
|
||||
<div className="ml-auto">
|
||||
{" "}
|
||||
<ToggleShadComponent
|
||||
|
|
@ -446,9 +477,14 @@ export default function CodeTabsComponent({
|
|||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "file" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"file"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
editNode={true}
|
||||
|
|
@ -473,9 +509,14 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "float" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"float"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<FloatComponent
|
||||
disabled={false}
|
||||
|
|
@ -518,12 +559,17 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "str" &&
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].options ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"str" &&
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].options
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<Dropdown
|
||||
editNode={true}
|
||||
|
|
@ -565,9 +611,14 @@ export default function CodeTabsComponent({
|
|||
}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "int" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"int"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<IntComponent
|
||||
disabled={false}
|
||||
|
|
@ -610,9 +661,14 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "prompt" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"prompt"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
readonly={true}
|
||||
|
|
@ -651,9 +707,14 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "code" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"code"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
disabled={false}
|
||||
|
|
@ -692,9 +753,14 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "dict" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"dict"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto overflow-auto custom-scroll">
|
||||
<KeypairListComponent
|
||||
disabled={false}
|
||||
|
|
@ -712,6 +778,10 @@ export default function CodeTabsComponent({
|
|||
.template[
|
||||
templateField
|
||||
].value,
|
||||
type(
|
||||
node,
|
||||
templateField,
|
||||
),
|
||||
)
|
||||
}
|
||||
duplicateKey={
|
||||
|
|
@ -755,9 +825,14 @@ export default function CodeTabsComponent({
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "NestedDict" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"NestedDict"
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<DictComponent
|
||||
disabled={false}
|
||||
|
|
@ -765,7 +840,7 @@ export default function CodeTabsComponent({
|
|||
value={
|
||||
node.data.node!.template[
|
||||
templateField
|
||||
].value.toString() === "{}"
|
||||
].value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
|
|
@ -795,13 +870,16 @@ export default function CodeTabsComponent({
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : node.data.node.template[
|
||||
templateField
|
||||
].type === "Any" ? (
|
||||
"-"
|
||||
) : (
|
||||
<div className="hidden"></div>
|
||||
)}
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(node, templateField) ===
|
||||
"Any"
|
||||
}
|
||||
>
|
||||
<>-</>
|
||||
</Case>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -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) => ({
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default function Dropdown({
|
|||
? "dropdown-component-outline"
|
||||
: "dropdown-component-false-outline",
|
||||
"w-full justify-between font-normal",
|
||||
editNode ? "input-edit-node" : "py-2"
|
||||
editNode ? "input-edit-node" : "py-2",
|
||||
)}
|
||||
>
|
||||
<span data-testid={`value-dropdown-` + id}>
|
||||
|
|
@ -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
|
||||
|
|
@ -105,7 +107,7 @@ export default function Dropdown({
|
|||
name="Check"
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4 text-primary",
|
||||
value === option ? "opacity-100" : "opacity-0"
|
||||
value === option ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -59,7 +59,7 @@ export async function sendAll(data: sendAllProps) {
|
|||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string
|
||||
code: string,
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ export async function postValidateCode(
|
|||
export async function postValidatePrompt(
|
||||
name: string,
|
||||
template: string,
|
||||
frontend_node: APIClassType
|
||||
frontend_node: APIClassType,
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return api.post(`${BASE_URL_API}validate/prompt`, {
|
||||
name,
|
||||
|
|
@ -145,7 +145,7 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
* @throws Will throw an error if the update fails.
|
||||
*/
|
||||
export async function updateFlowInDatabase(
|
||||
updatedFlow: FlowType
|
||||
updatedFlow: FlowType,
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||
|
|
@ -321,7 +321,7 @@ export async function getHealth() {
|
|||
*
|
||||
*/
|
||||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
flowId: string,
|
||||
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
|
||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ export async function getBuildStatus(
|
|||
*
|
||||
*/
|
||||
export async function postBuildInit(
|
||||
flow: FlowType
|
||||
flow: FlowType,
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
|
@ -350,7 +350,7 @@ export async function postBuildInit(
|
|||
*/
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
id: string
|
||||
id: string,
|
||||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
|
@ -359,7 +359,7 @@ export async function uploadFile(
|
|||
|
||||
export async function postCustomComponent(
|
||||
code: string,
|
||||
apiClass: APIClassType
|
||||
apiClass: APIClassType,
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
// let template = apiClass.template;
|
||||
return await api.post(`${BASE_URL_API}custom_component`, {
|
||||
|
|
@ -372,7 +372,7 @@ export async function postCustomComponentUpdate(
|
|||
code: string,
|
||||
template: APITemplateType,
|
||||
field: string,
|
||||
field_value: any
|
||||
field_value: any,
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
||||
code,
|
||||
|
|
@ -394,7 +394,7 @@ export async function onLogin(user: LoginType) {
|
|||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
|
|
@ -456,11 +456,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
|||
|
||||
export async function getUsersPage(
|
||||
skip: number,
|
||||
limit: number
|
||||
limit: number,
|
||||
): Promise<Array<Users>> {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
|
||||
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -497,7 +497,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
|
|||
try {
|
||||
const res = await api.patch(
|
||||
`${BASE_URL_API}users/${user_id}/reset-password`,
|
||||
user
|
||||
user,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -571,7 +571,7 @@ export async function saveFlowStore(
|
|||
last_tested_version?: string;
|
||||
},
|
||||
tags: string[],
|
||||
publicFlow = false
|
||||
publicFlow = false,
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}store/components/`, {
|
||||
|
|
@ -700,7 +700,7 @@ export async function postStoreComponents(component: Component) {
|
|||
export async function getComponent(component_id: string) {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}store/components/${component_id}`
|
||||
`${BASE_URL_API}store/components/${component_id}`,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -715,7 +715,7 @@ export async function searchComponent(
|
|||
page?: number | null,
|
||||
limit?: number | null,
|
||||
status?: string | null,
|
||||
tags?: string[]
|
||||
tags?: string[],
|
||||
): Promise<StoreComponentResponse | undefined> {
|
||||
try {
|
||||
let url = `${BASE_URL_API}store/components/`;
|
||||
|
|
@ -827,7 +827,7 @@ export async function updateFlowStore(
|
|||
},
|
||||
tags: string[],
|
||||
publicFlow = false,
|
||||
id: string
|
||||
id: string,
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
|
||||
|
|
@ -911,7 +911,7 @@ export async function deleteGlobalVariable(id: string) {
|
|||
export async function updateGlobalVariable(
|
||||
name: string,
|
||||
value: string,
|
||||
id: string
|
||||
id: string,
|
||||
) {
|
||||
try {
|
||||
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
|
||||
|
|
@ -929,36 +929,40 @@ 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(
|
||||
flowId: string,
|
||||
vertexId: string,
|
||||
input_value: string
|
||||
input_value: string,
|
||||
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
||||
// input_value is optional and is a query parameter
|
||||
return await api.post(
|
||||
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
|
||||
input_value ? { inputs: { input_value: input_value } } : undefined
|
||||
input_value ? { inputs: { input_value: input_value } } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -982,7 +986,7 @@ export async function getFlowPool({
|
|||
}
|
||||
|
||||
export async function deleteFlowPool(
|
||||
flowId: string
|
||||
flowId: string,
|
||||
): Promise<AxiosResponse<any>> {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: flowId };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||
import DictComponent from "../../../../components/dictComponent";
|
||||
|
|
@ -18,20 +18,15 @@ import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
|||
import { Button } from "../../../../components/ui/button";
|
||||
import { RefreshButton } from "../../../../components/ui/refreshButton";
|
||||
import {
|
||||
INPUT_HANDLER_HOVER,
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
OUTPUT_HANDLER_HOVER,
|
||||
TOOLTIP_EMPTY,
|
||||
} from "../../../../constants/constants";
|
||||
import { Case } from "../../../../shared/components/caseComponent";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import {
|
||||
APIClassType,
|
||||
ResponseErrorDetailAPI,
|
||||
ResponseErrorTypeAPI,
|
||||
} from "../../../../types/api";
|
||||
import { APIClassType } from "../../../../types/api";
|
||||
import { ParameterComponentType } from "../../../../types/components";
|
||||
import {
|
||||
debouncedHandleUpdateValues,
|
||||
|
|
@ -44,13 +39,13 @@ import {
|
|||
isValidConnection,
|
||||
scapedJSONStringfy,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import {
|
||||
nodeColors,
|
||||
nodeIconsLucide,
|
||||
nodeNames,
|
||||
} from "../../../../utils/styleUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import groupByFamily from "./utils/group-by-family";
|
||||
import { nodeColors } from "../../../../utils/styleUtils";
|
||||
import { classNames, groupByFamily } from "../../../../utils/utils";
|
||||
import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount";
|
||||
import useHandleOnNewValue from "../../../hooks/use-handle-new-value";
|
||||
import useHandleNodeClass from "../../../hooks/use-handle-node-class";
|
||||
import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons";
|
||||
import TooltipRenderComponent from "../tooltipRenderComponent";
|
||||
|
||||
export default function ParameterComponent({
|
||||
left,
|
||||
|
|
@ -76,175 +71,69 @@ export default function ParameterComponent({
|
|||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
|
||||
const myData = useTypesStore((state) => state.data);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
const flow = currentFlow?.data?.nodes ?? null;
|
||||
|
||||
const groupedEdge = useRef(null);
|
||||
|
||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||
|
||||
const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue(
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
handleUpdateValues,
|
||||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
);
|
||||
|
||||
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
setNode,
|
||||
updateNodeInternals,
|
||||
renderTooltips,
|
||||
);
|
||||
|
||||
const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
|
||||
useHandleRefreshButtonPress(setIsLoading, setNode, renderTooltips);
|
||||
|
||||
let disabled =
|
||||
edges.some(
|
||||
(edge) =>
|
||||
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
|
||||
) ?? false;
|
||||
|
||||
const myData = useTypesStore((state) => state.data);
|
||||
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
|
||||
const handleRefreshButtonPress = async (name, data) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
let newTemplate = await handleUpdateValues(name, data);
|
||||
|
||||
if (newTemplate) {
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
newNode.data.node.template = newTemplate;
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorDetailAPI;
|
||||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
handleRefreshButtonPressHook(name, data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
if (
|
||||
(data.node?.template[name]?.real_time_refresh ||
|
||||
data.node?.template[name]?.refresh_button) &&
|
||||
// options can be undefined but not an empty array
|
||||
(data.node?.template[name]?.options?.length ?? 0) === 0
|
||||
) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
let newTemplate = await handleUpdateValues(name, data);
|
||||
|
||||
if (newTemplate) {
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
newNode.data.node.template = newTemplate;
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorDetailAPI;
|
||||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, []);
|
||||
useFetchDataOnMount(
|
||||
data,
|
||||
name,
|
||||
handleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
setIsLoading,
|
||||
);
|
||||
|
||||
const handleOnNewValue = async (
|
||||
newValue: string | string[] | boolean | Object[],
|
||||
skipSnapshot: boolean | undefined = false,
|
||||
): Promise<void> => {
|
||||
const nodeTemplate = data.node!.template[name];
|
||||
const currentValue = nodeTemplate.value;
|
||||
|
||||
if (currentValue !== newValue && !skipSnapshot) {
|
||||
takeSnapshot();
|
||||
}
|
||||
|
||||
const shouldUpdate =
|
||||
data.node?.template[name].real_time_refresh &&
|
||||
!data.node?.template[name].refresh_button &&
|
||||
currentValue !== newValue;
|
||||
|
||||
const typeToDebounce = nodeTemplate.type;
|
||||
|
||||
nodeTemplate.value = newValue;
|
||||
|
||||
let newTemplate;
|
||||
if (shouldUpdate) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (["int"].includes(typeToDebounce)) {
|
||||
newTemplate = await handleUpdateValues(name, data);
|
||||
} else {
|
||||
newTemplate = await debouncedHandleUpdateValues(name, data);
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorTypeAPI;
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail.error ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setNode(data.id, (oldNode) => {
|
||||
const newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
|
||||
if (data.node?.template[name].real_time_refresh && newTemplate) {
|
||||
newNode.data.node.template = newTemplate;
|
||||
} else {
|
||||
newNode.data.node.template[name].value = newValue;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
renderTooltips();
|
||||
handleOnNewValueHook(newValue, skipSnapshot);
|
||||
};
|
||||
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
|
||||
if (!data.node) return;
|
||||
if (data.node!.template[name].value !== code) {
|
||||
takeSnapshot();
|
||||
}
|
||||
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
node: newNodeClass,
|
||||
description: newNodeClass.description ?? data.node!.description,
|
||||
display_name: newNodeClass.display_name ?? data.node!.display_name,
|
||||
};
|
||||
|
||||
newNode.data.node.template[name].value = code;
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
updateNodeInternals(data.id);
|
||||
|
||||
renderTooltips();
|
||||
handleNodeClassHook(newNodeClass, code);
|
||||
};
|
||||
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
infoHtml.current = (
|
||||
|
|
@ -265,88 +154,7 @@ export default function ParameterComponent({
|
|||
if (groupedObj && groupedObj.length > 0) {
|
||||
//@ts-ignore
|
||||
refHtml.current = groupedObj.map((item, index) => {
|
||||
const Icon: any =
|
||||
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
data-testid={`available-${left ? "input" : "output"}-${
|
||||
item.family
|
||||
}`}
|
||||
>
|
||||
{index === 0 && (
|
||||
<span>{left ? INPUT_HANDLER_HOVER : OUTPUT_HANDLER_HOVER}</span>
|
||||
)}
|
||||
<span
|
||||
key={index}
|
||||
className={classNames(
|
||||
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="h-5 w-5"
|
||||
style={{
|
||||
color: nodeColors[item.family],
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className="h-5 w-5"
|
||||
strokeWidth={1.5}
|
||||
style={{
|
||||
color: nodeColors[item.family] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="ps-2 text-xs text-foreground"
|
||||
data-testid={`tooltip-${nodeNames[item.family] ?? "Other"}`}
|
||||
>
|
||||
{nodeNames[item.family] ?? "Other"}{" "}
|
||||
{item?.display_name && item?.display_name?.length > 0 ? (
|
||||
<span
|
||||
className="text-xs"
|
||||
data-testid={`tooltip-${item?.display_name}`}
|
||||
>
|
||||
{" "}
|
||||
{item.display_name === "" ? "" : " - "}
|
||||
{item.display_name.split(", ").length > 2
|
||||
? item.display_name.split(", ").map((el, index) => (
|
||||
<React.Fragment key={el + name}>
|
||||
<span>
|
||||
{index ===
|
||||
item.display_name.split(", ").length - 1
|
||||
? el
|
||||
: (el += `, `)}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))
|
||||
: item.display_name}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className="text-xs"
|
||||
data-testid={`tooltip-${item?.type}`}
|
||||
>
|
||||
{" "}
|
||||
{item.type === "" ? "" : " - "}
|
||||
{item.type.split(", ").length > 2
|
||||
? item.type.split(", ").map((el, index) => (
|
||||
<React.Fragment key={el + name}>
|
||||
<span>
|
||||
{index === item.type.split(", ").length - 1
|
||||
? el
|
||||
: (el += `, `)}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))
|
||||
: item.type}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
return <TooltipRenderComponent index={index} item={item} left={left} />;
|
||||
});
|
||||
} else {
|
||||
//@ts-ignore
|
||||
|
|
@ -355,6 +163,7 @@ export default function ParameterComponent({
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If optionalHandle is an empty list, then it is not an optional handle
|
||||
if (optionalHandle && optionalHandle.length === 0) {
|
||||
optionalHandle = null;
|
||||
|
|
@ -363,6 +172,9 @@ export default function ParameterComponent({
|
|||
useEffect(() => {
|
||||
renderTooltips();
|
||||
}, [tooltipTitle, flow]);
|
||||
|
||||
console.log(left === true && type === "dict");
|
||||
|
||||
return !showNode ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
|
|
@ -428,11 +240,12 @@ export default function ParameterComponent({
|
|||
(left ? "" : " justify-end")
|
||||
}
|
||||
>
|
||||
{!left && data.node?.frozen && (
|
||||
<Case condition={left && data.node?.frozen}>
|
||||
<div className="pr-1">
|
||||
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
|
||||
</div>
|
||||
)}
|
||||
</Case>
|
||||
|
||||
{proxy ? (
|
||||
<ShadTooltip content={<span>{proxy.id}</span>}>
|
||||
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
|
||||
|
|
@ -478,45 +291,37 @@ export default function ParameterComponent({
|
|||
}`}
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
key={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
id={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
key={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
|
||||
id={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, nodes, edges)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "-ml-0.5 " : "-mr-0.5 ",
|
||||
left ? "-ml-0.5" : "-mr-0.5",
|
||||
"h-3 w-3 rounded-full border-2 bg-background",
|
||||
)}
|
||||
style={{
|
||||
borderColor: color ?? nodeColors.unknown,
|
||||
}}
|
||||
onClick={() => {
|
||||
setFilterEdge(groupedEdge.current);
|
||||
}}
|
||||
></Handle>
|
||||
style={{ borderColor: color ?? nodeColors.unknown }}
|
||||
onClick={() => setFilterEdge(groupedEdge.current)}
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{left === true &&
|
||||
type === "str" &&
|
||||
!data.node?.template[name].options ? (
|
||||
<Case
|
||||
condition={
|
||||
left === true &&
|
||||
type === "str" &&
|
||||
!data.node?.template[name]?.options
|
||||
}
|
||||
>
|
||||
<div className="w-full">
|
||||
{data.node?.template[name].list ? (
|
||||
<Case condition={data.node?.template[name]?.list}>
|
||||
<div
|
||||
className={
|
||||
// Commenting this out until we have a better
|
||||
// way to display
|
||||
// (data.node?.template[name].refresh ? "w-5/6 " : "") +
|
||||
// (data.node?.template[name]?.refresh ? "w-5/6 " : "") +
|
||||
"flex-grow"
|
||||
}
|
||||
>
|
||||
|
|
@ -524,39 +329,27 @@ export default function ParameterComponent({
|
|||
componentName={name}
|
||||
disabled={disabled}
|
||||
value={
|
||||
!data.node.template[name].value ||
|
||||
data.node.template[name].value === ""
|
||||
!data.node!.template[name]?.value ||
|
||||
data.node!.template[name]?.value === ""
|
||||
? [""]
|
||||
: data.node.template[name].value
|
||||
: data.node!.template[name]?.value
|
||||
}
|
||||
onChange={handleOnNewValue}
|
||||
/>
|
||||
{/* {data.node?.template[name].refresh_button && (
|
||||
<div className="w-1/6">
|
||||
<RefreshButton
|
||||
isLoading={isLoading}
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
data={data}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
) : data.node?.template[name].multiline ? (
|
||||
</Case>
|
||||
<Case condition={data.node?.template[name]?.multiline}>
|
||||
<div className="mt-2 flex w-full flex-col ">
|
||||
<div className="flex-grow">
|
||||
<TextAreaComponent
|
||||
disabled={disabled}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
value={data.node!.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"textarea-" + data.node.template[name].name}
|
||||
data-testid={"textarea-" + data.node.template[name].name}
|
||||
id={"textarea-" + data.node!.template[name]?.name}
|
||||
data-testid={"textarea-" + data.node!.template[name]?.name}
|
||||
/>
|
||||
</div>
|
||||
{data.node?.template[name].refresh_button && (
|
||||
{data.node?.template[name]?.refresh_button && (
|
||||
<div className="flex-grow">
|
||||
<RefreshButton
|
||||
isLoading={isLoading}
|
||||
|
|
@ -564,7 +357,7 @@ export default function ParameterComponent({
|
|||
name={name}
|
||||
data={data}
|
||||
button_text={
|
||||
data.node?.template[name].refresh_button_text ??
|
||||
data.node?.template[name]?.refresh_button_text ??
|
||||
"Refresh"
|
||||
}
|
||||
className="extra-side-bar-buttons mt-1"
|
||||
|
|
@ -574,12 +367,18 @@ export default function ParameterComponent({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
</Case>
|
||||
<Case
|
||||
condition={
|
||||
!data.node?.template[name]?.multiline &&
|
||||
!data.node?.template[name]?.list
|
||||
}
|
||||
>
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div
|
||||
className={
|
||||
"flex-grow " +
|
||||
(data.node?.template[name].refresh_button ? "w-5/6" : "")
|
||||
(data.node?.template[name]?.refresh_button ? "w-5/6" : "")
|
||||
}
|
||||
>
|
||||
<InputGlobalComponent
|
||||
|
|
@ -599,7 +398,7 @@ export default function ParameterComponent({
|
|||
data={data}
|
||||
/>
|
||||
</div>
|
||||
{data.node?.template[name].refresh_button && (
|
||||
{data.node?.template[name]?.refresh_button && (
|
||||
<div className="w-1/6">
|
||||
<RefreshButton
|
||||
isLoading={isLoading}
|
||||
|
|
@ -607,7 +406,7 @@ export default function ParameterComponent({
|
|||
name={name}
|
||||
data={data}
|
||||
button_text={
|
||||
data.node?.template[name].refresh_button_text ??
|
||||
data.node?.template[name]?.refresh_button_text ??
|
||||
"Refresh"
|
||||
}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
|
|
@ -617,52 +416,61 @@ export default function ParameterComponent({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Case>
|
||||
</div>
|
||||
) : left === true && type === "bool" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "bool"}>
|
||||
<div className="mt-2 w-full">
|
||||
<ToggleShadComponent
|
||||
id={"toggle-" + name}
|
||||
disabled={disabled}
|
||||
enabled={data.node?.template[name].value ?? false}
|
||||
enabled={data.node?.template[name]?.value ?? false}
|
||||
setEnabled={handleOnNewValue}
|
||||
size="large"
|
||||
editNode={false}
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "float" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "float"}>
|
||||
<div className="mt-2 w-full">
|
||||
<FloatComponent
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
rangeSpec={data.node?.template[name]?.rangeSpec}
|
||||
onChange={handleOnNewValue}
|
||||
/>
|
||||
</div>
|
||||
) : left === true &&
|
||||
type === "str" &&
|
||||
(data.node?.template[name].options ||
|
||||
data.node?.template[name]?.real_time_refresh) ? (
|
||||
// TODO: Improve CSS
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
left === true &&
|
||||
type === "str" &&
|
||||
(data.node?.template[name]?.options ||
|
||||
data.node?.template[name]?.real_time_refresh)
|
||||
}
|
||||
>
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div className="w-5/6 flex-grow">
|
||||
<Dropdown
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
options={data.node.template[name].options}
|
||||
options={data.node!.template[name]?.options}
|
||||
onSelect={handleOnNewValue}
|
||||
value={data.node.template[name].value}
|
||||
value={data.node!.template[name]?.value}
|
||||
id={"dropdown-" + name}
|
||||
/>
|
||||
</div>
|
||||
{data.node?.template[name].refresh_button && (
|
||||
{data.node?.template[name]?.refresh_button && (
|
||||
<div className="w-1/6">
|
||||
<RefreshButton
|
||||
isLoading={isLoading}
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
data={data}
|
||||
button_text={data.node?.template[name].refresh_button_text}
|
||||
button_text={data.node?.template[name]?.refresh_button_text}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
|
|
@ -670,46 +478,54 @@ export default function ParameterComponent({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : left === true && type === "code" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "code"}>
|
||||
<div className="mt-2 w-full">
|
||||
<CodeAreaComponent
|
||||
readonly={
|
||||
data.node?.flow && data.node.template[name].dynamic
|
||||
data.node?.flow && data.node.template[name]?.dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
dynamic={data.node?.template[name].dynamic ?? false}
|
||||
dynamic={data.node?.template[name]?.dynamic ?? false}
|
||||
setNodeClass={handleNodeClass}
|
||||
nodeClass={data.node}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"code-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "file" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "file"}>
|
||||
<div className="mt-2 w-full">
|
||||
<InputFileComponent
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
fileTypes={data.node?.template[name].fileTypes}
|
||||
fileTypes={data.node?.template[name]?.fileTypes}
|
||||
onFileChange={(filePath: string) => {
|
||||
data.node!.template[name].file_path = filePath;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : left === true && type === "int" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "int"}>
|
||||
<div className="mt-2 w-full">
|
||||
<IntComponent
|
||||
rangeSpec={data.node?.template[name].rangeSpec}
|
||||
rangeSpec={data.node?.template[name]?.rangeSpec}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"int-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "prompt" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "prompt"}>
|
||||
<div className="mt-2 w-full">
|
||||
<PromptAreaComponent
|
||||
readonly={data.node?.flow ? true : false}
|
||||
|
|
@ -717,57 +533,59 @@ export default function ParameterComponent({
|
|||
setNodeClass={handleNodeClass}
|
||||
nodeClass={data.node}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name].value ?? ""}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"prompt-input-" + name}
|
||||
data-testid={"prompt-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "NestedDict" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "NestedDict"}>
|
||||
<div className="mt-2 w-full">
|
||||
<DictComponent
|
||||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
!data.node!.template[name].value ||
|
||||
data.node!.template[name].value?.toString() === "{}"
|
||||
!data.node!.template[name]?.value ||
|
||||
data.node!.template[name]?.value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
: data.node!.template[name].value
|
||||
: data.node!.template[name]?.value
|
||||
}
|
||||
onChange={handleOnNewValue}
|
||||
id="div-dict-input"
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "dict" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "dict"}>
|
||||
<div className="mt-2 w-full">
|
||||
<KeypairListComponent
|
||||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
data.node!.template[name].value?.length === 0 ||
|
||||
!data.node!.template[name].value
|
||||
data.node!.template[name]?.value?.length === 0 ||
|
||||
!data.node!.template[name]?.value
|
||||
? [{ "": "" }]
|
||||
: convertObjToArray(data.node!.template[name].value)
|
||||
: convertObjToArray(data.node!.template[name]?.value, type!)
|
||||
}
|
||||
duplicateKey={errorDuplicateKey}
|
||||
onChange={(newValue) => {
|
||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||
// if data.node?.template[name].list is true, then the value is an array of objects
|
||||
// if data.node?.template[name]?.list is true, then the value is an array of objects
|
||||
// else we need to get the first object of the array
|
||||
|
||||
if (data.node?.template[name].list) {
|
||||
if (data.node?.template[name]?.list) {
|
||||
handleOnNewValue(valueToNumbers);
|
||||
} else handleOnNewValue(valueToNumbers[0]);
|
||||
}}
|
||||
isList={data.node?.template[name].list ?? false}
|
||||
isList={data.node?.template[name]?.list ?? false}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Case>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import React from "react";
|
||||
import {
|
||||
INPUT_HANDLER_HOVER,
|
||||
OUTPUT_HANDLER_HOVER,
|
||||
} from "../../../../constants/constants";
|
||||
import {
|
||||
nodeColors,
|
||||
nodeIconsLucide,
|
||||
nodeNames,
|
||||
} from "../../../../utils/styleUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
|
||||
const TooltipRenderComponent = ({ item, index, left }) => {
|
||||
const Icon = nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
data-testid={`available-${left ? "input" : "output"}-${item.family}`}
|
||||
>
|
||||
{index === 0 && (
|
||||
<span>{left ? INPUT_HANDLER_HOVER : OUTPUT_HANDLER_HOVER}</span>
|
||||
)}
|
||||
<span
|
||||
key={index}
|
||||
className={classNames(
|
||||
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="h-5 w-5"
|
||||
style={{
|
||||
color: nodeColors[item.family],
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className="h-5 w-5"
|
||||
strokeWidth={1.5}
|
||||
style={{
|
||||
color: nodeColors[item.family] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="ps-2 text-xs text-foreground"
|
||||
data-testid={`tooltip-${nodeNames[item.family] ?? "Other"}`}
|
||||
>
|
||||
{nodeNames[item.family] ?? "Other"}{" "}
|
||||
{item?.display_name && item?.display_name?.length > 0 ? (
|
||||
<span
|
||||
className="text-xs"
|
||||
data-testid={`tooltip-${item?.display_name}`}
|
||||
>
|
||||
{" "}
|
||||
{item.display_name === "" ? "" : " - "}
|
||||
{item.display_name.split(", ").length > 2
|
||||
? item.display_name.split(", ").map((el, index) => (
|
||||
<React.Fragment key={el + name}>
|
||||
<span>
|
||||
{index === item.display_name.split(", ").length - 1
|
||||
? el
|
||||
: (el += `, `)}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))
|
||||
: item.display_name}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs" data-testid={`tooltip-${item?.type}`}>
|
||||
{" "}
|
||||
{item.type === "" ? "" : " - "}
|
||||
{item.type.split(", ").length > 2
|
||||
? item.type.split(", ").map((el, index) => (
|
||||
<React.Fragment key={el + name}>
|
||||
<span>
|
||||
{index === item.type.split(", ").length - 1
|
||||
? el
|
||||
: (el += `, `)}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))
|
||||
: item.type}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TooltipRenderComponent;
|
||||
|
|
@ -54,14 +54,14 @@ export default function GenericNode({
|
|||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!
|
||||
data.node?.description!,
|
||||
);
|
||||
const [isOutdated, setIsOutdated] = useState(false);
|
||||
const buildStatus = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.status
|
||||
(state) => state.flowBuildStatus[data.id]?.status,
|
||||
);
|
||||
const lastRunTime = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp,
|
||||
);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
useState<validationStatusType | null>(null);
|
||||
|
|
@ -118,7 +118,7 @@ export default function GenericNode({
|
|||
|
||||
updateNodeInternals(data.id);
|
||||
},
|
||||
[data.id, data.node, setNode, setIsOutdated]
|
||||
[data.id, data.node, setNode, setIsOutdated],
|
||||
);
|
||||
|
||||
if (!data.node!.template) {
|
||||
|
|
@ -258,7 +258,7 @@ export default function GenericNode({
|
|||
const isDark = useDarkStore((state) => state.dark);
|
||||
const renderIconStatus = (
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
if (buildStatus === BuildStatus.BUILDING) {
|
||||
return <Loading className="text-medium-indigo" />;
|
||||
|
|
@ -299,11 +299,11 @@ export default function GenericNode({
|
|||
};
|
||||
const getSpecificClassFromBuildStatus = (
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
let isInvalid = validationStatus && !validationStatus.valid;
|
||||
|
||||
if (buildStatus === BuildStatus.INACTIVE && isInvalid) {
|
||||
if (buildStatus === BuildStatus.INACTIVE) {
|
||||
// INACTIVE should have its own class
|
||||
return "inactive-status";
|
||||
}
|
||||
|
|
@ -323,19 +323,20 @@ export default function GenericNode({
|
|||
selected: boolean,
|
||||
showNode: boolean,
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
||||
buildStatus,
|
||||
validationStatus
|
||||
validationStatus,
|
||||
);
|
||||
|
||||
const baseBorderClass = getBaseBorderClass(selected);
|
||||
const nodeSizeClass = getNodeSizeClass(showNode);
|
||||
return classNames(
|
||||
baseBorderClass,
|
||||
nodeSizeClass,
|
||||
"generic-node-div",
|
||||
specificClassFromBuildStatus
|
||||
specificClassFromBuildStatus,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -383,7 +384,6 @@ export default function GenericNode({
|
|||
isOutdated,
|
||||
selected,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{memoizedNodeToolbarComponent}
|
||||
|
|
@ -392,7 +392,7 @@ export default function GenericNode({
|
|||
selected,
|
||||
showNode,
|
||||
buildStatus,
|
||||
validationStatus
|
||||
validationStatus,
|
||||
)}
|
||||
>
|
||||
{data.node?.beta && showNode && (
|
||||
|
|
@ -537,7 +537,7 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField
|
||||
templateField,
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
|
|
@ -565,7 +565,7 @@ export default function GenericNode({
|
|||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
/>
|
||||
)
|
||||
),
|
||||
)}
|
||||
<ParameterComponent
|
||||
key={scapedJSONStringfy({
|
||||
|
|
@ -722,7 +722,7 @@ export default function GenericNode({
|
|||
!data.node?.description) &&
|
||||
nameEditable
|
||||
? "font-light italic"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
onDoubleClick={(e) => {
|
||||
setInputDescription(true);
|
||||
|
|
@ -784,13 +784,13 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField
|
||||
templateField,
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
tooltipTitle={
|
||||
data.node?.template[templateField].input_types?.join(
|
||||
"\n"
|
||||
"\n",
|
||||
) ?? data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node!.template[templateField].required}
|
||||
|
|
@ -817,7 +817,7 @@ export default function GenericNode({
|
|||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
"flex-max-width justify-center"
|
||||
"flex-max-width justify-center",
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import { useEffect } from "react";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { ResponseErrorDetailAPI } from "../../types/api";
|
||||
|
||||
const useFetchDataOnMount = (
|
||||
data,
|
||||
name,
|
||||
handleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
setIsLoading,
|
||||
) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
if (
|
||||
(data.node?.template[name]?.real_time_refresh ||
|
||||
data.node?.template[name]?.refresh_button) &&
|
||||
// options can be undefined but not an empty array
|
||||
(data.node?.template[name]?.options?.length ?? 0) === 0
|
||||
) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
let newTemplate = await handleUpdateValues(name, data);
|
||||
|
||||
if (newTemplate) {
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
newNode.data.node.template = newTemplate;
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorDetailAPI;
|
||||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, []); // Empty dependency array ensures that this effect runs only once, on mount
|
||||
};
|
||||
|
||||
export default useFetchDataOnMount;
|
||||
75
src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
Normal file
75
src/frontend/src/customNodes/hooks/use-handle-new-value.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { ResponseErrorTypeAPI } from "../../types/api";
|
||||
|
||||
const useHandleOnNewValue = (
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
handleUpdateValues,
|
||||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const handleOnNewValue = async (newValue, skipSnapshot = false) => {
|
||||
const nodeTemplate = data.node!.template[name];
|
||||
const currentValue = nodeTemplate.value;
|
||||
|
||||
if (currentValue !== newValue && !skipSnapshot) {
|
||||
takeSnapshot();
|
||||
}
|
||||
|
||||
const shouldUpdate =
|
||||
data.node?.template[name].real_time_refresh &&
|
||||
!data.node?.template[name].refresh_button &&
|
||||
currentValue !== newValue;
|
||||
|
||||
const typeToDebounce = nodeTemplate.type;
|
||||
|
||||
nodeTemplate.value = newValue;
|
||||
|
||||
let newTemplate;
|
||||
if (shouldUpdate) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (["int"].includes(typeToDebounce)) {
|
||||
newTemplate = await handleUpdateValues(name, data);
|
||||
} else {
|
||||
newTemplate = await debouncedHandleUpdateValues(name, data);
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorTypeAPI;
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail.error ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setNode(data.id, (oldNode) => {
|
||||
const newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
|
||||
if (data.node?.template[name].real_time_refresh && newTemplate) {
|
||||
newNode.data.node.template = newTemplate;
|
||||
} else {
|
||||
newNode.data.node.template[name].value = newValue;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleOnNewValue };
|
||||
};
|
||||
|
||||
export default useHandleOnNewValue;
|
||||
40
src/frontend/src/customNodes/hooks/use-handle-node-class.tsx
Normal file
40
src/frontend/src/customNodes/hooks/use-handle-node-class.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
|
||||
const useHandleNodeClass = (
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
setNode,
|
||||
updateNodeInternals,
|
||||
renderTooltips,
|
||||
) => {
|
||||
const handleNodeClass = (newNodeClass, code) => {
|
||||
if (!data.node) return;
|
||||
if (data.node!.template[name].value !== code) {
|
||||
takeSnapshot();
|
||||
}
|
||||
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
node: newNodeClass,
|
||||
description: newNodeClass.description ?? data.node!.description,
|
||||
display_name: newNodeClass.display_name ?? data.node!.display_name,
|
||||
};
|
||||
|
||||
newNode.data.node.template[name].value = code;
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
updateNodeInternals(data.id);
|
||||
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleNodeClass };
|
||||
};
|
||||
|
||||
export default useHandleNodeClass;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { ResponseErrorDetailAPI } from "../../types/api";
|
||||
import { handleUpdateValues } from "../../utils/parameterUtils";
|
||||
|
||||
const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const handleRefreshButtonPress = async (name, data) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
let newTemplate = await handleUpdateValues(name, data);
|
||||
|
||||
if (newTemplate) {
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
newNode.data.node.template = newTemplate;
|
||||
return newNode;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
let responseError = error as ResponseErrorDetailAPI;
|
||||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleRefreshButtonPress };
|
||||
};
|
||||
|
||||
export default useHandleRefreshButtonPress;
|
||||
|
|
@ -114,19 +114,19 @@ export default function ChatMessage({
|
|||
<div
|
||||
className={classNames(
|
||||
"form-modal-chat-position",
|
||||
chat.isSend ? "" : " "
|
||||
chat.isSend ? "" : " ",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"mr-3 mt-1 flex w-24 flex-col items-center gap-1 overflow-hidden px-3 pb-3"
|
||||
"mr-3 mt-1 flex w-24 flex-col items-center gap-1 overflow-hidden px-3 pb-3",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl",
|
||||
!chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon"
|
||||
!chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon",
|
||||
)}
|
||||
>
|
||||
<img
|
||||
|
|
@ -210,12 +210,12 @@ dark:prose-invert"
|
|||
|
||||
children[0] = (children[0] as string).replace(
|
||||
"`▍`",
|
||||
"▍"
|
||||
"▍",
|
||||
);
|
||||
}
|
||||
|
||||
const match = /language-(\w+)/.exec(
|
||||
className || ""
|
||||
className || "",
|
||||
);
|
||||
|
||||
return !inline ? (
|
||||
|
|
@ -230,7 +230,7 @@ dark:prose-invert"
|
|||
language: (match && match[1]) || "",
|
||||
code: String(children).replace(
|
||||
/\n$/,
|
||||
""
|
||||
"",
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
|
@ -248,7 +248,7 @@ dark:prose-invert"
|
|||
{chatMessage}
|
||||
</Markdown>
|
||||
),
|
||||
[chat.message, chatMessage]
|
||||
[chat.message, chatMessage],
|
||||
)}
|
||||
</div>
|
||||
{chat.files && (
|
||||
|
|
@ -306,7 +306,7 @@ dark:prose-invert"
|
|||
parts.push(
|
||||
<span className="chat-message-highlight">
|
||||
{chat.message[match[1]]}
|
||||
</span>
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ const ApiModal = forwardRef(
|
|||
}}
|
||||
activeTweaks={activeTweaks}
|
||||
setActiveTweaks={setActiveTweaks}
|
||||
allowExport
|
||||
/>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
LANGFLOW_SUPPORTED_TYPES,
|
||||
limitScrollFieldsModal,
|
||||
} from "../../constants/constants";
|
||||
import { Case } from "../../shared/components/caseComponent";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import {
|
||||
|
|
@ -52,7 +53,7 @@ const EditNodeModal = forwardRef(
|
|||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
},
|
||||
ref
|
||||
ref,
|
||||
) => {
|
||||
const [myData, setMyData] = useState(data);
|
||||
|
||||
|
|
@ -84,6 +85,10 @@ const EditNodeModal = forwardRef(
|
|||
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
||||
const type = (templateParam) => {
|
||||
return myData.node?.template[templateParam].type;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
key={data.id}
|
||||
|
|
@ -116,7 +121,7 @@ const EditNodeModal = forwardRef(
|
|||
"edit-node-modal-box",
|
||||
nodeLength > limitScrollFieldsModal
|
||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
{nodeLength > 0 && (
|
||||
|
|
@ -138,8 +143,8 @@ const EditNodeModal = forwardRef(
|
|||
templateParam.charAt(0) !== "_" &&
|
||||
myData.node?.template[templateParam].show &&
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
myData.node.template[templateParam].type
|
||||
)
|
||||
myData.node!.template[templateParam].type,
|
||||
),
|
||||
)
|
||||
.map((templateParam, index) => {
|
||||
let id = {
|
||||
|
|
@ -161,8 +166,8 @@ const EditNodeModal = forwardRef(
|
|||
myData.node?.template[templateParam]
|
||||
.proxy,
|
||||
}
|
||||
: id
|
||||
)
|
||||
: id,
|
||||
),
|
||||
) ?? false;
|
||||
return (
|
||||
<TableRow
|
||||
|
|
@ -170,8 +175,7 @@ const EditNodeModal = forwardRef(
|
|||
className={
|
||||
"h-10 " +
|
||||
((templateParam === "code" &&
|
||||
myData.node?.template[templateParam].type ===
|
||||
"code") ||
|
||||
type(templateParam) === "code") ||
|
||||
(templateParam.includes("code") &&
|
||||
myData.node?.template[templateParam].proxy)
|
||||
? " hidden "
|
||||
|
|
@ -190,7 +194,7 @@ const EditNodeModal = forwardRef(
|
|||
<span>
|
||||
{myData.node?.template[templateParam]
|
||||
.display_name
|
||||
? myData.node.template[templateParam]
|
||||
? myData.node!.template[templateParam]
|
||||
.display_name
|
||||
: myData.node?.template[templateParam]
|
||||
.name}
|
||||
|
|
@ -198,58 +202,62 @@ const EditNodeModal = forwardRef(
|
|||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
||||
{myData.node?.template[templateParam].type ===
|
||||
"str" &&
|
||||
!myData.node.template[templateParam].options ? (
|
||||
<Case
|
||||
condition={
|
||||
type(templateParam) === "str" &&
|
||||
!myData.node!.template[templateParam]
|
||||
.options
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
{myData.node.template[templateParam]
|
||||
{myData.node!.template[templateParam]
|
||||
?.list ? (
|
||||
<InputListComponent
|
||||
componentName={templateParam}
|
||||
editNode={true}
|
||||
disabled={disabled}
|
||||
value={
|
||||
!myData.node.template[templateParam]
|
||||
!myData.node!.template[templateParam]
|
||||
.value ||
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value === ""
|
||||
? [""]
|
||||
: myData.node.template[
|
||||
: myData.node!.template[
|
||||
templateParam
|
||||
].value
|
||||
}
|
||||
onChange={(value: string[]) => {
|
||||
handleOnNewValue(
|
||||
value,
|
||||
templateParam
|
||||
templateParam,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : myData.node.template[templateParam]
|
||||
) : myData.node!.template[templateParam]
|
||||
.multiline ? (
|
||||
<TextAreaComponent
|
||||
id={
|
||||
"textarea-edit-" +
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
data-testid={
|
||||
"textarea-edit-" +
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(
|
||||
value: string | string[]
|
||||
value: string | string[],
|
||||
) => {
|
||||
handleOnNewValue(
|
||||
value,
|
||||
templateParam
|
||||
templateParam,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -274,8 +282,13 @@ const EditNodeModal = forwardRef(
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "NestedDict" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(templateParam) === "NestedDict"
|
||||
}
|
||||
>
|
||||
<div className=" w-full">
|
||||
<DictComponent
|
||||
disabled={disabled}
|
||||
|
|
@ -296,21 +309,24 @@ const EditNodeModal = forwardRef(
|
|||
].value = newValue;
|
||||
handleOnNewValue(
|
||||
newValue,
|
||||
templateParam
|
||||
templateParam,
|
||||
);
|
||||
}}
|
||||
id="editnode-div-dict-input"
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "dict" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "dict"}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"max-h-48 w-full overflow-auto custom-scroll",
|
||||
myData.node!.template[templateParam].value
|
||||
?.length > 1
|
||||
? "my-3"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<KeypairListComponent
|
||||
|
|
@ -325,7 +341,8 @@ const EditNodeModal = forwardRef(
|
|||
: convertObjToArray(
|
||||
myData.node!.template[
|
||||
templateParam
|
||||
].value
|
||||
].value,
|
||||
type(templateParam)!,
|
||||
)
|
||||
}
|
||||
duplicateKey={errorDuplicateKey}
|
||||
|
|
@ -336,11 +353,11 @@ const EditNodeModal = forwardRef(
|
|||
templateParam
|
||||
].value = valueToNumbers;
|
||||
setErrorDuplicateKey(
|
||||
hasDuplicateKeys(valueToNumbers)
|
||||
hasDuplicateKeys(valueToNumbers),
|
||||
);
|
||||
handleOnNewValue(
|
||||
valueToNumbers,
|
||||
templateParam
|
||||
templateParam,
|
||||
);
|
||||
}}
|
||||
isList={
|
||||
|
|
@ -349,32 +366,39 @@ const EditNodeModal = forwardRef(
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "bool" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "bool"}
|
||||
>
|
||||
<div className="ml-auto">
|
||||
{" "}
|
||||
<ToggleShadComponent
|
||||
id={
|
||||
"toggle-edit-" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
disabled={disabled}
|
||||
enabled={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value
|
||||
}
|
||||
setEnabled={(isEnabled) => {
|
||||
handleOnNewValue(
|
||||
isEnabled,
|
||||
templateParam
|
||||
templateParam,
|
||||
);
|
||||
}}
|
||||
size="small"
|
||||
editNode={true}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "float" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "float"}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<FloatComponent
|
||||
disabled={disabled}
|
||||
|
|
@ -384,7 +408,7 @@ const EditNodeModal = forwardRef(
|
|||
.rangeSpec
|
||||
}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value) => {
|
||||
|
|
@ -392,32 +416,38 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "str" &&
|
||||
myData.node.template[templateParam]
|
||||
.options ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
type(templateParam) === "str" &&
|
||||
myData.node!.template[templateParam].options
|
||||
}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<Dropdown
|
||||
editNode={true}
|
||||
options={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.options
|
||||
}
|
||||
onSelect={(value) =>
|
||||
handleOnNewValue(value, templateParam)
|
||||
}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? "Choose an option"
|
||||
}
|
||||
id={
|
||||
"dropdown-edit-" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "int" ? (
|
||||
</Case>
|
||||
|
||||
<Case condition={type(templateParam) === "int"}>
|
||||
<div className="mx-auto">
|
||||
<IntComponent
|
||||
rangeSpec={
|
||||
|
|
@ -426,12 +456,13 @@ const EditNodeModal = forwardRef(
|
|||
}
|
||||
id={
|
||||
"edit-int-input-" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value) => {
|
||||
|
|
@ -439,21 +470,24 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "file" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "file"}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
editNode={true}
|
||||
disabled={disabled}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
fileTypes={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.fileTypes
|
||||
}
|
||||
onFileChange={(filePath: string) => {
|
||||
|
|
@ -463,8 +497,11 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "prompt" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "prompt"}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
readonly={
|
||||
|
|
@ -478,7 +515,7 @@ const EditNodeModal = forwardRef(
|
|||
myData.node = nodeClass;
|
||||
}}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
|
|
@ -486,21 +523,26 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
id={
|
||||
"prompt-area-edit-" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
data-testid={
|
||||
"modal-prompt-input-" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "code" ? (
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={type(templateParam) === "code"}
|
||||
>
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
readonly={
|
||||
myData.node?.flow &&
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.dynamic
|
||||
? true
|
||||
: false
|
||||
|
|
@ -516,7 +558,7 @@ const EditNodeModal = forwardRef(
|
|||
disabled={disabled}
|
||||
editNode={true}
|
||||
value={
|
||||
myData.node.template[templateParam]
|
||||
myData.node!.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string | string[]) => {
|
||||
|
|
@ -524,16 +566,16 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
id={
|
||||
"code-area-edit" +
|
||||
myData.node.template[templateParam].name
|
||||
myData.node!.template[templateParam]
|
||||
.name
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node?.template[templateParam]
|
||||
.type === "Any" ? (
|
||||
"-"
|
||||
) : (
|
||||
<div className="hidden"></div>
|
||||
)}
|
||||
</Case>
|
||||
|
||||
<Case condition={type(templateParam) === "Any"}>
|
||||
<>-</>
|
||||
</Case>
|
||||
</TableCell>
|
||||
<TableCell className="p-0 text-right">
|
||||
<div className="items-center text-center">
|
||||
|
|
@ -588,7 +630,7 @@ const EditNodeModal = forwardRef(
|
|||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default EditNodeModal;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ 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(
|
||||
(state) => state.setCurrentFlowId
|
||||
(state) => state.setCurrentFlowId,
|
||||
);
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
||||
|
|
@ -21,7 +21,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
|||
useEffect(() => {
|
||||
setCurrentFlowId(id!);
|
||||
setOnFlowPage(true);
|
||||
|
||||
|
||||
return () => {
|
||||
setOnFlowPage(false);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ import { gradients } from "../../../../utils/styleUtils";
|
|||
|
||||
export default function GeneralPage() {
|
||||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId
|
||||
(state) => state.setCurrentFlowId,
|
||||
);
|
||||
|
||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||
CONTROL_PATCH_USER_STATE
|
||||
CONTROL_PATCH_USER_STATE,
|
||||
);
|
||||
|
||||
const { autoLogin } = useContext(AuthContext);
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
15
src/frontend/src/shared/components/caseComponent/index.tsx
Normal file
15
src/frontend/src/shared/components/caseComponent/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { memo } from "react";
|
||||
|
||||
type BooleanLike = boolean | string | number | null | undefined;
|
||||
|
||||
type Props = {
|
||||
condition: (() => BooleanLike) | BooleanLike;
|
||||
children: React.ReactNode | any;
|
||||
};
|
||||
|
||||
export const Case = memo(({ condition, children }: Props) => {
|
||||
const conditionResult =
|
||||
typeof condition === "function" ? condition() : condition;
|
||||
|
||||
return conditionResult ? children : null;
|
||||
});
|
||||
|
|
@ -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) => ({
|
||||
|
|
@ -465,8 +464,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
status: BuildStatus,
|
||||
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) {
|
||||
|
|
@ -483,9 +487,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
const next_vertices_ids = vertexBuildData.next_vertices_ids.filter(
|
||||
(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,10 +510,7 @@ 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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -83,13 +82,13 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
const { data, flows } = processFlows(dbData, false);
|
||||
get().setExamples(
|
||||
flows.filter(
|
||||
(f) => f.folder === STARTER_FOLDER_NAME && !f.user_id
|
||||
)
|
||||
(f) => f.folder === STARTER_FOLDER_NAME && !f.user_id,
|
||||
),
|
||||
);
|
||||
get().setFlows(
|
||||
flows.filter(
|
||||
(f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id)
|
||||
)
|
||||
(f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id),
|
||||
),
|
||||
);
|
||||
useTypesStore.setState((state) => ({
|
||||
data: { ...state.data, ["saved_components"]: data },
|
||||
|
|
@ -115,7 +114,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
if (get().currentFlow) {
|
||||
get().saveFlow(
|
||||
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -141,7 +140,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
return updatedFlow;
|
||||
}
|
||||
return flow;
|
||||
})
|
||||
}),
|
||||
);
|
||||
//update tabs state
|
||||
|
||||
|
|
@ -191,7 +190,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
newProject: Boolean,
|
||||
flow?: FlowType,
|
||||
override?: boolean,
|
||||
position?: XYPosition
|
||||
position?: XYPosition,
|
||||
): Promise<string | undefined> => {
|
||||
if (newProject) {
|
||||
let flowData = flow
|
||||
|
|
@ -255,7 +254,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
.getState()
|
||||
.paste(
|
||||
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
|
||||
position ?? { x: 10, y: 10 }
|
||||
position ?? { x: 10, y: 10 },
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -265,7 +264,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
if (index >= 0) {
|
||||
deleteFlowFromDatabase(id).then(() => {
|
||||
const { data, flows } = processFlows(
|
||||
get().flows.filter((flow) => flow.id !== id)
|
||||
get().flows.filter((flow) => flow.id !== id),
|
||||
);
|
||||
get().setFlows(flows);
|
||||
set({ isLoading: false });
|
||||
|
|
@ -285,7 +284,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
return new Promise<void>((resolve) => {
|
||||
let componentFlow = get().flows.find(
|
||||
(componentFlow) =>
|
||||
componentFlow.is_component && componentFlow.name === key
|
||||
componentFlow.is_component && componentFlow.name === key,
|
||||
);
|
||||
|
||||
if (componentFlow) {
|
||||
|
|
@ -367,7 +366,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
return get().addFlow(
|
||||
true,
|
||||
createFlowComponent(component, useDarkStore.getState().version),
|
||||
override
|
||||
override,
|
||||
);
|
||||
},
|
||||
takeSnapshot: () => {
|
||||
|
|
@ -388,7 +387,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
if (pastLength > 0) {
|
||||
past[currentFlowId] = past[currentFlowId].slice(
|
||||
pastLength - defaultOptions.maxHistorySize + 1,
|
||||
pastLength
|
||||
pastLength,
|
||||
);
|
||||
|
||||
past[currentFlowId].push(newState);
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ export type StoreApiKeyType = {
|
|||
export type groupedObjType = {
|
||||
family: string;
|
||||
type: string;
|
||||
display_name?: string;
|
||||
};
|
||||
|
||||
export type nodeGroupedObjType = {
|
||||
|
|
@ -663,6 +664,7 @@ export type codeTabsPropsType = {
|
|||
};
|
||||
activeTweaks?: boolean;
|
||||
setActiveTweaks?: (value: boolean) => void;
|
||||
allowExport?: boolean;
|
||||
};
|
||||
|
||||
export type crashComponentPropsType = {
|
||||
|
|
|
|||
|
|
@ -70,26 +70,32 @@ export type FlowStoreType = {
|
|||
state:
|
||||
| FlowState
|
||||
| undefined
|
||||
| ((oldState: FlowState | undefined) => FlowState)
|
||||
| ((oldState: FlowState | undefined) => FlowState),
|
||||
) => void;
|
||||
nodes: Node[];
|
||||
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;
|
||||
deleteEdge: (edgeId: string | Array<string>) => void;
|
||||
paste: (
|
||||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number },
|
||||
) => void;
|
||||
lastCopiedSelection: { nodes: any; edges: any } | null;
|
||||
setLastCopiedSelection: (
|
||||
newSelection: { nodes: any; edges: any } | null,
|
||||
isCrop?: boolean
|
||||
isCrop?: boolean,
|
||||
) => void;
|
||||
cleanFlow: () => void;
|
||||
setFilterEdge: (newState) => void;
|
||||
|
|
@ -113,7 +119,7 @@ export type FlowStoreType = {
|
|||
verticesLayers: VertexLayerElementType[][];
|
||||
runId: string;
|
||||
verticesToRun: string[];
|
||||
} | null
|
||||
} | null,
|
||||
) => void;
|
||||
addToVerticesBuild: (vertices: string[]) => void;
|
||||
removeFromVerticesBuild: (vertices: string[]) => void;
|
||||
|
|
@ -131,7 +137,7 @@ export type FlowStoreType = {
|
|||
updateFlowPool: (
|
||||
nodeId: string,
|
||||
data: FlowPoolObjectType | ChatOutputType | chatInputType,
|
||||
buildId?: string
|
||||
buildId?: string,
|
||||
) => void;
|
||||
getNodePosition: (nodeId: string) => { x: number; y: number };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ export type GlobalVariablesStore = {
|
|||
id: string,
|
||||
type?: string,
|
||||
default_fields?: string[],
|
||||
value?: string
|
||||
value?: string,
|
||||
) => 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -457,7 +457,8 @@ export function getConnectedNodes(
|
|||
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
|
||||
}
|
||||
|
||||
export function convertObjToArray(singleObject: object | string) {
|
||||
export function convertObjToArray(singleObject: object | string, type: string) {
|
||||
if (type !== "dict") return [{ "": "" }];
|
||||
if (typeof singleObject === "string") {
|
||||
singleObject = JSON.parse(singleObject);
|
||||
}
|
||||
|
|
@ -736,28 +737,30 @@ export function validateSelection(
|
|||
selection: OnSelectionChangeParams,
|
||||
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(
|
||||
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),
|
||||
|
|
@ -769,8 +772,8 @@ export function validateSelection(
|
|||
}
|
||||
//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");
|
||||
|
|
@ -778,10 +781,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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -23,7 +22,7 @@ export default function cloneFLowWithParent(
|
|||
|
||||
export function getTagsIds(
|
||||
tags: string[],
|
||||
tagListId: { name: string; id: string }[]
|
||||
tagListId: { name: string; id: string }[],
|
||||
) {
|
||||
return tags
|
||||
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { FaApple, FaGithub } from "react-icons/fa";
|
||||
import { AWSIcon } from "../icons/AWS";
|
||||
import { FaDiscord } from "react-icons/fa";
|
||||
import { AirbyteIcon } from "../icons/Airbyte";
|
||||
import { AnthropicIcon } from "../icons/Anthropic";
|
||||
import { AstraDBIcon } from "../icons/AstraDB";
|
||||
|
|
@ -517,4 +518,5 @@ export const nodeIconsLucide: iconsType = {
|
|||
Command,
|
||||
ArrowBigUp,
|
||||
Dot,
|
||||
Discord: FaDiscord,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ export function getPythonApiCode(
|
|||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
return `import requests
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -197,7 +198,7 @@ BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
|
|||
FLOW_ID = "${flowId}"
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
def run_flow(message: str,
|
||||
flow_id: str,
|
||||
|
|
@ -247,6 +248,8 @@ export function getCurlCode(
|
|||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `curl -X POST \\
|
||||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
|
|
@ -257,7 +260,7 @@ export function getCurlCode(
|
|||
-d '{"input_value": "message",
|
||||
"output_type": "chat",
|
||||
"input_type": "chat",
|
||||
"tweaks": ${JSON.stringify(tweaksBuildedObject, null, 2)}'
|
||||
"tweaks": ${JSON.stringify(tweaksObject, null, 2)}'
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -285,11 +288,14 @@ export function getOutputIds(flow) {
|
|||
* @returns {string} - The python code
|
||||
*/
|
||||
export function getPythonCode(flowName: string, tweaksBuildedObject): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
TWEAKS = ${JSON.stringify(tweaksBuildedObject, null, 2)}
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
result = run_flow_from_json(flow="${flowName}.json",
|
||||
input_value="message",
|
||||
fallback_to_env_vars=True, # False by default
|
||||
tweaks=TWEAKS)`;
|
||||
}
|
||||
|
||||
|
|
@ -481,6 +487,120 @@ export function sensitiveSort(a: string, b: string): number {
|
|||
return a.localeCompare(b);
|
||||
}
|
||||
}
|
||||
|
||||
export function groupByFamily(
|
||||
data: APIDataType,
|
||||
baseClasses: string,
|
||||
left: boolean,
|
||||
flow?: NodeType[],
|
||||
): groupedObjType[] {
|
||||
const baseClassesSet = new Set(baseClasses.split("\n"));
|
||||
let arrOfPossibleInputs: Array<{
|
||||
category: string;
|
||||
nodes: nodeGroupedObjType[];
|
||||
full: boolean;
|
||||
display_name?: string;
|
||||
}> = [];
|
||||
let arrOfPossibleOutputs: Array<{
|
||||
category: string;
|
||||
nodes: nodeGroupedObjType[];
|
||||
full: boolean;
|
||||
display_name?: string;
|
||||
}> = [];
|
||||
let checkedNodes = new Map();
|
||||
const excludeTypes = new Set(["bool", "float", "code", "file", "int"]);
|
||||
|
||||
const checkBaseClass = (template: TemplateVariableType) => {
|
||||
return (
|
||||
template.type &&
|
||||
template.show &&
|
||||
((!excludeTypes.has(template.type) &&
|
||||
baseClassesSet.has(template.type)) ||
|
||||
(template.input_types &&
|
||||
template.input_types.some((inputType) =>
|
||||
baseClassesSet.has(inputType),
|
||||
)))
|
||||
);
|
||||
};
|
||||
|
||||
if (flow) {
|
||||
// se existir o flow
|
||||
for (const node of flow) {
|
||||
// para cada node do flow
|
||||
if (node!.data!.node!.flow || !node!.data!.node!.template) break; // não faz nada se o node for um group
|
||||
const nodeData = node.data;
|
||||
|
||||
const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado
|
||||
checkedNodes.set(nodeData.type, {
|
||||
hasBaseClassInTemplate:
|
||||
foundNode?.hasBaseClassInTemplate ||
|
||||
Object.values(nodeData.node!.template).some(checkBaseClass),
|
||||
hasBaseClassInBaseClasses:
|
||||
foundNode?.hasBaseClassInBaseClasses ||
|
||||
nodeData.node!.base_classes.some((baseClass) =>
|
||||
baseClassesSet.has(baseClass),
|
||||
), //seta como anterior ou verifica se o node tem base class
|
||||
displayName: nodeData.node?.display_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [d, nodes] of Object.entries(data)) {
|
||||
let tempInputs: nodeGroupedObjType[] = [],
|
||||
tempOutputs: nodeGroupedObjType[] = [];
|
||||
|
||||
for (const [n, node] of Object.entries(nodes!)) {
|
||||
let foundNode = checkedNodes.get(n);
|
||||
|
||||
if (!foundNode) {
|
||||
foundNode = {
|
||||
hasBaseClassInTemplate: Object.values(node!.template).some(
|
||||
checkBaseClass,
|
||||
),
|
||||
hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
|
||||
baseClassesSet.has(baseClass),
|
||||
),
|
||||
displayName: node?.display_name,
|
||||
};
|
||||
}
|
||||
|
||||
if (foundNode.hasBaseClassInTemplate)
|
||||
tempInputs.push({ node: n, displayName: foundNode.displayName });
|
||||
if (foundNode.hasBaseClassInBaseClasses)
|
||||
tempOutputs.push({ node: n, displayName: foundNode.displayName });
|
||||
}
|
||||
|
||||
const totalNodes = Object.keys(nodes!).length;
|
||||
|
||||
if (tempInputs.length)
|
||||
arrOfPossibleInputs.push({
|
||||
category: d,
|
||||
nodes: tempInputs,
|
||||
full: tempInputs.length === totalNodes,
|
||||
});
|
||||
if (tempOutputs.length)
|
||||
arrOfPossibleOutputs.push({
|
||||
category: d,
|
||||
nodes: tempOutputs,
|
||||
full: tempOutputs.length === totalNodes,
|
||||
});
|
||||
}
|
||||
|
||||
return left
|
||||
? arrOfPossibleOutputs.map((output) => ({
|
||||
family: output.category,
|
||||
type: output.full
|
||||
? ""
|
||||
: output.nodes.map((item) => item.node).join(", "),
|
||||
display_name: "",
|
||||
}))
|
||||
: arrOfPossibleInputs.map((input) => ({
|
||||
family: input.category,
|
||||
type: input.full ? "" : input.nodes.map((item) => item.node).join(", "),
|
||||
display_name: input.nodes.map((item) => item.displayName).join(", "),
|
||||
}));
|
||||
}
|
||||
|
||||
// this function is used to get the set of keys from an object
|
||||
export function getSetFromObject(obj: object, key?: string): Set<string> {
|
||||
const set = new Set<string>();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ test("shoud delete a flow", async ({ page }) => {
|
|||
await page.goto("/");
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByText("Store").nth(0).click();
|
||||
|
||||
await page.getByText("API Key", { exact: true }).click();
|
||||
await page
|
||||
.getByPlaceholder("Insert your API Key", { exact: true })
|
||||
.fill(process.env.STORE_API_KEY ?? "");
|
||||
await page.getByText("Save").last().click();
|
||||
await page.waitForTimeout(8000);
|
||||
|
||||
await page.getByTestId("install-Website Content QA").click();
|
||||
await page.waitForTimeout(5000);
|
||||
await page.getByText("My Collection").nth(0).click();
|
||||
|
|
@ -16,7 +24,9 @@ test("shoud delete a flow", async ({ page }) => {
|
|||
await page.getByTestId("icon-Trash2").first().click();
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
await page.getByText("Confirm deletion of component?").isVisible();
|
||||
await page
|
||||
.getByText("Are you sure you want to delete the selected component?")
|
||||
.isVisible();
|
||||
await page.getByText("Delete").nth(1).click();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByText("Successfully").first().isVisible();
|
||||
|
|
@ -39,7 +49,9 @@ test("shoud delete a component", async ({ page }) => {
|
|||
await page.getByTestId("icon-Trash2").first().click();
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
await page.getByText("Confirm deletion of component?").isVisible();
|
||||
await page
|
||||
.getByText("Are you sure you want to delete the selected component?")
|
||||
.isVisible();
|
||||
await page.getByText("Delete").nth(1).click();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByText("Successfully").first().isVisible();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ test("NestedComponent", async ({ page }) => {
|
|||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ test("NestedComponent", async ({ page }) => {
|
|||
await page.locator('//*[@id="showpool_threads"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpool_threads"]').isChecked()
|
||||
await page.locator('//*[@id="showpool_threads"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
//showtext_key
|
||||
|
|
@ -53,182 +53,140 @@ test("NestedComponent", async ({ page }) => {
|
|||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeFalsy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
// showindex_name
|
||||
await page.locator('//*[@id="showindex_name"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked()
|
||||
await page.locator('//*[@id="showindex_name"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// shownamespace
|
||||
await page.locator('//*[@id="shownamespace"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked()
|
||||
await page.locator('//*[@id="shownamespace"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_api_key
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked()
|
||||
).toBeTruthy();
|
||||
|
||||
// showpinecone_env
|
||||
await page.locator('//*[@id="showpinecone_env"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpinecone_env"]').isChecked()
|
||||
await page.locator('//*[@id="showpinecone_api_key"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
//showpool_threads
|
||||
await page.locator('//*[@id="showpool_threads"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showpool_threads"]').isChecked()
|
||||
await page.locator('//*[@id="showpool_threads"]').isChecked(),
|
||||
).toBeFalsy();
|
||||
|
||||
//showtext_key
|
||||
await page.locator('//*[@id="showtext_key"]').click();
|
||||
|
||||
expect(
|
||||
await page.locator('//*[@id="showtext_key"]').isChecked()
|
||||
await page.locator('//*[@id="showtext_key"]').isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
await page.locator('//*[@id="saveChangesBtn"]').click();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ test.describe("save component tests", () => {
|
|||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ test.describe("save component tests", () => {
|
|||
// Read your file into a buffer.
|
||||
const jsonContent = readFileSync(
|
||||
"tests/end-to-end/assets/flow_group_test.json",
|
||||
"utf-8"
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// Create the DataTransfer and File
|
||||
|
|
@ -49,7 +49,7 @@ test.describe("save component tests", () => {
|
|||
"drop",
|
||||
{
|
||||
dataTransfer,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const genericNoda = page.getByTestId("div-generic-node");
|
||||
|
|
@ -61,9 +61,6 @@ test.describe("save component tests", () => {
|
|||
.locator('//*[@id="react-flow-id"]/div[1]/div[2]/button[3]')
|
||||
.click();
|
||||
|
||||
await page.getByTestId("title-PythonFunctionTool").click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
await page.getByTestId("title-ChatOpenAI").click({
|
||||
modifiers: ["Control"],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ test("should add API-KEY", async ({ page }) => {
|
|||
await page.getByTestId("api-key-button-store").click();
|
||||
await page
|
||||
.getByPlaceholder("Insert your API Key")
|
||||
.fill("x1fOKU0v2e5zL5d-BZW6CxZBZvoyuFgF");
|
||||
.fill(process.env.STORE_API_KEY ?? "");
|
||||
await page.getByTestId("api-key-save-button-store").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
|
@ -165,7 +165,7 @@ test("should like and add components and flows", async ({ page }) => {
|
|||
|
||||
await page
|
||||
.getByPlaceholder("Insert your API Key")
|
||||
.fill("x1fOKU0v2e5zL5d-BZW6CxZBZvoyuFgF");
|
||||
.fill(process.env.STORE_API_KEY ?? "");
|
||||
await page.getByTestId("api-key-save-button-store").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
|
@ -174,6 +174,8 @@ test("should like and add components and flows", async ({ page }) => {
|
|||
await page.waitForTimeout(2000);
|
||||
await page.getByText("API Key Error").isHidden();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const likedValue = await page
|
||||
.getByTestId("likes-Website Content QA")
|
||||
.innerText();
|
||||
|
|
@ -214,7 +216,7 @@ test("should like and add components and flows", async ({ page }) => {
|
|||
await page.getByText("My Collection").click();
|
||||
await page.getByText("Website Content QA").first().isVisible();
|
||||
|
||||
await page.getByTestId("sidebar-nav-Components").click();
|
||||
await page.getByText("Components").first().click();
|
||||
await page.getByText("Basic RAG").first().isVisible();
|
||||
});
|
||||
|
||||
|
|
@ -233,7 +235,7 @@ test("should share component with share button", async ({ page }) => {
|
|||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
|
@ -255,7 +257,7 @@ test("should share component with share button", async ({ page }) => {
|
|||
await page.getByText("Set workflow status to public").isVisible();
|
||||
await page
|
||||
.getByText(
|
||||
"Attention: API keys in specified fields are automatically removed upon sharing."
|
||||
"Attention: API keys in specified fields are automatically removed upon sharing.",
|
||||
)
|
||||
.isVisible();
|
||||
await page.getByText("Export").first().isVisible();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ test("curl_api_generation", async ({ page, context }) => {
|
|||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ test("check if tweaks are updating when someothing on the flow changes", async (
|
|||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.locator('//*[@id="new-project-btn"]').click();
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
import pytest
|
||||
from langflow.interface.utils import build_template_from_class
|
||||
from langflow.utils.constants import CHAT_OPENAI_MODELS, OPENAI_MODELS
|
||||
from langflow.utils.util import build_template_from_function, format_dict, get_base_classes, get_default_factory
|
||||
from langflow.utils.util import build_template_from_function, get_base_classes, get_default_factory
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
|
|
@ -88,171 +87,6 @@ def test_build_template_from_class():
|
|||
build_template_from_class("InvalidClass", type_to_cls_dict)
|
||||
|
||||
|
||||
# Test format_dict
|
||||
def test_format_dict():
|
||||
# Test 1: Optional type removal
|
||||
input_dict = {
|
||||
"field1": {"type": "Optional[str]", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 2: List type processing
|
||||
input_dict = {
|
||||
"field1": {"type": "List[str]", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": True,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 3: Mapping type replacement
|
||||
input_dict = {
|
||||
"field1": {"type": "Mapping[str, int]", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "dict[str, int]", # Mapping type is replaced with dict which is replaced with code
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 4: Replace default value with actual value
|
||||
input_dict = {
|
||||
"field1": {"type": "str", "required": False, "default": "test"},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
"value": "test",
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 5: Add password field
|
||||
input_dict = {
|
||||
"field1": {"type": "str", "required": False},
|
||||
"api_key": {"type": "str", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
"api_key": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": True,
|
||||
"password": True,
|
||||
"multiline": False,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 6: Add multiline
|
||||
input_dict = {
|
||||
"field1": {"type": "str", "required": False},
|
||||
"prefix": {"type": "str", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
"prefix": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": True,
|
||||
"password": False,
|
||||
"multiline": True,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
# Test 7: Check class name-specific cases (OpenAI, ChatOpenAI)
|
||||
input_dict = {
|
||||
"model_name": {"type": "str", "required": False},
|
||||
}
|
||||
expected_output_openai = {
|
||||
"model_name": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": True,
|
||||
"show": True,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
"options": OPENAI_MODELS,
|
||||
"value": "text-davinci-003",
|
||||
},
|
||||
}
|
||||
expected_output_openai_chat = {
|
||||
"model_name": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": True,
|
||||
"show": True,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
"options": CHAT_OPENAI_MODELS,
|
||||
"value": "gpt-4-turbo-preview",
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict, "OpenAI") == expected_output_openai
|
||||
assert format_dict(input_dict, "ChatOpenAI") == expected_output_openai_chat
|
||||
|
||||
# Test 8: Replace dict type with str
|
||||
input_dict = {
|
||||
"field1": {"type": "Dict[str, int]", "required": False},
|
||||
}
|
||||
expected_output = {
|
||||
"field1": {
|
||||
"type": "Dict[str, int]",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": False,
|
||||
"password": False,
|
||||
"multiline": False,
|
||||
},
|
||||
}
|
||||
assert format_dict(input_dict) == expected_output
|
||||
|
||||
|
||||
# Test get_base_classes
|
||||
def test_get_base_classes():
|
||||
base_classes_parent = get_base_classes(Parent)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue