Adds Google Generative AI Component and other small bugfixes (#1207)
This commit is contained in:
commit
bba6aa70e9
43 changed files with 5778 additions and 40 deletions
|
|
@ -1 +1,2 @@
|
|||
.venv/
|
||||
.venv/
|
||||
**/aws
|
||||
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
|
|
@ -1,17 +0,0 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 45
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -40,5 +40,5 @@ jobs:
|
|||
run: docker compose up --exit-code-from tests tests result_backend broker celeryworker db --build
|
||||
continue-on-error: true
|
||||
|
||||
- name: Stop services
|
||||
run: docker compose down
|
||||
# - name: Stop services
|
||||
# run: docker compose down
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ Each option is detailed below:
|
|||
- `--install-completion [bash|zsh|fish|powershell|pwsh]`: Installs completion for the specified shell.
|
||||
- `--show-completion [bash|zsh|fish|powershell|pwsh]`: Shows completion for the specified shell, allowing you to copy it or customize the installation.
|
||||
- `--backend-only`: This parameter, with a default value of `False`, allows running only the backend server without the frontend. It can also be set using the `LANGFLOW_BACKEND_ONLY` environment variable.
|
||||
- `store`: This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable.
|
||||
- `--store`: This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable.
|
||||
|
||||
These parameters are important for users who need to customize the behavior of Langflow, especially in development or specialized deployment scenarios. You may want to update the documentation to include these parameters for completeness and clarity.
|
||||
|
||||
|
|
|
|||
20
cdk.Dockerfile
Normal file
20
cdk.Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
FROM --platform=linux/amd64 python:3.10-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Poetry
|
||||
RUN apt-get update && apt-get install gcc g++ curl build-essential postgresql-server-dev-all -y
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
# # Add Poetry to PATH
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
# # Copy the pyproject.toml and poetry.lock files
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
# Copy the rest of the application codes
|
||||
COPY ./ ./
|
||||
|
||||
# Install dependencies
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
||||
|
||||
RUN poetry add pymysql==1.0.2
|
||||
|
||||
CMD ["sh", "./container-cmd-cdk.sh"]
|
||||
3
container-cmd-cdk.sh
Normal file
3
container-cmd-cdk.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export LANGFLOW_DATABASE_URL="mysql+pymysql://${username}:${password}@${host}:3306/${dbname}"
|
||||
# echo $LANGFLOW_DATABASE_URL
|
||||
uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug
|
||||
|
|
@ -1,28 +1,35 @@
|
|||
version: "3"
|
||||
networks:
|
||||
langflow:
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./dev.Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "7860:7860"
|
||||
volumes:
|
||||
- ./:/app
|
||||
command: bash -c "uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
|
||||
|
||||
networks:
|
||||
- langflow
|
||||
frontend:
|
||||
build:
|
||||
context: ./src/frontend
|
||||
dockerfile: ./dev.Dockerfile
|
||||
dockerfile: ./cdk.Dockerfile
|
||||
args:
|
||||
- BACKEND_URL=http://backend:7860
|
||||
environment:
|
||||
- VITE_PROXY_TARGET=http://backend:7860
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "8080:3000"
|
||||
volumes:
|
||||
- ./src/frontend/public:/home/node/app/public
|
||||
- ./src/frontend/src:/home/node/app/src
|
||||
- ./src/frontend/package.json:/home/node/app/package.json
|
||||
restart: on-failure
|
||||
networks:
|
||||
- langflow
|
||||
|
|
|
|||
65
poetry.lock
generated
65
poetry.lock
generated
|
|
@ -2058,6 +2058,22 @@ certifi = "*"
|
|||
gevent = ">=0.13"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "google-ai-generativelanguage"
|
||||
version = "0.4.0"
|
||||
description = "Google Ai Generativelanguage API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google-ai-generativelanguage-0.4.0.tar.gz", hash = "sha256:c8199066c08f74c4e91290778329bb9f357ba1ea5d6f82de2bc0d10552bf4f8c"},
|
||||
{file = "google_ai_generativelanguage-0.4.0-py3-none-any.whl", hash = "sha256:e4c425376c1ee26c78acbc49a24f735f90ebfa81bf1a06495fae509a2433232c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
|
||||
proto-plus = ">=1.22.3,<2.0.0dev"
|
||||
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.15.0"
|
||||
|
|
@ -2140,13 +2156,13 @@ httplib2 = ">=0.19.0"
|
|||
|
||||
[[package]]
|
||||
name = "google-cloud-aiplatform"
|
||||
version = "1.38.0"
|
||||
version = "1.38.1"
|
||||
description = "Vertex AI API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "google-cloud-aiplatform-1.38.0.tar.gz", hash = "sha256:dff91f79b64e279f0e61dfd63c4e067ba5fa75ef0f4614289bbdca70d086a9e2"},
|
||||
{file = "google_cloud_aiplatform-1.38.0-py2.py3-none-any.whl", hash = "sha256:7eec50d9a36d43e163f019a1ade9284d4580602a5108738a0ebff8940ea47ce0"},
|
||||
{file = "google-cloud-aiplatform-1.38.1.tar.gz", hash = "sha256:30439d914bb028443c0506cc0e6dd825cff5401aeb8233e13d8cfd77c3c87da1"},
|
||||
{file = "google_cloud_aiplatform-1.38.1-py2.py3-none-any.whl", hash = "sha256:5e1fcd1068dd2c4f0fc89aa616e34a8b9434eaa72ea6216f5036ef26f08bd448"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -2179,13 +2195,13 @@ xai = ["tensorflow (>=2.3.0,<3.0.0dev)"]
|
|||
|
||||
[[package]]
|
||||
name = "google-cloud-bigquery"
|
||||
version = "3.14.0"
|
||||
version = "3.14.1"
|
||||
description = "Google BigQuery API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google-cloud-bigquery-3.14.0.tar.gz", hash = "sha256:76c919f771ac82ba372f5a8d326c032229c5fdab738d03a2b6e73b412c22c9eb"},
|
||||
{file = "google_cloud_bigquery-3.14.0-py2.py3-none-any.whl", hash = "sha256:3304f4742546be70e531232f31bbf5b4b257aa63a508101ab7c4582c9503b636"},
|
||||
{file = "google-cloud-bigquery-3.14.1.tar.gz", hash = "sha256:aa15bd86f79ea76824c7d710f5ae532323c4b3ba01ef4abff42d4ee7a2e9b142"},
|
||||
{file = "google_cloud_bigquery-3.14.1-py2.py3-none-any.whl", hash = "sha256:a8ded18455da71508db222b7c06197bc12b6dbc6ed5b0b64e7007b76d7016957"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -2344,6 +2360,26 @@ files = [
|
|||
[package.extras]
|
||||
testing = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "google-generativeai"
|
||||
version = "0.3.1"
|
||||
description = "Google Generative AI High level API client library and tools."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "google_generativeai-0.3.1-py3-none-any.whl", hash = "sha256:800ec6041ca537b897d7ba654f4125651c64b38506f2bfce3b464370e3333a1b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-ai-generativelanguage = "0.4.0"
|
||||
google-api-core = "*"
|
||||
google-auth = "*"
|
||||
protobuf = "*"
|
||||
tqdm = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "google-resumable-media"
|
||||
version = "2.7.0"
|
||||
|
|
@ -3618,6 +3654,21 @@ langchain-core = ">=0.1,<0.2"
|
|||
[package.extras]
|
||||
extended-testing = ["faker (>=19.3.1,<20.0.0)", "jinja2 (>=3,<4)", "presidio-analyzer (>=2.2.33,<3.0.0)", "presidio-anonymizer (>=2.2.33,<3.0.0)", "sentence-transformers (>=2,<3)", "vowpal-wabbit-next (==0.6.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-google-genai"
|
||||
version = "0.0.2"
|
||||
description = "An integration package connecting Google's genai package and LangChain"
|
||||
optional = false
|
||||
python-versions = ">=3.9,<4.0"
|
||||
files = [
|
||||
{file = "langchain_google_genai-0.0.2-py3-none-any.whl", hash = "sha256:d98b1bb5eb0b65e7582fe18031ec0fe35d78820deb2825bddfe6c37218008e5c"},
|
||||
{file = "langchain_google_genai-0.0.2.tar.gz", hash = "sha256:6209991f8c5b07efc194514ab04baf3e8ffa4a95ae697db7d1c6ba8cd430ead8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
google-generativeai = ">=0.3.1,<0.4.0"
|
||||
langchain-core = ">=0.1,<0.2"
|
||||
|
||||
[[package]]
|
||||
name = "langdetect"
|
||||
version = "1.0.9"
|
||||
|
|
@ -9217,4 +9268,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "a3b010e02c9cb3c943898847ab78644a5dcc38c8bf5108d833b06fb3378e7c96"
|
||||
content-hash = "6bed78abe6228dfd01ba4cf4052a95d24bcb0746294a8da442bbaa846187071f"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Logspace <contact@logspace.ai>"]
|
||||
maintainers = [
|
||||
|
|
@ -102,6 +102,7 @@ numexpr = "^2.8.6"
|
|||
qianfan = "0.0.5"
|
||||
pgvector = "^0.2.3"
|
||||
pyautogen = "^0.2.0"
|
||||
langchain-google-genai = "^0.0.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest-asyncio = "^0.23.1"
|
||||
|
|
|
|||
11
scripts/aws/.env.example
Normal file
11
scripts/aws/.env.example
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Description: Example of .env file
|
||||
# Usage: Copy this file to .env and change the values
|
||||
# according to your needs
|
||||
# Do not commit .env file to git
|
||||
# Do not change .env.example file
|
||||
# You can set up a superuser's username and password
|
||||
# If there is no need for user management, set LANGFLOW_AUTO_LOGIN=true and delete LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD.
|
||||
|
||||
LANGFLOW_AUTO_LOGIN=false
|
||||
LANGFLOW_SUPERUSER=admin
|
||||
LANGFLOW_SUPERUSER_PASSWORD=123456
|
||||
9
scripts/aws/.gitignore
vendored
Normal file
9
scripts/aws/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
*.js
|
||||
!jest.config.js
|
||||
*.d.ts
|
||||
node_modules
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
!/lib
|
||||
6
scripts/aws/.npmignore
Normal file
6
scripts/aws/.npmignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*.ts
|
||||
!*.d.ts
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
54
scripts/aws/README.ja.md
Normal file
54
scripts/aws/README.ja.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Langflow on AWS
|
||||
|
||||
**想定時間**: 30 分
|
||||
|
||||
## 説明
|
||||
Langflow on AWS では、 [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK) を用いて Langflow を AWS 上にデプロイする方法を学べます。
|
||||
このチュートリアルは、AWS アカウントと AWS に関する基本的な知識を有していることを前提としています。
|
||||
|
||||
作成するアプリケーションのアーキテクチャです。
|
||||

|
||||
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) を作成します。
|
||||
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. 以下のコマンドを実行します。
|
||||
```shell
|
||||
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
|
||||
cd cloud9-setup-for-prototyping
|
||||
./bin/bootstrap
|
||||
```
|
||||
|
||||
1. `Done!` と表示されたら [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/) から `cloud9-for-prototyping` を開きます。
|
||||

|
||||
|
||||
1. 以下のコマンドを実行します。
|
||||
```shell
|
||||
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
|
||||
cd langflow/scripts/aws
|
||||
cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
|
||||
npm ci
|
||||
cdk bootstrap
|
||||
cdk deploy
|
||||
```
|
||||
1. 表示される URL にアクセスします。
|
||||
```shell
|
||||
Outputs:
|
||||
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
|
||||
```
|
||||
1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
|
||||

|
||||
|
||||
# 環境の削除
|
||||
1. `Cloud9` で以下のコマンドを実行します。
|
||||
```shell
|
||||
bash delete-resources.sh
|
||||
```
|
||||
|
||||
|
||||
1. [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started)を開き、`aws-cloud9-cloud9-for-prototyping-XXXX` を選択して削除します。
|
||||

|
||||
53
scripts/aws/README.md
Normal file
53
scripts/aws/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Deploy Langflow on AWS
|
||||
|
||||
**Duration**: 30 minutes
|
||||
|
||||
## Introduction
|
||||
|
||||
In this tutorial, you will learn how to deploy langflow on AWS using [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK).
|
||||
This tutorial assumes you have an AWS account and basic knowledge of AWS.
|
||||
|
||||
The architecture of the application to be created:
|
||||

|
||||
|
||||
[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.
|
||||
The aurora's secrets are 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
|
||||
|
||||
1. Open [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1).
|
||||
1. Run the following commands in Cloudshell:
|
||||
```shell
|
||||
git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
|
||||
cd cloud9-setup-for-prototyping
|
||||
./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. Run the following command in the Cloud9 terminal.
|
||||
```shell
|
||||
git clone -b aws-cdk https://github.com/logspace-ai/langflow.git
|
||||
cd langflow/scripts/aws
|
||||
cp .env.example .env # Edit this file if you need environment settings
|
||||
npm ci
|
||||
cdk bootstrap
|
||||
cdk deploy
|
||||
```
|
||||
1. Access the URL displayed.
|
||||
```shell
|
||||
Outputs:
|
||||
LangflowAppStack.NetworkURLXXXXXX = http://alb-XXXXXXXXXXX.elb.amazonaws.com
|
||||
```
|
||||
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`.
|
||||

|
||||
|
||||
# Cleanup
|
||||
|
||||
1. Run the following command in the Cloud9 terminal.
|
||||
```shell
|
||||
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.
|
||||

|
||||
21
scripts/aws/bin/cdk.ts
Normal file
21
scripts/aws/bin/cdk.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env node
|
||||
import 'source-map-support/register';
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { LangflowAppStack } from '../lib/cdk-stack';
|
||||
|
||||
const app = new cdk.App();
|
||||
new LangflowAppStack(app, 'LangflowAppStack', {
|
||||
/* If you don't specify 'env', this stack will be environment-agnostic.
|
||||
* Account/Region-dependent features and context lookups will not work,
|
||||
* but a single synthesized template can be deployed anywhere. */
|
||||
|
||||
/* Uncomment the next line to specialize this stack for the AWS Account
|
||||
* and Region that are implied by the current CLI configuration. */
|
||||
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
|
||||
|
||||
/* Uncomment the next line if you know exactly what Account and Region you
|
||||
* want to deploy the stack to. */
|
||||
// env: { account: '123456789012', region: 'us-east-1' },
|
||||
|
||||
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
|
||||
});
|
||||
55
scripts/aws/cdk.json
Normal file
55
scripts/aws/cdk.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
|
||||
"watch": {
|
||||
"include": [
|
||||
"**"
|
||||
],
|
||||
"exclude": [
|
||||
"README.md",
|
||||
"cdk*.json",
|
||||
"**/*.d.ts",
|
||||
"**/*.js",
|
||||
"tsconfig.json",
|
||||
"package*.json",
|
||||
"yarn.lock",
|
||||
"node_modules",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||
"@aws-cdk/core:checkSecretUsage": true,
|
||||
"@aws-cdk/core:target-partitions": [
|
||||
"aws",
|
||||
"aws-cn"
|
||||
],
|
||||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
|
||||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
|
||||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
|
||||
"@aws-cdk/aws-iam:minimizePolicies": true,
|
||||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
|
||||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
|
||||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
|
||||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
|
||||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
|
||||
"@aws-cdk/core:enablePartitionLiterals": true,
|
||||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
|
||||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
|
||||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
|
||||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
|
||||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
|
||||
"@aws-cdk/aws-route53-patters:useCertificate": true,
|
||||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
|
||||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
|
||||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
|
||||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
|
||||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
|
||||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
|
||||
"@aws-cdk/aws-redshift:columnId": true,
|
||||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
|
||||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
|
||||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
|
||||
"@aws-cdk/aws-kms:aliasNameRef": true,
|
||||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true
|
||||
}
|
||||
}
|
||||
3
scripts/aws/delete-docker-images.sh
Normal file
3
scripts/aws/delete-docker-images.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
docker stop $(docker ps -aq)
|
||||
docker rm $(docker ps -aq)
|
||||
docker rmi -f $(docker images -aq)
|
||||
4
scripts/aws/delete-resources.sh
Normal file
4
scripts/aws/delete-resources.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
aws cloudformation delete-stack --stack-name LangflowAppStack
|
||||
aws ecr delete-repository --repository-name langflow-backend-repository --force
|
||||
aws ecr delete-repository --repository-name langflow-frontend-repository --force
|
||||
# aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
|
||||
BIN
scripts/aws/img/langflow-archi.png
Normal file
BIN
scripts/aws/img/langflow-archi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
BIN
scripts/aws/img/langflow-cfn.png
Normal file
BIN
scripts/aws/img/langflow-cfn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
scripts/aws/img/langflow-cloud9-en.png
Normal file
BIN
scripts/aws/img/langflow-cloud9-en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
BIN
scripts/aws/img/langflow-signin.png
Normal file
BIN
scripts/aws/img/langflow-signin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
8
scripts/aws/jest.config.js
Normal file
8
scripts/aws/jest.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/test'],
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'ts-jest'
|
||||
}
|
||||
};
|
||||
64
scripts/aws/lib/cdk-stack.ts
Normal file
64
scripts/aws/lib/cdk-stack.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
||||
|
||||
import { Network, EcrRepository, FrontEndCluster, BackEndCluster, Rds, EcsIAM } from './construct';
|
||||
// import * as sqs from 'aws-cdk-lib/aws-sqs';
|
||||
|
||||
export class LangflowAppStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
super(scope, id, props);
|
||||
// Arch
|
||||
const arch = ecs.CpuArchitecture.X86_64
|
||||
|
||||
// VPC
|
||||
const { vpc, cluster, alb, targetGroup, cloudmapNamespace, ecsFrontSG, ecsBackSG, dbSG, albSG, backendLogGroup, frontendLogGroup} = new Network(this, 'Network')
|
||||
|
||||
// ECR
|
||||
const { ecrFrontEndRepository,ecrBackEndRepository} = new EcrRepository(this, 'Ecr', {
|
||||
cloudmapNamespace:cloudmapNamespace,
|
||||
arch:arch
|
||||
})
|
||||
|
||||
// RDS
|
||||
// VPCとSGのリソース情報をPropsとして引き渡す
|
||||
const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
|
||||
|
||||
// IAM
|
||||
const { frontendTaskRole, frontendTaskExecutionRole, backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
|
||||
rdsCluster:rdsCluster
|
||||
})
|
||||
|
||||
const backendService = new BackEndCluster(this, 'backend', {
|
||||
cluster:cluster,
|
||||
ecsBackSG:ecsBackSG,
|
||||
ecrBackEndRepository:ecrBackEndRepository,
|
||||
backendTaskRole:backendTaskRole,
|
||||
backendTaskExecutionRole:backendTaskExecutionRole,
|
||||
backendLogGroup:backendLogGroup,
|
||||
cloudmapNamespace:cloudmapNamespace,
|
||||
rdsCluster:rdsCluster,
|
||||
alb:alb,
|
||||
arch:arch
|
||||
})
|
||||
backendService.node.addDependency(rdsCluster);
|
||||
|
||||
const frontendService = new FrontEndCluster(this, 'frontend',{
|
||||
cluster:cluster,
|
||||
ecsFrontSG:ecsFrontSG,
|
||||
ecrFrontEndRepository:ecrFrontEndRepository,
|
||||
targetGroup: targetGroup,
|
||||
backendServiceName: backendService.backendServiceName,
|
||||
frontendTaskRole: frontendTaskRole,
|
||||
frontendTaskExecutionRole: frontendTaskExecutionRole,
|
||||
frontendLogGroup: frontendLogGroup,
|
||||
cloudmapNamespace: cloudmapNamespace,
|
||||
arch:arch
|
||||
})
|
||||
frontendService.node.addDependency(backendService);
|
||||
|
||||
|
||||
// S3+CloudFront
|
||||
// new Web(this,'Cloudfront-S3')
|
||||
}
|
||||
}
|
||||
105
scripts/aws/lib/construct/backend.ts
Normal file
105
scripts/aws/lib/construct/backend.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { Duration } from 'aws-cdk-lib'
|
||||
import { Construct } from 'constructs';
|
||||
import {
|
||||
aws_ec2 as ec2,
|
||||
aws_ecs as ecs,
|
||||
aws_ecr as ecr,
|
||||
aws_rds as rds,
|
||||
aws_servicediscovery as servicediscovery,
|
||||
aws_iam as iam,
|
||||
aws_logs as logs,
|
||||
aws_elasticloadbalancingv2 as elb,
|
||||
} from 'aws-cdk-lib';
|
||||
import * as dotenv from 'dotenv';
|
||||
const path = require('path');
|
||||
dotenv.config({path: path.join(__dirname, "../../.env")});
|
||||
|
||||
interface BackEndProps {
|
||||
cluster: ecs.Cluster
|
||||
ecsBackSG:ec2.SecurityGroup
|
||||
ecrBackEndRepository:ecr.Repository
|
||||
backendTaskRole: iam.Role;
|
||||
backendTaskExecutionRole: iam.Role;
|
||||
backendLogGroup: logs.LogGroup;
|
||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||
rdsCluster:rds.DatabaseCluster
|
||||
alb:elb.IApplicationLoadBalancer
|
||||
arch:ecs.CpuArchitecture
|
||||
}
|
||||
|
||||
export class BackEndCluster extends Construct {
|
||||
readonly backendServiceName: string
|
||||
|
||||
constructor(scope: Construct, id: string, props:BackEndProps) {
|
||||
super(scope, id)
|
||||
const containerPort = 7860
|
||||
// Secrets ManagerからDB認証情報を取ってくる
|
||||
const secretsDB = props.rdsCluster.secret!;
|
||||
|
||||
// Create Backend Fargate Service
|
||||
const backendTaskDefinition = new ecs.FargateTaskDefinition(
|
||||
this,
|
||||
'BackEndTaskDef',
|
||||
{
|
||||
memoryLimitMiB: 3072,
|
||||
cpu: 1024,
|
||||
executionRole: props.backendTaskExecutionRole,
|
||||
runtimePlatform:{
|
||||
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||
cpuArchitecture: props.arch,
|
||||
},
|
||||
taskRole: props.backendTaskRole,
|
||||
}
|
||||
);
|
||||
backendTaskDefinition.addContainer('backendContainer', {
|
||||
image: ecs.ContainerImage.fromEcrRepository(props.ecrBackEndRepository, "latest"),
|
||||
containerName:'langflow-back-container',
|
||||
logging: ecs.LogDriver.awsLogs({
|
||||
streamPrefix: 'my-stream',
|
||||
logGroup: props.backendLogGroup,
|
||||
}),
|
||||
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_SUPERUSER" : process.env.LANGFLOW_SUPERUSER ?? "admin",
|
||||
"LANGFLOW_SUPERUSER_PASSWORD" : process.env.LANGFLOW_SUPERUSER_PASSWORD ?? "123456"
|
||||
},
|
||||
portMappings: [
|
||||
{
|
||||
containerPort: containerPort,
|
||||
protocol: ecs.Protocol.TCP,
|
||||
},
|
||||
],
|
||||
// Secretの設定
|
||||
secrets: {
|
||||
"dbname": ecs.Secret.fromSecretsManager(secretsDB, 'dbname'),
|
||||
"username": ecs.Secret.fromSecretsManager(secretsDB, 'username'),
|
||||
"host": ecs.Secret.fromSecretsManager(secretsDB, 'host'),
|
||||
"password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
|
||||
},
|
||||
});
|
||||
this.backendServiceName = 'backend'
|
||||
const backendService = new ecs.FargateService(this, 'BackEndService', {
|
||||
cluster: props.cluster,
|
||||
serviceName: this.backendServiceName,
|
||||
taskDefinition: backendTaskDefinition,
|
||||
enableExecuteCommand: true,
|
||||
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 },
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
65
scripts/aws/lib/construct/db.ts
Normal file
65
scripts/aws/lib/construct/db.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2'
|
||||
import * as rds from "aws-cdk-lib/aws-rds";
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
|
||||
interface RdsProps {
|
||||
vpc: ec2.Vpc
|
||||
dbSG:ec2.SecurityGroup
|
||||
}
|
||||
|
||||
export class Rds extends Construct{
|
||||
readonly rdsCluster: rds.DatabaseCluster
|
||||
|
||||
constructor(scope: Construct, id:string, props: RdsProps){
|
||||
super(scope, id);
|
||||
|
||||
const {vpc, dbSG} = props
|
||||
const instanceType = ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM)
|
||||
|
||||
// RDSのパスワードを自動生成してSecrets Managerに格納
|
||||
const rdsCredentials = rds.Credentials.fromGeneratedSecret('db_user',{
|
||||
secretName: 'langflow-DbSecret',
|
||||
})
|
||||
|
||||
// DB クラスターのパラメータグループ作成
|
||||
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0
|
||||
}),
|
||||
description: 'for-langflow',
|
||||
})
|
||||
clusterParameterGroup.bindToCluster({})
|
||||
|
||||
// DB インスタンスのパラメタグループ作成
|
||||
const instanceParameterGroup = new rds.ParameterGroup(scope, 'InstanceParameterGroup',{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0,
|
||||
}),
|
||||
description: 'for-langflow',
|
||||
})
|
||||
instanceParameterGroup.bindToInstance({})
|
||||
|
||||
this.rdsCluster = new rds.DatabaseCluster(scope, 'LangflowDbCluster', {
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0,
|
||||
}),
|
||||
storageEncrypted: true,
|
||||
credentials: rdsCredentials,
|
||||
instanceIdentifierBase: 'langflow-instance',
|
||||
vpc:vpc,
|
||||
vpcSubnets:vpc.selectSubnets({
|
||||
subnetGroupName: 'langflow-Isolated',
|
||||
}),
|
||||
securityGroups:[dbSG],
|
||||
writer: rds.ClusterInstance.provisioned("WriterInstance", {
|
||||
instanceType: instanceType,
|
||||
enablePerformanceInsights: true,
|
||||
parameterGroup:instanceParameterGroup,
|
||||
}),
|
||||
// 2台目以降はreaders:で設定
|
||||
parameterGroup: clusterParameterGroup,
|
||||
defaultDatabaseName: 'langflow',
|
||||
})
|
||||
}
|
||||
}
|
||||
78
scripts/aws/lib/construct/ecr.ts
Normal file
78
scripts/aws/lib/construct/ecr.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { RemovalPolicy } from 'aws-cdk-lib'
|
||||
import * as ecr from 'aws-cdk-lib/aws-ecr'
|
||||
import * as ecrdeploy from 'cdk-ecr-deployment'
|
||||
import * as ecs from 'aws-cdk-lib/aws-ecs'
|
||||
import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery'
|
||||
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets'
|
||||
import * as path from "path";
|
||||
import { Construct } from 'constructs'
|
||||
|
||||
|
||||
interface ECRProps {
|
||||
cloudmapNamespace: servicediscovery.PrivateDnsNamespace;
|
||||
arch:ecs.CpuArchitecture;
|
||||
}
|
||||
|
||||
export class EcrRepository extends Construct {
|
||||
readonly ecrFrontEndRepository: ecr.Repository
|
||||
readonly ecrBackEndRepository: ecr.Repository
|
||||
|
||||
constructor(scope: Construct, id: string, props: ECRProps) {
|
||||
super(scope, id)
|
||||
|
||||
const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
|
||||
const backendPath = path.join(__dirname, "../../../../../", "langflow")
|
||||
const frontendPath = path.join(__dirname, "../../../../src/", "frontend")
|
||||
const excludeDir = ['node_modules','.git', 'cdk.out']
|
||||
const LifecycleRule = {
|
||||
tagStatus: ecr.TagStatus.ANY,
|
||||
description: 'Delete more than 30 image',
|
||||
maxImageCount: 30,
|
||||
}
|
||||
|
||||
// リポジトリ作成
|
||||
this.ecrFrontEndRepository = new ecr.Repository(scope, 'LangflowFrontEndRepository', {
|
||||
repositoryName: 'langflow-frontend-repository',
|
||||
removalPolicy: RemovalPolicy.RETAIN,
|
||||
imageScanOnPush: true,
|
||||
})
|
||||
this.ecrBackEndRepository = new ecr.Repository(scope, 'LangflowBackEndRepository', {
|
||||
repositoryName: 'langflow-backend-repository',
|
||||
removalPolicy: RemovalPolicy.RETAIN,
|
||||
imageScanOnPush: true,
|
||||
})
|
||||
// LifecycleRule作成
|
||||
this.ecrFrontEndRepository.addLifecycleRule(LifecycleRule)
|
||||
this.ecrBackEndRepository.addLifecycleRule(LifecycleRule)
|
||||
|
||||
// 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", {
|
||||
directory: backendPath,
|
||||
file:"cdk.Dockerfile",
|
||||
exclude: excludeDir,
|
||||
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
|
||||
new ecrdeploy.ECRDeployment(this, "DeployBackEndImage", {
|
||||
src: new ecrdeploy.DockerImageName(dockerBackEndImageAsset.imageUri),
|
||||
dest: new ecrdeploy.DockerImageName(this.ecrBackEndRepository.repositoryUri)
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
117
scripts/aws/lib/construct/frontend.ts
Normal file
117
scripts/aws/lib/construct/frontend.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { Duration } from 'aws-cdk-lib'
|
||||
import { Construct } from 'constructs'
|
||||
import {
|
||||
aws_ec2 as ec2,
|
||||
aws_ecs as ecs,
|
||||
aws_ecr as ecr,
|
||||
aws_servicediscovery as servicediscovery,
|
||||
aws_iam as iam,
|
||||
aws_logs as logs,
|
||||
aws_elasticloadbalancingv2 as elb,
|
||||
} from 'aws-cdk-lib';
|
||||
import { CpuArchitecture } from 'aws-cdk-lib/aws-ecs';
|
||||
|
||||
interface FrontEndProps {
|
||||
cluster:ecs.Cluster
|
||||
ecsFrontSG:ec2.SecurityGroup
|
||||
ecrFrontEndRepository:ecr.Repository
|
||||
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 {
|
||||
constructor(scope: Construct, id: string, props:FrontEndProps) {
|
||||
super(scope, id)
|
||||
|
||||
const containerPort = 3000
|
||||
const frontendTaskDefinition = new ecs.FargateTaskDefinition(
|
||||
this,
|
||||
'FrontendTaskDef',
|
||||
{
|
||||
memoryLimitMiB: 3072,
|
||||
cpu: 1024,
|
||||
executionRole: props.frontendTaskExecutionRole,
|
||||
runtimePlatform:{
|
||||
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||
cpuArchitecture: props.arch,
|
||||
},
|
||||
taskRole: props.frontendTaskRole,
|
||||
}
|
||||
);
|
||||
const frontendServiceName = 'frontend'
|
||||
frontendTaskDefinition.addContainer('frontendContainer', {
|
||||
image: ecs.ContainerImage.fromEcrRepository(props.ecrFrontEndRepository, "latest"),
|
||||
containerName:'langflow-front-container',
|
||||
environment: {
|
||||
BACKEND_SERVICE_NAME: props.backendServiceName,
|
||||
BACKEND_URL: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
||||
VITE_PROXY_TARGET: `http://${props.backendServiceName}.${props.cloudmapNamespace.namespaceName}:7860/`,
|
||||
},
|
||||
logging: ecs.LogDriver.awsLogs({
|
||||
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);
|
||||
|
||||
// // Create ALB and ECS Fargate Service
|
||||
// const frontService = new ecs_patterns.ApplicationLoadBalancedFargateService(
|
||||
// this,
|
||||
// "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,
|
||||
// }
|
||||
// );
|
||||
|
||||
}
|
||||
}
|
||||
101
scripts/aws/lib/construct/iam.ts
Normal file
101
scripts/aws/lib/construct/iam.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { RemovalPolicy, Duration } from 'aws-cdk-lib'
|
||||
import { Construct } from 'constructs'
|
||||
import {
|
||||
aws_rds as rds,
|
||||
aws_iam as iam,
|
||||
} from 'aws-cdk-lib';
|
||||
|
||||
interface IAMProps {
|
||||
rdsCluster:rds.DatabaseCluster
|
||||
}
|
||||
|
||||
export class EcsIAM extends Construct {
|
||||
readonly frontendTaskRole: iam.Role;
|
||||
readonly frontendTaskExecutionRole: iam.Role;
|
||||
readonly backendTaskRole: iam.Role;
|
||||
readonly backendTaskExecutionRole: iam.Role;
|
||||
|
||||
constructor(scope: Construct, id: string, props:IAMProps) {
|
||||
super(scope, id)
|
||||
|
||||
// Policy Statements
|
||||
// ECS Policy State
|
||||
const ECSExecPolicyStatement = new iam.PolicyStatement({
|
||||
sid: 'allowECSExec',
|
||||
resources: ['*'],
|
||||
actions: [
|
||||
'ecr:GetAuthorizationToken',
|
||||
'ecr:BatchCheckLayerAvailability',
|
||||
'ecr:GetDownloadUrlForLayer',
|
||||
'ecr:BatchGetImage',
|
||||
],
|
||||
});
|
||||
// Bedrock Policy State
|
||||
const BedrockPolicyStatement = new iam.PolicyStatement({
|
||||
sid: 'allowBedrockAccess',
|
||||
resources: ['*'],
|
||||
actions: [
|
||||
'bedrock:*',
|
||||
],
|
||||
});
|
||||
// Kendra Policy State
|
||||
const KendraPolicyStatement = new iam.PolicyStatement({
|
||||
sid: 'allowKendraAccess',
|
||||
resources: ['*'],
|
||||
actions: [
|
||||
'kendra:*'
|
||||
],
|
||||
});
|
||||
// Create Rag Policy
|
||||
const RagAccessPolicy = new iam.Policy(this, 'RAGFullAccess', {
|
||||
statements: [KendraPolicyStatement,BedrockPolicyStatement],
|
||||
})
|
||||
// Secrets ManagerからDB認証情報を取ってくるためのPolicy
|
||||
const SecretsManagerPolicy = new iam.Policy(this, 'SMGetPolicy', {
|
||||
statements: [new iam.PolicyStatement({
|
||||
actions: ['secretsmanager:GetSecretValue'],
|
||||
resources: [props.rdsCluster.secret!.secretArn],
|
||||
})],
|
||||
})
|
||||
|
||||
// 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
|
||||
this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
});
|
||||
// ECS Exec Policyの付与
|
||||
this.backendTaskRole.addToPolicy(ECSExecPolicyStatement);
|
||||
// KendraとBedrockのアクセス権付与
|
||||
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
|
||||
this.backendTaskExecutionRole = new iam.Role(this, 'backendTaskExecutionRole', {
|
||||
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
|
||||
managedPolicies: [
|
||||
{
|
||||
managedPolicyArn:
|
||||
'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.backendTaskExecutionRole.attachInlinePolicy(SecretsManagerPolicy);
|
||||
this.backendTaskExecutionRole.attachInlinePolicy(RagAccessPolicy);
|
||||
}
|
||||
}
|
||||
6
scripts/aws/lib/construct/index.ts
Normal file
6
scripts/aws/lib/construct/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export * from './db';
|
||||
export * from './ecr';
|
||||
export * from './iam';
|
||||
export * from './frontend';
|
||||
export * from './backend';
|
||||
export * from './network';
|
||||
143
scripts/aws/lib/construct/network.ts
Normal file
143
scripts/aws/lib/construct/network.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import { RemovalPolicy, Duration, CfnOutput } from 'aws-cdk-lib'
|
||||
import { Construct } from 'constructs'
|
||||
import {
|
||||
aws_ec2 as ec2,
|
||||
aws_ecs as ecs,
|
||||
aws_logs as logs,
|
||||
aws_servicediscovery as servicediscovery,
|
||||
aws_elasticloadbalancingv2 as elb,
|
||||
} from 'aws-cdk-lib';
|
||||
|
||||
export class Network extends Construct {
|
||||
readonly vpc: ec2.Vpc;
|
||||
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 dbSG: ec2.SecurityGroup;
|
||||
readonly albSG: ec2.SecurityGroup;
|
||||
readonly backendLogGroup: logs.LogGroup;
|
||||
readonly frontendLogGroup: logs.LogGroup;
|
||||
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id)
|
||||
const alb_listen_port=80
|
||||
const front_service_port=3000
|
||||
const back_service_port=7860
|
||||
|
||||
// VPC等リソースの作成
|
||||
this.vpc = new ec2.Vpc(scope, 'VPC', {
|
||||
vpcName: 'langflow-vpc',
|
||||
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
|
||||
maxAzs: 3,
|
||||
subnetConfiguration: [
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'langflow-Isolated',
|
||||
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
||||
},
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'langflow-Public',
|
||||
subnetType: ec2.SubnetType.PUBLIC,
|
||||
},
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'langflow-Private',
|
||||
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
|
||||
},
|
||||
],
|
||||
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に設定するセキュリティグループ
|
||||
this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
|
||||
securityGroupName: 'alb-sg',
|
||||
description: 'for alb',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
|
||||
|
||||
this.alb = new elb.ApplicationLoadBalancer(this,'langflow-alb',{
|
||||
internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
|
||||
loadBalancerName: 'langflow-alb',
|
||||
securityGroup: this.albSG, //作成したセキュリティグループを割り当てる
|
||||
vpc:this.vpc,
|
||||
})
|
||||
|
||||
const listener = this.alb.addListener('Listener', { port: alb_listen_port });
|
||||
|
||||
this.targetGroup = listener.addTargets('targetGroup', {
|
||||
port: front_service_port,
|
||||
protocol: elb.ApplicationProtocol.HTTP,
|
||||
healthCheck: {
|
||||
enabled: true,
|
||||
path: '/health',
|
||||
healthyThresholdCount: 2,
|
||||
unhealthyThresholdCount: 4,
|
||||
interval: Duration.seconds(100),
|
||||
timeout: Duration.seconds(30),
|
||||
healthyHttpCodes: '200',
|
||||
},
|
||||
});
|
||||
|
||||
// ECS FrontEndに設定するセキュリティグループ
|
||||
this.ecsFrontSG = new ec2.SecurityGroup(scope, 'ECSFrontEndSecurityGroup', {
|
||||
securityGroupName: 'langflow-ecs-front-sg',
|
||||
description: 'for langflow-front-ecs',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.ecsFrontSG.addIngressRule(this.albSG, ec2.Port.allTcp())
|
||||
|
||||
// ECS BackEndに設定するセキュリティグループ
|
||||
this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
|
||||
securityGroupName: 'langflow-ecs-back-sg',
|
||||
description: 'for langflow-back-ecs',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
this.ecsBackSG.addIngressRule(this.ecsFrontSG, ec2.Port.tcp(back_service_port))
|
||||
|
||||
// RDSに設定するセキュリティグループ
|
||||
this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
|
||||
allowAllOutbound: true,
|
||||
securityGroupName: 'langflow-db',
|
||||
description: 'for langflow-db',
|
||||
vpc: this.vpc,
|
||||
})
|
||||
// AppRunnerSecurityGroupからのポート3306:mysql(5432:postgres)のインバウンドを許可
|
||||
this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
|
||||
|
||||
// Create CloudWatch Log Group
|
||||
this.backendLogGroup = new logs.LogGroup(this, 'backendLogGroup', {
|
||||
logGroupName: 'langflow-backend-logs',
|
||||
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}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
4613
scripts/aws/package-lock.json
generated
Normal file
4613
scripts/aws/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
scripts/aws/package.json
Normal file
29
scripts/aws/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "cdk",
|
||||
"version": "0.1.0",
|
||||
"bin": {
|
||||
"cdk": "bin/cdk.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"test": "jest",
|
||||
"cdk": "cdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "20.1.7",
|
||||
"aws-cdk": "2.86.0",
|
||||
"jest": "^29.5.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "~5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-cdk-lib": "^2.86.0",
|
||||
"cdk-ecr-deployment": "^2.5.30",
|
||||
"constructs": "^10.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"source-map-support": "^0.5.21"
|
||||
}
|
||||
}
|
||||
17
scripts/aws/test/cdk.test.ts
Normal file
17
scripts/aws/test/cdk.test.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// import * as cdk from 'aws-cdk-lib';
|
||||
// import { Template } from 'aws-cdk-lib/assertions';
|
||||
// import * as Cdk from '../lib/cdk-stack';
|
||||
|
||||
// example test. To run these tests, uncomment this file along with the
|
||||
// example resource in lib/cdk-stack.ts
|
||||
test('SQS Queue Created', () => {
|
||||
// const app = new cdk.App();
|
||||
// // WHEN
|
||||
// const stack = new Cdk.CdkStack(app, 'MyTestStack');
|
||||
// // THEN
|
||||
// const template = Template.fromStack(stack);
|
||||
|
||||
// template.hasResourceProperties('AWS::SQS::Queue', {
|
||||
// VisibilityTimeout: 300
|
||||
// });
|
||||
});
|
||||
72
src/backend/langflow/components/llms/GoogleGenerativeAI.py
Normal file
72
src/backend/langflow/components/llms/GoogleGenerativeAI.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
|
||||
|
||||
from langflow import CustomComponent
|
||||
from langflow.field_typing import BaseLanguageModel, RangeSpec, TemplateField
|
||||
|
||||
|
||||
class GoogleGenerativeAIComponent(CustomComponent):
|
||||
display_name: str = "Google Generative AI"
|
||||
description: str = "A component that uses Google Generative AI to generate text."
|
||||
documentation: str = "http://docs.langflow.org/components/custom"
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
"google_api_key": TemplateField(
|
||||
display_name="Google API Key",
|
||||
info="The Google API Key to use for the Google Generative AI.",
|
||||
),
|
||||
"max_output_tokens": TemplateField(
|
||||
display_name="Max Output Tokens",
|
||||
info="The maximum number of tokens to generate.",
|
||||
),
|
||||
"temperature": TemplateField(
|
||||
display_name="Temperature",
|
||||
info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
|
||||
),
|
||||
"top_k": TemplateField(
|
||||
display_name="Top K",
|
||||
info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
|
||||
range_spec=RangeSpec(min=0, max=2, step=0.1),
|
||||
advanced=True,
|
||||
),
|
||||
"top_p": TemplateField(
|
||||
display_name="Top P",
|
||||
info="The maximum cumulative probability of tokens to consider when sampling.",
|
||||
advanced=True,
|
||||
),
|
||||
"n": TemplateField(
|
||||
display_name="N",
|
||||
info="Number of chat completions to generate for each prompt. Note that the API may not return the full n completions if duplicates are generated.",
|
||||
advanced=True,
|
||||
),
|
||||
"model": TemplateField(
|
||||
display_name="Model",
|
||||
info="The name of the model to use. Supported examples: gemini-pro",
|
||||
options=["gemini-pro", "gemini-pro-vision"],
|
||||
),
|
||||
"code": TemplateField(
|
||||
advanced=True,
|
||||
),
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
google_api_key: str,
|
||||
model: str,
|
||||
max_output_tokens: Optional[int] = None,
|
||||
temperature: float = 0.1,
|
||||
top_k: Optional[int] = None,
|
||||
top_p: Optional[float] = None,
|
||||
n: Optional[int] = 1,
|
||||
) -> BaseLanguageModel:
|
||||
return ChatGoogleGenerativeAI(
|
||||
model=model,
|
||||
max_output_tokens=max_output_tokens or None,
|
||||
temperature=temperature,
|
||||
top_k=top_k or None,
|
||||
top_p=top_p or None,
|
||||
n=n or 1,
|
||||
google_api_key=google_api_key,
|
||||
)
|
||||
|
|
@ -5,6 +5,7 @@ from typing import Any, Dict, List
|
|||
import orjson
|
||||
from langchain.agents import ZeroShotAgent
|
||||
from langchain.schema import BaseOutputParser, Document
|
||||
|
||||
from langflow.services.database.models.base import orjson_dumps
|
||||
|
||||
|
||||
|
|
@ -16,6 +17,8 @@ def handle_node_type(node_type, class_object, params: Dict):
|
|||
prompt = instantiate_from_template(class_object, params)
|
||||
elif node_type == "ChatPromptTemplate":
|
||||
prompt = class_object.from_messages(**params)
|
||||
elif hasattr(class_object, "from_template") and params.get("template"):
|
||||
prompt = class_object.from_template(template=params.pop("template"))
|
||||
else:
|
||||
prompt = class_object(**params)
|
||||
return params, prompt
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class Settings(BaseSettings):
|
|||
else:
|
||||
logger.debug("No DATABASE_URL env variable, using sqlite database")
|
||||
value = "sqlite:///./langflow.db"
|
||||
|
||||
return value
|
||||
|
||||
@validator("COMPONENTS_PATH", pre=True)
|
||||
|
|
|
|||
26
src/frontend/cdk.Dockerfile
Normal file
26
src/frontend/cdk.Dockerfile
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#baseline
|
||||
FROM --platform=linux/amd64 node:19-bullseye-slim AS base
|
||||
RUN mkdir -p /home/node/app
|
||||
RUN chown -R node:node /home/node && chmod -R 770 /home/node
|
||||
RUN apt-get update && apt-get install -y jq curl
|
||||
WORKDIR /home/node/app
|
||||
|
||||
# client build
|
||||
FROM base AS builder-client
|
||||
ARG BACKEND_URL
|
||||
ENV BACKEND_URL $BACKEND_URL
|
||||
RUN echo "BACKEND_URL: $BACKEND_URL"
|
||||
|
||||
WORKDIR /home/node/app
|
||||
COPY --chown=node:node . ./
|
||||
|
||||
COPY ./set_proxy.sh .
|
||||
RUN chmod +x set_proxy.sh && \
|
||||
cat set_proxy.sh | tr -d '\r' > set_proxy_unix.sh && \
|
||||
chmod +x set_proxy_unix.sh && \
|
||||
./set_proxy_unix.sh
|
||||
|
||||
USER node
|
||||
|
||||
RUN npm install --loglevel warn
|
||||
CMD ["npm", "run", "dev:docker"]
|
||||
|
|
@ -364,10 +364,9 @@ export default function GenericNode({
|
|||
<div
|
||||
className={
|
||||
showNode
|
||||
? "overflow-hidden " +
|
||||
(data.node?.description === "" && !nameEditable
|
||||
? "pb-5"
|
||||
: "py-5")
|
||||
? data.node?.description === "" && !nameEditable
|
||||
? "pb-5"
|
||||
: "py-5"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ export default function PageLayout({
|
|||
description,
|
||||
children,
|
||||
button,
|
||||
betaIcon,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
children: React.ReactNode;
|
||||
button?: React.ReactNode;
|
||||
betaIcon: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex h-screen w-full flex-col">
|
||||
|
|
@ -18,7 +20,10 @@ export default function PageLayout({
|
|||
<div className="flex h-full w-full flex-col justify-between overflow-auto bg-background px-16">
|
||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5 py-8 pb-2">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="text-2xl font-bold tracking-tight">{title}</h2>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
{title}
|
||||
{betaIcon && <span className="store-beta-icon">BETA</span>}
|
||||
</h2>
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">{button && button}</div>
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ export default function FormModal({
|
|||
window.location.protocol === "https:" || window.location.port === "443";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = isDevelopment ? "localhost:7860" : window.location.host;
|
||||
|
||||
const chatEndpoint = `/api/v1/chat/${chatId}`;
|
||||
|
||||
return `${
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export default function StorePage(): JSX.Element {
|
|||
|
||||
return (
|
||||
<PageLayout
|
||||
betaIcon
|
||||
title="Langflow Store"
|
||||
description="Search flows and components from the community."
|
||||
button={
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@
|
|||
@apply hover:text-accent-foreground hover:transition-all;
|
||||
}
|
||||
.generic-node-desc {
|
||||
@apply h-full px-5 mb-4 w-full text-foreground;
|
||||
@apply mb-4 h-full w-full px-5 text-foreground;
|
||||
}
|
||||
.generic-node-desc-text {
|
||||
@apply w-full text-sm text-muted-foreground;
|
||||
|
|
@ -1040,7 +1040,7 @@
|
|||
|
||||
.fade-container::before,
|
||||
.fade-container::after {
|
||||
@apply pointer-events-none absolute bottom-0 top-0 bg-gradient-to-r to-transparent from-background;
|
||||
@apply pointer-events-none absolute bottom-0 top-0 bg-gradient-to-r from-background to-transparent;
|
||||
content: "";
|
||||
width: 50px;
|
||||
opacity: 0;
|
||||
|
|
@ -1084,4 +1084,8 @@
|
|||
.scroll-container {
|
||||
@apply flex overflow-x-scroll scrollbar-hide;
|
||||
}
|
||||
|
||||
.store-beta-icon {
|
||||
@apply relative bottom-3 left-1 ml-2 rounded-full bg-beta-background px-2 py-1 text-center text-xs font-semibold text-beta-foreground;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue