Merge remote-tracking branch 'origin/zustand/io/migration' into globalVariables
This commit is contained in:
commit
0f867dc485
46 changed files with 1157 additions and 485 deletions
|
|
@ -73,12 +73,12 @@ LANGFLOW_SUPERUSER_PASSWORD=
|
||||||
|
|
||||||
# STORE_URL
|
# STORE_URL
|
||||||
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
|
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
|
||||||
LANGFLOW_STORE_URL=
|
# LANGFLOW_STORE_URL=
|
||||||
|
|
||||||
# DOWNLOAD_WEBHOOK_URL
|
# DOWNLOAD_WEBHOOK_URL
|
||||||
#
|
#
|
||||||
LANGFLOW_DOWNLOAD_WEBHOOK_URL=
|
# LANGFLOW_DOWNLOAD_WEBHOOK_URL=
|
||||||
|
|
||||||
# LIKE_WEBHOOK_URL
|
# LIKE_WEBHOOK_URL
|
||||||
#
|
#
|
||||||
LANGFLOW_LIKE_WEBHOOK_URL=
|
# LANGFLOW_LIKE_WEBHOOK_URL=
|
||||||
|
|
@ -15,6 +15,7 @@ COPY ./ ./
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
||||||
|
|
||||||
RUN poetry add pymysql==1.0.2
|
RUN poetry add botocore
|
||||||
|
RUN poetry add pymysql
|
||||||
|
|
||||||
CMD ["sh", "./container-cmd-cdk.sh"]
|
CMD ["sh", "./container-cmd-cdk.sh"]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
export LANGFLOW_DATABASE_URL="mysql+pymysql://${username}:${password}@${host}:3306/${dbname}"
|
export LANGFLOW_DATABASE_URL="mysql+pymysql://${username}:${password}@${host}:3306/${dbname}"
|
||||||
# echo $LANGFLOW_DATABASE_URL
|
# echo $LANGFLOW_DATABASE_URL
|
||||||
uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug
|
uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug
|
||||||
|
|
||||||
|
# python -m langflow run --host 0.0.0.0 --port 7860
|
||||||
17
poetry.lock
generated
17
poetry.lock
generated
|
|
@ -6976,6 +6976,21 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
six = ">=1.5"
|
six = ">=1.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-docx"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "Create, read, and update Microsoft Word .docx files."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "python-docx-1.1.0.tar.gz", hash = "sha256:5829b722141cf1ab79aedf0c34d9fe9924b29764584c0f2164eb2b02dcdf17c9"},
|
||||||
|
{file = "python_docx-1.1.0-py3-none-any.whl", hash = "sha256:bac9773278098a1ddc43a52d84e22f5909c4a3080a624530b3ecb3771b07c6cd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
lxml = ">=3.1.0"
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
@ -9969,4 +9984,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.10,<3.12"
|
python-versions = ">=3.10,<3.12"
|
||||||
content-hash = "13fbf814f41f22cddd661bce5c37abcd36b50fad392d70271597c358694886bc"
|
content-hash = "dedb98bf70db438c28d18a8289a9bb61c79d4ff983b96b7f1d52e11b9c32900b"
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ unstructured = { extras = ["md"], version = "^0.12.4" }
|
||||||
dspy-ai = "^2.4.0"
|
dspy-ai = "^2.4.0"
|
||||||
crewai = "^0.22.5"
|
crewai = "^0.22.5"
|
||||||
langchain-anthropic = "^0.1.4"
|
langchain-anthropic = "^0.1.4"
|
||||||
|
python-docx = "^1.1.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest-asyncio = "^0.23.1"
|
pytest-asyncio = "^0.23.1"
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,9 @@ Langflow on AWS では、 [AWS Cloud Development Kit](https://aws.amazon.com/cdk
|
||||||
|
|
||||||
作成するアプリケーションのアーキテクチャです。
|
作成するアプリケーションのアーキテクチャです。
|
||||||

|

|
||||||
AWS CDK によって [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls)、[AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a)、[Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) を作成します。
|
AWS CDK によって Langflow のアプリケーションをデプロイします。アプリケーションは [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls) を介して配信されます。CloudFront は 2 つのオリジンを有しています。1 つ目は静的な Web サイトを配信するための [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3)、2 つ目は バックエンドと通信するための [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) です。ALB の背後には FastAPI が動作する [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a) 、データベースの [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) が作成されます。
|
||||||
|
Fargate は [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR) に保存された Docker イメージを使用します。
|
||||||
Auroraのシークレットは [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a) によって管理されます。
|
Auroraのシークレットは [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a) によって管理されます。
|
||||||
Fargate のタスクはフロントエンドとバックエンドに分かれており、サービス検出によって通信します。
|
|
||||||
リソースをデプロイするだけであれば、上記の各サービスについて深い知識は必要ありません。
|
|
||||||
|
|
||||||
# 環境構築とデプロイ方法
|
# 環境構築とデプロイ方法
|
||||||
1. [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1)を開きます。
|
1. [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1)を開きます。
|
||||||
|
|
@ -28,7 +27,7 @@ Fargate のタスクはフロントエンドとバックエンドに分かれて
|
||||||
|
|
||||||
1. 以下のコマンドを実行します。
|
1. 以下のコマンドを実行します。
|
||||||
```shell
|
```shell
|
||||||
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
|
git clone https://github.com/logspace-ai/langflow.git
|
||||||
cd langflow/scripts/aws
|
cd langflow/scripts/aws
|
||||||
cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
|
cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
|
||||||
npm ci
|
npm ci
|
||||||
|
|
@ -38,7 +37,7 @@ Fargate のタスクはフロントエンドとバックエンドに分かれて
|
||||||
1. 表示される URL にアクセスします。
|
1. 表示される URL にアクセスします。
|
||||||
```shell
|
```shell
|
||||||
Outputs:
|
Outputs:
|
||||||
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
|
LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
|
||||||
```
|
```
|
||||||
1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
|
1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,9 @@ This tutorial assumes you have an AWS account and basic knowledge of AWS.
|
||||||
|
|
||||||
The architecture of the application to be created:
|
The architecture of the application to be created:
|
||||||

|

|
||||||
|
Langflow is deployed using AWS CDK. The application is distributed via [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls), which has two origins: the first is [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3) for serving a static website, and the second is an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) for communicating with the backend. [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a), where FastAPI runs and [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a), the database, are created behind the ALB.
|
||||||
[Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls), [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a) and [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) are created by AWS CDK.
|
Fargate uses a Docker image stored in [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR).
|
||||||
The aurora's secrets are managed by [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a).
|
Aurora's secret is managed by [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a).
|
||||||
The Fargate task is divided into a frontend and a backend, which communicate through service discovery.
|
|
||||||
If you just want to deploy resources, you do not need in-depth knowledge of each of the above services.
|
|
||||||
|
|
||||||
# How to set up your environment and deploy langflow
|
# How to set up your environment and deploy langflow
|
||||||
|
|
||||||
|
|
@ -22,13 +20,14 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
|
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
|
||||||
cd cloud9-setup-for-prototyping
|
cd cloud9-setup-for-prototyping
|
||||||
|
cat params.json | jq '.name |= "c9-for-langflow"'
|
||||||
./bin/bootstrap
|
./bin/bootstrap
|
||||||
```
|
```
|
||||||
1. When you see `Done!` in Cloudshell, open `cloud9-for-prototyping` from [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/).
|
1. When you see `Done!` in Cloudshell, open `c9-for-langflow` from [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/).
|
||||||

|

|
||||||
1. Run the following command in the Cloud9 terminal.
|
1. Run the following command in the Cloud9 terminal.
|
||||||
```shell
|
```shell
|
||||||
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
|
git clone https://github.com/logspace-ai/langflow.git
|
||||||
cd langflow/scripts/aws
|
cd langflow/scripts/aws
|
||||||
cp .env.example .env # Edit this file if you need environment settings
|
cp .env.example .env # Edit this file if you need environment settings
|
||||||
npm ci
|
npm ci
|
||||||
|
|
@ -38,7 +37,7 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
|
||||||
1. Access the URL displayed.
|
1. Access the URL displayed.
|
||||||
```shell
|
```shell
|
||||||
Outputs:
|
Outputs:
|
||||||
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
|
LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
|
||||||
```
|
```
|
||||||
1. Enter your user name and password to sign in. If you have not set a user name and password in your `.env` file, the user name will be set to `admin` and the password to `123456`.
|
1. Enter your user name and password to sign in. If you have not set a user name and password in your `.env` file, the user name will be set to `admin` and the password to `123456`.
|
||||||

|

|
||||||
|
|
@ -49,5 +48,6 @@ If you just want to deploy resources, you do not need in-depth knowledge of each
|
||||||
```shell
|
```shell
|
||||||
bash delete-resources.sh
|
bash delete-resources.sh
|
||||||
```
|
```
|
||||||
1. Open [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started), select `aws-cloud9-cloud9-for-prototyping-XXXX` and delete it.
|
1. Open [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started), select `aws-cloud9-c9-for-langflow-XXXX` and delete it.
|
||||||

|

|
||||||
|
s
|
||||||
|
|
@ -4,6 +4,7 @@ import * as cdk from 'aws-cdk-lib';
|
||||||
import { LangflowAppStack } from '../lib/cdk-stack';
|
import { LangflowAppStack } from '../lib/cdk-stack';
|
||||||
|
|
||||||
const app = new cdk.App();
|
const app = new cdk.App();
|
||||||
|
|
||||||
new LangflowAppStack(app, 'LangflowAppStack', {
|
new LangflowAppStack(app, 'LangflowAppStack', {
|
||||||
/* If you don't specify 'env', this stack will be environment-agnostic.
|
/* If you don't specify 'env', this stack will be environment-agnostic.
|
||||||
* Account/Region-dependent features and context lookups will not work,
|
* Account/Region-dependent features and context lookups will not work,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"context": {
|
"context": {
|
||||||
|
"ragEnabled": false,
|
||||||
|
"kendraIndexArn": null,
|
||||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||||
"@aws-cdk/core:checkSecretUsage": true,
|
"@aws-cdk/core:checkSecretUsage": true,
|
||||||
"@aws-cdk/core:target-partitions": [
|
"@aws-cdk/core:target-partitions": [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
aws cloudformation delete-stack --stack-name LangflowAppStack
|
# aws cloudformation delete-stack --stack-name LangflowAppStack
|
||||||
aws ecr delete-repository --repository-name langflow-backend-repository --force
|
aws ecr delete-repository --repository-name langflow-backend-repository --force
|
||||||
aws ecr delete-repository --repository-name langflow-frontend-repository --force
|
# aws ecr delete-repository --repository-name langflow-frontend-repository --force
|
||||||
# aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
|
# aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 291 KiB After Width: | Height: | Size: 224 KiB |
|
|
@ -2,21 +2,38 @@ import * as cdk from 'aws-cdk-lib';
|
||||||
import { Construct } from 'constructs';
|
import { Construct } from 'constructs';
|
||||||
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
||||||
|
|
||||||
import { Network, EcrRepository, FrontEndCluster, BackEndCluster, Rds, EcsIAM } from './construct';
|
import { Network, EcrRepository, Web, BackEndCluster, Rds, EcsIAM, Rag} from './construct';
|
||||||
// import * as sqs from 'aws-cdk-lib/aws-sqs';
|
// import * as sqs from 'aws-cdk-lib/aws-sqs';
|
||||||
|
|
||||||
|
const errorMessageForBooleanContext = (key: string) => {
|
||||||
|
return `There was an error setting $ {key}. Possible causes are as follows.
|
||||||
|
- Trying to set it with the -c option instead of changing cdk.json
|
||||||
|
- cdk.json is set to a value that is not a boolean (e.g. “true” double quotes are not required)
|
||||||
|
- no items in cdk.json (unset) `;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export class LangflowAppStack extends cdk.Stack {
|
export class LangflowAppStack extends cdk.Stack {
|
||||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||||
super(scope, id, props);
|
super(scope, id, props);
|
||||||
|
// Kendra Enable
|
||||||
|
const ragEnabled: boolean = this.node.tryGetContext('ragEnabled')!;
|
||||||
|
if (typeof ragEnabled !== 'boolean') {
|
||||||
|
throw new Error(errorMessageForBooleanContext('ragEnabled'));
|
||||||
|
}
|
||||||
|
if (ragEnabled) {
|
||||||
|
new Rag(this, 'Rag', {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Arch
|
// Arch
|
||||||
const arch = ecs.CpuArchitecture.X86_64
|
const arch = ecs.CpuArchitecture.X86_64
|
||||||
|
|
||||||
// VPC
|
// VPC
|
||||||
const { vpc, cluster, alb, targetGroup, cloudmapNamespace, ecsFrontSG, ecsBackSG, dbSG, albSG, backendLogGroup, frontendLogGroup} = new Network(this, 'Network')
|
const { vpc, cluster, ecsBackSG, dbSG, backendLogGroup, alb, albTG, albSG} = new Network(this, 'Network')
|
||||||
|
|
||||||
// ECR
|
// ECR
|
||||||
const { ecrFrontEndRepository,ecrBackEndRepository} = new EcrRepository(this, 'Ecr', {
|
const { ecrBackEndRepository } = new EcrRepository(this, 'Ecr', {
|
||||||
cloudmapNamespace:cloudmapNamespace,
|
|
||||||
arch:arch
|
arch:arch
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -25,7 +42,7 @@ export class LangflowAppStack extends cdk.Stack {
|
||||||
const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
|
const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
|
||||||
|
|
||||||
// IAM
|
// IAM
|
||||||
const { frontendTaskRole, frontendTaskExecutionRole, backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
|
const { backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
|
||||||
rdsCluster:rdsCluster
|
rdsCluster:rdsCluster
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -36,29 +53,18 @@ export class LangflowAppStack extends cdk.Stack {
|
||||||
backendTaskRole:backendTaskRole,
|
backendTaskRole:backendTaskRole,
|
||||||
backendTaskExecutionRole:backendTaskExecutionRole,
|
backendTaskExecutionRole:backendTaskExecutionRole,
|
||||||
backendLogGroup:backendLogGroup,
|
backendLogGroup:backendLogGroup,
|
||||||
cloudmapNamespace:cloudmapNamespace,
|
|
||||||
rdsCluster:rdsCluster,
|
rdsCluster:rdsCluster,
|
||||||
alb:alb,
|
arch:arch,
|
||||||
arch:arch
|
albTG:albTG
|
||||||
})
|
})
|
||||||
backendService.node.addDependency(rdsCluster);
|
backendService.node.addDependency(rdsCluster);
|
||||||
|
|
||||||
const frontendService = new FrontEndCluster(this, 'frontend',{
|
const frontendService = new Web(this, 'frontend',{
|
||||||
cluster:cluster,
|
cluster:cluster,
|
||||||
ecsFrontSG:ecsFrontSG,
|
alb:alb,
|
||||||
ecrFrontEndRepository:ecrFrontEndRepository,
|
albSG:albSG
|
||||||
targetGroup: targetGroup,
|
|
||||||
backendServiceName: backendService.backendServiceName,
|
|
||||||
frontendTaskRole: frontendTaskRole,
|
|
||||||
frontendTaskExecutionRole: frontendTaskExecutionRole,
|
|
||||||
frontendLogGroup: frontendLogGroup,
|
|
||||||
cloudmapNamespace: cloudmapNamespace,
|
|
||||||
arch:arch
|
|
||||||
})
|
})
|
||||||
frontendService.node.addDependency(backendService);
|
frontendService.node.addDependency(backendService);
|
||||||
|
|
||||||
|
|
||||||
// S3+CloudFront
|
|
||||||
// new Web(this,'Cloudfront-S3')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,17 @@ interface BackEndProps {
|
||||||
backendTaskRole: iam.Role;
|
backendTaskRole: iam.Role;
|
||||||
backendTaskExecutionRole: iam.Role;
|
backendTaskExecutionRole: iam.Role;
|
||||||
backendLogGroup: logs.LogGroup;
|
backendLogGroup: logs.LogGroup;
|
||||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
|
||||||
rdsCluster:rds.DatabaseCluster
|
rdsCluster:rds.DatabaseCluster
|
||||||
alb:elb.IApplicationLoadBalancer
|
|
||||||
arch:ecs.CpuArchitecture
|
arch:ecs.CpuArchitecture
|
||||||
|
albTG: elb.ApplicationTargetGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BackEndCluster extends Construct {
|
export class BackEndCluster extends Construct {
|
||||||
readonly backendServiceName: string
|
|
||||||
|
|
||||||
constructor(scope: Construct, id: string, props:BackEndProps) {
|
constructor(scope: Construct, id: string, props:BackEndProps) {
|
||||||
super(scope, id)
|
super(scope, id)
|
||||||
const containerPort = 7860
|
const backendServiceName = 'backend'
|
||||||
|
const backendServicePort = 7860
|
||||||
// Secrets ManagerからDB認証情報を取ってくる
|
// Secrets ManagerからDB認証情報を取ってくる
|
||||||
const secretsDB = props.rdsCluster.secret!;
|
const secretsDB = props.rdsCluster.secret!;
|
||||||
|
|
||||||
|
|
@ -59,20 +58,13 @@ export class BackEndCluster extends Construct {
|
||||||
logGroup: props.backendLogGroup,
|
logGroup: props.backendLogGroup,
|
||||||
}),
|
}),
|
||||||
environment:{
|
environment:{
|
||||||
// user:pass@endpoint:port/dbname
|
|
||||||
// "LANGFLOW_DATABASE_URL" : `mysql+pymysql://${username}:${password}@${host}:3306/${dbname}`,
|
|
||||||
// "LANGFLOW_DATABASE_URL" : "sqlite:///./langflow.db",
|
|
||||||
// "LANGFLOW_LANGCHAIN_CACHE" : "SQLiteCache",
|
|
||||||
// "LANGFLOW_AUTO_LOGIN" : "false",
|
|
||||||
// "LANGFLOW_SUPERUSER" : "admin",
|
|
||||||
// "LANGFLOW_SUPERUSER_PASSWORD" : "1234567"
|
|
||||||
"LANGFLOW_AUTO_LOGIN" : process.env.LANGFLOW_AUTO_LOGIN ?? 'false',
|
"LANGFLOW_AUTO_LOGIN" : process.env.LANGFLOW_AUTO_LOGIN ?? 'false',
|
||||||
"LANGFLOW_SUPERUSER" : process.env.LANGFLOW_SUPERUSER ?? "admin",
|
"LANGFLOW_SUPERUSER" : process.env.LANGFLOW_SUPERUSER ?? "admin",
|
||||||
"LANGFLOW_SUPERUSER_PASSWORD" : process.env.LANGFLOW_SUPERUSER_PASSWORD ?? "123456"
|
"LANGFLOW_SUPERUSER_PASSWORD" : process.env.LANGFLOW_SUPERUSER_PASSWORD ?? "123456"
|
||||||
},
|
},
|
||||||
portMappings: [
|
portMappings: [
|
||||||
{
|
{
|
||||||
containerPort: containerPort,
|
containerPort: backendServicePort,
|
||||||
protocol: ecs.Protocol.TCP,
|
protocol: ecs.Protocol.TCP,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -84,22 +76,15 @@ export class BackEndCluster extends Construct {
|
||||||
"password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
|
"password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.backendServiceName = 'backend'
|
|
||||||
const backendService = new ecs.FargateService(this, 'BackEndService', {
|
const backendService = new ecs.FargateService(this, 'BackEndService', {
|
||||||
cluster: props.cluster,
|
cluster: props.cluster,
|
||||||
serviceName: this.backendServiceName,
|
serviceName: backendServiceName,
|
||||||
taskDefinition: backendTaskDefinition,
|
taskDefinition: backendTaskDefinition,
|
||||||
enableExecuteCommand: true,
|
enableExecuteCommand: true,
|
||||||
securityGroups: [props.ecsBackSG],
|
securityGroups: [props.ecsBackSG],
|
||||||
cloudMapOptions: {
|
|
||||||
cloudMapNamespace: props.cloudmapNamespace,
|
|
||||||
containerPort: containerPort,
|
|
||||||
dnsRecordType: servicediscovery.DnsRecordType.A,
|
|
||||||
dnsTtl: Duration.seconds(10),
|
|
||||||
name: this.backendServiceName
|
|
||||||
},
|
|
||||||
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
||||||
});
|
});
|
||||||
|
props.albTG.addTarget(backendService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,12 +9,10 @@ import { Construct } from 'constructs'
|
||||||
|
|
||||||
|
|
||||||
interface ECRProps {
|
interface ECRProps {
|
||||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
|
||||||
arch:ecs.CpuArchitecture;
|
arch:ecs.CpuArchitecture;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EcrRepository extends Construct {
|
export class EcrRepository extends Construct {
|
||||||
readonly ecrFrontEndRepository: ecr.Repository
|
|
||||||
readonly ecrBackEndRepository: ecr.Repository
|
readonly ecrBackEndRepository: ecr.Repository
|
||||||
|
|
||||||
constructor(scope: Construct, id: string, props: ECRProps) {
|
constructor(scope: Construct, id: string, props: ECRProps) {
|
||||||
|
|
@ -22,7 +20,6 @@ export class EcrRepository extends Construct {
|
||||||
|
|
||||||
const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
|
const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
|
||||||
const backendPath = path.join(__dirname, "../../../../../", "langflow")
|
const backendPath = path.join(__dirname, "../../../../../", "langflow")
|
||||||
const frontendPath = path.join(__dirname, "../../../../src/", "frontend")
|
|
||||||
const excludeDir = ['node_modules','.git', 'cdk.out']
|
const excludeDir = ['node_modules','.git', 'cdk.out']
|
||||||
const LifecycleRule = {
|
const LifecycleRule = {
|
||||||
tagStatus: ecr.TagStatus.ANY,
|
tagStatus: ecr.TagStatus.ANY,
|
||||||
|
|
@ -30,31 +27,16 @@ export class EcrRepository extends Construct {
|
||||||
maxImageCount: 30,
|
maxImageCount: 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
// リポジトリ作成
|
// Backend ECR リポジトリ作成
|
||||||
this.ecrFrontEndRepository = new ecr.Repository(scope, 'LangflowFrontEndRepository', {
|
|
||||||
repositoryName: 'langflow-frontend-repository',
|
|
||||||
removalPolicy: RemovalPolicy.RETAIN,
|
|
||||||
imageScanOnPush: true,
|
|
||||||
})
|
|
||||||
this.ecrBackEndRepository = new ecr.Repository(scope, 'LangflowBackEndRepository', {
|
this.ecrBackEndRepository = new ecr.Repository(scope, 'LangflowBackEndRepository', {
|
||||||
repositoryName: 'langflow-backend-repository',
|
repositoryName: 'langflow-backend-repository',
|
||||||
removalPolicy: RemovalPolicy.RETAIN,
|
removalPolicy: RemovalPolicy.RETAIN,
|
||||||
imageScanOnPush: true,
|
imageScanOnPush: true,
|
||||||
})
|
})
|
||||||
// LifecycleRule作成
|
// LifecycleRule作成
|
||||||
this.ecrFrontEndRepository.addLifecycleRule(LifecycleRule)
|
|
||||||
this.ecrBackEndRepository.addLifecycleRule(LifecycleRule)
|
this.ecrBackEndRepository.addLifecycleRule(LifecycleRule)
|
||||||
|
|
||||||
// Create Docker Image Asset
|
// Create Docker Image Asset
|
||||||
const dockerFrontEndImageAsset = new DockerImageAsset(this, "DockerFrontEndImageAsset", {
|
|
||||||
directory: frontendPath,
|
|
||||||
file:"cdk.Dockerfile",
|
|
||||||
buildArgs:{
|
|
||||||
"BACKEND_URL":`http://backend.${props.cloudmapNamespace.namespaceName}:7860`
|
|
||||||
},
|
|
||||||
exclude: excludeDir,
|
|
||||||
platform: imagePlatform,
|
|
||||||
});
|
|
||||||
const dockerBackEndImageAsset = new DockerImageAsset(this, "DockerBackEndImageAsset", {
|
const dockerBackEndImageAsset = new DockerImageAsset(this, "DockerBackEndImageAsset", {
|
||||||
directory: backendPath,
|
directory: backendPath,
|
||||||
file:"cdk.Dockerfile",
|
file:"cdk.Dockerfile",
|
||||||
|
|
@ -62,12 +44,6 @@ export class EcrRepository extends Construct {
|
||||||
platform: imagePlatform,
|
platform: imagePlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deploy Docker Image to ECR Repository
|
|
||||||
new ecrdeploy.ECRDeployment(this, "DeployFrontEndImage", {
|
|
||||||
src: new ecrdeploy.DockerImageName(dockerFrontEndImageAsset.imageUri),
|
|
||||||
dest: new ecrdeploy.DockerImageName(this.ecrFrontEndRepository.repositoryUri)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Deploy Docker Image to ECR Repository
|
// Deploy Docker Image to ECR Repository
|
||||||
new ecrdeploy.ECRDeployment(this, "DeployBackEndImage", {
|
new ecrdeploy.ECRDeployment(this, "DeployBackEndImage", {
|
||||||
src: new ecrdeploy.DockerImageName(dockerBackEndImageAsset.imageUri),
|
src: new ecrdeploy.DockerImageName(dockerBackEndImageAsset.imageUri),
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,141 @@
|
||||||
import { Duration } from 'aws-cdk-lib'
|
import { Stack, Duration, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
|
||||||
import { Construct } from 'constructs'
|
import { Construct } from 'constructs';
|
||||||
import {
|
import {
|
||||||
aws_ec2 as ec2,
|
aws_ec2 as ec2,
|
||||||
aws_ecs as ecs,
|
aws_ecs as ecs,
|
||||||
aws_ecr as ecr,
|
aws_s3 as s3,
|
||||||
aws_servicediscovery as servicediscovery,
|
|
||||||
aws_iam as iam,
|
aws_iam as iam,
|
||||||
aws_logs as logs,
|
aws_logs as logs,
|
||||||
aws_elasticloadbalancingv2 as elb,
|
aws_elasticloadbalancingv2 as elb,
|
||||||
|
aws_cloudfront as cloudfront,
|
||||||
|
aws_cloudfront_origins as origins,
|
||||||
|
aws_s3_deployment as s3_deployment
|
||||||
} from 'aws-cdk-lib';
|
} from 'aws-cdk-lib';
|
||||||
import { CpuArchitecture } from 'aws-cdk-lib/aws-ecs';
|
import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3';
|
||||||
|
import { CfnDistribution, Distribution } from 'aws-cdk-lib/aws-cloudfront';
|
||||||
|
import { NodejsBuild } from 'deploy-time-build';
|
||||||
|
|
||||||
interface FrontEndProps {
|
interface WebProps {
|
||||||
cluster:ecs.Cluster
|
cluster:ecs.Cluster
|
||||||
ecsFrontSG:ec2.SecurityGroup
|
alb:elb.IApplicationLoadBalancer;
|
||||||
ecrFrontEndRepository:ecr.Repository
|
albSG:ec2.SecurityGroup;
|
||||||
targetGroup: elb.ApplicationTargetGroup;
|
|
||||||
backendServiceName: string;
|
|
||||||
frontendTaskRole: iam.Role;
|
|
||||||
frontendTaskExecutionRole: iam.Role;
|
|
||||||
frontendLogGroup: logs.LogGroup;
|
|
||||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
|
||||||
arch:ecs.CpuArchitecture;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontEndCluster extends Construct {
|
export class Web extends Construct {
|
||||||
constructor(scope: Construct, id: string, props:FrontEndProps) {
|
readonly distribution;
|
||||||
|
constructor(scope: Construct, id: string, props:WebProps) {
|
||||||
super(scope, id)
|
super(scope, id)
|
||||||
|
|
||||||
|
const commonBucketProps: s3.BucketProps = {
|
||||||
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
||||||
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||||
|
autoDeleteObjects: true,
|
||||||
|
removalPolicy: RemovalPolicy.DESTROY,
|
||||||
|
objectOwnership: s3.ObjectOwnership.OBJECT_WRITER,
|
||||||
|
enforceSSL: true,
|
||||||
|
};
|
||||||
|
|
||||||
const containerPort = 3000
|
// CDKにて 静的WebサイトをホストするためのAmazon S3バケットを作成
|
||||||
const frontendTaskDefinition = new ecs.FargateTaskDefinition(
|
const websiteBucket = new s3.Bucket(this, 'LangflowWebsiteBucket', commonBucketProps);
|
||||||
this,
|
|
||||||
'FrontendTaskDef',
|
const originAccessIdentity = new cloudfront.OriginAccessIdentity(
|
||||||
{
|
this,
|
||||||
memoryLimitMiB: 3072,
|
'OriginAccessIdentity',
|
||||||
cpu: 1024,
|
{
|
||||||
executionRole: props.frontendTaskExecutionRole,
|
comment: 'langflow-distribution-originAccessIdentity',
|
||||||
runtimePlatform:{
|
}
|
||||||
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
|
||||||
cpuArchitecture: props.arch,
|
|
||||||
},
|
|
||||||
taskRole: props.frontendTaskRole,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
const frontendServiceName = 'frontend'
|
|
||||||
frontendTaskDefinition.addContainer('frontendContainer', {
|
const webSiteBucketPolicyStatement = new iam.PolicyStatement({
|
||||||
image: ecs.ContainerImage.fromEcrRepository(props.ecrFrontEndRepository, "latest"),
|
actions: ['s3:GetObject'],
|
||||||
containerName:'langflow-front-container',
|
effect: iam.Effect.ALLOW,
|
||||||
environment: {
|
principals: [
|
||||||
BACKEND_SERVICE_NAME: props.backendServiceName,
|
new iam.CanonicalUserPrincipal(
|
||||||
BACKEND_URL: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
|
||||||
VITE_PROXY_TARGET: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
),
|
||||||
},
|
],
|
||||||
logging: ecs.LogDriver.awsLogs({
|
resources: [`${websiteBucket.bucketArn}/*`],
|
||||||
streamPrefix: 'my-stream',
|
|
||||||
logGroup: props.frontendLogGroup,
|
|
||||||
}),
|
|
||||||
portMappings: [
|
|
||||||
{
|
|
||||||
name:frontendServiceName,
|
|
||||||
containerPort: containerPort,
|
|
||||||
protocol: ecs.Protocol.TCP,
|
|
||||||
appProtocol:ecs.AppProtocol.http,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
const frontendService = new ecs.FargateService(
|
|
||||||
this,
|
|
||||||
'FrontendService',
|
|
||||||
{
|
|
||||||
serviceName: frontendServiceName,
|
|
||||||
cluster: props.cluster,
|
|
||||||
desiredCount: 1,
|
|
||||||
assignPublicIp: false,
|
|
||||||
taskDefinition: frontendTaskDefinition,
|
|
||||||
enableExecuteCommand: true,
|
|
||||||
securityGroups: [props.ecsFrontSG],
|
|
||||||
cloudMapOptions: {
|
|
||||||
cloudMapNamespace: props.cloudmapNamespace,
|
|
||||||
containerPort: containerPort,
|
|
||||||
dnsRecordType: servicediscovery.DnsRecordType.A,
|
|
||||||
dnsTtl: Duration.seconds(10),
|
|
||||||
name: frontendServiceName
|
|
||||||
},
|
|
||||||
healthCheckGracePeriod: Duration.seconds(1000),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
props.targetGroup.addTarget(frontendService);
|
websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);
|
||||||
|
websiteBucket.grantRead(originAccessIdentity);
|
||||||
|
|
||||||
// // Create ALB and ECS Fargate Service
|
const s3SpaOrigin = new origins.S3Origin(websiteBucket);
|
||||||
// const frontService = new ecs_patterns.ApplicationLoadBalancedFargateService(
|
const ApiSpaOrigin = new origins.LoadBalancerV2Origin(props.alb,{
|
||||||
// this,
|
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY
|
||||||
// "FrontEndService",
|
});
|
||||||
// {
|
|
||||||
// cluster: cluster,
|
|
||||||
// serviceName: 'langflow-frontend-service',
|
|
||||||
// cpu: 256,
|
|
||||||
// memoryLimitMiB: 512,
|
|
||||||
// listenerPort: 80,
|
|
||||||
// assignPublicIp: true, // Public facing - ALB
|
|
||||||
// taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
|
||||||
// securityGroups:[ecsFrontSG],
|
|
||||||
// taskImageOptions: {
|
|
||||||
// family: 'langflow-taskdef',
|
|
||||||
// containerName: 'langflow-front-container',
|
|
||||||
// image: ecs.ContainerImage.fromEcrRepository(ecrFrontEndRepository, "latest"),
|
|
||||||
// containerPort: 3000, // L2なので、TargetGroupのportが3000で設定されるはず
|
|
||||||
// },
|
|
||||||
// loadBalancer:alb,
|
|
||||||
// openListener:false,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
const albBehaviorOptions = {
|
||||||
|
origin: ApiSpaOrigin,
|
||||||
|
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
|
||||||
|
|
||||||
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL,
|
||||||
|
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
|
||||||
|
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cloudFrontWebDistribution = new cloudfront.Distribution(this, 'distribution', {
|
||||||
|
comment: 'langflow-distribution',
|
||||||
|
defaultRootObject: 'index.html',
|
||||||
|
errorResponses: [
|
||||||
|
{
|
||||||
|
httpStatus: 403,
|
||||||
|
responseHttpStatus: 200,
|
||||||
|
responsePagePath: '/index.html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
httpStatus: 404,
|
||||||
|
responseHttpStatus: 200,
|
||||||
|
responsePagePath: '/index.html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultBehavior: { origin: s3SpaOrigin },
|
||||||
|
additionalBehaviors: {
|
||||||
|
'/api/v1/*': albBehaviorOptions,
|
||||||
|
'/health' : albBehaviorOptions,
|
||||||
|
},
|
||||||
|
enableLogging: true, // ログ出力設定
|
||||||
|
logBucket: new s3.Bucket(this, 'LogBucket',commonBucketProps),
|
||||||
|
logFilePrefix: 'distribution-access-logs/',
|
||||||
|
logIncludesCookies: true,
|
||||||
|
});
|
||||||
|
this.distribution = cloudFrontWebDistribution;
|
||||||
|
|
||||||
|
|
||||||
|
new NodejsBuild(this, 'BuildFrontEnd', {
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
path: '../../src/frontend',
|
||||||
|
exclude: [
|
||||||
|
'.git',
|
||||||
|
'.github',
|
||||||
|
'.gitignore',
|
||||||
|
'.prettierignore',
|
||||||
|
'build',
|
||||||
|
'node_modules'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nodejsVersion:20,
|
||||||
|
destinationBucket: websiteBucket,
|
||||||
|
distribution: cloudFrontWebDistribution,
|
||||||
|
outputSourceDirectory: 'build',
|
||||||
|
buildCommands: ['npm install', 'npm run build'],
|
||||||
|
buildEnvironment: {
|
||||||
|
// VITE_AXIOS_BASE_URL: `https://${this.distribution.domainName}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// distribution から backendへのinbound 許可
|
||||||
|
const alb_listen_port=80
|
||||||
|
props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
|
||||||
|
const alb_listen_port_443=443
|
||||||
|
props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port_443))
|
||||||
|
|
||||||
|
|
||||||
|
new CfnOutput(this, 'URL', {
|
||||||
|
value: `https://${this.distribution.domainName}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,8 +10,6 @@ interface IAMProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EcsIAM extends Construct {
|
export class EcsIAM extends Construct {
|
||||||
readonly frontendTaskRole: iam.Role;
|
|
||||||
readonly frontendTaskExecutionRole: iam.Role;
|
|
||||||
readonly backendTaskRole: iam.Role;
|
readonly backendTaskRole: iam.Role;
|
||||||
readonly backendTaskExecutionRole: iam.Role;
|
readonly backendTaskExecutionRole: iam.Role;
|
||||||
|
|
||||||
|
|
@ -58,12 +56,6 @@ export class EcsIAM extends Construct {
|
||||||
})],
|
})],
|
||||||
})
|
})
|
||||||
|
|
||||||
// FrontEnd Task Role
|
|
||||||
this.frontendTaskRole = new iam.Role(this, 'FrontendTaskRole', {
|
|
||||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
|
||||||
});
|
|
||||||
this.frontendTaskRole.addToPolicy(ECSExecPolicyStatement);
|
|
||||||
|
|
||||||
// BackEnd Task Role
|
// BackEnd Task Role
|
||||||
this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
|
this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
|
||||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||||
|
|
@ -73,17 +65,6 @@ export class EcsIAM extends Construct {
|
||||||
// KendraとBedrockのアクセス権付与
|
// KendraとBedrockのアクセス権付与
|
||||||
this.backendTaskRole.attachInlinePolicy(RagAccessPolicy);
|
this.backendTaskRole.attachInlinePolicy(RagAccessPolicy);
|
||||||
|
|
||||||
// FrontEnd Task ExecutionRole
|
|
||||||
this.frontendTaskExecutionRole = new iam.Role(this, 'frontendTaskExecutionRole', {
|
|
||||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
|
||||||
managedPolicies: [
|
|
||||||
{
|
|
||||||
managedPolicyArn:
|
|
||||||
'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// BackEnd Task ExecutionRole
|
// BackEnd Task ExecutionRole
|
||||||
this.backendTaskExecutionRole = new iam.Role(this, 'backendTaskExecutionRole', {
|
this.backendTaskExecutionRole = new iam.Role(this, 'backendTaskExecutionRole', {
|
||||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@ export * from './ecr';
|
||||||
export * from './iam';
|
export * from './iam';
|
||||||
export * from './frontend';
|
export * from './frontend';
|
||||||
export * from './backend';
|
export * from './backend';
|
||||||
export * from './network';
|
export * from './network';
|
||||||
|
export * from './kendra';
|
||||||
141
scripts/aws/lib/construct/kendra.ts
Normal file
141
scripts/aws/lib/construct/kendra.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
import * as kendra from 'aws-cdk-lib/aws-kendra';
|
||||||
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||||
|
import { Construct } from 'constructs';
|
||||||
|
import { Duration, Token, Arn } from 'aws-cdk-lib';
|
||||||
|
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
|
||||||
|
import { Runtime } from 'aws-cdk-lib/aws-lambda';
|
||||||
|
|
||||||
|
export interface RagProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAG を実行するためのリソースを作成する
|
||||||
|
*/
|
||||||
|
export class Rag extends Construct {
|
||||||
|
constructor(scope: Construct, id: string, props: RagProps) {
|
||||||
|
super(scope, id);
|
||||||
|
|
||||||
|
const kendraIndexArnInCdkContext =
|
||||||
|
this.node.tryGetContext('kendraIndexArn');
|
||||||
|
|
||||||
|
let kendraIndexArn: string;
|
||||||
|
let kendraIndexId: string;
|
||||||
|
|
||||||
|
if (kendraIndexArnInCdkContext) {
|
||||||
|
// 既存の Kendra Index を利用する場合
|
||||||
|
kendraIndexArn = kendraIndexArnInCdkContext!;
|
||||||
|
kendraIndexId = Arn.extractResourceName(
|
||||||
|
kendraIndexArnInCdkContext,
|
||||||
|
'index'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 新規に Kendra Index を作成する場合
|
||||||
|
const indexRole = new iam.Role(this, 'KendraIndexRole', {
|
||||||
|
assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
|
||||||
|
});
|
||||||
|
|
||||||
|
indexRole.addToPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
resources: ['*'],
|
||||||
|
actions: ['s3:GetObject'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
indexRole.addManagedPolicy(
|
||||||
|
iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess')
|
||||||
|
);
|
||||||
|
|
||||||
|
const index = new kendra.CfnIndex(this, 'KendraIndex', {
|
||||||
|
name: 'langflow-index',
|
||||||
|
edition: 'DEVELOPER_EDITION',
|
||||||
|
roleArn: indexRole.roleArn,
|
||||||
|
});
|
||||||
|
|
||||||
|
kendraIndexArn = Token.asString(index.getAtt('Arn'));
|
||||||
|
kendraIndexId = index.ref;
|
||||||
|
|
||||||
|
// WebCrawler を作成
|
||||||
|
const webCrawlerRole = new iam.Role(this, 'KendraWebCrawlerRole', {
|
||||||
|
assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
|
||||||
|
});
|
||||||
|
webCrawlerRole.addToPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
resources: [kendraIndexArn],
|
||||||
|
actions: ['kendra:BatchPutDocument', 'kendra:BatchDeleteDocument'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new kendra.CfnDataSource(this, 'WebCrawler', {
|
||||||
|
indexId: kendraIndexId,
|
||||||
|
name: 'WebCrawler',
|
||||||
|
type: 'WEBCRAWLER',
|
||||||
|
roleArn: webCrawlerRole.roleArn,
|
||||||
|
languageCode: 'ja',
|
||||||
|
dataSourceConfiguration: {
|
||||||
|
webCrawlerConfiguration: {
|
||||||
|
urls: {
|
||||||
|
seedUrlConfiguration: {
|
||||||
|
webCrawlerMode: 'HOST_ONLY',
|
||||||
|
// デモ用に AWS の GenAI 関連のページを取り込む
|
||||||
|
seedUrls: [
|
||||||
|
'https://aws.amazon.com/jp/what-is/generative-ai/',
|
||||||
|
'https://aws.amazon.com/jp/generative-ai/',
|
||||||
|
'https://aws.amazon.com/jp/generative-ai/use-cases/',
|
||||||
|
'https://aws.amazon.com/jp/bedrock/',
|
||||||
|
'https://aws.amazon.com/jp/bedrock/features/',
|
||||||
|
'https://aws.amazon.com/jp/bedrock/testimonials/',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
crawlDepth: 1,
|
||||||
|
urlInclusionPatterns: ['https://aws.amazon.com/jp/.*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAG 関連の API を追加する
|
||||||
|
// Lambda
|
||||||
|
const queryFunction = new NodejsFunction(this, 'Query', {
|
||||||
|
runtime: Runtime.NODEJS_18_X,
|
||||||
|
entry: './lambda/queryKendra.ts',
|
||||||
|
timeout: Duration.minutes(15),
|
||||||
|
bundling: {
|
||||||
|
// 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
|
||||||
|
externalModules: [],
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
INDEX_ID: kendraIndexId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
queryFunction.role?.addToPrincipalPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
resources: [kendraIndexArn],
|
||||||
|
actions: ['kendra:Query'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const retrieveFunction = new NodejsFunction(this, 'Retrieve', {
|
||||||
|
runtime: Runtime.NODEJS_18_X,
|
||||||
|
entry: './lambda/retrieveKendra.ts',
|
||||||
|
timeout: Duration.minutes(15),
|
||||||
|
bundling: {
|
||||||
|
// 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
|
||||||
|
externalModules: [],
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
INDEX_ID: kendraIndexId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
retrieveFunction.role?.addToPrincipalPolicy(
|
||||||
|
new iam.PolicyStatement({
|
||||||
|
effect: iam.Effect.ALLOW,
|
||||||
|
resources: [kendraIndexArn],
|
||||||
|
actions: ['kendra:Retrieve'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,20 +11,16 @@ import {
|
||||||
export class Network extends Construct {
|
export class Network extends Construct {
|
||||||
readonly vpc: ec2.Vpc;
|
readonly vpc: ec2.Vpc;
|
||||||
readonly cluster: ecs.Cluster;
|
readonly cluster: ecs.Cluster;
|
||||||
readonly alb: elb.IApplicationLoadBalancer;
|
|
||||||
readonly targetGroup: elb.ApplicationTargetGroup;
|
|
||||||
readonly cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
|
||||||
readonly ecsFrontSG: ec2.SecurityGroup;
|
|
||||||
readonly ecsBackSG: ec2.SecurityGroup;
|
readonly ecsBackSG: ec2.SecurityGroup;
|
||||||
readonly dbSG: ec2.SecurityGroup;
|
readonly dbSG: ec2.SecurityGroup;
|
||||||
readonly albSG: ec2.SecurityGroup;
|
|
||||||
readonly backendLogGroup: logs.LogGroup;
|
readonly backendLogGroup: logs.LogGroup;
|
||||||
readonly frontendLogGroup: logs.LogGroup;
|
readonly alb: elb.IApplicationLoadBalancer;
|
||||||
|
readonly albTG: elb.ApplicationTargetGroup;
|
||||||
|
readonly albSG: ec2.SecurityGroup;
|
||||||
|
|
||||||
constructor(scope: Construct, id: string) {
|
constructor(scope: Construct, id: string) {
|
||||||
super(scope, id)
|
super(scope, id)
|
||||||
const alb_listen_port=80
|
const alb_listen_port=80
|
||||||
const front_service_port=3000
|
|
||||||
const back_service_port=7860
|
const back_service_port=7860
|
||||||
|
|
||||||
// VPC等リソースの作成
|
// VPC等リソースの作成
|
||||||
|
|
@ -51,22 +47,6 @@ export class Network extends Construct {
|
||||||
],
|
],
|
||||||
natGateways: 1,
|
natGateways: 1,
|
||||||
})
|
})
|
||||||
// Cluster
|
|
||||||
this.cluster = new ecs.Cluster(this, 'EcsCluster', {
|
|
||||||
clusterName: 'langflow-cluster',
|
|
||||||
vpc: this.vpc,
|
|
||||||
enableFargateCapacityProviders: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Private DNS
|
|
||||||
this.cloudmapNamespace = new servicediscovery.PrivateDnsNamespace(
|
|
||||||
this,
|
|
||||||
'Namespace',
|
|
||||||
{
|
|
||||||
name: 'ecs-deploy.com',
|
|
||||||
vpc: this.vpc,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// ALBに設定するセキュリティグループ
|
// ALBに設定するセキュリティグループ
|
||||||
this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
|
this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
|
||||||
|
|
@ -74,7 +54,6 @@ export class Network extends Construct {
|
||||||
description: 'for alb',
|
description: 'for alb',
|
||||||
vpc: this.vpc,
|
vpc: this.vpc,
|
||||||
})
|
})
|
||||||
this.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
|
|
||||||
|
|
||||||
this.alb = new elb.ApplicationLoadBalancer(this,'langflow-alb',{
|
this.alb = new elb.ApplicationLoadBalancer(this,'langflow-alb',{
|
||||||
internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
|
internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
|
||||||
|
|
@ -85,8 +64,8 @@ export class Network extends Construct {
|
||||||
|
|
||||||
const listener = this.alb.addListener('Listener', { port: alb_listen_port });
|
const listener = this.alb.addListener('Listener', { port: alb_listen_port });
|
||||||
|
|
||||||
this.targetGroup = listener.addTargets('targetGroup', {
|
this.albTG = listener.addTargets('targetGroup', {
|
||||||
port: front_service_port,
|
port: back_service_port,
|
||||||
protocol: elb.ApplicationProtocol.HTTP,
|
protocol: elb.ApplicationProtocol.HTTP,
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -99,13 +78,12 @@ export class Network extends Construct {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ECS FrontEndに設定するセキュリティグループ
|
// Cluster
|
||||||
this.ecsFrontSG = new ec2.SecurityGroup(scope, 'ECSFrontEndSecurityGroup', {
|
this.cluster = new ecs.Cluster(this, 'EcsCluster', {
|
||||||
securityGroupName: 'langflow-ecs-front-sg',
|
clusterName: 'langflow-cluster',
|
||||||
description: 'for langflow-front-ecs',
|
|
||||||
vpc: this.vpc,
|
vpc: this.vpc,
|
||||||
})
|
enableFargateCapacityProviders: true,
|
||||||
this.ecsFrontSG.addIngressRule(this.albSG, ec2.Port.allTcp())
|
});
|
||||||
|
|
||||||
// ECS BackEndに設定するセキュリティグループ
|
// ECS BackEndに設定するセキュリティグループ
|
||||||
this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
|
this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
|
||||||
|
|
@ -113,7 +91,7 @@ export class Network extends Construct {
|
||||||
description: 'for langflow-back-ecs',
|
description: 'for langflow-back-ecs',
|
||||||
vpc: this.vpc,
|
vpc: this.vpc,
|
||||||
})
|
})
|
||||||
this.ecsBackSG.addIngressRule(this.ecsFrontSG, ec2.Port.tcp(back_service_port))
|
this.ecsBackSG.addIngressRule(this.albSG,ec2.Port.tcp(back_service_port))
|
||||||
|
|
||||||
// RDSに設定するセキュリティグループ
|
// RDSに設定するセキュリティグループ
|
||||||
this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
|
this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
|
||||||
|
|
@ -122,7 +100,7 @@ export class Network extends Construct {
|
||||||
description: 'for langflow-db',
|
description: 'for langflow-db',
|
||||||
vpc: this.vpc,
|
vpc: this.vpc,
|
||||||
})
|
})
|
||||||
// AppRunnerSecurityGroupからのポート3306:mysql(5432:postgres)のインバウンドを許可
|
// langflow-ecs-back-sg からのポート3306:mysql(5432:postgres)のインバウンドを許可
|
||||||
this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
|
this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
|
||||||
|
|
||||||
// Create CloudWatch Log Group
|
// Create CloudWatch Log Group
|
||||||
|
|
@ -131,13 +109,5 @@ export class Network extends Construct {
|
||||||
removalPolicy: RemovalPolicy.DESTROY,
|
removalPolicy: RemovalPolicy.DESTROY,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.frontendLogGroup = new logs.LogGroup(this, 'frontendLogGroup', {
|
|
||||||
logGroupName: 'langflow-frontend-logs',
|
|
||||||
removalPolicy: RemovalPolicy.DESTROY,
|
|
||||||
});
|
|
||||||
|
|
||||||
new CfnOutput(this, 'URL', {
|
|
||||||
value: `http://${this.alb.loadBalancerDnsName}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
364
scripts/aws/package-lock.json
generated
364
scripts/aws/package-lock.json
generated
|
|
@ -8,9 +8,11 @@
|
||||||
"name": "cdk",
|
"name": "cdk",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-cdk-lib": "^2.86.0",
|
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.49.0",
|
||||||
|
"aws-cdk-lib": "^2.124.0",
|
||||||
"cdk-ecr-deployment": "^2.5.30",
|
"cdk-ecr-deployment": "^2.5.30",
|
||||||
"constructs": "^10.0.0",
|
"constructs": "^10.0.0",
|
||||||
|
"deploy-time-build": "^0.3.12",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"source-map-support": "^0.5.21"
|
"source-map-support": "^0.5.21"
|
||||||
},
|
},
|
||||||
|
|
@ -20,7 +22,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.1",
|
"@types/jest": "^29.5.1",
|
||||||
"@types/node": "20.1.7",
|
"@types/node": "20.1.7",
|
||||||
"aws-cdk": "2.86.0",
|
"aws-cdk": "^2.86.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
|
@ -41,19 +43,324 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aws-cdk/asset-awscli-v1": {
|
"node_modules/@aws-cdk/asset-awscli-v1": {
|
||||||
"version": "2.2.201",
|
"version": "2.2.202",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.201.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz",
|
||||||
"integrity": "sha512-INZqcwDinNaIdb5CtW3ez5s943nX5stGBQS6VOP2JDlOFP81hM3fds/9NDknipqfUkZM43dx+HgVvkXYXXARCQ=="
|
"integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg=="
|
||||||
},
|
},
|
||||||
"node_modules/@aws-cdk/asset-kubectl-v20": {
|
"node_modules/@aws-cdk/asset-kubectl-v20": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz",
|
||||||
"integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg=="
|
"integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg=="
|
||||||
},
|
},
|
||||||
"node_modules/@aws-cdk/asset-node-proxy-agent-v5": {
|
"node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
|
||||||
"version": "2.0.166",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v5/-/asset-node-proxy-agent-v5-2.0.166.tgz",
|
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz",
|
||||||
"integrity": "sha512-j0xnccpUQHXJKPgCwQcGGNu4lRiC1PptYfdxBIH1L4dRK91iBxtSQHESRQX+yB47oGLaF/WfNN/aF3WXwlhikg=="
|
"integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg=="
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/aws-cloudfront-s3": {
|
||||||
|
"version": "2.49.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-solutions-constructs/aws-cloudfront-s3/-/aws-cloudfront-s3-2.49.0.tgz",
|
||||||
|
"integrity": "sha512-0VMtl+Ma+aLZo3Nuvq9bTl/Oz9vn5OaPEOELr9pe8kYuoHwGQUGuxXP/xJlQUWbXwR3JCQVcN5k4kdnD5eLXFw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-solutions-constructs/core": "2.49.0",
|
||||||
|
"@aws-solutions-constructs/resources": "2.49.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-solutions-constructs/core": "2.49.0",
|
||||||
|
"@aws-solutions-constructs/resources": "2.49.0",
|
||||||
|
"aws-cdk-lib": "^2.118.0",
|
||||||
|
"constructs": "^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core": {
|
||||||
|
"version": "2.49.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-solutions-constructs/core/-/core-2.49.0.tgz",
|
||||||
|
"integrity": "sha512-SZ3CBYF6jVQj0DSNwhksqwz5ohW4w+DlgKiZhrO9gLiW4YcRrxFzwQ7I9FHFUDPZOxsN4NmsuQNj/3s02gycmQ==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"deepmerge",
|
||||||
|
"npmlog",
|
||||||
|
"deep-diff"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"deep-diff": "^1.0.2",
|
||||||
|
"deepmerge": "^4.0.0",
|
||||||
|
"npmlog": "^4.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"aws-cdk-lib": "^2.118.0",
|
||||||
|
"constructs": "^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/ansi-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/aproba": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/are-we-there-yet": {
|
||||||
|
"version": "1.1.7",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"delegates": "^1.0.0",
|
||||||
|
"readable-stream": "^2.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/code-point-at": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/console-control-strings": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/core-util-is": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/deep-diff": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/delegates": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/gauge": {
|
||||||
|
"version": "2.7.4",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"aproba": "^1.0.3",
|
||||||
|
"console-control-strings": "^1.0.0",
|
||||||
|
"has-unicode": "^2.0.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"signal-exit": "^3.0.0",
|
||||||
|
"string-width": "^1.0.1",
|
||||||
|
"strip-ansi": "^3.0.1",
|
||||||
|
"wide-align": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/has-unicode": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"number-is-nan": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/npmlog": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"are-we-there-yet": "~1.1.2",
|
||||||
|
"console-control-strings": "~1.1.0",
|
||||||
|
"gauge": "~2.7.3",
|
||||||
|
"set-blocking": "~2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/number-is-nan": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/readable-stream": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/set-blocking": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/string-width": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"code-point-at": "^1.0.0",
|
||||||
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
"strip-ansi": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/strip-ansi": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/wide-align": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/wide-align/node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/wide-align/node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/wide-align/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/core/node_modules/wide-align/node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"inBundle": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@aws-solutions-constructs/resources": {
|
||||||
|
"version": "2.49.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@aws-solutions-constructs/resources/-/resources-2.49.0.tgz",
|
||||||
|
"integrity": "sha512-HHx9tTA36plfVxzzQ+qjHCJOo6ikHR+xEqtb1dDBQvgtWo3SnEbWAKBDqNPA6DGn7PoFhxE0zA0ntj7RfFjeLQ==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"@aws-sdk/client-kms",
|
||||||
|
"@aws-sdk/client-s3",
|
||||||
|
"aws-sdk-client-mock"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-kms": "^3.478.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.478.0",
|
||||||
|
"@aws-solutions-constructs/core": "2.49.0",
|
||||||
|
"aws-sdk-client-mock": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-solutions-constructs/core": "2.49.0",
|
||||||
|
"aws-cdk-lib": "^2.118.0",
|
||||||
|
"constructs": "^10.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.22.13",
|
"version": "7.22.13",
|
||||||
|
|
@ -1313,9 +1620,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib": {
|
"node_modules/aws-cdk-lib": {
|
||||||
"version": "2.86.0",
|
"version": "2.124.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.86.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.124.0.tgz",
|
||||||
"integrity": "sha512-76yZ2MawAGXLD3ox4FjhUIPmAMXteGKkeo3tPMthemusDCCkD2X6DBssXBHjB7r9GnrOMMf8JH5BGq2lOZ539g==",
|
"integrity": "sha512-K/Tey8TMw30GO6UD0qb19CPhBMZhleGshz520ZnbDUJwNfFtejwZOnpmRMOdUP9f4tHc5BrXl1VGsZtXtUaGhg==",
|
||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"@balena/dockerignore",
|
"@balena/dockerignore",
|
||||||
"case",
|
"case",
|
||||||
|
|
@ -1329,17 +1636,17 @@
|
||||||
"yaml"
|
"yaml"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-cdk/asset-awscli-v1": "^2.2.177",
|
"@aws-cdk/asset-awscli-v1": "^2.2.202",
|
||||||
"@aws-cdk/asset-kubectl-v20": "^2.1.1",
|
"@aws-cdk/asset-kubectl-v20": "^2.1.2",
|
||||||
"@aws-cdk/asset-node-proxy-agent-v5": "^2.0.148",
|
"@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1",
|
||||||
"@balena/dockerignore": "^1.0.2",
|
"@balena/dockerignore": "^1.0.2",
|
||||||
"case": "1.6.3",
|
"case": "1.6.3",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.2.0",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.3.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"punycode": "^2.3.0",
|
"punycode": "^2.3.1",
|
||||||
"semver": "^7.5.1",
|
"semver": "^7.5.4",
|
||||||
"table": "^6.8.1",
|
"table": "^6.8.1",
|
||||||
"yaml": "1.10.2"
|
"yaml": "1.10.2"
|
||||||
},
|
},
|
||||||
|
|
@ -1454,7 +1761,7 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
|
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
|
||||||
"version": "11.1.1",
|
"version": "11.2.0",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1472,7 +1779,7 @@
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib/node_modules/ignore": {
|
"node_modules/aws-cdk-lib/node_modules/ignore": {
|
||||||
"version": "5.2.4",
|
"version": "5.3.0",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1539,7 +1846,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib/node_modules/punycode": {
|
"node_modules/aws-cdk-lib/node_modules/punycode": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.1",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1555,7 +1862,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib/node_modules/semver": {
|
"node_modules/aws-cdk-lib/node_modules/semver": {
|
||||||
"version": "7.5.2",
|
"version": "7.5.4",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1624,7 +1931,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aws-cdk-lib/node_modules/universalify": {
|
"node_modules/aws-cdk-lib/node_modules/universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -2400,6 +2707,15 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deploy-time-build": {
|
||||||
|
"version": "0.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/deploy-time-build/-/deploy-time-build-0.3.12.tgz",
|
||||||
|
"integrity": "sha512-fVyuwB0Nh7mOLYswhHIHv3NOCeTWyNAjzM8cqSBnuhfvRKSdLZUFPnfwlPk1VgvYp9lfrxBZ+eicjWPBrAr53g==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"aws-cdk-lib": "^2.38.0",
|
||||||
|
"constructs": "^10.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-newline": {
|
"node_modules/detect-newline": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,18 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.1",
|
"@types/jest": "^29.5.1",
|
||||||
"@types/node": "20.1.7",
|
"@types/node": "20.1.7",
|
||||||
"aws-cdk": "2.86.0",
|
"aws-cdk": "^2.86.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "~5.1.3"
|
"typescript": "~5.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-cdk-lib": "^2.86.0",
|
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.49.0",
|
||||||
|
"aws-cdk-lib": "^2.124.0",
|
||||||
"cdk-ecr-deployment": "^2.5.30",
|
"cdk-ecr-deployment": "^2.5.30",
|
||||||
"constructs": "^10.0.0",
|
"constructs": "^10.0.0",
|
||||||
|
"deploy-time-build": "^0.3.12",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"source-map-support": "^0.5.21"
|
"source-map-support": "^0.5.21"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,7 @@ from langflow.schema.schema import Record
|
||||||
|
|
||||||
# Types of files that can be read simply by file.read()
|
# Types of files that can be read simply by file.read()
|
||||||
# and have 100% to be completely readable
|
# and have 100% to be completely readable
|
||||||
TEXT_FILE_TYPES = [
|
TEXT_FILE_TYPES = ["txt", "md", "mdx", "csv", "json", "yaml", "yml", "xml", "html", "htm", "pdf", "docx"]
|
||||||
"txt",
|
|
||||||
"md",
|
|
||||||
"mdx",
|
|
||||||
"csv",
|
|
||||||
"json",
|
|
||||||
"yaml",
|
|
||||||
"yml",
|
|
||||||
"xml",
|
|
||||||
"html",
|
|
||||||
"htm",
|
|
||||||
"pdf",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def is_hidden(path: Path) -> bool:
|
def is_hidden(path: Path) -> bool:
|
||||||
|
|
@ -84,6 +72,13 @@ def read_text_file(file_path: str) -> str:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def read_docx_file(file_path: str) -> str:
|
||||||
|
from docx import Document # type: ignore
|
||||||
|
|
||||||
|
doc = Document(file_path)
|
||||||
|
return "\n\n".join([p.text for p in doc.paragraphs])
|
||||||
|
|
||||||
|
|
||||||
def parse_pdf_to_text(file_path: str) -> str:
|
def parse_pdf_to_text(file_path: str) -> str:
|
||||||
from pypdf import PdfReader # type: ignore
|
from pypdf import PdfReader # type: ignore
|
||||||
|
|
||||||
|
|
@ -96,6 +91,8 @@ def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[R
|
||||||
try:
|
try:
|
||||||
if file_path.endswith(".pdf"):
|
if file_path.endswith(".pdf"):
|
||||||
text = parse_pdf_to_text(file_path)
|
text = parse_pdf_to_text(file_path)
|
||||||
|
elif file_path.endswith(".docx"):
|
||||||
|
text = read_docx_file(file_path)
|
||||||
else:
|
else:
|
||||||
text = read_text_file(file_path)
|
text = read_text_file(file_path)
|
||||||
# if file is json, yaml, or xml, we can parse it
|
# if file is json, yaml, or xml, we can parse it
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_record
|
from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_record
|
||||||
|
|
@ -6,12 +6,17 @@ from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
class FileComponent(CustomComponent):
|
class FileComponent(CustomComponent):
|
||||||
display_name = "File"
|
display_name = "Files"
|
||||||
description = "Load a file."
|
description = "Read Text Files"
|
||||||
|
|
||||||
def build_config(self) -> Dict[str, Any]:
|
def build_config(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"path": {"display_name": "Path"},
|
"paths": {
|
||||||
|
"display_name": "Paths",
|
||||||
|
"field_type": "file",
|
||||||
|
"file_types": TEXT_FILE_TYPES,
|
||||||
|
"info": f"Supported file types: {', '.join(TEXT_FILE_TYPES)}",
|
||||||
|
},
|
||||||
"silent_errors": {
|
"silent_errors": {
|
||||||
"display_name": "Silent Errors",
|
"display_name": "Silent Errors",
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
|
|
@ -19,13 +24,22 @@ class FileComponent(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def load_file(self, path: str, silent_errors: bool = False) -> Record:
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
silent_errors: bool = False,
|
|
||||||
) -> Optional[Record]:
|
|
||||||
resolved_path = self.resolve_path(path)
|
resolved_path = self.resolve_path(path)
|
||||||
extension = resolved_path.split(".")[-1]
|
extension = resolved_path.split(".")[-1]
|
||||||
|
if extension == "doc":
|
||||||
|
raise ValueError("doc files are not supported. Please save as .docx")
|
||||||
if extension not in TEXT_FILE_TYPES:
|
if extension not in TEXT_FILE_TYPES:
|
||||||
raise ValueError(f"Unsupported file type: {extension}")
|
raise ValueError(f"Unsupported file type: {extension}")
|
||||||
return parse_text_file_to_record(resolved_path, silent_errors)
|
record = parse_text_file_to_record(resolved_path, silent_errors)
|
||||||
|
self.status = record if record else "No data"
|
||||||
|
return record or Record()
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
paths: List[str],
|
||||||
|
silent_errors: bool = False,
|
||||||
|
) -> List[Record]:
|
||||||
|
records = [self.load_file(path, silent_errors) for path in paths]
|
||||||
|
self.status = records
|
||||||
|
return records
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
from langchain_core.vectorstores import VectorStoreRetriever
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.field_typing import VectorStore
|
||||||
|
|
||||||
|
|
||||||
|
class VectoStoreRetrieverComponent(CustomComponent):
|
||||||
|
display_name = "VectorStore Retriever"
|
||||||
|
description = "A vector store retriever"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"vectorstore": {"display_name": "Vector Store", "type": VectorStore},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, vectorstore: VectorStore) -> VectorStoreRetriever:
|
||||||
|
return vectorstore.as_retriever()
|
||||||
32
src/backend/langflow/components/tools/RetrieverTool.py
Normal file
32
src/backend/langflow/components/tools/RetrieverTool.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from langchain.tools.retriever import create_retriever_tool
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.field_typing import BaseRetriever, Tool
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieverToolComponent(CustomComponent):
|
||||||
|
display_name = "RetrieverTool"
|
||||||
|
description = "Tool for interacting with retriever"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"retriever": {
|
||||||
|
"display_name": "Retriever",
|
||||||
|
"info": "Retriever to interact with",
|
||||||
|
"type": BaseRetriever,
|
||||||
|
},
|
||||||
|
"name": {"display_name": "Name", "info": "Name of the tool"},
|
||||||
|
"description": {"display_name": "Description", "info": "Description of the tool"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
retriever: BaseRetriever,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
) -> Tool:
|
||||||
|
return create_retriever_tool(
|
||||||
|
retriever=retriever,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
0
src/backend/langflow/components/tools/__init__.py
Normal file
0
src/backend/langflow/components/tools/__init__.py
Normal file
|
|
@ -36,13 +36,12 @@ class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent)
|
||||||
mongodb_atlas_cluster_uri: str = "",
|
mongodb_atlas_cluster_uri: str = "",
|
||||||
search_kwargs: Optional[NestedDict] = None,
|
search_kwargs: Optional[NestedDict] = None,
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
|
search_kwargs = search_kwargs or {}
|
||||||
vector_store = super().build(
|
vector_store = super().build(
|
||||||
|
connection_string=mongodb_atlas_cluster_uri,
|
||||||
|
namespace=f"{db_name}.{collection_name}",
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
collection_name=collection_name,
|
|
||||||
db_name=db_name,
|
|
||||||
index_name=index_name,
|
index_name=index_name,
|
||||||
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
|
|
||||||
search_kwargs=search_kwargs,
|
|
||||||
)
|
)
|
||||||
if not vector_store:
|
if not vector_store:
|
||||||
raise ValueError("Failed to create MongoDB Atlas Vector Store")
|
raise ValueError("Failed to create MongoDB Atlas Vector Store")
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,9 @@ from jose import JWTError, jwt
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
from starlette.websockets import WebSocket
|
from starlette.websockets import WebSocket
|
||||||
|
|
||||||
from langflow.services.database.models.api_key.model import ApiKey
|
|
||||||
from langflow.services.database.models.api_key.crud import check_key
|
from langflow.services.database.models.api_key.crud import check_key
|
||||||
from langflow.services.database.models.user.crud import (
|
from langflow.services.database.models.api_key.model import ApiKey
|
||||||
get_user_by_id,
|
from langflow.services.database.models.user.crud import get_user_by_id, get_user_by_username, update_user_last_login_at
|
||||||
get_user_by_username,
|
|
||||||
update_user_last_login_at,
|
|
||||||
)
|
|
||||||
from langflow.services.database.models.user.model import User
|
from langflow.services.database.models.user.model import User
|
||||||
from langflow.services.deps import get_session, get_settings_service
|
from langflow.services.deps import get_session, get_settings_service
|
||||||
|
|
||||||
|
|
@ -107,13 +103,13 @@ async def get_current_user_by_jwt(
|
||||||
if isinstance(token, Coroutine):
|
if isinstance(token, Coroutine):
|
||||||
token = await token
|
token = await token
|
||||||
|
|
||||||
if settings_service.auth_settings.SECRET_KEY is None:
|
if settings_service.auth_settings.SECRET_KEY.get_secret_value() is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token,
|
token,
|
||||||
settings_service.auth_settings.SECRET_KEY,
|
settings_service.auth_settings.SECRET_KEY.get_secret_value(),
|
||||||
algorithms=[settings_service.auth_settings.ALGORITHM],
|
algorithms=[settings_service.auth_settings.ALGORITHM],
|
||||||
)
|
)
|
||||||
user_id: UUID = payload.get("sub") # type: ignore
|
user_id: UUID = payload.get("sub") # type: ignore
|
||||||
|
|
@ -183,7 +179,7 @@ def create_token(data: dict, expires_delta: timedelta):
|
||||||
|
|
||||||
return jwt.encode(
|
return jwt.encode(
|
||||||
to_encode,
|
to_encode,
|
||||||
settings_service.auth_settings.SECRET_KEY,
|
settings_service.auth_settings.SECRET_KEY.get_secret_value(),
|
||||||
algorithm=settings_service.auth_settings.ALGORITHM,
|
algorithm=settings_service.auth_settings.ALGORITHM,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -287,7 +283,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session))
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
refresh_token,
|
refresh_token,
|
||||||
settings_service.auth_settings.SECRET_KEY,
|
settings_service.auth_settings.SECRET_KEY.get_secret_value(),
|
||||||
algorithms=[settings_service.auth_settings.ALGORITHM],
|
algorithms=[settings_service.auth_settings.ALGORITHM],
|
||||||
)
|
)
|
||||||
user_id: UUID = payload.get("sub") # type: ignore
|
user_id: UUID = payload.get("sub") # type: ignore
|
||||||
|
|
@ -326,7 +322,7 @@ def add_padding(s):
|
||||||
|
|
||||||
|
|
||||||
def get_fernet(settings_service=Depends(get_settings_service)):
|
def get_fernet(settings_service=Depends(get_settings_service)):
|
||||||
SECRET_KEY = settings_service.auth_settings.SECRET_KEY
|
SECRET_KEY = settings_service.auth_settings.SECRET_KEY.get_secret_value()
|
||||||
# It's important that your secret key is 32 url-safe base64-encoded byte
|
# It's important that your secret key is 32 url-safe base64-encoded byte
|
||||||
padded_secret_key = add_padding(SECRET_KEY)
|
padded_secret_key = add_padding(SECRET_KEY)
|
||||||
fernet = Fernet(padded_secret_key)
|
fernet = Fernet(padded_secret_key)
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import secrets
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
from loguru import logger
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from pydantic import Field, SecretStr, validator
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
from langflow.services.settings.constants import (
|
from langflow.services.settings.constants import (
|
||||||
DEFAULT_SUPERUSER,
|
DEFAULT_SUPERUSER,
|
||||||
DEFAULT_SUPERUSER_PASSWORD,
|
DEFAULT_SUPERUSER_PASSWORD,
|
||||||
)
|
)
|
||||||
from langflow.services.settings.utils import read_secret_from_file, write_secret_to_file
|
from langflow.services.settings.utils import read_secret_from_file, write_secret_to_file
|
||||||
from loguru import logger
|
|
||||||
from passlib.context import CryptContext
|
|
||||||
from pydantic import Field, validator
|
|
||||||
from pydantic_settings import BaseSettings
|
|
||||||
|
|
||||||
|
|
||||||
class AuthSettings(BaseSettings):
|
class AuthSettings(BaseSettings):
|
||||||
# Login settings
|
# Login settings
|
||||||
CONFIG_DIR: str
|
CONFIG_DIR: str
|
||||||
SECRET_KEY: str = Field(
|
SECRET_KEY: SecretStr = Field(
|
||||||
default="",
|
default=None,
|
||||||
description="Secret key for JWT. If not provided, a random one will be generated.",
|
description="Secret key for JWT. If not provided, a random one will be generated.",
|
||||||
frozen=False,
|
frozen=False,
|
||||||
)
|
)
|
||||||
|
|
@ -26,7 +26,6 @@ class AuthSettings(BaseSettings):
|
||||||
REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12 * 7
|
REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12 * 7
|
||||||
|
|
||||||
# API Key to execute /process endpoint
|
# API Key to execute /process endpoint
|
||||||
API_KEY_SECRET_KEY: Optional[str] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a"
|
|
||||||
API_KEY_ALGORITHM: str = "HS256"
|
API_KEY_ALGORITHM: str = "HS256"
|
||||||
API_V1_STR: str = "/api/v1"
|
API_V1_STR: str = "/api/v1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import "reactflow/dist/style.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import ErrorAlert from "./alerts/error";
|
import ErrorAlert from "./alerts/error";
|
||||||
import NoticeAlert from "./alerts/notice";
|
import NoticeAlert from "./alerts/notice";
|
||||||
import SuccessAlert from "./alerts/success";
|
import SuccessAlert from "./alerts/success";
|
||||||
|
|
@ -47,6 +48,9 @@ export default function App() {
|
||||||
(state) => state.setGlobalVariables
|
(state) => state.setGlobalVariables
|
||||||
);
|
);
|
||||||
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [isLoadingHealth, setIsLoadingHealth] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshStars();
|
refreshStars();
|
||||||
|
|
@ -67,11 +71,12 @@ export default function App() {
|
||||||
}, [isAuthenticated]);
|
}, [isAuthenticated]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
checkApplicationHealth();
|
||||||
// Timer to call getHealth every 5 seconds
|
// Timer to call getHealth every 5 seconds
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
getHealth()
|
getHealth()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (fetchError) setFetchError(false);
|
onHealthCheck();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setFetchError(true);
|
setFetchError(true);
|
||||||
|
|
@ -84,6 +89,30 @@ export default function App() {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const checkApplicationHealth = () => {
|
||||||
|
setIsLoadingHealth(true);
|
||||||
|
getHealth()
|
||||||
|
.then(() => {
|
||||||
|
onHealthCheck();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setFetchError(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoadingHealth(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onHealthCheck = () => {
|
||||||
|
setFetchError(false);
|
||||||
|
//This condition is necessary to avoid infinite loop on starter page when the application is not healthy
|
||||||
|
if (isLoading === true && window.location.pathname === "/") {
|
||||||
|
navigate("/flows");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//need parent component with width and height
|
//need parent component with width and height
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
|
|
@ -93,20 +122,29 @@ export default function App() {
|
||||||
}}
|
}}
|
||||||
FallbackComponent={CrashErrorComponent}
|
FallbackComponent={CrashErrorComponent}
|
||||||
>
|
>
|
||||||
{fetchError ? (
|
<>
|
||||||
<FetchErrorComponent
|
{
|
||||||
description={FETCH_ERROR_DESCRIPION}
|
<FetchErrorComponent
|
||||||
message={FETCH_ERROR_MESSAGE}
|
description={FETCH_ERROR_DESCRIPION}
|
||||||
></FetchErrorComponent>
|
message={FETCH_ERROR_MESSAGE}
|
||||||
) : isLoading ? (
|
openModal={fetchError}
|
||||||
<div className="loading-page-panel">
|
setRetry={() => {
|
||||||
<LoadingComponent remSize={50} />
|
checkApplicationHealth();
|
||||||
</div>
|
}}
|
||||||
) : (
|
isLoadingHealth={isLoadingHealth}
|
||||||
<>
|
></FetchErrorComponent>
|
||||||
<Router />
|
}
|
||||||
</>
|
|
||||||
)}
|
{isLoading ? (
|
||||||
|
<div className="loading-page-panel">
|
||||||
|
<LoadingComponent remSize={50} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Router />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div></div>
|
<div></div>
|
||||||
<div className="app-div">
|
<div className="app-div">
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,8 @@ export default function GenericNode({
|
||||||
name="Play"
|
name="Play"
|
||||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0"
|
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0"
|
||||||
/>
|
/>
|
||||||
) : validationStatus && !validationStatus.valid ? (
|
) : buildStatus === BuildStatus.ERROR ||
|
||||||
|
(validationStatus && !validationStatus.valid) ? (
|
||||||
<Xmark
|
<Xmark
|
||||||
isVisible={true}
|
isVisible={true}
|
||||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0"
|
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0"
|
||||||
|
|
@ -301,7 +302,10 @@ export default function GenericNode({
|
||||||
// INACTIVE should have its own class
|
// INACTIVE should have its own class
|
||||||
return "inactive-status";
|
return "inactive-status";
|
||||||
}
|
}
|
||||||
if (buildStatus === BuildStatus.BUILT && isInvalid) {
|
if (
|
||||||
|
(buildStatus === BuildStatus.BUILT && isInvalid) ||
|
||||||
|
buildStatus === BuildStatus.ERROR
|
||||||
|
) {
|
||||||
return isDark ? "built-invalid-status-dark" : "built-invalid-status";
|
return isDark ? "built-invalid-status-dark" : "built-invalid-status";
|
||||||
} else if (buildStatus === BuildStatus.BUILDING) {
|
} else if (buildStatus === BuildStatus.BUILDING) {
|
||||||
return "building-status";
|
return "building-status";
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import Loading from "../../../components/ui/loading";
|
||||||
import { FlowType } from "../../../types/flow";
|
import { FlowType } from "../../../types/flow";
|
||||||
|
|
||||||
import { MISSED_ERROR_ALERT } from "../../../constants/alerts_constants";
|
import { MISSED_ERROR_ALERT } from "../../../constants/alerts_constants";
|
||||||
|
import { BuildStatus } from "../../../constants/enums";
|
||||||
import useAlertStore from "../../../stores/alertStore";
|
import useAlertStore from "../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../stores/flowStore";
|
import useFlowStore from "../../../stores/flowStore";
|
||||||
import { validateNodes } from "../../../utils/reactflowUtils";
|
import { validateNodes } from "../../../utils/reactflowUtils";
|
||||||
|
|
@ -22,6 +23,7 @@ export default function BuildTrigger({
|
||||||
const buildFlow = useFlowStore((state) => state.buildFlow);
|
const buildFlow = useFlowStore((state) => state.buildFlow);
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
const eventClick = isBuilding ? "pointer-events-none" : "";
|
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||||
|
|
@ -32,12 +34,15 @@ export default function BuildTrigger({
|
||||||
if (isBuilding) {
|
if (isBuilding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const errors = validateNodes(nodes, edges);
|
const errorsObjs = validateNodes(nodes, edges);
|
||||||
|
const errors = errorsObjs.flatMap((errorObj) => errorObj.errors);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: MISSED_ERROR_ALERT,
|
title: MISSED_ERROR_ALERT,
|
||||||
list: errors,
|
list: errors,
|
||||||
});
|
});
|
||||||
|
const ids = errorsObjs.map((errorObj) => errorObj.id);
|
||||||
|
updateBuildStatus(ids, BuildStatus.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const minimumLoadingTime = 200; // in milliseconds
|
const minimumLoadingTime = 200; // in milliseconds
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,51 @@
|
||||||
|
import BaseModal from "../../modals/baseModal";
|
||||||
import { fetchErrorComponentType } from "../../types/components";
|
import { fetchErrorComponentType } from "../../types/components";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
export default function FetchErrorComponent({
|
export default function FetchErrorComponent({
|
||||||
message,
|
message,
|
||||||
description,
|
description,
|
||||||
|
openModal,
|
||||||
|
setRetry,
|
||||||
|
isLoadingHealth,
|
||||||
}: fetchErrorComponentType) {
|
}: fetchErrorComponentType) {
|
||||||
return (
|
return (
|
||||||
<div role="status" className="m-auto flex flex-col items-center">
|
<>
|
||||||
<IconComponent className={`h-16 w-16`} name="Unplug"></IconComponent>
|
<BaseModal size="small-h-full" open={openModal} type="modal">
|
||||||
<br></br>
|
<BaseModal.Content>
|
||||||
<span className="text-lg text-almost-medium-blue">{message}</span>
|
<div role="status" className="m-auto flex flex-col items-center">
|
||||||
<span className="text-lg text-almost-medium-blue">{description}</span>
|
<IconComponent
|
||||||
</div>
|
className={`h-16 w-16`}
|
||||||
|
name="Unplug"
|
||||||
|
></IconComponent>
|
||||||
|
<br></br>
|
||||||
|
<span className="text-lg text-almost-medium-blue">{message}</span>
|
||||||
|
<span className="text-lg text-almost-medium-blue">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</BaseModal.Content>
|
||||||
|
|
||||||
|
<BaseModal.Footer>
|
||||||
|
<div className="m-auto">
|
||||||
|
<Button
|
||||||
|
disabled={isLoadingHealth}
|
||||||
|
onClick={() => {
|
||||||
|
setRetry();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoadingHealth ? (
|
||||||
|
<div>
|
||||||
|
<IconComponent name={"Loader2"} className={"animate-spin"} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
"Retry"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</BaseModal.Footer>
|
||||||
|
</BaseModal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export default function PromptAreaComponent({
|
||||||
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
||||||
<GenericModal
|
<GenericModal
|
||||||
id={id}
|
id={id}
|
||||||
|
field_name={field_name}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
type={TypeModal.PROMPT}
|
type={TypeModal.PROMPT}
|
||||||
value={value}
|
value={value}
|
||||||
|
|
|
||||||
119
src/frontend/src/components/ui/dialog-with-no-close.tsx
Normal file
119
src/frontend/src/components/ui/dialog-with-no-close.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "../../utils/utils";
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
|
|
||||||
|
const DialogPortal = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: DialogPrimitive.DialogPortalProps) => (
|
||||||
|
<DialogPrimitive.Portal {...props}>
|
||||||
|
<div className="nopan nodelete nodrag noundo nocopy fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
);
|
||||||
|
DialogPortal.displayName = DialogPrimitive.Portal.displayName;
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"nopan nodelete nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed z-50 flex w-full max-w-lg flex-col gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] sm:rounded-lg md:w-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
));
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DialogHeader.displayName = "DialogHeader";
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DialogFooter.displayName = "DialogFooter";
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
};
|
||||||
|
|
@ -12,4 +12,5 @@ export enum BuildStatus {
|
||||||
TO_BUILD = "TO_BUILD",
|
TO_BUILD = "TO_BUILD",
|
||||||
BUILT = "BUILT",
|
BUILT = "BUILT",
|
||||||
INACTIVE = "INACTIVE",
|
INACTIVE = "INACTIVE",
|
||||||
|
ERROR = "ERROR",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,12 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../../components/ui/dialog";
|
} from "../../components/ui/dialog";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog as Modal,
|
||||||
|
DialogContent as ModalContent,
|
||||||
|
} from "../../components/ui/dialog-with-no-close";
|
||||||
|
|
||||||
import { modalHeaderType } from "../../types/components";
|
import { modalHeaderType } from "../../types/components";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
|
|
||||||
|
|
@ -76,6 +82,7 @@ interface BaseModalProps {
|
||||||
|
|
||||||
disable?: boolean;
|
disable?: boolean;
|
||||||
onChangeOpenModal?: (open?: boolean) => void;
|
onChangeOpenModal?: (open?: boolean) => void;
|
||||||
|
type?: "modal" | "dialog";
|
||||||
}
|
}
|
||||||
function BaseModal({
|
function BaseModal({
|
||||||
open,
|
open,
|
||||||
|
|
@ -83,6 +90,7 @@ function BaseModal({
|
||||||
children,
|
children,
|
||||||
size = "large",
|
size = "large",
|
||||||
onChangeOpenModal,
|
onChangeOpenModal,
|
||||||
|
type = "dialog",
|
||||||
}: BaseModalProps) {
|
}: BaseModalProps) {
|
||||||
const headerChild = React.Children.toArray(children).find(
|
const headerChild = React.Children.toArray(children).find(
|
||||||
(child) => (child as React.ReactElement).type === Header
|
(child) => (child as React.ReactElement).type === Header
|
||||||
|
|
@ -156,22 +164,43 @@ function BaseModal({
|
||||||
|
|
||||||
//UPDATE COLORS AND STYLE CLASSSES
|
//UPDATE COLORS AND STYLE CLASSSES
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<>
|
||||||
{triggerChild}
|
{type === "modal" ? (
|
||||||
<DialogContent className={cn(minWidth, "duration-300")}>
|
<Modal open={open} onOpenChange={setOpen}>
|
||||||
<div className="truncate-doubleline word-break-break-word">
|
{triggerChild}
|
||||||
{headerChild}
|
<ModalContent className={cn(minWidth, "duration-300")}>
|
||||||
</div>
|
<div className="truncate-doubleline word-break-break-word">
|
||||||
<div
|
{headerChild}
|
||||||
className={`flex flex-col ${height!} w-full transition-all duration-300`}
|
</div>
|
||||||
>
|
<div
|
||||||
{ContentChild}
|
className={`flex flex-col ${height!} w-full transition-all duration-300`}
|
||||||
</div>
|
>
|
||||||
{ContentFooter && (
|
{ContentChild}
|
||||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
</div>
|
||||||
)}
|
{ContentFooter && (
|
||||||
</DialogContent>
|
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||||
</Dialog>
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
) : (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
{triggerChild}
|
||||||
|
<DialogContent className={cn(minWidth, "duration-300")}>
|
||||||
|
<div className="truncate-doubleline word-break-break-word">
|
||||||
|
{headerChild}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`flex flex-col ${height!} w-full transition-all duration-300`}
|
||||||
|
>
|
||||||
|
{ContentChild}
|
||||||
|
</div>
|
||||||
|
{ContentFooter && (
|
||||||
|
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
CHAT_SECOND_INITIAL_TEXT,
|
CHAT_SECOND_INITIAL_TEXT,
|
||||||
LANGFLOW_CHAT_TITLE,
|
LANGFLOW_CHAT_TITLE,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
|
import { BuildStatus } from "../../constants/enums";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { getBuildStatus } from "../../controllers/API";
|
import { getBuildStatus } from "../../controllers/API";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
|
@ -49,6 +50,7 @@ export default function FormModal({
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
|
||||||
const flowState = useFlowStore((state) => state.flowState);
|
const flowState = useFlowStore((state) => state.flowState);
|
||||||
const setFlowState = useFlowStore((state) => state.setFlowState);
|
const setFlowState = useFlowStore((state) => state.setFlowState);
|
||||||
const [chatValue, setChatValue] = useState(() => {
|
const [chatValue, setChatValue] = useState(() => {
|
||||||
|
|
@ -388,7 +390,8 @@ export default function FormModal({
|
||||||
|
|
||||||
function sendMessage(): void {
|
function sendMessage(): void {
|
||||||
let nodeValidationErrors = validateNodes(nodes, edges);
|
let nodeValidationErrors = validateNodes(nodes, edges);
|
||||||
if (nodeValidationErrors.length === 0) {
|
const errors = nodeValidationErrors.flatMap((error) => error.errors);
|
||||||
|
if (errors.length === 0) {
|
||||||
setLockChat(true);
|
setLockChat(true);
|
||||||
let inputs = flowState?.input_keys;
|
let inputs = flowState?.input_keys;
|
||||||
setChatValue("");
|
setChatValue("");
|
||||||
|
|
@ -412,8 +415,10 @@ export default function FormModal({
|
||||||
} else {
|
} else {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: INFO_MISSING_ALERT,
|
title: INFO_MISSING_ALERT,
|
||||||
list: nodeValidationErrors,
|
list: errors,
|
||||||
});
|
});
|
||||||
|
const ids = nodeValidationErrors.map((error) => error.id);
|
||||||
|
updateBuildStatus(ids, BuildStatus.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function clearChat(): void {
|
function clearChat(): void {
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,49 @@ export default function Page({
|
||||||
const [lastSelection, setLastSelection] =
|
const [lastSelection, setLastSelection] =
|
||||||
useState<OnSelectionChangeParams | null>(null);
|
useState<OnSelectionChangeParams | null>(null);
|
||||||
|
|
||||||
|
function handleGroupNode() {
|
||||||
|
takeSnapshot();
|
||||||
|
if (validateSelection(lastSelection!, edges).length === 0) {
|
||||||
|
const clonedNodes = cloneDeep(nodes);
|
||||||
|
const clonedEdges = cloneDeep(edges);
|
||||||
|
const clonedSelection = cloneDeep(lastSelection);
|
||||||
|
updateIds({ nodes: clonedNodes, edges: clonedEdges }, clonedSelection!);
|
||||||
|
const { newFlow, removedEdges } = generateFlow(
|
||||||
|
clonedSelection!,
|
||||||
|
clonedNodes,
|
||||||
|
clonedEdges,
|
||||||
|
getRandomName()
|
||||||
|
);
|
||||||
|
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
|
||||||
|
const newEdges = reconnectEdges(newGroupNode, removedEdges);
|
||||||
|
setNodes([
|
||||||
|
...clonedNodes.filter(
|
||||||
|
(oldNodes) =>
|
||||||
|
!clonedSelection?.nodes.some(
|
||||||
|
(selectionNode) => selectionNode.id === oldNodes.id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
newGroupNode,
|
||||||
|
]);
|
||||||
|
setEdges([
|
||||||
|
...clonedEdges.filter(
|
||||||
|
(oldEdge) =>
|
||||||
|
!clonedSelection!.nodes.some(
|
||||||
|
(selectionNode) =>
|
||||||
|
selectionNode.id === oldEdge.target ||
|
||||||
|
selectionNode.id === oldEdge.source
|
||||||
|
)
|
||||||
|
),
|
||||||
|
...newEdges,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: INVALID_SELECTION_ERROR_ALERT,
|
||||||
|
list: validateSelection(lastSelection!, edges),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|
@ -104,49 +147,7 @@ export default function Page({
|
||||||
event.key === "g"
|
event.key === "g"
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
takeSnapshot();
|
handleGroupNode();
|
||||||
if (validateSelection(lastSelection!, edges).length === 0) {
|
|
||||||
const clonedNodes = cloneDeep(nodes);
|
|
||||||
const clonedEdges = cloneDeep(edges);
|
|
||||||
const clonedSelection = cloneDeep(lastSelection);
|
|
||||||
updateIds(
|
|
||||||
{ nodes: clonedNodes, edges: clonedEdges },
|
|
||||||
clonedSelection!
|
|
||||||
);
|
|
||||||
const { newFlow, removedEdges } = generateFlow(
|
|
||||||
clonedSelection!,
|
|
||||||
clonedNodes,
|
|
||||||
clonedEdges,
|
|
||||||
getRandomName()
|
|
||||||
);
|
|
||||||
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
|
|
||||||
const newEdges = reconnectEdges(newGroupNode, removedEdges);
|
|
||||||
setNodes([
|
|
||||||
...clonedNodes.filter(
|
|
||||||
(oldNodes) =>
|
|
||||||
!clonedSelection?.nodes.some(
|
|
||||||
(selectionNode) => selectionNode.id === oldNodes.id
|
|
||||||
)
|
|
||||||
),
|
|
||||||
newGroupNode,
|
|
||||||
]);
|
|
||||||
setEdges([
|
|
||||||
...clonedEdges.filter(
|
|
||||||
(oldEdge) =>
|
|
||||||
!clonedSelection!.nodes.some(
|
|
||||||
(selectionNode) =>
|
|
||||||
selectionNode.id === oldEdge.target ||
|
|
||||||
selectionNode.id === oldEdge.source
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...newEdges,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: INVALID_SELECTION_ERROR_ALERT,
|
|
||||||
list: validateSelection(lastSelection!, edges),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
|
@ -484,58 +485,7 @@ export default function Page({
|
||||||
isVisible={selectionMenuVisible}
|
isVisible={selectionMenuVisible}
|
||||||
nodes={lastSelection?.nodes}
|
nodes={lastSelection?.nodes}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
takeSnapshot();
|
handleGroupNode();
|
||||||
if (
|
|
||||||
validateSelection(lastSelection!, edges).length === 0
|
|
||||||
) {
|
|
||||||
const clonedNodes = cloneDeep(nodes);
|
|
||||||
const clonedEdges = cloneDeep(edges);
|
|
||||||
const clonedSelection = cloneDeep(lastSelection);
|
|
||||||
updateIds(
|
|
||||||
{ nodes: clonedNodes, edges: clonedEdges },
|
|
||||||
clonedSelection!
|
|
||||||
);
|
|
||||||
const { newFlow, removedEdges } = generateFlow(
|
|
||||||
clonedSelection!,
|
|
||||||
clonedNodes,
|
|
||||||
clonedEdges,
|
|
||||||
getRandomName()
|
|
||||||
);
|
|
||||||
const newGroupNode = generateNodeFromFlow(
|
|
||||||
newFlow,
|
|
||||||
getNodeId
|
|
||||||
);
|
|
||||||
const newEdges = reconnectEdges(
|
|
||||||
newGroupNode,
|
|
||||||
removedEdges
|
|
||||||
);
|
|
||||||
setNodes([
|
|
||||||
...clonedNodes.filter(
|
|
||||||
(oldNodes) =>
|
|
||||||
!clonedSelection?.nodes.some(
|
|
||||||
(selectionNode) =>
|
|
||||||
selectionNode.id === oldNodes.id
|
|
||||||
)
|
|
||||||
),
|
|
||||||
newGroupNode,
|
|
||||||
]);
|
|
||||||
setEdges([
|
|
||||||
...clonedEdges.filter(
|
|
||||||
(oldEdge) =>
|
|
||||||
!clonedSelection!.nodes.some(
|
|
||||||
(selectionNode) =>
|
|
||||||
selectionNode.id === oldEdge.target ||
|
|
||||||
selectionNode.id === oldEdge.source
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...newEdges,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
setErrorData({
|
|
||||||
title: INVALID_SELECTION_ERROR_ALERT,
|
|
||||||
list: validateSelection(lastSelection!, edges),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
|
||||||
|
|
@ -270,19 +270,10 @@ export default function NodeToolbarComponent({
|
||||||
selected &&
|
selected &&
|
||||||
isGroup &&
|
isGroup &&
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "u"
|
event.key === "g"
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
takeSnapshot();
|
handleSelectChange("ungroup");
|
||||||
expandGroupNode(
|
|
||||||
data.id,
|
|
||||||
updateFlowPosition(position, data.node?.flow!),
|
|
||||||
data.node!.template,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
setNodes,
|
|
||||||
setEdges
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
selected &&
|
selected &&
|
||||||
|
|
@ -571,7 +562,7 @@ export default function NodeToolbarComponent({
|
||||||
Ctrl +{" "}
|
Ctrl +{" "}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="absolute right-2 top-[0.43em]">U</span>
|
<span className="absolute right-2 top-[0.43em]">G</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -438,16 +438,20 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
const setNoticeData = useAlertStore.getState().setNoticeData;
|
const setNoticeData = useAlertStore.getState().setNoticeData;
|
||||||
function validateSubgraph(nodes: string[]) {
|
function validateSubgraph(nodes: string[]) {
|
||||||
const errors = validateNodes(
|
const errorsObjs = validateNodes(
|
||||||
get().nodes.filter((node) => nodes.includes(node.id)),
|
get().nodes.filter((node) => nodes.includes(node.id)),
|
||||||
get().edges
|
get().edges
|
||||||
);
|
);
|
||||||
|
const errors = errorsObjs.map((obj) => obj.errors).flat();
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: MISSED_ERROR_ALERT,
|
title: MISSED_ERROR_ALERT,
|
||||||
list: errors,
|
list: errors,
|
||||||
});
|
});
|
||||||
get().setIsBuilding(false);
|
get().setIsBuilding(false);
|
||||||
|
const ids = errorsObjs.map((obj) => obj.id).flat();
|
||||||
|
console.log("ids", ids);
|
||||||
|
get().updateBuildStatus(ids, BuildStatus.ERROR);
|
||||||
throw new Error("Invalid nodes");
|
throw new Error("Invalid nodes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -538,7 +542,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
|
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
|
||||||
},
|
},
|
||||||
validateNodes: validateSubgraph,
|
onValidateNodes: validateSubgraph,
|
||||||
});
|
});
|
||||||
get().setIsBuilding(false);
|
get().setIsBuilding(false);
|
||||||
get().revertBuiltStatusFromBuilding();
|
get().revertBuiltStatusFromBuilding();
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
set({ isLoading: false });
|
||||||
useAlertStore.getState().setErrorData({
|
useAlertStore.getState().setErrorData({
|
||||||
title: "Could not load flows from database",
|
title: "Could not load flows from database",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
useAlertStore.getState().setErrorData({
|
|
||||||
title: "An error has occurred while fetching types.",
|
|
||||||
list: ["Please refresh the page."],
|
|
||||||
});
|
|
||||||
console.error("An error has occurred while fetching types.");
|
console.error("An error has occurred while fetching types.");
|
||||||
console.log(error);
|
console.log(error);
|
||||||
reject();
|
reject();
|
||||||
|
|
|
||||||
|
|
@ -672,6 +672,9 @@ export type ApiKey = {
|
||||||
export type fetchErrorComponentType = {
|
export type fetchErrorComponentType = {
|
||||||
message: string;
|
message: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
openModal?: boolean;
|
||||||
|
setRetry: () => void;
|
||||||
|
isLoadingHealth: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type dropdownButtonPropsType = {
|
export type dropdownButtonPropsType = {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ type BuildVerticesParams = {
|
||||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||||
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
|
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
|
||||||
onBuildStart?: (idList: VertexLayerElementType[]) => void;
|
onBuildStart?: (idList: VertexLayerElementType[]) => void;
|
||||||
validateNodes?: (nodes: string[]) => void;
|
onValidateNodes?: (nodes: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
||||||
|
|
@ -114,7 +114,7 @@ export async function buildVertices({
|
||||||
onBuildComplete,
|
onBuildComplete,
|
||||||
onBuildError,
|
onBuildError,
|
||||||
onBuildStart,
|
onBuildStart,
|
||||||
validateNodes,
|
onValidateNodes,
|
||||||
}: BuildVerticesParams) {
|
}: BuildVerticesParams) {
|
||||||
let verticesBuild = useFlowStore.getState().verticesBuild;
|
let verticesBuild = useFlowStore.getState().verticesBuild;
|
||||||
// if startNodeId and stopNodeId are provided
|
// if startNodeId and stopNodeId are provided
|
||||||
|
|
@ -133,10 +133,10 @@ export async function buildVertices({
|
||||||
|
|
||||||
if (onGetOrderSuccess) onGetOrderSuccess();
|
if (onGetOrderSuccess) onGetOrderSuccess();
|
||||||
|
|
||||||
if (validateNodes) {
|
if (onValidateNodes) {
|
||||||
try {
|
try {
|
||||||
const nodes = useFlowStore.getState().nodes;
|
const nodes = useFlowStore.getState().nodes;
|
||||||
validateNodes(nodes.map((node) => node.id));
|
onValidateNodes(nodes.map((node) => node.id));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -313,27 +313,40 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
||||||
) {
|
) {
|
||||||
if (hasDuplicateKeys(template[t].value))
|
if (hasDuplicateKeys(template[t].value))
|
||||||
errors.push(
|
errors.push(
|
||||||
`${type} (${getFieldTitle(
|
`${displayName || type} (${getFieldTitle(
|
||||||
template,
|
template,
|
||||||
t
|
t
|
||||||
)}) contains duplicate keys with the same values.`
|
)}) contains duplicate keys with the same values.`
|
||||||
);
|
);
|
||||||
if (hasEmptyKey(template[t].value))
|
if (hasEmptyKey(template[t].value))
|
||||||
errors.push(
|
errors.push(
|
||||||
`${type} (${getFieldTitle(template, t)}) field must not be empty.`
|
`${displayName || type} (${getFieldTitle(
|
||||||
|
template,
|
||||||
|
t
|
||||||
|
)}) field must not be empty.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}, [] as string[]);
|
}, [] as string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateNodes(nodes: Node[], edges: Edge[]) {
|
export function validateNodes(
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[]
|
||||||
|
): // this returns an array of tuples with the node id and the errors
|
||||||
|
Array<{ id: string; errors: Array<string> }> {
|
||||||
if (nodes.length === 0) {
|
if (nodes.length === 0) {
|
||||||
return [
|
return [
|
||||||
"No nodes found in the flow. Please add at least one node to the flow.",
|
{
|
||||||
|
id: "",
|
||||||
|
errors: [
|
||||||
|
"No nodes found in the flow. Please add at least one node to the flow.",
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return nodes.flatMap((n: NodeType) => validateNode(n, edges));
|
// validateNode(n, edges) returns an array of errors for the node
|
||||||
|
return nodes.map((n) => ({ id: n.id, errors: validateNode(n, edges) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateEdges(edges: Edge[]) {
|
export function updateEdges(edges: Edge[]) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue