Merge branch 'release' into langfuse_integration

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-08-30 09:53:21 -03:00
commit 2fe8e29546
71 changed files with 1279 additions and 2105 deletions

View file

@ -1,32 +1,33 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal // README at: https://github.com/devcontainers/templates/tree/main/src/universal
{ {
"name": "LangChain Demo Container", "name": "Langflow Demo Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:3.10", "image": "mcr.microsoft.com/devcontainers/python:3.10",
"features": { "features": {
"ghcr.io/devcontainers/features/aws-cli:1": {}, "ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}, "ghcr.io/devcontainers/features/docker-in-docker": {},
"ghcr.io/devcontainers/features/node": {} "ghcr.io/devcontainers/features/node": {}
}, },
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [
"actboy168.tasks", "actboy168.tasks",
"GitHub.copilot", "GitHub.copilot",
"ms-python.python", "ms-python.python",
"eamodio.gitlens" "eamodio.gitlens",
] "GitHub.vscode-pull-request-github"
} ]
}, }
// Features to add to the dev container. More info: https://containers.dev/features. },
// "features": {}, // Features to add to the dev container. More info: https://containers.dev/features.
// Use 'forwardPorts' to make a list of ports inside the container available locally. // "features": {},
// "forwardPorts": [], // Use 'forwardPorts' to make a list of ports inside the container available locally.
// Use 'postCreateCommand' to run commands after the container is created. // "forwardPorts": [],
"postCreateCommand": "pipx install 'langflow>=0.0.33' && langflow --host 0.0.0.0" // Use 'postCreateCommand' to run commands after the container is created.
// Configure tool-specific properties. "postCreateCommand": "pipx install 'langflow>=0.0.33' && langflow --host 0.0.0.0"
// "customizations": {}, // Configure tool-specific properties.
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "customizations": {},
// "remoteUser": "root" // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
} // "remoteUser": "root"
}

View file

@ -1,35 +1,41 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal // README at: https://github.com/devcontainers/templates/tree/main/src/universal
{ {
"name": "LangChain Dev Container", "name": "Langflow Dev Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/universal:2-linux", "image": "mcr.microsoft.com/devcontainers/python:1-3.10-bullseye",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}
},
"customizations": {
"vscode": {"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"sourcery.sourcery",
"eamodio.gitlens"
]}
},
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, "features": {
"ghcr.io/devcontainers/features/node": {},
"ghcr.io/devcontainers-contrib/features/poetry": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "poetry install" "postCreateCommand": "make install_frontend && make install_backend",
// Configure tool-specific properties. "containerEnv": {
// "customizations": {}, "POETRY_VIRTUALENVS_IN_PROJECT": "true"
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // Configure tool-specific properties.
// "remoteUser": "root" "customizations": {
"vscode": {
"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"sourcery.sourcery",
"eamodio.gitlens",
"ms-vscode.makefile-tools",
"GitHub.vscode-pull-request-github"
]
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
} }

View file

@ -27,7 +27,8 @@ format:
cd src/frontend && npm run format cd src/frontend && npm run format
lint: lint:
poetry run mypy . # skip .venv folder
poetry run mypy --exclude .venv .
poetry run black . --check poetry run black . --check
poetry run ruff . --fix poetry run ruff . --fix

View file

@ -1,11 +1,13 @@
import Admonition from '@theme/Admonition'; import Admonition from "@theme/Admonition";
# Text Splitters # Text Splitters
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION"> <Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
<p> <p>
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝 We appreciate your understanding as we polish our documentation – it may
</p> contain some rough edges. Share your feedback or report issues to help us
improve! 🛠️📝
</p>
</Admonition> </Admonition>
A text splitter is a tool that divides a document or text into smaller chunks or segments. It is used to break down large texts into more manageable pieces for analysis or processing. A text splitter is a tool that divides a document or text into smaller chunks or segments. It is used to break down large texts into more manageable pieces for analysis or processing.
@ -22,13 +24,13 @@ The `CharacterTextSplitter` is used to split a long text into smaller chunks bas
- **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk. - **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk.
For example, if the `chunk_overlap` is set to 20 and the `chunk_size` is set to 100, the splitter will create chunks of 100 characters each, but the last 20 characters of each chunk will overlap with the first 20 characters of the next chunk. This allows for a smoother transition between chunks and ensures that no information is lost – defaults to `200`. For example, if the `chunk_overlap` is set to 20 and the `chunk_size` is set to 100, the splitter will create chunks of 100 characters each, but the last 20 characters of each chunk will overlap with the first 20 characters of the next chunk. This allows for a smoother transition between chunks and ensures that no information is lost – defaults to `200`.
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk. - **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
For example, if the chunk_size is set to 100, the splitter will create chunks of 100 characters each. If the text is longer than 100 characters, it will be divided into multiple chunks of equal size, except for the last chunk, which may be smaller if there are remaining characters –defaults to `1000`. For example, if the chunk_size is set to 100, the splitter will create chunks of 100 characters each. If the text is longer than 100 characters, it will be divided into multiple chunks of equal size, except for the last chunk, which may be smaller if there are remaining characters –defaults to `1000`.
- **separator:** Specifies the character that will be used to split the text into chunks – defaults to `.` - **separator:** Specifies the character that will be used to split the text into chunks – defaults to `.`
--- ---
@ -44,6 +46,18 @@ The `RecursiveCharacterTextSplitter` splits the text by trying to keep paragra
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk. - **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Text, Ruby, Python, Solidity, Java, and more. Defaults to `Text`. - **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to ["\n\n", "\n", " ", ""].
- **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to `.` ### LanguageRecursiveTextSplitter
The `LanguageRecursiveTextSplitter` is a text splitter that splits the text into smaller chunks based on the (programming) language of the text.
**Params**
- **Documents:** Input documents to split.
- **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk.
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Ruby, Python, Solidity, Java, and more. Defaults to `Python`.

View file

@ -1,10 +1,76 @@
import Admonition from '@theme/Admonition'; import Admonition from "@theme/Admonition";
# Utilities # Utilities
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION"> <Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
<p> <p>
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝 We appreciate your understanding as we polish our documentation – it may
</p> contain some rough edges. Share your feedback or report issues to help us
improve! 🛠️📝
</p>
</Admonition> </Admonition>
Utilities are a set of actions that can be used to perform common tasks in a flow. They are available in the **Utilities** section in the sidebar.
---
### GET Request
Make a GET request to the given URL.
**Params**
- **URL:** The URL to make the request to. There can be more than one URL, in which case the request will be made to each URL in order.
- **Headers:** A dictionary of headers to send with the request.
**Output**
- **List of Documents:** A list of Documents containing the JSON response from each request.
---
### POST Request
Make a POST request to the given URL.
**Params**
- **URL:** The URL to make the request to.
- **Headers:** A dictionary of headers to send with the request.
- **Document:** The Document containing a JSON object to send with the request.
**Output**
- **Document:** The JSON response from the request as a Document.
---
### Update Request
Make a PATCH or PUT request to the given URL.
**Params**
- **URL:** The URL to make the request to.
- **Headers:** A dictionary of headers to send with the request.
- **Document:** The Document containing a JSON object to send with the request.
- **Method:** The HTTP method to use for the request. Can be either `PATCH` or `PUT`.
**Output**
- **Document:** The JSON response from the request as a Document.
---
### JSON Document Builder
Build a Document containing a JSON object using a key and another Document page content.
**Params**
- **Key:** The key to use for the JSON object.
- **Document:** The Document page to use for the JSON object.
**Output**
- **List of Documents:** A list containing the Document with the JSON object.

71
docs/package-lock.json generated
View file

@ -16,7 +16,7 @@
"@docusaurus/theme-classic": "^2.4.1", "@docusaurus/theme-classic": "^2.4.1",
"@docusaurus/theme-search-algolia": "^2.4.1", "@docusaurus/theme-search-algolia": "^2.4.1",
"@mdx-js/react": "^2.3.0", "@mdx-js/react": "^2.3.0",
"@mendable/search": "^0.0.114", "@mendable/search": "^0.0.154",
"@pbe/react-yandex-maps": "^1.2.4", "@pbe/react-yandex-maps": "^1.2.4",
"@prismicio/client": "^7.0.1", "@prismicio/client": "^7.0.1",
"@uiball/loaders": "^1.2.6", "@uiball/loaders": "^1.2.6",
@ -3250,10 +3250,11 @@
} }
}, },
"node_modules/@mendable/search": { "node_modules/@mendable/search": {
"version": "0.0.114", "version": "0.0.154",
"resolved": "https://registry.npmjs.org/@mendable/search/-/search-0.0.114.tgz", "resolved": "https://registry.npmjs.org/@mendable/search/-/search-0.0.154.tgz",
"integrity": "sha512-0uR+zxONuu/16bpLli49Jocr5fee1WIjs06KzU1AnHsR+fdFBmfrlpgTDWctgGuXPzS5Dorlw4VMlR5dPW5qVQ==", "integrity": "sha512-adNwXlIaMXVMCkPU2uUdghfn05Dmxb0BnE95SRLQJ6evHajsNFQdRl5Ltj3WijG+qo4ozTIJcPOBYrDPKMTPVw==",
"dependencies": { "dependencies": {
"html-react-parser": "^4.2.0",
"posthog-js": "^1.45.1" "posthog-js": "^1.45.1"
}, },
"peerDependencies": { "peerDependencies": {
@ -9351,6 +9352,33 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"node_modules/html-dom-parser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-4.0.0.tgz",
"integrity": "sha512-TUa3wIwi80f5NF8CVWzkopBVqVAtlawUzJoLwVLHns0XSJGynss4jiY0mTWpiDOsuyw+afP+ujjMgRh9CoZcXw==",
"dependencies": {
"domhandler": "5.0.3",
"htmlparser2": "9.0.0"
}
},
"node_modules/html-dom-parser/node_modules/htmlparser2": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz",
"integrity": "sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"entities": "^4.5.0"
}
},
"node_modules/html-entities": { "node_modules/html-entities": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz",
@ -9394,6 +9422,20 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/html-react-parser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.1.tgz",
"integrity": "sha512-Dxzdowj5Zu/+7mr8X8PzCFbPXGuwCwGB2u4cB6oxZGES9inw85qlvnlfPD75VGKUGjcgsXs+9Dpj+THWNQyOBw==",
"dependencies": {
"domhandler": "5.0.3",
"html-dom-parser": "4.0.0",
"react-property": "2.0.0",
"style-to-js": "1.1.3"
},
"peerDependencies": {
"react": "0.14 || 15 || 16 || 17 || 18"
}
},
"node_modules/html-tags": { "node_modules/html-tags": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
@ -15324,6 +15366,11 @@
"react": ">=16.6.0" "react": ">=16.6.0"
} }
}, },
"node_modules/react-property": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
"integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "5.3.4", "version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
@ -17510,6 +17557,22 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/style-to-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz",
"integrity": "sha512-zKI5gN/zb7LS/Vm0eUwjmjrXWw8IMtyA8aPBJZdYiQTXj4+wQ3IucOLIOnF7zCHxvW8UhIGh/uZh/t9zEHXNTQ==",
"dependencies": {
"style-to-object": "0.4.1"
}
},
"node_modules/style-to-js/node_modules/style-to-object": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
"integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==",
"dependencies": {
"inline-style-parser": "0.1.1"
}
},
"node_modules/style-to-object": { "node_modules/style-to-object": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",

View file

@ -22,7 +22,7 @@
"@docusaurus/theme-classic": "^2.4.1", "@docusaurus/theme-classic": "^2.4.1",
"@docusaurus/theme-search-algolia": "^2.4.1", "@docusaurus/theme-search-algolia": "^2.4.1",
"@mdx-js/react": "^2.3.0", "@mdx-js/react": "^2.3.0",
"@mendable/search": "^0.0.114", "@mendable/search": "^0.0.154",
"@pbe/react-yandex-maps": "^1.2.4", "@pbe/react-yandex-maps": "^1.2.4",
"@prismicio/client": "^7.0.1", "@prismicio/client": "^7.0.1",
"@uiball/loaders": "^1.2.6", "@uiball/loaders": "^1.2.6",
@ -69,4 +69,4 @@
"engines": { "engines": {
"node": ">=16.14" "node": ">=16.14"
} }
} }

View file

@ -42,6 +42,7 @@ module.exports = {
"components/text-splitters", "components/text-splitters",
"components/toolkits", "components/toolkits",
"components/tools", "components/tools",
"components/utilities",
"components/vector-stores", "components/vector-stores",
"components/wrappers", "components/wrappers",
], ],

View file

@ -37,7 +37,7 @@ export default function FooterWrapper(props) {
const mendableFloatingButton = React.createElement(MendableFloatingButton, { const mendableFloatingButton = React.createElement(MendableFloatingButton, {
floatingButtonStyle: { color: "#000000", backgroundColor: "#f6f6f6" }, floatingButtonStyle: { color: "#000000", backgroundColor: "#f6f6f6" },
anon_key: customFields.mendableAnonKey, // Mendable Search Public ANON key, ok to be public anon_key: 'b7f52734-297c-41dc-8737-edbd13196394', // Mendable Search Public ANON key, ok to be public
showSimpleSearch: true, showSimpleSearch: true,
icon: icon, icon: icon,
}); });

1551
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
{
"devDependencies": {
"@svgr/cli": "^8.0.1"
}
}

255
poetry.lock generated
View file

@ -206,17 +206,6 @@ files = [
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
] ]
[[package]]
name = "argilla"
version = "0.0.1"
description = ""
optional = false
python-versions = "*"
files = [
{file = "argilla-0.0.1-py3-none-any.whl", hash = "sha256:8bdc3c505bcfb47ba4b91f5658034eae53bf7d4f9317980397605c0c55817396"},
{file = "argilla-0.0.1.tar.gz", hash = "sha256:5017854754e89f573b31af25b25b803f51cea9ca1fa0bcf00505dee1f45cf7c9"},
]
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.7.2" version = "3.7.2"
@ -1235,16 +1224,19 @@ gmpy = ["gmpy"]
gmpy2 = ["gmpy2"] gmpy2 = ["gmpy2"]
[[package]] [[package]]
name = "et-xmlfile" name = "emoji"
version = "1.1.0" version = "2.8.0"
description = "An implementation of lxml.xmlfile for the standard library" description = "Emoji for Python"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
{file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, {file = "emoji-2.8.0-py2.py3-none-any.whl", hash = "sha256:a8468fd836b7ecb6d1eac054c9a591701ce0ccd6c6f7779ad71b66f76664df90"},
{file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, {file = "emoji-2.8.0.tar.gz", hash = "sha256:8d8b5dec3c507444b58890e598fc895fcec022b3f5acb49497c6ccc5208b8b00"},
] ]
[package.extras]
dev = ["coverage", "coveralls", "pytest"]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.1.3" version = "1.1.3"
@ -3160,24 +3152,6 @@ docs = ["sphinx (>=1.6.0)", "sphinx-bootstrap-theme"]
flake8 = ["flake8"] flake8 = ["flake8"]
tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"]
[[package]]
name = "markdown"
version = "3.4.4"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.7"
files = [
{file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"},
{file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"},
]
[package.dependencies]
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"]
testing = ["coverage", "pyyaml"]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "3.0.0" version = "3.0.0"
@ -3327,6 +3301,21 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
] ]
[[package]]
name = "metal-sdk"
version = "2.0.2"
description = "SDK for getmetal.io"
optional = false
python-versions = ">=3.7"
files = [
{file = "metal_sdk-2.0.2-py3-none-any.whl", hash = "sha256:a5a95fb3796979085965667ffc41fd9eeddc87b60671619671a0eaebd0bec4a1"},
{file = "metal_sdk-2.0.2.tar.gz", hash = "sha256:e5e35d230f00b1b838cfcffbcb7042f450b23b59448f37bdd385761871a1490d"},
]
[package.dependencies]
httpx = "*"
typing-extensions = "*"
[[package]] [[package]]
name = "metaphor-python" name = "metaphor-python"
version = "0.1.16" version = "0.1.16"
@ -3380,23 +3369,6 @@ docs = ["sphinx"]
gmpy = ["gmpy2 (>=2.1.0a4)"] gmpy = ["gmpy2 (>=2.1.0a4)"]
tests = ["pytest (>=4.6)"] tests = ["pytest (>=4.6)"]
[[package]]
name = "msg-parser"
version = "1.2.0"
description = "This module enables reading, parsing and converting Microsoft Outlook MSG E-Mail files."
optional = false
python-versions = ">=3.4"
files = [
{file = "msg_parser-1.2.0-py2.py3-none-any.whl", hash = "sha256:d47a2f0b2a359cb189fad83cc991b63ea781ecc70d91410324273fbf93e95375"},
{file = "msg_parser-1.2.0.tar.gz", hash = "sha256:0de858d4fcebb6c8f6f028da83a17a20fe01cdce67c490779cf43b3b0162aa66"},
]
[package.dependencies]
olefile = ">=0.46"
[package.extras]
rtf = ["compressed-rtf (>=1.0.5)"]
[[package]] [[package]]
name = "multidict" name = "multidict"
version = "6.0.4" version = "6.0.4"
@ -3695,16 +3667,6 @@ files = [
{file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"},
] ]
[[package]]
name = "olefile"
version = "0.46"
description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"},
]
[[package]] [[package]]
name = "onnxruntime" name = "onnxruntime"
version = "1.15.1" version = "1.15.1"
@ -3782,20 +3744,6 @@ files = [
[package.dependencies] [package.dependencies]
pydantic = ">=1.8.2" pydantic = ">=1.8.2"
[[package]]
name = "openpyxl"
version = "3.1.2"
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
optional = false
python-versions = ">=3.6"
files = [
{file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"},
{file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"},
]
[package.dependencies]
et-xmlfile = "*"
[[package]] [[package]]
name = "opentelemetry-api" name = "opentelemetry-api"
version = "1.19.0" version = "1.19.0"
@ -4254,40 +4202,6 @@ files = [
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
] ]
[[package]]
name = "pdf2image"
version = "1.16.3"
description = "A wrapper around the pdftoppm and pdftocairo command line tools to convert PDF to a PIL Image list."
optional = false
python-versions = "*"
files = [
{file = "pdf2image-1.16.3-py3-none-any.whl", hash = "sha256:b6154164af3677211c22cbb38b2bd778b43aca02758e962fe1e231f6d3b0e380"},
{file = "pdf2image-1.16.3.tar.gz", hash = "sha256:74208810c2cef4d9e347769b8e62a52303982ddb4f2dfd744c7ab4b940ae287e"},
]
[package.dependencies]
pillow = "*"
[[package]]
name = "pdfminer-six"
version = "20221105"
description = "PDF parser and analyzer"
optional = false
python-versions = ">=3.6"
files = [
{file = "pdfminer.six-20221105-py3-none-any.whl", hash = "sha256:1eaddd712d5b2732f8ac8486824533514f8ba12a0787b3d5fe1e686cd826532d"},
{file = "pdfminer.six-20221105.tar.gz", hash = "sha256:8448ab7b939d18b64820478ecac5394f482d7a79f5f7eaa7703c6c959c175e1d"},
]
[package.dependencies]
charset-normalizer = ">=2.0.0"
cryptography = ">=36.0.0"
[package.extras]
dev = ["black", "mypy (==0.931)", "nox", "pytest"]
docs = ["sphinx", "sphinx-argparse"]
image = ["Pillow"]
[[package]] [[package]]
name = "pexpect" name = "pexpect"
version = "4.8.0" version = "4.8.0"
@ -5088,17 +5002,6 @@ ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3
snappy = ["python-snappy"] snappy = ["python-snappy"]
zstd = ["zstandard"] zstd = ["zstandard"]
[[package]]
name = "pypandoc"
version = "1.11"
description = "Thin wrapper for pandoc."
optional = false
python-versions = ">=3.6"
files = [
{file = "pypandoc-1.11-py3-none-any.whl", hash = "sha256:b260596934e9cfc6513056110a7c8600171d414f90558bf4407e68b209be8007"},
{file = "pypandoc-1.11.tar.gz", hash = "sha256:7f6d68db0e57e0f6961bec2190897118c4d305fc2d31c22cd16037f22ee084a5"},
]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.1.1" version = "3.1.1"
@ -5212,19 +5115,6 @@ files = [
[package.dependencies] [package.dependencies]
six = ">=1.5" six = ">=1.5"
[[package]]
name = "python-docx"
version = "0.8.11"
description = "Create and update Microsoft Word .docx files."
optional = false
python-versions = "*"
files = [
{file = "python-docx-0.8.11.tar.gz", hash = "sha256:1105d233a0956dd8dd1e710d20b159e2d72ac3c301041b95f4d4ceb3e0ebebc4"},
]
[package.dependencies]
lxml = ">=2.3.2"
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.0.0" version = "1.0.0"
@ -5304,21 +5194,6 @@ files = [
[package.extras] [package.extras]
dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"] dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"]
[[package]]
name = "python-pptx"
version = "0.6.21"
description = "Generate and manipulate Open XML PowerPoint (.pptx) files"
optional = false
python-versions = "*"
files = [
{file = "python-pptx-0.6.21.tar.gz", hash = "sha256:7798a2aaf89563565b3c7120c0acfe9aff775db0db3580544e3bf4840c2e378f"},
]
[package.dependencies]
lxml = ">=3.1.0"
Pillow = ">=3.3.2"
XlsxWriter = ">=0.5.7"
[[package]] [[package]]
name = "python-semantic-release" name = "python-semantic-release"
version = "7.33.2" version = "7.33.2"
@ -6995,50 +6870,67 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]] [[package]]
name = "unstructured" name = "unstructured"
version = "0.7.12" version = "0.10.9"
description = "A library that prepares raw documents for downstream ML tasks." description = "A library that prepares raw documents for downstream ML tasks."
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
{file = "unstructured-0.7.12-py3-none-any.whl", hash = "sha256:6dec4f23574e213f30bccb680a4fb84c95617092ce4abf5d8955cc71af402fef"}, {file = "unstructured-0.10.9-py3-none-any.whl", hash = "sha256:182062983cf5ade923e871be52154829d92e5b72dfd7575847618fb44f7debd6"},
{file = "unstructured-0.7.12.tar.gz", hash = "sha256:3dcddea34f52e1070f38fd10063b3b0f64bc4cbe5b778d6b86b5d33262d625cd"}, {file = "unstructured-0.10.9.tar.gz", hash = "sha256:fd62f84abf12c85ef8b81567a4fcb841b7450f010454b69754bfc5b10b68d869"},
] ]
[package.dependencies] [package.dependencies]
argilla = "*" beautifulsoup4 = "*"
chardet = "*" chardet = "*"
emoji = "*"
filetype = "*" filetype = "*"
lxml = "*" lxml = "*"
markdown = "*"
msg-parser = "*"
nltk = "*" nltk = "*"
openpyxl = "*"
pandas = "*"
pdf2image = "*"
"pdfminer.six" = "*"
pillow = "*"
pypandoc = "*"
python-docx = "*"
python-magic = "*" python-magic = "*"
python-pptx = "*"
requests = "*" requests = "*"
tabulate = "*" tabulate = "*"
xlrd = "*"
[package.extras] [package.extras]
airtable = ["pyairtable"]
all-docs = ["Pillow (<10)", "ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx", "unstructured-inference", "xlrd"]
azure = ["adlfs", "fsspec"] azure = ["adlfs", "fsspec"]
biomed = ["bs4"]
box = ["boxfs", "fsspec"]
confluence = ["atlassian-python-api"]
csv = ["pandas"]
delta-table = ["deltalake", "fsspec"]
discord = ["discord-py"] discord = ["discord-py"]
doc = ["python-docx"]
docx = ["python-docx"]
dropbox = ["dropboxdrivefs", "fsspec"] dropbox = ["dropboxdrivefs", "fsspec"]
gcs = ["fsspec", "gcsfs"] elasticsearch = ["elasticsearch", "jq"]
github = ["pygithub (==1.58.2)"] epub = ["ebooklib"]
gcs = ["bs4", "fsspec", "gcsfs"]
github = ["pygithub (>1.58.0)"]
gitlab = ["python-gitlab"] gitlab = ["python-gitlab"]
google-drive = ["google-api-python-client"] google-drive = ["google-api-python-client"]
huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"] huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"]
local-inference = ["unstructured-inference (==0.5.4)"] image = ["Pillow (<10)", "pdf2image", "pdfminer.six", "unstructured-inference"]
local-inference = ["Pillow (<10)", "ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx", "unstructured-inference", "xlrd"]
md = ["markdown"]
msg = ["msg-parser"]
notion = ["htmlBuilder", "notion-client"]
odt = ["pypandoc", "python-docx"]
onedrive = ["Office365-REST-Python-Client (<2.4.3)", "bs4", "msal"]
org = ["pypandoc"]
outlook = ["Office365-REST-Python-Client (<2.4.3)", "msal"]
pdf = ["Pillow (<10)", "pdf2image", "pdfminer.six", "unstructured-inference"]
ppt = ["python-pptx"]
pptx = ["python-pptx"]
reddit = ["praw"] reddit = ["praw"]
rst = ["pypandoc"]
rtf = ["pypandoc"]
s3 = ["fsspec", "s3fs"] s3 = ["fsspec", "s3fs"]
sharepoint = ["Office365-REST-Python-Client (<2.4.3)", "msal"]
slack = ["slack-sdk"] slack = ["slack-sdk"]
tsv = ["pandas"]
wikipedia = ["wikipedia"] wikipedia = ["wikipedia"]
xlsx = ["openpyxl", "pandas", "xlrd"]
[[package]] [[package]]
name = "uritemplate" name = "uritemplate"
@ -7443,33 +7335,6 @@ files = [
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
] ]
[[package]]
name = "xlrd"
version = "2.0.1"
description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
{file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"},
{file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"},
]
[package.extras]
build = ["twine", "wheel"]
docs = ["sphinx"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "xlsxwriter"
version = "3.1.2"
description = "A Python module for creating Excel XLSX files."
optional = false
python-versions = ">=3.6"
files = [
{file = "XlsxWriter-3.1.2-py3-none-any.whl", hash = "sha256:331508ff39d610ecdaf979e458840bc1eab6e6a02cfd5d08f044f0f73636236f"},
{file = "XlsxWriter-3.1.2.tar.gz", hash = "sha256:78751099a770273f1c98b8d6643351f68f98ae8e6acf9d09d37dc6798f8cd3de"},
]
[[package]] [[package]]
name = "yarl" name = "yarl"
version = "1.9.2" version = "1.9.2"
@ -7638,4 +7503,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "3517d8ba2744a059d3f107a826392c25ed57b5279ccf45ffdb8e188f959b256a" content-hash = "2390a7da4e661d287aa15bfe00ba84789301af5cca5dbc584f0093461670364e"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.4.9" version = "0.4.17"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -32,33 +32,33 @@ beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1" google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.79.0"
typer = "^0.9.0" typer = "^0.9.0"
gunicorn = "^21.1.0" gunicorn = "^21.2.0"
langchain = "^0.0.256" langchain = "^0.0.256"
openai = "^0.27.8" openai = "^0.27.8"
pandas = "^2.0.0" pandas = "^2.0.0"
chromadb = "^0.3.21" chromadb = "^0.3.0"
huggingface-hub = { version = "^0.16.0", extras = ["inference"] } huggingface-hub = { version = "^0.16.0", extras = ["inference"] }
rich = "^13.4.2" rich = "^13.5.0"
llama-cpp-python = { version = "~0.1.0", optional = true } llama-cpp-python = { version = "~0.1.0", optional = true }
networkx = "^3.1" networkx = "^3.1"
unstructured = "^0.7.0" unstructured = "^0.10.0"
pypdf = "^3.11.0" pypdf = "^3.15.0"
lxml = "^4.9.2" lxml = "^4.9.2"
pysrt = "^1.1.2" pysrt = "^1.1.2"
fake-useragent = "^1.1.3" fake-useragent = "^1.2.1"
docstring-parser = "^0.15" docstring-parser = "^0.15"
psycopg2-binary = "^2.9.6" psycopg2-binary = "^2.9.6"
pyarrow = "^12.0.0" pyarrow = "^12.0.0"
tiktoken = "~0.4.0" tiktoken = "~0.4.0"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
langchain-serve = { version = ">0.0.51", optional = true } langchain-serve = { version = ">0.0.51", optional = true }
qdrant-client = "^1.3.0" qdrant-client = "^1.4.0"
websockets = "^10.3" websockets = "^10.3"
weaviate-client = "^3.21.0" weaviate-client = "^3.23.0"
jina = "3.15.2" jina = "3.15.2"
sentence-transformers = { version = "^2.2.2", optional = true } sentence-transformers = { version = "^2.2.2", optional = true }
ctransformers = { version = "^0.2.10", optional = true } ctransformers = { version = "^0.2.10", optional = true }
cohere = "^4.11.0" cohere = "^4.21.0"
python-multipart = "^0.0.6" python-multipart = "^0.0.6"
sqlmodel = "^0.0.8" sqlmodel = "^0.0.8"
faiss-cpu = "^1.7.4" faiss-cpu = "^1.7.4"
@ -80,6 +80,8 @@ fastavro = "^1.8.0"
langchain-experimental = "^0.0.8" langchain-experimental = "^0.0.8"
metaphor-python = "^0.1.11" metaphor-python = "^0.1.11"
langfuse = "^1.0.13" langfuse = "^1.0.13"
pillow = "^10.0.0"
metal-sdk = "^2.0.2"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"

View file

@ -104,14 +104,9 @@ async def stream_build(flow_id: str):
return return
logger.debug("Building langchain object") logger.debug("Building langchain object")
try:
# Some error could happen when building the graph # Some error could happen when building the graph
graph = Graph.from_payload(graph_data) graph = Graph.from_payload(graph_data)
except Exception as exc:
logger.exception(exc)
error_message = str(exc)
yield str(StreamData(event="error", data={"error": error_message}))
return
number_of_nodes = len(graph.nodes) number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
@ -126,7 +121,9 @@ async def stream_build(flow_id: str):
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug(f"Building node {str(vertex.vertex_type)}") logger.debug(f"Building node {str(vertex.vertex_type)}")
logger.debug(f"Output: {params}") logger.debug(
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
)
if vertex.artifacts: if vertex.artifacts:
# The artifacts will be prompt variables # The artifacts will be prompt variables
# passed to build_input_keys_response # passed to build_input_keys_response

View file

@ -56,12 +56,9 @@ def get_all():
logger.info(f"Loading {len(custom_component_dicts)} category(ies)") logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
for custom_component_dict in custom_component_dicts: for custom_component_dict in custom_component_dicts:
# custom_component_dict is a dict of dicts logger.debug(
category = list(custom_component_dict.keys())[0] {key: len(value) for key, value in custom_component_dict.items()}
logger.info(
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
) )
logger.debug(custom_component_dict)
custom_components_from_file = merge_nested_dicts_with_renaming( custom_components_from_file = merge_nested_dicts_with_renaming(
custom_components_from_file, custom_component_dict custom_components_from_file, custom_component_dict
) )

View file

@ -1,5 +1,6 @@
from typing import List from typing import List
from uuid import UUID from uuid import UUID
from fastapi.encoders import jsonable_encoder
from langflow.settings import settings from langflow.settings import settings
from langflow.api.utils import remove_api_keys from langflow.api.utils import remove_api_keys
from langflow.api.v1.schemas import FlowListCreate, FlowListRead from langflow.api.v1.schemas import FlowListCreate, FlowListRead
@ -11,12 +12,11 @@ from langflow.database.models.flow import (
FlowUpdate, FlowUpdate,
) )
from langflow.database.base import get_session from langflow.database.base import get_session
import orjson
from sqlmodel import Session, select from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi import File, UploadFile from fastapi import File, UploadFile
import json
# build router # build router
router = APIRouter(prefix="/flows", tags=["Flows"]) router = APIRouter(prefix="/flows", tags=["Flows"])
@ -105,7 +105,7 @@ async def upload_file(
): ):
"""Upload flows from a file.""" """Upload flows from a file."""
contents = await file.read() contents = await file.read()
data = json.loads(contents) data = orjson.loads(contents)
if "flows" in data: if "flows" in data:
flow_list = FlowListCreate(**data) flow_list = FlowListCreate(**data)
else: else:

View file

@ -1,9 +1,9 @@
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from langflow.database.models.base import orjson_dumps
from langflow.database.models.flow import FlowCreate, FlowRead from langflow.database.models.flow import FlowCreate, FlowRead
from pydantic import BaseModel, Field, validator from pydantic import BaseModel, Field, validator
import json
class BuildStatus(Enum): class BuildStatus(Enum):
@ -115,7 +115,9 @@ class StreamData(BaseModel):
data: dict data: dict
def __str__(self) -> str: def __str__(self) -> str:
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n" return (
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
)
class CustomComponentCode(BaseModel): class CustomComponentCode(BaseModel):

View file

@ -2,13 +2,13 @@ import base64
import contextlib import contextlib
import functools import functools
import hashlib import hashlib
import json
import os import os
import tempfile import tempfile
from collections import OrderedDict from collections import OrderedDict
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
from appdirs import user_cache_dir from appdirs import user_cache_dir
from langflow.database.models.base import orjson_dumps
CACHE: Dict[str, Any] = {} CACHE: Dict[str, Any] = {}
@ -76,7 +76,8 @@ def clear_old_cache_files(max_cache_size: int = 3):
def compute_dict_hash(graph_data): def compute_dict_hash(graph_data):
graph_data = filter_json(graph_data) graph_data = filter_json(graph_data)
cleaned_graph_json = json.dumps(graph_data, sort_keys=True) cleaned_graph_json = orjson_dumps(graph_data, sort_keys=True)
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest() return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()

View file

@ -10,10 +10,10 @@ from langflow.utils.logger import logger
import asyncio import asyncio
import json
from typing import Any, Dict, List from typing import Any, Dict, List
from langflow.cache.flow import InMemoryCache from langflow.cache.flow import InMemoryCache
import orjson
class ChatHistory(Subject): class ChatHistory(Subject):
@ -193,8 +193,8 @@ class ChatManager:
while True: while True:
json_payload = await websocket.receive_json() json_payload = await websocket.receive_json()
try: try:
payload = json.loads(json_payload) payload = orjson.loads(json_payload)
except TypeError: except Exception:
payload = json_payload payload = json_payload
if "clear_history" in payload: if "clear_history" in payload:
self.chat_history.history[client_id] = [] self.chat_history.history[client_id] = []

View file

@ -42,8 +42,8 @@ class ConversationalAgent(CustomComponent):
self, self,
model_name: str, model_name: str,
openai_api_key: str, openai_api_key: str,
openai_api_base: str,
tools: Tool, tools: Tool,
openai_api_base: Optional[str] = None,
memory: Optional[BaseMemory] = None, memory: Optional[BaseMemory] = None,
system_message: Optional[SystemMessagePromptTemplate] = None, system_message: Optional[SystemMessagePromptTemplate] = None,
max_token_limit: int = 2000, max_token_limit: int = 2000,

View file

@ -0,0 +1,42 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms import HuggingFaceEndpoint
from langchain.llms.base import BaseLLM
class HuggingFaceEndpointsComponent(CustomComponent):
display_name: str = "Hugging Face Inference API"
description: str = "LLM model from Hugging Face Inference API."
def build_config(self):
return {
"endpoint_url": {"display_name": "Endpoint URL", "password": True},
"task": {
"display_name": "Task",
"type": "select",
"options": ["text2text-generation", "text-generation", "summarization"],
},
"huggingfacehub_api_token": {"display_name": "API token", "password": True},
"model_kwargs": {
"display_name": "Model Keyword Arguments",
"field_type": "code",
},
"code": {"show": False},
}
def build(
self,
endpoint_url: str,
task="text2text-generation",
huggingfacehub_api_token: Optional[str] = None,
model_kwargs: Optional[dict] = None,
) -> BaseLLM:
try:
output = HuggingFaceEndpoint(
endpoint_url=endpoint_url,
task=task,
huggingfacehub_api_token=huggingfacehub_api_token,
)
except Exception as e:
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
return output

View file

@ -0,0 +1,28 @@
from typing import Optional
from langflow import CustomComponent
from langchain.retrievers import MetalRetriever
from langchain.schema import BaseRetriever
from metal_sdk.metal import Metal # type: ignore
class MetalRetrieverComponent(CustomComponent):
display_name: str = "Metal Retriever"
description: str = "Retriever that uses the Metal API."
def build_config(self):
return {
"api_key": {"display_name": "API Key", "password": True},
"client_id": {"display_name": "Client ID", "password": True},
"index_id": {"display_name": "Index ID"},
"params": {"display_name": "Parameters", "field_type": "code"},
"code": {"show": False},
}
def build(
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
) -> BaseRetriever:
try:
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
except Exception as e:
raise ValueError("Could not connect to Metal API.") from e
return MetalRetriever(client=metal, params=params or {})

View file

@ -0,0 +1,82 @@
from typing import Optional
from langflow import CustomComponent
from langchain.text_splitter import Language
from langchain.schema import Document
from langflow.utils.util import build_loader_repr_from_documents
class LanguageRecursiveTextSplitterComponent(CustomComponent):
display_name: str = "Language Recursive Text Splitter"
description: str = "Split text into chunks of a specified length based on language."
documentation: str = "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
def build_config(self):
options = [x.value for x in Language]
return {
"documents": {
"display_name": "Documents",
"info": "The documents to split.",
},
"separator_type": {
"display_name": "Separator Type",
"info": "The type of separator to use.",
"field_type": "str",
"options": options,
"value": "Python",
},
"separators": {
"display_name": "Separators",
"info": "The characters to split on.",
"is_list": True,
},
"chunk_size": {
"display_name": "Chunk Size",
"info": "The maximum length of each chunk.",
"field_type": "int",
"value": 1000,
},
"chunk_overlap": {
"display_name": "Chunk Overlap",
"info": "The amount of overlap between chunks.",
"field_type": "int",
"value": 200,
},
"code": {"show": False},
}
def build(
self,
documents: list[Document],
chunk_size: Optional[int] = 1000,
chunk_overlap: Optional[int] = 200,
separator_type: Optional[str] = "Python",
) -> list[Document]:
"""
Split text into chunks of a specified length.
Args:
separators (list[str]): The characters to split on.
chunk_size (int): The maximum length of each chunk.
chunk_overlap (int): The amount of overlap between chunks.
length_function (function): The function to use to calculate the length of the text.
Returns:
list[str]: The chunks of text.
"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str):
chunk_size = int(chunk_size)
if isinstance(chunk_overlap, str):
chunk_overlap = int(chunk_overlap)
splitter = RecursiveCharacterTextSplitter.from_language(
language=Language(separator_type),
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
docs = splitter.split_documents(documents)
self.repr_value = build_loader_repr_from_documents(docs)
return docs

View file

@ -0,0 +1,79 @@
from typing import Optional
from langflow import CustomComponent
from langchain.schema import Document
class RecursiveCharacterTextSplitterComponent(CustomComponent):
display_name: str = "Recursive Character Text Splitter"
description: str = "Split text into chunks of a specified length."
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
def build_config(self):
return {
"documents": {
"display_name": "Documents",
"info": "The documents to split.",
},
"separators": {
"display_name": "Separators",
"info": 'The characters to split on.\nIf left empty defaults to ["\\n\\n", "\\n", " ", ""].',
"is_list": True,
},
"chunk_size": {
"display_name": "Chunk Size",
"info": "The maximum length of each chunk.",
"field_type": "int",
"value": 1000,
},
"chunk_overlap": {
"display_name": "Chunk Overlap",
"info": "The amount of overlap between chunks.",
"field_type": "int",
"value": 200,
},
"code": {"show": False},
}
def build(
self,
documents: list[Document],
separators: Optional[list[str]] = None,
chunk_size: Optional[int] = 1000,
chunk_overlap: Optional[int] = 200,
) -> list[Document]:
"""
Split text into chunks of a specified length.
Args:
separators (list[str]): The characters to split on.
chunk_size (int): The maximum length of each chunk.
chunk_overlap (int): The amount of overlap between chunks.
length_function (function): The function to use to calculate the length of the text.
Returns:
list[str]: The chunks of text.
"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
if separators == "":
separators = None
elif separators:
# check if the separators list has escaped characters
# if there are escaped characters, unescape them
separators = [x.encode().decode("unicode-escape") for x in separators]
# Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str):
chunk_size = int(chunk_size)
if isinstance(chunk_overlap, str):
chunk_overlap = int(chunk_overlap)
splitter = RecursiveCharacterTextSplitter(
separators=separators,
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
docs = splitter.split_documents(documents)
# self.repr_value = build_loader_repr_from_documents(docs)
self.repr_value = separators
return docs

View file

@ -0,0 +1,76 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
import requests
from typing import Optional
class GetRequest(CustomComponent):
display_name: str = "GET Request"
description: str = "Make a GET request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#get-request"
beta = True
field_config = {
"url": {
"display_name": "URL",
"info": "The URL to make the request to",
"is_list": True,
},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"timeout": {
"display_name": "Timeout",
"field_type": "int",
"info": "The timeout to use for the request.",
"value": 5,
},
}
def get_document(
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
) -> Document:
try:
response = session.get(url, headers=headers, timeout=int(timeout))
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except requests.Timeout:
return Document(
page_content="Request Timed Out",
metadata={"source": url, "headers": headers, "status_code": 408},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
url: str,
headers: Optional[dict] = None,
timeout: int = 5,
) -> list[Document]:
if headers is None:
headers = {}
urls = url if isinstance(url, list) else [url]
with requests.Session() as session:
documents = [self.get_document(session, u, headers, timeout) for u in urls]
self.repr_value = documents
return documents

View file

@ -0,0 +1,55 @@
### JSON Document Builder
# Build a Document containing a JSON object using a key and another Document page content.
# **Params**
# - **Key:** The key to use for the JSON object.
# - **Document:** The Document page to use for the JSON object.
# **Output**
# - **Document:** The Document containing the JSON object.
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
class JSONDocumentBuilder(CustomComponent):
display_name: str = "JSON Document Builder"
description: str = "Build a Document containing a JSON object using a key and another Document page content."
output_types: list[str] = ["Document"]
beta = True
documentation: str = (
"https://docs.langflow.org/components/utilities#json-document-builder"
)
field_config = {
"key": {"display_name": "Key"},
"document": {"display_name": "Document"},
}
def build(
self,
key: str,
document: Document,
) -> Document:
documents = None
if isinstance(document, list):
documents = [
Document(
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
)
for doc in document
]
elif isinstance(document, Document):
documents = Document(
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
)
else:
raise TypeError(
f"Expected Document or list of Documents, got {type(document)}"
)
self.repr_value = documents
return documents

View file

@ -0,0 +1,81 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
import requests
from typing import Optional
class PostRequest(CustomComponent):
display_name: str = "POST Request"
description: str = "Make a POST request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#post-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
}
def post_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> Document:
try:
response = session.post(url, headers=headers, data=document.page_content)
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={
"source": url,
"headers": headers,
"status_code": 500,
},
)
def build(
self,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> list[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.post_document(session, doc, url, headers) for doc in documents
]
self.repr_value = documents
return documents

View file

@ -0,0 +1,94 @@
from typing import List, Optional
import requests
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
class UpdateRequest(CustomComponent):
display_name: str = "Update Request"
description: str = "Make a PATCH request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#update-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
"method": {
"display_name": "Method",
"field_type": "str",
"info": "The HTTP method to use.",
"options": ["PATCH", "PUT"],
"value": "PATCH",
},
}
def update_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
method: str = "PATCH",
) -> Document:
try:
if method == "PATCH":
response = session.patch(
url, headers=headers, data=document.page_content
)
elif method == "PUT":
response = session.put(url, headers=headers, data=document.page_content)
else:
raise ValueError(f"Unsupported method: {method}")
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
method: str,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> List[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.update_document(session, doc, url, headers, method)
for doc in documents
]
self.repr_value = documents
return documents

View file

@ -169,8 +169,6 @@ prompts:
textsplitters: textsplitters:
CharacterTextSplitter: CharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter" documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
RecursiveCharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
toolkits: toolkits:
OpenAPIToolkit: OpenAPIToolkit:
documentation: "" documentation: ""

View file

@ -2,9 +2,20 @@ from sqlmodel import SQLModel
import orjson import orjson
def orjson_dumps(v, *, default): def orjson_dumps(v, *, default=None, sort_keys=False, indent_2=True):
# orjson.dumps returns bytes, to match standard json.dumps we need to decode option = orjson.OPT_SORT_KEYS if sort_keys else None
return orjson.dumps(v, default=default).decode() if indent_2:
# orjson.dumps returns bytes, to match standard json.dumps we need to decode
# option
# To modify how data is serialized, specify option. Each option is an integer constant in orjson.
# To specify multiple options, mask them together, e.g., option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC
if option is None:
option = orjson.OPT_INDENT_2
else:
option |= orjson.OPT_INDENT_2
if default is None:
return orjson.dumps(v, option=option).decode()
return orjson.dumps(v, default=default, option=option).decode()
class SQLModelSerializable(SQLModel): class SQLModelSerializable(SQLModel):

View file

@ -40,7 +40,6 @@ class Edge:
if no_matched_type: if no_matched_type:
logger.debug(self.source_types) logger.debug(self.source_types)
logger.debug(self.target_reqs) logger.debug(self.target_reqs)
if no_matched_type:
raise ValueError( raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
f"has no matched type" f"has no matched type"

View file

@ -3,6 +3,7 @@ from fastapi import HTTPException
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.component import Component from langflow.interface.custom.component import Component
from langflow.interface.custom.directory_reader import DirectoryReader from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.interface.custom.utils import extract_inner_type
from langflow.utils import validate from langflow.utils import validate
@ -19,7 +20,7 @@ class CustomComponent(Component, extra=Extra.allow):
function_entrypoint_name = "build" function_entrypoint_name = "build"
function: Optional[Callable] = None function: Optional[Callable] = None
return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys()) return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
repr_value: Optional[str] = "" repr_value: Optional[Any] = ""
def __init__(self, **data): def __init__(self, **data):
super().__init__(**data) super().__init__(**data)
@ -122,6 +123,10 @@ class CustomComponent(Component, extra=Extra.allow):
return_type = build_method["return_type"] return_type = build_method["return_type"]
if not return_type: if not return_type:
return [] return []
# If list or List is in the return type, then we remove it and return the inner type
if return_type.startswith("list") or return_type.startswith("List"):
return_type = extract_inner_type(return_type)
# If the return type is not a Union, then we just return it as a list # If the return type is not a Union, then we just return it as a list
if "Union" not in return_type: if "Union" not in return_type:
return [return_type] if return_type in self.return_type_valid_list else [] return [return_type] if return_type in self.return_type_valid_list else []

View file

@ -77,7 +77,7 @@ class DirectoryReader:
] ]
filtered = [menu for menu in items if menu["components"]] filtered = [menu for menu in items if menu["components"]]
logger.debug( logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {filtered}' f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
) )
return {"menu": filtered} return {"menu": filtered}

View file

@ -0,0 +1,10 @@
import re
def extract_inner_type(return_type: str) -> str:
"""
Extracts the inner type from a type hint that is a list.
"""
if match := re.match(r"list\[(.*)\]", return_type, re.IGNORECASE):
return match[1]
return return_type

View file

@ -1,4 +1,5 @@
import json import json
import orjson
from typing import Any, Callable, Dict, Sequence, Type from typing import Any, Callable, Dict, Sequence, Type
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
@ -66,7 +67,7 @@ def convert_kwargs(params):
for key in kwargs_keys: for key in kwargs_keys:
if isinstance(params[key], str): if isinstance(params[key], str):
try: try:
params[key] = json.loads(params[key]) params[key] = orjson.loads(params[key])
except json.JSONDecodeError: except json.JSONDecodeError:
# if the string is not a valid json string, we will # if the string is not a valid json string, we will
# remove the key from the params # remove the key from the params
@ -306,7 +307,7 @@ def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
metadata = params.pop("metadata", None) metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str): if metadata and isinstance(metadata, str):
try: try:
metadata = json.loads(metadata) metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc: except json.JSONDecodeError as exc:
raise ValueError( raise ValueError(
"The metadata you provided is not a valid JSON string." "The metadata you provided is not a valid JSON string."

View file

@ -1,5 +1,7 @@
import contextlib import contextlib
import json import json
from langflow.database.models.base import orjson_dumps
import orjson
from typing import Any, Dict, List from typing import Any, Dict, List
from langchain.agents import ZeroShotAgent from langchain.agents import ZeroShotAgent
@ -95,9 +97,11 @@ def format_content(variable):
def try_to_load_json(content): def try_to_load_json(content):
with contextlib.suppress(json.JSONDecodeError): with contextlib.suppress(json.JSONDecodeError):
content = json.loads(content) content = orjson.loads(content)
if isinstance(content, list): if isinstance(content, list):
content = ",".join([str(item) for item in content]) content = ",".join([str(item) for item in content])
else:
content = orjson_dumps(content)
return content return content

View file

@ -1,4 +1,3 @@
import json
from typing import Any, Callable, Dict, Type from typing import Any, Callable, Dict, Type
from langchain.vectorstores import ( from langchain.vectorstores import (
Pinecone, Pinecone,
@ -12,6 +11,8 @@ from langchain.vectorstores import (
import os import os
import orjson
def docs_in_params(params: dict) -> bool: def docs_in_params(params: dict) -> bool:
"""Check if params has documents OR texts and one of them is not an empty list, """Check if params has documents OR texts and one of them is not an empty list,
@ -92,7 +93,7 @@ def initialize_weaviate(class_object: Type[Weaviate], params: dict):
import weaviate # type: ignore import weaviate # type: ignore
client_kwargs_json = params.get("client_kwargs", "{}") client_kwargs_json = params.get("client_kwargs", "{}")
client_kwargs = json.loads(client_kwargs_json) client_kwargs = orjson.loads(client_kwargs_json)
client_params = { client_params = {
"url": params.get("weaviate_url"), "url": params.get("weaviate_url"),
} }

View file

@ -5,6 +5,7 @@ from langflow.api.utils import merge_nested_dicts_with_renaming
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.utils import extract_inner_type
from langflow.interface.document_loaders.base import documentloader_creator from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.importing.utils import get_function_custom from langflow.interface.importing.utils import get_function_custom
@ -84,6 +85,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
def process_type(field_type: str): def process_type(field_type: str):
if field_type.startswith("list") or field_type.startswith("List"):
return extract_inner_type(field_type)
return "prompt" if field_type == "Prompt" else field_type return "prompt" if field_type == "Prompt" else field_type
@ -100,6 +103,7 @@ def add_new_custom_field(
# if it is, update the value # if it is, update the value
display_name = field_config.pop("display_name", field_name) display_name = field_config.pop("display_name", field_name)
field_type = field_config.pop("field_type", field_type) field_type = field_config.pop("field_type", field_type)
field_contains_list = "list" in field_type.lower()
field_type = process_type(field_type) field_type = process_type(field_type)
field_value = field_config.pop("value", field_value) field_value = field_config.pop("value", field_value)
field_advanced = field_config.pop("advanced", False) field_advanced = field_config.pop("advanced", False)
@ -110,7 +114,9 @@ def add_new_custom_field(
# If options is a list, then it's a dropdown # If options is a list, then it's a dropdown
# If options is None, then it's a list of strings # If options is None, then it's a list of strings
is_list = isinstance(field_config.get("options"), list) is_list = isinstance(field_config.get("options"), list)
field_config["is_list"] = is_list or field_config.get("is_list", False) field_config["is_list"] = (
is_list or field_config.get("is_list", False) or field_contains_list
)
if "name" in field_config: if "name" in field_config:
warnings.warn( warnings.warn(
@ -172,7 +178,7 @@ def extract_type_from_optional(field_type):
Returns: Returns:
str: The extracted type, or an empty string if no type was found. str: The extracted type, or an empty string if no type was found.
""" """
match = re.search(r"\[(.*?)\]", field_type) match = re.search(r"\[(.*?)\]$", field_type)
return match[1] if match else None return match[1] if match else None
@ -190,17 +196,16 @@ def build_frontend_node(custom_component: CustomComponent):
def update_attributes(frontend_node, template_config): def update_attributes(frontend_node, template_config):
"""Update the display name and description of a frontend node""" """Update the display name and description of a frontend node"""
if "display_name" in template_config: attributes = [
frontend_node["display_name"] = template_config["display_name"] "display_name",
"description",
if "description" in template_config: "beta",
frontend_node["description"] = template_config["description"] "documentation",
"output_types",
if "beta" in template_config: ]
frontend_node["beta"] = template_config["beta"] for attribute in attributes:
if attribute in template_config:
if "documentation" in template_config: frontend_node[attribute] = template_config[attribute]
frontend_node["documentation"] = template_config["documentation"]
def build_field_config(custom_component: CustomComponent): def build_field_config(custom_component: CustomComponent):
@ -338,7 +343,9 @@ def build_valid_menu(valid_components):
valid_menu[menu_name] = {} valid_menu[menu_name] = {}
for component in menu_item["components"]: for component in menu_item["components"]:
logger.debug(f"Building component: {component}") logger.debug(
f"Building component: {component.get('name'), component.get('output_types')}"
)
try: try:
component_name = component["name"] component_name = component["name"]
component_code = component["code"] component_code = component["code"]

View file

@ -1,6 +1,6 @@
import json
from pathlib import Path from pathlib import Path
from langchain.schema import AgentAction from langchain.schema import AgentAction
import json
from langflow.interface.run import ( from langflow.interface.run import (
build_sorted_vertices_with_caching, build_sorted_vertices_with_caching,
get_memory_key, get_memory_key,

View file

@ -1,5 +1,6 @@
import contextlib import contextlib
import json import json
import orjson
import os import os
from typing import Optional, List from typing import Optional, List
from pathlib import Path from pathlib import Path
@ -130,7 +131,7 @@ class Settings(BaseSettings):
if isinstance(getattr(self, key), list): if isinstance(getattr(self, key), list):
# value might be a '[something]' string # value might be a '[something]' string
with contextlib.suppress(json.decoder.JSONDecodeError): with contextlib.suppress(json.decoder.JSONDecodeError):
value = json.loads(str(value)) value = orjson.loads(str(value))
if isinstance(value, list): if isinstance(value, list):
for item in value: for item in value:
if isinstance(item, Path): if isinstance(item, Path):

View file

@ -1,5 +1,5 @@
import json
from typing import Optional from typing import Optional
from langflow.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
@ -89,7 +89,7 @@ class LLMFrontendNode(FrontendNode):
if field.name == "config": if field.name == "config":
field.show = True field.show = True
field.advanced = True field.advanced = True
field.value = json.dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent=2) field.value = orjson_dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent_2=True)
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:

View file

@ -1,6 +1,6 @@
import ast import ast
import json
from typing import Optional from typing import Optional
from langflow.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
@ -22,4 +22,4 @@ class UtilitiesFrontendNode(FrontendNode):
if isinstance(field.value, dict): if isinstance(field.value, dict):
field.field_type = "code" field.field_type = "code"
field.value = json.dumps(field.value, indent=4) field.value = orjson_dumps(field.value)

View file

@ -2,7 +2,7 @@ import re
import inspect import inspect
import importlib import importlib
from functools import wraps from functools import wraps
from typing import Optional, Dict, Any, Union from typing import List, Optional, Dict, Any, Union
from docstring_parser import parse # type: ignore from docstring_parser import parse # type: ignore
@ -10,6 +10,7 @@ from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants from langflow.utils import constants
from langflow.utils.logger import logger from langflow.utils.logger import logger
from multiprocess import cpu_count # type: ignore from multiprocess import cpu_count # type: ignore
from langchain.schema import Document
def build_template_from_function( def build_template_from_function(
@ -462,3 +463,12 @@ def get_number_of_workers(workers=None):
workers = (cpu_count() * 2) + 1 workers = (cpu_count() * 2) + 1
logger.debug(f"Number of workers: {workers}") logger.debug(f"Number of workers: {workers}")
return workers return workers
def build_loader_repr_from_documents(documents: List[Document]) -> str:
if documents:
avg_length = sum(len(doc.page_content) for doc in documents) / len(documents)
return f"""{len(documents)} documents
\nAvg. Document Length (characters): {int(avg_length)}
Documents: {documents[:3]}..."""
return "0 documents"

View file

@ -9,10 +9,16 @@ 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";
import CrashErrorComponent from "./components/CrashErrorComponent"; import CrashErrorComponent from "./components/CrashErrorComponent";
import Header from "./components/headerComponent"; import FetchErrorComponent from "./components/fetchErrorComponent";
import LoadingComponent from "./components/loadingComponent";
import {
FETCH_ERROR_DESCRIPION,
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { alertContext } from "./contexts/alertContext"; import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext"; import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext"; import { TabsContext } from "./contexts/tabsContext";
import { typesContext } from "./contexts/typesContext";
import Router from "./routes"; import Router from "./routes";
export default function App() { export default function App() {
@ -25,6 +31,7 @@ export default function App() {
setIsStackedOpen(true); setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]); }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset } = useContext(TabsContext); const { hardReset } = useContext(TabsContext);
const { const {
errorData, errorData,
errorOpen, errorOpen,
@ -35,7 +42,9 @@ export default function App() {
successData, successData,
successOpen, successOpen,
setSuccessOpen, setSuccessOpen,
loading,
} = useContext(alertContext); } = useContext(alertContext);
const { fetchError } = useContext(typesContext);
// Initialize state variable for the list of alerts // Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState< const [alertsList, setAlertsList] = useState<
@ -133,8 +142,22 @@ export default function App() {
}} }}
FallbackComponent={CrashErrorComponent} FallbackComponent={CrashErrorComponent}
> >
<Header /> {loading ? (
<Router /> <div className="loading-page-panel">
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : (
<LoadingComponent remSize={50} />
)}
</div>
) : (
<>
<Router />
</>
)}
</ErrorBoundary> </ErrorBoundary>
<div></div> <div></div>
<div className="app-div" style={{ zIndex: 999 }}> <div className="app-div" style={{ zIndex: 999 }}>

View file

@ -54,21 +54,9 @@ export const EditFlowSettings: React.FC<InputProps> = ({
} }
} }
setName(value); setName(value);
setCurrentName(value);
}; };
const [currentName, setCurrentName] = useState(name);
const [currentDescription, setCurrentDescription] = useState(description);
useEffect(() => {
setCurrentName(name);
setCurrentDescription(description);
}, [name, description]);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => { const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setCurrentDescription(flows.find((f) => f.id === tabId).description);
setDescription(event.target.value); setDescription(event.target.value);
}; };
@ -89,7 +77,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleNameChange} onChange={handleNameChange}
type="text" type="text"
name="name" name="name"
value={currentName ?? ""} value={name ?? ""}
placeholder="File name" placeholder="File name"
id="name" id="name"
maxLength={maxLength} maxLength={maxLength}
@ -104,7 +92,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
name="description" name="description"
id="description" id="description"
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
value={currentDescription} value={description}
placeholder="Flow description" placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal" className="mt-2 max-h-[100px] font-normal"
rows={3} rows={3}

View file

@ -0,0 +1,16 @@
import { fetchErrorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
export default function FetchErrorComponent({
message,
description,
}: fetchErrorComponentType) {
return (
<div role="status" className="m-auto flex flex-col items-center">
<IconComponent 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>
);
}

View file

@ -7,5 +7,11 @@ export default function IconComponent({
iconColor, iconColor,
}: IconComponentProps): JSX.Element { }: IconComponentProps): JSX.Element {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"]; const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return <TargetIcon className={className} style={{ color: iconColor }} />; return (
<TargetIcon
className={className}
style={{ color: iconColor }}
stroke-width={1.5}
/>
);
} }

View file

@ -18,6 +18,9 @@ export default function InputListComponent({
} }
}, [disabled]); }, [disabled]);
// @TODO Recursive Character Text Splitter - the value might be in string format, whereas the InputListComponent specifically requires an array format. To ensure smooth operation and prevent potential errors, it's crucial that we handle the conversion from a string to an array with the string as its element.
typeof value === 'string' ? value = [value] : value = value;
return ( return (
<div <div
className={classNames( className={classNames(

View file

@ -1,6 +1,4 @@
type LoadingComponentProps = { import { LoadingComponentProps } from "../../types/components";
remSize: number;
};
export default function LoadingComponent({ remSize }: LoadingComponentProps) { export default function LoadingComponent({ remSize }: LoadingComponentProps) {
return ( return (

View file

@ -509,3 +509,9 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/custom_component", "/api/v1/custom_component",
"/api/v1/validate/prompt", "/api/v1/validate/prompt",
]; ];
export const skipNodeUpdate = ["CustomComponent"];
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
export const FETCH_ERROR_DESCRIPION =
"Check if everything is working properly and try again.";

View file

@ -23,12 +23,16 @@ type alertContextType = {
pushNotificationList: (Object: AlertItemType) => void; pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void; clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void; removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
}; };
//initial values to alertContextType //initial values to alertContextType
const initialValue: alertContextType = { const initialValue: alertContextType = {
errorData: { title: "", list: [] }, errorData: { title: "", list: [] },
setErrorData: () => {}, setErrorData: () => {},
loading: true,
setLoading: () => {},
errorOpen: false, errorOpen: false,
setErrorOpen: () => {}, setErrorOpen: () => {},
noticeData: { title: "", link: "" }, noticeData: { title: "", link: "" },
@ -55,6 +59,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
list?: Array<string>; list?: Array<string>;
}>({ title: "", list: [] }); }>({ title: "", list: [] });
const [errorOpen, setErrorOpen] = useState(false); const [errorOpen, setErrorOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [noticeData, setNoticeDataState] = useState<{ const [noticeData, setNoticeDataState] = useState<{
title: string; title: string;
link?: string; link?: string;
@ -141,6 +146,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
removeFromNotificationList, removeFromNotificationList,
clearNotificationList, clearNotificationList,
notificationList, notificationList,
loading,
setLoading,
pushNotificationList, pushNotificationList,
setNotificationCenter, setNotificationCenter,
notificationCenter, notificationCenter,

View file

@ -16,17 +16,17 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<TooltipProvider> <TooltipProvider>
<ReactFlowProvider> <ReactFlowProvider>
<DarkProvider> <DarkProvider>
<TypesProvider> <AlertProvider>
<LocationProvider> <TypesProvider>
<AlertProvider> <LocationProvider>
<SSEProvider> <SSEProvider>
<TabsProvider> <TabsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider> <UndoRedoProvider>{children}</UndoRedoProvider>
</TabsProvider> </TabsProvider>
</SSEProvider> </SSEProvider>
</AlertProvider> </LocationProvider>
</LocationProvider> </TypesProvider>
</TypesProvider> </AlertProvider>
</DarkProvider> </DarkProvider>
</ReactFlowProvider> </ReactFlowProvider>
</TooltipProvider> </TooltipProvider>

View file

@ -9,6 +9,7 @@ import {
} from "react"; } from "react";
import { addEdge } from "reactflow"; import { addEdge } from "reactflow";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { skipNodeUpdate } from "../constants/constants";
import { import {
deleteFlowFromDatabase, deleteFlowFromDatabase,
downloadFlowsFromDatabase, downloadFlowsFromDatabase,
@ -163,6 +164,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function processFlowNodes(flow) { function processFlowNodes(flow) {
if (!flow.data || !flow.data.nodes) return; if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => { flow.data.nodes.forEach((node: NodeType) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type]; const template = templates[node.data.type];
if (!template) { if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` }); setErrorData({ title: `Unknown node type: ${node.data.type}` });
@ -506,6 +508,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updateNodes = (nodes, edges) => { const updateNodes = (nodes, edges) => {
nodes.forEach((node) => { nodes.forEach((node) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type]; const template = templates[node.data.type];
if (!template) { if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` }); setErrorData({ title: `Unknown node type: ${node.data.type}` });

View file

@ -1,8 +1,15 @@
import { createContext, ReactNode, useEffect, useState } from "react"; import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { Node } from "reactflow"; import { Node } from "reactflow";
import { getAll } from "../controllers/API"; import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api"; import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext"; import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
//context to share types adn functions from nodes to flow //context to share types adn functions from nodes to flow
@ -16,6 +23,8 @@ const initialValue: typesContextType = {
setTemplates: () => {}, setTemplates: () => {},
data: {}, data: {},
setData: () => {}, setData: () => {},
setFetchError: () => {},
fetchError: false,
}; };
export const typesContext = createContext<typesContextType>(initialValue); export const typesContext = createContext<typesContextType>(initialValue);
@ -25,13 +34,10 @@ export function TypesProvider({ children }: { children: ReactNode }) {
const [reactFlowInstance, setReactFlowInstance] = useState(null); const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [templates, setTemplates] = useState({}); const [templates, setTemplates] = useState({});
const [data, setData] = useState({}); const [data, setData] = useState({});
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
useEffect(() => { useEffect(() => {
let delay = 1000; // Start delay of 1 second
let intervalId = null;
let retryCount = 0; // Count of retry attempts
const maxRetryCount = 5; // Max retry attempts
// We will keep a flag to handle the case where the component is unmounted before the API call resolves. // We will keep a flag to handle the case where the component is unmounted before the API call resolves.
let isMounted = true; let isMounted = true;
@ -39,7 +45,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
try { try {
const result = await getAll(); const result = await getAll();
// Make sure to only update the state if the component is still mounted. // Make sure to only update the state if the component is still mounted.
if (isMounted) { if (isMounted && result?.status === 200) {
setLoading(false);
setData(result.data); setData(result.data);
setTemplates( setTemplates(
Object.keys(result.data).reduce((acc, curr) => { Object.keys(result.data).reduce((acc, curr) => {
@ -68,22 +75,15 @@ export function TypesProvider({ children }: { children: ReactNode }) {
}, {}) }, {})
); );
} }
// Clear the interval if successful.
clearInterval(intervalId);
} catch (error) { } catch (error) {
console.error("An error has occurred while fetching types."); console.error("An error has occurred while fetching types.");
await getHealth().catch((e) => {
setFetchError(true);
});
} }
} }
// Start the initial interval. getTypes();
intervalId = setInterval(getTypes, delay);
return () => {
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
clearInterval(intervalId);
// Indicate that the component has been unmounted.
isMounted = false;
};
}, []); }, []);
function deleteNode(idx: string) { function deleteNode(idx: string) {
@ -108,6 +108,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
templates, templates,
data, data,
setData, setData,
fetchError,
setFetchError,
}} }}
> >
{children} {children}

View file

@ -6,6 +6,7 @@ import { TabsContext } from "../../contexts/tabsContext";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent"; import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { getExamples } from "../../controllers/API"; import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow"; import { FlowType } from "../../types/flow";
export default function CommunityPage() { export default function CommunityPage() {
@ -42,61 +43,65 @@ export default function CommunityPage() {
handleExamples(); handleExamples();
}, []); }, []);
return ( return (
<div className="community-page-arrangement"> <>
<div className="community-page-nav-arrangement"> <Header />
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" /> <div className="community-page-arrangement">
Community Examples <div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Community Examples
</span>
<div className="community-page-nav-button">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
rel="noreferrer"
>
<Button variant="primary">
<IconComponent
name="GithubIcon"
className="main-page-nav-button"
/>
Add Your Example
</Button>
</a>
</div>
</div>
<span className="community-page-description-text">
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span> </span>
<div className="community-page-nav-button"> <div className="community-pages-flows-panel">
<a {!loadingExamples &&
href="https://github.com/logspace-ai/langflow_examples" examples.map((flow, idx) => (
target="_blank" <CardComponent
rel="noreferrer" key={idx}
> flow={flow}
<Button variant="primary"> id={flow.id}
<IconComponent button={
name="GithubIcon" <Button
className="main-page-nav-button" variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/> />
Add Your Example ))}
</Button>
</a>
</div> </div>
</div> </div>
<span className="community-page-description-text"> </>
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span>
<div className="community-pages-flows-panel">
{!loadingExamples &&
examples.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/>
))}
</div>
</div>
); );
} }

View file

@ -35,7 +35,13 @@ const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
}; };
export default function Page({ flow }: { flow: FlowType }) { export default function Page({
flow,
view,
}: {
flow: FlowType;
view?: boolean;
}) {
let { let {
updateFlow, updateFlow,
uploadFlow, uploadFlow,
@ -357,7 +363,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return ( return (
<div className="flex h-full overflow-hidden"> <div className="flex h-full overflow-hidden">
<ExtraSidebar /> {!view && <ExtraSidebar />}
{/* Main area */} {/* Main area */}
<main className="flex flex-1"> <main className="flex flex-1">
{/* Primary column */} {/* Primary column */}
@ -399,14 +405,21 @@ export default function Page({ flow }: { flow: FlowType }) {
className="theme-attribution" className="theme-attribution"
minZoom={0.01} minZoom={0.01}
maxZoom={8} maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
> >
<Background className="" /> <Background className="" />
<Controls {!view && (
className="bg-muted fill-foreground stroke-foreground text-primary <Controls
className="bg-muted fill-foreground stroke-foreground text-primary
[&>button]:border-b-border hover:[&>button]:bg-border" [&>button]:border-b-border hover:[&>button]:bg-border"
></Controls> ></Controls>
)}
</ReactFlow> </ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} /> {!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
)}
</div> </div>
) : ( ) : (
<></> <></>

View file

@ -1,5 +1,6 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Header from "../../components/headerComponent";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API"; import { getVersion } from "../../controllers/API";
import Page from "./components/PageComponent"; import Page from "./components/PageComponent";
@ -22,20 +23,23 @@ export default function FlowPage() {
}, []); }, []);
return ( return (
<div className="flow-page-positioning"> <>
{flows.length > 0 && <Header />
tabId !== "" && <div className="flow-page-positioning">
flows.findIndex((flow) => flow.id === tabId) !== -1 && ( {flows.length > 0 &&
<Page flow={flows.find((flow) => flow.id === tabId)} /> tabId !== "" &&
)} flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<a <Page flow={flows.find((flow) => flow.id === tabId)} />
target={"_blank"} )}
href="https://logspace.ai/" <a
className="logspace-page-icon" target={"_blank"}
> href="https://logspace.ai/"
{version && <div className="mt-1">⛓️ Langflow v{version}</div>} className="logspace-page-icon"
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div> >
</a> {version && <div className="mt-1">⛓️ Langflow v{version}</div>}
</div> <div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
</>
); );
} }

View file

@ -2,6 +2,7 @@ import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent"; import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
@ -17,74 +18,77 @@ export default function HomePage() {
// Personal flows display // Personal flows display
return ( return (
<div className="main-page-panel"> <>
<div className="main-page-nav-arrangement"> <Header />
<span className="main-page-nav-title"> <div className="main-page-panel">
<IconComponent name="Home" className="w-6" /> <div className="main-page-nav-arrangement">
{USER_PROJECTS_HEADER} <span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span> </span>
<div className="button-div-style"> <div className="main-page-flows-display">
<Button {flows.map((flow, idx) => (
variant="primary" <CardComponent
onClick={() => { key={idx}
downloadFlows(); flow={flow}
}} id={flow.id}
> button={
<IconComponent name="Download" className="main-page-nav-button" /> <Link to={"/flow/" + flow.id}>
Download Collection <Button
</Button> variant="outline"
<Button size="sm"
variant="primary" className="whitespace-nowrap "
onClick={() => { >
uploadFlows(); <IconComponent
}} name="ExternalLink"
> className="main-page-nav-button"
<IconComponent name="Upload" className="main-page-nav-button" /> />
Upload Collection Edit Flow
</Button> </Button>
<Button </Link>
variant="primary" }
onClick={() => { onDelete={() => {
addFlow(null, true).then((id) => { removeFlow(flow.id);
navigate("/flow/" + id); }}
}); />
}} ))}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div> </div>
</div> </div>
<span className="main-page-description-text"> </>
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
); );
} }

View file

@ -0,0 +1,33 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const { flows, tabId, setTabId } = useContext(TabsContext);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id);
}, [id]);
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {
getVersion().then((data) => {
setVersion(data.version);
});
}, []);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page view flow={flows.find((flow) => flow.id === tabId)} />
)}
</div>
);
}

View file

@ -2,6 +2,7 @@ import { Route, Routes } from "react-router-dom";
import CommunityPage from "./pages/CommunityPage"; import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage"; import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage"; import HomePage from "./pages/MainPage";
import ViewPage from "./pages/ViewPage";
const Router = () => { const Router = () => {
return ( return (
@ -10,6 +11,7 @@ const Router = () => {
<Route path="/community" element={<CommunityPage />} /> <Route path="/community" element={<CommunityPage />} />
<Route path="/flow/:id/"> <Route path="/flow/:id/">
<Route path="" element={<FlowPage />} /> <Route path="" element={<FlowPage />} />
<Route path="view" element={<ViewPage />} />
</Route> </Route>
<Route path="*" element={<HomePage />} /> <Route path="*" element={<HomePage />} />
</Routes> </Routes>

View file

@ -192,6 +192,10 @@
@apply flex w-full; @apply flex w-full;
} }
.loading-page-panel {
@apply h-full w-full flex justify-center items-center bg-muted;
}
.main-page-panel { .main-page-panel {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16; @apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
} }

View file

@ -171,3 +171,11 @@ export type IconComponentProps = {
export interface languageMap { export interface languageMap {
[key: string]: string | undefined; [key: string]: string | undefined;
} }
export type fetchErrorComponentType = {
message: string;
description: string;
};
export type LoadingComponentProps = {
remSize: number;
};

View file

@ -15,4 +15,6 @@ export type typesContextType = {
setTemplates: (newState: {}) => void; setTemplates: (newState: {}) => void;
data: typeof data; data: typeof data;
setData: (newState: {}) => void; setData: (newState: {}) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
}; };

View file

@ -56,6 +56,7 @@ import {
TerminalSquare, TerminalSquare,
Trash2, Trash2,
Undo, Undo,
Unplug,
Upload, Upload,
Users2, Users2,
Variable, Variable,
@ -275,4 +276,5 @@ export const nodeIconsLucide = {
Upload, Upload,
MessageSquare, MessageSquare,
MoreHorizontal, MoreHorizontal,
Unplug,
}; };

View file

@ -1,4 +1,6 @@
import json import json
from langflow.database.models.base import orjson_dumps
import orjson
from langflow.graph import Graph from langflow.graph import Graph
import pytest import pytest
@ -63,9 +65,9 @@ def test_cache_size_limit(basic_data_graph):
nodes = modified_data_graph["nodes"] nodes = modified_data_graph["nodes"]
node_id = nodes[0]["id"] node_id = nodes[0]["id"]
# Now we replace all instances ode node_id with a new id in the json # Now we replace all instances ode node_id with a new id in the json
json_string = json.dumps(modified_data_graph) json_string = orjson_dumps(modified_data_graph)
modified_json_string = json_string.replace(node_id, f"{node_id}_{i}") modified_json_string = json_string.replace(node_id, f"{node_id}_{i}")
modified_data_graph_new_id = json.loads(modified_json_string) modified_data_graph_new_id = orjson.loads(modified_json_string)
build_langchain_object_with_caching(modified_data_graph_new_id) build_langchain_object_with_caching(modified_data_graph_new_id)
assert len(build_langchain_object_with_caching.cache) == 10 assert len(build_langchain_object_with_caching.cache) == 10

View file

@ -1,11 +1,12 @@
import json from fastapi.encoders import jsonable_encoder
from langflow.database.models.base import orjson_dumps
import orjson
import pytest import pytest
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from fastapi.encoders import jsonable_encoder
from langflow.api.v1.schemas import FlowListCreate from langflow.api.v1.schemas import FlowListCreate
from langflow.database.models.flow import Flow, FlowCreate, FlowUpdate from langflow.database.models.flow import Flow, FlowCreate, FlowUpdate
@ -23,7 +24,7 @@ def json_style():
# color: str = Field(index=True) # color: str = Field(index=True)
# emoji: str = Field(index=False) # emoji: str = Field(index=False)
# flow_id: UUID = Field(default=None, foreign_key="flow.id") # flow_id: UUID = Field(default=None, foreign_key="flow.id")
return json.dumps( return orjson_dumps(
{ {
"color": "red", "color": "red",
"emoji": "👍", "emoji": "👍",
@ -32,7 +33,7 @@ def json_style():
def test_create_flow(client: TestClient, json_flow: str): def test_create_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data) flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict()) response = client.post("api/v1/flows/", json=flow.dict())
@ -48,7 +49,7 @@ def test_create_flow(client: TestClient, json_flow: str):
def test_read_flows(client: TestClient, json_flow: str): def test_read_flows(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data) flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict()) response = client.post("api/v1/flows/", json=flow.dict())
@ -89,7 +90,7 @@ def test_read_flows(client: TestClient, json_flow: str):
def test_read_flow(client: TestClient, json_flow: str): def test_read_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data) flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict()) response = client.post("api/v1/flows/", json=flow.dict())
@ -115,7 +116,7 @@ def test_read_flow(client: TestClient, json_flow: str):
def test_update_flow(client: TestClient, json_flow: str): def test_update_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data) flow = FlowCreate(name="Test Flow", description="description", data=data)
@ -136,7 +137,7 @@ def test_update_flow(client: TestClient, json_flow: str):
def test_delete_flow(client: TestClient, json_flow: str): def test_delete_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data) flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict()) response = client.post("api/v1/flows/", json=flow.dict())
@ -147,7 +148,7 @@ def test_delete_flow(client: TestClient, json_flow: str):
def test_create_flows(client: TestClient, session: Session, json_flow: str): def test_create_flows(client: TestClient, session: Session, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
# Create test data # Create test data
flow_list = FlowListCreate( flow_list = FlowListCreate(
@ -172,7 +173,7 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str):
def test_upload_file(client: TestClient, session: Session, json_flow: str): def test_upload_file(client: TestClient, session: Session, json_flow: str):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
# Create test data # Create test data
flow_list = FlowListCreate( flow_list = FlowListCreate(
@ -181,7 +182,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str):
FlowCreate(name="Flow 2", description="description", data=data), FlowCreate(name="Flow 2", description="description", data=data),
] ]
) )
file_contents = json.dumps(flow_list.dict()) file_contents = orjson_dumps(flow_list.dict())
response = client.post( response = client.post(
"api/v1/flows/upload/", "api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")}, files={"file": ("examples.json", file_contents, "application/json")},
@ -200,7 +201,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str):
def test_download_file(client: TestClient, session: Session, json_flow): def test_download_file(client: TestClient, session: Session, json_flow):
flow = json.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
# Create test data # Create test data
flow_list = FlowListCreate( flow_list = FlowListCreate(
@ -241,7 +242,7 @@ def test_get_nonexistent_flow(client: TestClient):
def test_update_flow_idempotency(client: TestClient, json_flow: str): def test_update_flow_idempotency(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data) flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.dict()) response = client.post("api/v1/flows/", json=flow_data.dict())
@ -253,7 +254,7 @@ def test_update_flow_idempotency(client: TestClient, json_flow: str):
def test_update_nonexistent_flow(client: TestClient, json_flow: str): def test_update_nonexistent_flow(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
uuid = uuid4() uuid = uuid4()
updated_flow = FlowCreate( updated_flow = FlowCreate(

View file

@ -1,13 +1,12 @@
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from langflow.settings import settings
def test_llms_settings(client: TestClient): # def test_llms_settings(client: TestClient):
response = client.get("api/v1/all") # response = client.get("api/v1/all")
assert response.status_code == 200 # assert response.status_code == 200
json_response = response.json() # json_response = response.json()
llms = json_response["llms"] # llms = json_response["llms"]
assert set(llms.keys()) == set(settings.LLMS) # assert set(llms.keys()) == set(settings.LLMS)
# def test_hugging_face_hub(client: TestClient): # def test_hugging_face_hub(client: TestClient):

View file

@ -1,5 +1,4 @@
import json import json
import pytest import pytest
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langflow.processing.process import load_flow_from_json from langflow.processing.process import load_flow_from_json