diff --git a/.env.example b/.env.example
index 852eaa2..af936de 100644
--- a/.env.example
+++ b/.env.example
@@ -16,5 +16,5 @@ OLLAMA_BASE_URL=http://localhost:11434
VECTOR_DB_PATH=./data/vector_db
# Default Models
-DEFAULT_OLLAMA_MODEL=llama2
+DEFAULT_OLLAMA_MODEL=llama3.2:1b
DEFAULT_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
diff --git a/.gitignore b/.gitignore
index 565e57e..bcb3c89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,26 @@ build/
*.egg-info/
# Logs
+logs
*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..b990a96
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socioeconomic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+contact@coderai.co.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..fcf7eb1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,69 @@
+# Contributing to CoderAI
+
+This guide is intended to help you get started contributing to CoderAI. As an open-source AI integration platform, we welcome contributions in the form of new features, improved infrastructure, better documentation, or bug fixes.
+
+To contribute to this project, please follow the [fork and pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
+
+## Reporting Bugs or Suggesting Improvements
+
+Our [GitHub Issues](https://github.com/yourusername/coderAI/issues) page is where we track bugs, improvements, and feature requests. When creating an issue, please:
+
+- **Describe your issue thoroughly:** Provide as many details as possible about what's going wrong. _How_ is it failing? Is there an error message? "It doesn't work" isn't helpful for tracking down problems.
+
+- **Include relevant code:** Share the code that's causing the issue, but only include the relevant parts. This makes it easier for us to reproduce and fix the problem.
+
+- **Format long code blocks:** When sharing long blocks of code or logs, wrap them in `` and ` ` tags. This collapses the content making the issue easier to read.
+
+## Development Setup
+
+1. Fork the repository
+2. Clone your fork:
+ ```bash
+ git clone https://github.com/yourusername/coderAI.git
+ cd coderAI
+ ```
+3. Install dependencies:
+ ```bash
+ pip install -r requirements.txt
+ ```
+4. Create a new branch for your feature:
+ ```bash
+ git checkout -b feature-name
+ ```
+
+## Code Style Guidelines
+
+- Follow PEP 8 style guidelines for Python code
+- Use meaningful variable and function names
+- Add comments for complex logic
+- Write clear commit messages following conventional commits format
+- Include docstrings for functions and classes
+- Write tests for new features
+
+## Opening a Pull Request
+
+Before submitting a pull request:
+
+1. Test your changes thoroughly
+2. Update documentation if needed
+3. Ensure all tests pass
+4. Rebase your branch on the latest main branch
+
+When creating the pull request:
+
+- Use a clear title following semantic commit conventions
+ - Example: `feat: add new AI model integration`
+ - Example: `fix: resolve issue with GitHub authentication`
+- Provide a detailed description of your changes
+- Link any related issues
+- Ensure CI checks pass
+
+## Questions and Discussions
+
+If you need help or want to discuss ideas:
+
+- Open a [Discussion](https://github.com/yourusername/coderAI/discussions) for general questions
+- Join our community channels (if available)
+- Check existing issues and discussions before creating new ones
+
+We aim to review all contributions promptly and provide constructive feedback. Thank you for helping improve CoderAI!
\ No newline at end of file
diff --git a/Helpmate-AI/.eslintrc.cjs b/Helpmate-AI/.eslintrc.cjs
new file mode 100644
index 0000000..3e212e1
--- /dev/null
+++ b/Helpmate-AI/.eslintrc.cjs
@@ -0,0 +1,21 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:react/recommended',
+ 'plugin:react/jsx-runtime',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+ settings: { react: { version: '18.2' } },
+ plugins: ['react-refresh'],
+ rules: {
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+}
diff --git a/Helpmate-AI/.gitignore b/Helpmate-AI/.gitignore
new file mode 100644
index 0000000..57e09c4
--- /dev/null
+++ b/Helpmate-AI/.gitignore
@@ -0,0 +1,25 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+.env
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
\ No newline at end of file
diff --git a/Helpmate-AI/README.md b/Helpmate-AI/README.md
new file mode 100644
index 0000000..3035956
--- /dev/null
+++ b/Helpmate-AI/README.md
@@ -0,0 +1,100 @@
+
+
+ 
+ 
+ 
+[](https://twitter.com/intent/follow?screen_name=warrior_aashuu)
+
+
+
🤖 Introducing to Helpmate AI 🤖
+
+
HELPMATE AI
+
✦ Let's take help with your AI mate! ✦
+
Click here to vote our APP
+
+
+ ` Helpmate is a AI ChatBot web app where you can ask any question and you will get the answer. `
+
+
+ ` Features 🌟 `
+
+
+```
+ 🤖 Conversational AI Interface
+ 📝 Text Summarization
+ 🧩 Question Answering
+ 📲 Responsive Layout
+ 🎨 Sleek Design
+ 📈 Scalability and Performance
+```
+
+
+` Technologies Used 💻 `
+
+          
+
+
+ Instructions for Testing Helpmate AI
+
+
+## Step 1: Open the App
+Click on the provided link to access the app in your browser.
+User Interface: You'll be welcomed by a clean, minimalistic interface designed for ease of use. In the text box provided, type in any question you want to ask Helpmate AI.
+Click the "Generate Answer" button to submit your question.
+
+## Step 2: Receive Answer
+Helpmate AI will process your question using Google Gemini API and provide you with a comprehensive answer. The answer will be displayed as text in the response section.
+
+## Step 3: Performance and Response Accuracy
+Assess the speed and accuracy of responses provided by the app
+
+## Step 4: Testing Edge Cases and Scenarios
+### Scenario 1: Basic Questions
+Ask questions that require factual information, such as:
+What is the capital of France?
+Who is the president of the United States?
+What is the chemical symbol for gold?
+
+### Scenario 2: Complex Questions
+Ask questions that require a more comprehensive understanding, such as:
+What are the ethical implications of artificial intelligence?
+How can I improve my writing skills?
+What is the best way to invest in cryptocurrency?
+
+### Scenario 3: Conversational Questions
+Engage in a conversation with Helpmate AI by asking follow-on questions, providing additional information, or expressing your opinions.
+Example: Ask a question about the history of the internet.
+Follow up with a question about the impact of the internet on society.
+Share your thoughts on the future of the internet.
+
+### Scenario 4: Versatility Across Domains
+Ask questions spanning different fields such as science, technology, history, and entertainment. Suggest specific cases to challenge the app’s capabilities.
+
+`Thank you for taking the time to test Helpmate AI.`
+
+
+` © License ✔️ `
+
+[](https://npmjs.org/package/badge-maker) [](https://npmjs.org/package/badge-maker) [](https://opensource.org/licenses/MIT)
+
+This project is licensed under the NPM or MIT - see the [LICENSE](LICENSE) file for details.
+
+` Getting Started 🚀 Setup Procedure ⚙️ `
+
+To run this web application locally, first get google gemini API key from: [api-key](https://aistudio.google.com/app/apikey)
+
+`1. Clone this repository to your local machine`
+`2. Open App.jsx and then open terminal`
+`3. Now install npm`
+`4. then type the command (npm run dev)`
+
+`How can you contribute in this projects? 🫱🏻🫲🏼`
+
+If you have a good knowledge in Tailwind CSS or React JS and want to contribute in this project just forked this repository and the improve the 𝐔𝐈 of Helpmate and then feel free to open an issue or submit a pull request. I reviewed your changes update and then merge your pull request. Please make sure to follow the existing code style and guidelines.
+
+`Don't forget to give star this repository ⭐`
+
+
+`👍🏻 All Set! 💌`
+
+
diff --git a/Helpmate-AI/index.html b/Helpmate-AI/index.html
new file mode 100644
index 0000000..b8c9aa9
--- /dev/null
+++ b/Helpmate-AI/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+ Helpmate AI
+
+
+
+
+
+
diff --git a/Helpmate-AI/package-lock.json b/Helpmate-AI/package-lock.json
new file mode 100644
index 0000000..385e509
--- /dev/null
+++ b/Helpmate-AI/package-lock.json
@@ -0,0 +1,6684 @@
+{
+ "name": "helpmate-ai",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "helpmate-ai",
+ "version": "0.0.0",
+ "dependencies": {
+ "axios": "^1.8.2",
+ "dotenv": "^16.4.7",
+ "openai": "^4.86.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.3.0",
+ "react-markdown": "^9.0.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^8.57.0",
+ "eslint-plugin-react": "^7.34.3",
+ "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-refresh": "^0.4.7",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.7",
+ "vite": "^5.3.4"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
+ "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.24.7",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz",
+ "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
+ "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.25.0",
+ "@babel/helper-compilation-targets": "^7.25.2",
+ "@babel/helper-module-transforms": "^7.25.2",
+ "@babel/helpers": "^7.25.0",
+ "@babel/parser": "^7.25.0",
+ "@babel/template": "^7.25.0",
+ "@babel/traverse": "^7.25.2",
+ "@babel/types": "^7.25.2",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
+ "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.25.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
+ "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.25.2",
+ "@babel/helper-validator-option": "^7.24.8",
+ "browserslist": "^4.23.1",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
+ "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
+ "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.24.7",
+ "@babel/helper-simple-access": "^7.24.7",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "@babel/traverse": "^7.25.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
+ "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
+ "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
+ "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+ "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
+ "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
+ "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.25.0",
+ "@babel/types": "^7.25.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
+ "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.25.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
+ "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.25.2"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
+ "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz",
+ "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
+ "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/parser": "^7.25.0",
+ "@babel/types": "^7.25.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.25.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
+ "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.25.0",
+ "@babel/parser": "^7.25.3",
+ "@babel/template": "^7.25.0",
+ "@babel/types": "^7.25.2",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
+ "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.24.8",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
+ "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
+ "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
+ "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
+ "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
+ "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
+ "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
+ "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
+ "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
+ "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
+ "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
+ "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
+ "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
+ "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
+ "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
+ "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
+ "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
+ "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.34",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.19.79",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.79.tgz",
+ "integrity": "sha512-90K8Oayimbctc5zTPHPfZloc/lGVs7f3phUAAMcTgEPtg8kKquGZDERC8K4vkBYkQQh48msiYUslYtxTWvqcAg==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.12",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
+ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
+ "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
+ "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz",
+ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ=="
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
+ "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-source": "^7.24.1",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.12.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+ "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
+ "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
+ "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
+ "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.2.1",
+ "get-intrinsic": "^1.2.3",
+ "is-array-buffer": "^3.0.4",
+ "is-shared-array-buffer": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.20",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
+ "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
+ "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001646",
+ "electron-to-chromium": "^1.5.4",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001702",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz",
+ "integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz",
+ "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz",
+ "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz",
+ "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+ "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz",
+ "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/es-abstract": {
+ "version": "1.23.3",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
+ "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "arraybuffer.prototype.slice": "^1.0.3",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "data-view-buffer": "^1.0.1",
+ "data-view-byte-length": "^1.0.1",
+ "data-view-byte-offset": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-set-tostringtag": "^2.0.3",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.6",
+ "get-intrinsic": "^1.2.4",
+ "get-symbol-description": "^1.0.2",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.3",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.0.7",
+ "is-array-buffer": "^3.0.4",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.1",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.3",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.13",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.13.1",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.5",
+ "regexp.prototype.flags": "^1.5.2",
+ "safe-array-concat": "^1.1.2",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.trim": "^1.2.9",
+ "string.prototype.trimend": "^1.0.8",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.2",
+ "typed-array-byte-length": "^1.0.1",
+ "typed-array-byte-offset": "^1.0.2",
+ "typed-array-length": "^1.0.6",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.15"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz",
+ "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.7",
+ "iterator.prototype": "^1.1.2",
+ "safe-array-concat": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
+ "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+ "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.35.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz",
+ "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.2",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.0.19",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.8",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.0",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.11",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.9",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz",
+ "integrity": "sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=7"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
+ "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
+ },
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz",
+ "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "functions-have-names": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+ "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
+ "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-object": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz",
+ "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
+ "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g=="
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.0",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
+ "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
+ "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==",
+ "dev": true,
+ "dependencies": {
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
+ "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
+ "dev": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.6",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "dev": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
+ "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
+ "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz",
+ "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-remove-position": "^5.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
+ "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
+ "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
+ "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
+ "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
+ "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
+ "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
+ "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
+ "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
+ "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
+ "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
+ "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
+ "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
+ "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
+ "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
+ "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
+ "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
+ "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
+ "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz",
+ "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
+ "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz",
+ "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz",
+ "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
+ "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
+ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+ "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
+ "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
+ "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
+ "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/openai": {
+ "version": "4.86.2",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.86.2.tgz",
+ "integrity": "sha512-nvYeFjmjdSu6/msld+22JoUlCICNk/lUFpSMmc6KNhpeNLpqL70TqbD/8Vura/tFmYqHKW0trcjgPwUpKSPwaA==",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "ws": "^8.18.0",
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "ws": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
+ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
+ "dev": true
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
+ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.41",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+ "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.1",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-import/node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-load-config/node_modules/lilconfig": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
+ "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
+ "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
+ "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true
+ },
+ "node_modules/react-markdown": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
+ "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
+ "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.1",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "which-builtin-type": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
+ "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "set-function-name": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz",
+ "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
+ "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.20.0",
+ "@rollup/rollup-android-arm64": "4.20.0",
+ "@rollup/rollup-darwin-arm64": "4.20.0",
+ "@rollup/rollup-darwin-x64": "4.20.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.20.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.20.0",
+ "@rollup/rollup-linux-arm64-musl": "4.20.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.20.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.20.0",
+ "@rollup/rollup-linux-x64-gnu": "4.20.0",
+ "@rollup/rollup-linux-x64-musl": "4.20.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.20.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.20.0",
+ "@rollup/rollup-win32-x64-msvc": "4.20.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
+ "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4",
+ "has-symbols": "^1.0.3",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
+ "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.1.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz",
+ "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.7",
+ "regexp.prototype.flags": "^1.5.2",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
+ "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz",
+ "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz",
+ "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==",
+ "dependencies": {
+ "inline-style-parser": "0.2.3"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz",
+ "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==",
+ "dev": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
+ "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
+ "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz",
+ "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-proto": "^1.0.3",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
+ "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/vfile": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz",
+ "integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
+ "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.39",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz",
+ "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==",
+ "dev": true,
+ "dependencies": {
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.0.5",
+ "is-finalizationregistry": "^1.0.2",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.1.4",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.15"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
+ "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yaml": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
+ "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
+ "dev": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/Helpmate-AI/package.json b/Helpmate-AI/package.json
new file mode 100644
index 0000000..0fc5a04
--- /dev/null
+++ b/Helpmate-AI/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "helpmate-ai",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.8.2",
+ "dotenv": "^16.4.7",
+ "openai": "^4.86.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.3.0",
+ "react-markdown": "^9.0.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^8.57.0",
+ "eslint-plugin-react": "^7.34.3",
+ "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-refresh": "^0.4.7",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.7",
+ "vite": "^5.3.4"
+ }
+}
diff --git a/Helpmate-AI/postcss.config.js b/Helpmate-AI/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/Helpmate-AI/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/Helpmate-AI/public/Helpmate Mockup.png b/Helpmate-AI/public/Helpmate Mockup.png
new file mode 100644
index 0000000..ccabd31
Binary files /dev/null and b/Helpmate-AI/public/Helpmate Mockup.png differ
diff --git a/Helpmate-AI/public/Helpmate Preview.jpg b/Helpmate-AI/public/Helpmate Preview.jpg
new file mode 100644
index 0000000..405aa2f
Binary files /dev/null and b/Helpmate-AI/public/Helpmate Preview.jpg differ
diff --git a/Helpmate-AI/public/Helpmate-AI-icon.png b/Helpmate-AI/public/Helpmate-AI-icon.png
new file mode 100644
index 0000000..c2a2dff
Binary files /dev/null and b/Helpmate-AI/public/Helpmate-AI-icon.png differ
diff --git a/Helpmate-AI/public/Helpmate-AI.png b/Helpmate-AI/public/Helpmate-AI.png
new file mode 100644
index 0000000..5c3746f
Binary files /dev/null and b/Helpmate-AI/public/Helpmate-AI.png differ
diff --git a/Helpmate-AI/public/technologist.png b/Helpmate-AI/public/technologist.png
new file mode 100644
index 0000000..472481c
Binary files /dev/null and b/Helpmate-AI/public/technologist.png differ
diff --git a/Helpmate-AI/public/vite.svg b/Helpmate-AI/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/Helpmate-AI/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Helpmate-AI/src/App.css b/Helpmate-AI/src/App.css
new file mode 100644
index 0000000..02b3838
--- /dev/null
+++ b/Helpmate-AI/src/App.css
@@ -0,0 +1,14 @@
+@keyframes fadeInSlide {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.answer-container {
+ animation: fadeInSlide 0.5s ease forwards; /* Fade and slide in effect */
+}
\ No newline at end of file
diff --git a/Helpmate-AI/src/App.jsx b/Helpmate-AI/src/App.jsx
new file mode 100644
index 0000000..9493a5b
--- /dev/null
+++ b/Helpmate-AI/src/App.jsx
@@ -0,0 +1,251 @@
+import { useState, useEffect } from "react";
+import "./App.css";
+import axios from "axios";
+import ReactMarkdown from "react-markdown";
+import Footer from "./Footer";
+import ShareButtons from "./components/ShareButtons";
+import CodeAnalysis from "./components/CodeAnalysis";
+import { FaMicrophone, FaPaperPlane, FaVolumeUp } from "react-icons/fa";
+
+const cache = new Map();
+
+function App() {
+ const [question, setQuestion] = useState("");
+ const [chatHistory, setChatHistory] = useState([]);
+ const [generatingAnswer, setGeneratingAnswer] = useState(false);
+ const [isListening, setIsListening] = useState(false);
+ const [recognition, setRecognition] = useState(null);
+ const [isSpeaking, setIsSpeaking] = useState(false);
+ const [mode, setMode] = useState("chat"); // "chat" or "code"
+ const [isEmbedded, setIsEmbedded] = useState(false);
+
+ useEffect(() => {
+ // Check if running in an iframe
+ setIsEmbedded(window.self !== window.top);
+
+ if ("webkitSpeechRecognition" in window) {
+ const recognition = new window.webkitSpeechRecognition();
+ recognition.continuous = false;
+ recognition.interimResults = false;
+ recognition.lang = "en-US";
+
+ recognition.onresult = (event) => {
+ const transcript = event.results[0][0].transcript;
+ setQuestion(transcript);
+ setIsListening(false);
+ };
+
+ recognition.onerror = (event) => {
+ console.error("Speech recognition error:", event.error);
+ setIsListening(false);
+ };
+
+ recognition.onend = () => {
+ setIsListening(false);
+ };
+
+ setRecognition(recognition);
+ }
+ }, []);
+
+ const toggleListening = () => {
+ if (isListening) {
+ recognition.stop();
+ setIsListening(false);
+ } else {
+ recognition.start();
+ setIsListening(true);
+ }
+ };
+
+ const toggleSpeaking = (text) => {
+ if (isSpeaking) {
+ window.speechSynthesis.cancel();
+ setIsSpeaking(false);
+ } else {
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.lang = "en-US";
+ utterance.onend = () => setIsSpeaking(false);
+ setIsSpeaking(true);
+ window.speechSynthesis.speak(utterance);
+ }
+ };
+
+ async function generateAnswer(e) {
+ e.preventDefault();
+ if (!question.trim()) return;
+
+ if (generatingAnswer) {
+ console.log("Already generating an answer. Please wait...");
+ return;
+ }
+
+ setGeneratingAnswer(true);
+
+ if (cache.has(question)) {
+ console.log("Fetching from cache...");
+ setChatHistory((prevChat) => [
+ ...prevChat,
+ { type: "question", text: question },
+ { type: "answer", text: cache.get(question) },
+ ]);
+ setQuestion("");
+ setGeneratingAnswer(false);
+ return;
+ }
+
+ const apiKey = import.meta.env.VITE_API_GENERATIVE_LANGUAGE_CLIENT;
+
+ let attempts = 0;
+ const maxAttempts = 3;
+ let delay = 2000;
+
+ while (attempts < maxAttempts) {
+ try {
+ const response = await axios({
+ url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
+ method: "post",
+ headers: { 'Content-Type': 'application/json' },
+ data: { contents: [{ parts: [{ text: question }] }] },
+ });
+
+ if (response.data?.candidates?.length > 0) {
+ const fullAnswer = response.data.candidates[0].content.parts[0].text;
+
+ cache.set(question, fullAnswer);
+
+ setChatHistory((prevChat) => [
+ ...prevChat,
+ { type: "question", text: question },
+ { type: "answer", text: fullAnswer },
+ ]);
+
+ setQuestion("");
+ } else {
+ throw new Error("Invalid API response structure");
+ }
+ break;
+ } catch (error) {
+ console.error("API Error:", error);
+
+ if (error.response?.status === 429) {
+ if (attempts < maxAttempts - 1) {
+ console.warn(`Rate limit hit. Retrying in ${delay / 1000} seconds...`);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ delay *= 2;
+ attempts++;
+ } else {
+ setChatHistory((prevChat) => [
+ ...prevChat,
+ { type: "answer", text: "Rate limit exceeded. Please try again later." },
+ ]);
+ break;
+ }
+ } else {
+ setChatHistory((prevChat) => [
+ ...prevChat,
+ { type: "answer", text: "Sorry, something went wrong. Please try again!" },
+ ]);
+ break;
+ }
+ }
+ }
+
+ setGeneratingAnswer(false);
+ }
+
+ return (
+
+
+
+
Helpmate AI
+
+ setMode("chat")}
+ className={`px-3 py-1 rounded-lg ${mode === "chat" ? "bg-blue-600" : "bg-gray-700"} text-white transition-colors text-sm`}
+ >
+ Chat
+
+ setMode("code")}
+ className={`px-3 py-1 rounded-lg ${mode === "code" ? "bg-blue-600" : "bg-gray-700"} text-white transition-colors text-sm`}
+ >
+ Code Analysis
+
+
+
+
+
+ {mode === "chat" ? (
+
+ {chatHistory.map((chat, index) => (
+
+
{chat.text}
+ {chat.type === "answer" && (
+
+ toggleSpeaking(chat.text)} className="flex items-center text-gray-300 mt-1 mr-1">
+
+
+
+
+ )}
+
+ ))}
+ {generatingAnswer && (
+
+ )}
+
+ ) : (
+
+ )}
+
+ {mode === "chat" && (
+
+ )}
+
+ );
+}
+
+export default App;
\ No newline at end of file
diff --git a/Helpmate-AI/src/Footer.jsx b/Helpmate-AI/src/Footer.jsx
new file mode 100644
index 0000000..d094f34
--- /dev/null
+++ b/Helpmate-AI/src/Footer.jsx
@@ -0,0 +1,32 @@
+import { FaGithub, FaRocket } from "react-icons/fa";
+
+const Footer = () => {
+ return (
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
diff --git a/Helpmate-AI/src/assets/react.svg b/Helpmate-AI/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/Helpmate-AI/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Helpmate-AI/src/components/CodeAnalysis.jsx b/Helpmate-AI/src/components/CodeAnalysis.jsx
new file mode 100644
index 0000000..dc6d691
--- /dev/null
+++ b/Helpmate-AI/src/components/CodeAnalysis.jsx
@@ -0,0 +1,65 @@
+import { useState } from 'react';
+import coderAIService from '../services/coderAIService';
+import ReactMarkdown from 'react-markdown';
+
+const CodeAnalysis = () => {
+ const [code, setCode] = useState('');
+ const [analysis, setAnalysis] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleAnalyze = async () => {
+ if (!code.trim()) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const result = await coderAIService.analyzeCode(code);
+ setAnalysis(result);
+ } catch (err) {
+ setError('Failed to analyze code. Please try again.');
+ console.error('Analysis error:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
Code Analysis
+
+ setCode(e.target.value)}
+ placeholder="Paste your code here..."
+ />
+
+
+ {loading ? 'Analyzing...' : 'Analyze Code'}
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {analysis && (
+
+
Analysis Results
+
+ {analysis.result}
+
+
+ )}
+
+ );
+};
+
+export default CodeAnalysis;
\ No newline at end of file
diff --git a/Helpmate-AI/src/components/ShareButtons.jsx b/Helpmate-AI/src/components/ShareButtons.jsx
new file mode 100644
index 0000000..46550fb
--- /dev/null
+++ b/Helpmate-AI/src/components/ShareButtons.jsx
@@ -0,0 +1,94 @@
+import { useState } from "react";
+import PropTypes from 'prop-types';
+
+const ShareButtons = ({ answer }) => {
+ const [copySuccess, setCopySuccess] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(answer);
+ setCopySuccess(true);
+ setTimeout(() => setCopySuccess(false), 2000);
+ } catch (err) {
+ console.error("Failed to copy text: ", err);
+ }
+ };
+
+ const handleShare = async () => {
+ if (navigator.share) {
+ try {
+ await navigator.share({
+ title: "Helpmate AI Answer",
+ text: answer, // Share the answer text instead of the URL
+ });
+ } catch (err) {
+ if (err.name !== "AbortError") {
+ console.error("Share failed:", err);
+ // Fallback to copy if sharing fails
+ handleCopy();
+ alert(
+ "Sharing failed. The answer has been copied to your clipboard instead."
+ );
+ }
+ }
+ } else {
+ // Fallback for browsers that don't support Web Share API
+ handleCopy();
+ alert(
+ "Sharing is not supported on this browser. The answer has been copied to your clipboard instead."
+ );
+ }
+ };
+
+ return (
+
+
+
+
+
+ {copySuccess ? "Copied!" : "Copy"}
+
+
+
+
+
+
+ Share
+
+
+ );
+};
+
+ShareButtons.propTypes = {
+ answer: PropTypes.string.isRequired,
+};
+
+export default ShareButtons;
\ No newline at end of file
diff --git a/Helpmate-AI/src/index.css b/Helpmate-AI/src/index.css
new file mode 100644
index 0000000..5fa2e88
--- /dev/null
+++ b/Helpmate-AI/src/index.css
@@ -0,0 +1,100 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* App.css */
+
+body, html {
+ margin: 0;
+ padding: 0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background-color: #111827; /* Dark theme background */
+ color: #ffffff;
+ }
+
+ #root {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ }
+
+ footer {
+ margin-top: auto;
+ }
+
+
+ @keyframes send-animation {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 25% {
+ transform: translate(10px, -10px); /* Adjust values to your needs */
+ opacity: 0;
+ }
+ 50%{
+ transform: translate(-10px, 10px);
+ opacity: 0;
+ }
+ 100% {
+ transform: translate(0, 0); /* Returns from bottom left */
+ opacity: 1;
+ }
+ }
+
+ .send-animation {
+ animation: send-animation 1.5s ease forwards;
+ }
+
+ @keyframes flicker {
+ 0%, 100% {
+ opacity: 1; /* Fully visible */
+ }
+ 50% {
+ opacity: 0.5; /* Invisible */
+ }
+ }
+
+ .flicker {
+ animation: flicker 2s infinite; /* Adjust duration and repetition as needed */
+ }
+
+/* Chat container scroll styling */
+/* .chat-container {
+ overflow-y: auto;
+ /* max-height: 60vh;
+ padding-right: 0.5rem;
+} */
+
+/* App.css */
+
+/* Style for chat-display to handle horizontal scrolling without word breaks */
+.chat-display {
+ max-height: 100%; /* Set a maximum height for vertical scrolling */
+ /* overflow-y: auto; Vertical scrolling for long content */
+ overflow-x: auto; /* Horizontal scrolling for wide content */
+ white-space: pre-wrap; /* Preserve whitespace formatting and avoid line breaks */
+ padding-right: 1rem; /* Add padding to avoid content sticking to the edge */
+}
+
+
+.flex-grow {
+ overflow-y: auto; /* Enables the outermost scroll */
+}
+
+/* Optional smooth scrollbar styling */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px; /* For horizontal scrollbar */
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #4a5568;
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background-color: #2d3748;
+}
+
diff --git a/Helpmate-AI/src/main.jsx b/Helpmate-AI/src/main.jsx
new file mode 100644
index 0000000..54b39dd
--- /dev/null
+++ b/Helpmate-AI/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/Helpmate-AI/src/services/coderAIService.js b/Helpmate-AI/src/services/coderAIService.js
new file mode 100644
index 0000000..c9bfc76
--- /dev/null
+++ b/Helpmate-AI/src/services/coderAIService.js
@@ -0,0 +1,56 @@
+import axios from 'axios';
+
+const CODER_AI_BASE_URL = 'http://localhost:5000';
+
+class CoderAIService {
+ constructor() {
+ this.client = axios.create({
+ baseURL: CODER_AI_BASE_URL,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ }
+
+ async analyzeCode(code) {
+ try {
+ const response = await this.client.post('/analyze', { code });
+ return response.data;
+ } catch (error) {
+ console.error('Error analyzing code:', error);
+ throw error;
+ }
+ }
+
+ async getProjectSuggestions() {
+ try {
+ const response = await this.client.get('/suggestions');
+ return response.data;
+ } catch (error) {
+ console.error('Error getting project suggestions:', error);
+ throw error;
+ }
+ }
+
+ async reviewCode(codeSnippet) {
+ try {
+ const response = await this.client.post('/review', { code: codeSnippet });
+ return response.data;
+ } catch (error) {
+ console.error('Error reviewing code:', error);
+ throw error;
+ }
+ }
+
+ async getGitHubIntegration() {
+ try {
+ const response = await this.client.get('/github/status');
+ return response.data;
+ } catch (error) {
+ console.error('Error getting GitHub integration status:', error);
+ throw error;
+ }
+ }
+}
+
+export default new CoderAIService();
\ No newline at end of file
diff --git a/Helpmate-AI/tailwind.config.js b/Helpmate-AI/tailwind.config.js
new file mode 100644
index 0000000..d37737f
--- /dev/null
+++ b/Helpmate-AI/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+
diff --git a/Helpmate-AI/vite.config.js b/Helpmate-AI/vite.config.js
new file mode 100644
index 0000000..5a33944
--- /dev/null
+++ b/Helpmate-AI/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e149e4d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Ashutosh Singh of Helpmate AI
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index b77b026..c76d9e7 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,17 @@
# CoderAI - AI Integration Platform
-CoderAI is a powerful SaaS platform that enables seamless integration with various AI solution providers. This application allows users to:
+
+
+
+
+CoderAI is a powerful SaaS platform that enables seamless integration with various AI solution providers and development tools. This application allows users to:
- Connect to local Ollama models or external AI providers
- Process and analyze documents using state-of-the-art AI models
- Generate embeddings using Hugging Face models
- Create and manage vector databases for semantic search
- Build custom AI workflows with a user-friendly interface
+- Integrate with GitHub for automated code reviews and project management
## Features
@@ -15,6 +20,17 @@ CoderAI is a powerful SaaS platform that enables seamless integration with vario
- **Vector Database**: Store and search through document embeddings
- **Custom Workflows**: Create tailored AI workflows for specific use cases
- **Analytics Dashboard**: Monitor usage and performance metrics
+- **GitHub Integration**: Automated code reviews, pull request analysis, and issue tracking
+- **Project Management**: Built-in tools for managing development workflows and tasks
+- **Code Review**: AI-powered code analysis and improvement suggestions
+
+## Technologies Used
+
+
+
+
+
+
## Getting Started
@@ -28,6 +44,7 @@ CoderAI is a powerful SaaS platform that enables seamless integration with vario
OPENAI_API_KEY=your_openai_key_here (optional)
ANTHROPIC_API_KEY=your_anthropic_key_here (optional)
HF_TOKEN=your_huggingface_token_here (optional)
+ GITHUB_TOKEN=your_github_token_here (required for GitHub integration)
```
3. Run the application:
@@ -48,5 +65,49 @@ CoderAI is a powerful SaaS platform that enables seamless integration with vario
- `models/`: Model integration modules
- `utils/`: Utility functions
- `components/`: Streamlit UI components
-- `services/`: Core services (embedding, vector store, etc.)
+ - `code_review.py`: Code review interface and logic
+ - `github_integration.py`: GitHub API integration
+ - `project_management.py`: Project management features
+- `services/`: Core services
+ - `embedding_service.py`: Text embedding generation
+ - `vector_store_service.py`: Vector database management
+ - `github_service.py`: GitHub API service
+ - `code_analysis_service.py`: Code analysis and review
- `data/`: Data storage directory
+
+## GitHub Integration
+
+To use the GitHub integration features:
+
+1. Generate a GitHub Personal Access Token with the following permissions:
+ - repo (full access)
+ - workflow
+ - read:org
+
+2. Add your GitHub token to the `.env` file
+
+3. Configure your GitHub repositories in the application settings
+
+## Code Review Features
+
+- Automated code quality analysis
+- Pull request review suggestions
+- Security vulnerability scanning
+- Best practices recommendations
+- Performance optimization tips
+
+## Contributing
+
+If you want to contribute to this project:
+
+1. Fork the repository
+2. Create a new branch for your feature
+3. Submit a pull request with a clear description of your changes
+
+Please make sure to follow the existing code style and guidelines.
+
+## License
+
+[](https://opensource.org/licenses/MIT)
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
diff --git a/app.py b/app.py
index c2026be..8808520 100644
--- a/app.py
+++ b/app.py
@@ -7,16 +7,19 @@
from components.sidebar import render_sidebar
from components.main_panel import render_main_panel
from components.provider_config import render_provider_config
+from components.code_review import render_code_review
+from components.github_integration import render_github_integration
from services.model_service import ModelService
from services.embedding_service import EmbeddingService
from services.vector_store_service import VectorStoreService
+from components.project_management import ProjectManagement
# Load environment variables
load_dotenv()
# Set page configuration
st.set_page_config(
- page_title="CoderAI - AI Integration Platform",
+ page_title="CoderAI",
page_icon="🧠",
layout="wide",
initial_sidebar_state="expanded"
@@ -53,7 +56,7 @@ def initialize_session_state():
"huggingface": {"active": True, "token": os.getenv("HF_TOKEN", "")}
}
-def main():
+async def main():
"""Main application entry point"""
# Initialize session state
initialize_session_state()
@@ -103,6 +106,14 @@ def main():
render_main_panel()
elif st.session_state.current_page == "Provider Configuration":
render_provider_config()
-
+ elif st.session_state.current_page == "Code Review":
+ render_code_review()
+ elif st.session_state.current_page == "GitHub Integration":
+ render_github_integration()
+
+# Initialize project management
+project_mgmt = ProjectManagement()
+
if __name__ == "__main__":
- main()
+ import asyncio
+ asyncio.run(main())
diff --git a/components/code_review.py b/components/code_review.py
new file mode 100644
index 0000000..3930a09
--- /dev/null
+++ b/components/code_review.py
@@ -0,0 +1,105 @@
+import streamlit as st
+import os
+from services.code_analysis_service import CodeAnalysisService
+
+def render_code_review():
+ """Render the code review and analysis interface"""
+ # Create a header with logo and title
+ col1, col2 = st.columns([1, 3])
+ with col1:
+ logo_path = os.path.join(os.getcwd(), "Logo.png")
+ st.image(logo_path, width=100)
+ with col2:
+ st.markdown("Code Review & Analysis ", unsafe_allow_html=True)
+ st.markdown("", unsafe_allow_html=True)
+
+ # Initialize code analysis service if not exists
+ if 'code_analysis_service' not in st.session_state:
+ st.session_state.code_analysis_service = CodeAnalysisService()
+
+ # Code input section
+ st.subheader("Code Input")
+ code = st.text_area(
+ "Enter or paste your code here",
+ height=300,
+ help="Paste your code here for analysis"
+ )
+
+ # Analysis options
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ analyze_complexity = st.checkbox("Analyze Complexity", value=True)
+ with col2:
+ check_best_practices = st.checkbox("Check Best Practices", value=True)
+ with col3:
+ detect_smells = st.checkbox("Detect Code Smells", value=True)
+
+ # Analyze button
+ if st.button("Analyze Code", type="primary", disabled=not code):
+ with st.spinner("Analyzing code..."):
+ # Get analysis results
+ analysis = st.session_state.code_analysis_service.analyze_code(code)
+ metrics = st.session_state.code_analysis_service.get_code_metrics(code)
+
+ # Display results in tabs
+ tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Code Smells", "Best Practices", "Suggestions"])
+
+ # Overview tab
+ with tab1:
+ st.subheader("Code Metrics")
+ col1, col2, col3, col4 = st.columns(4)
+
+ with col1:
+ st.metric("Lines of Code", metrics['loc'])
+ with col2:
+ st.metric("Classes", metrics['classes'])
+ with col3:
+ st.metric("Functions", metrics['functions'])
+ with col4:
+ st.metric("Imports", metrics['imports'])
+
+ st.subheader("Complexity Analysis")
+ st.progress(min(analysis['complexity_score'] / 20, 1.0))
+ st.write(f"Complexity Score: {analysis['complexity_score']}")
+
+ # Code Smells tab
+ with tab2:
+ st.subheader("Detected Code Smells")
+ if analysis['code_smells']:
+ for smell in analysis['code_smells']:
+ with st.expander(f"{smell['type']} at line {smell['location']}"):
+ st.write(smell['message'])
+ else:
+ st.success("No code smells detected!")
+
+ # Best Practices tab
+ with tab3:
+ st.subheader("Best Practices Analysis")
+ if analysis['best_practices']:
+ for violation in analysis['best_practices']:
+ with st.expander(f"Issue at line {violation['location']}"):
+ st.write(violation['message'])
+ else:
+ st.success("All best practices are followed!")
+
+ # Suggestions tab
+ with tab4:
+ st.subheader("Improvement Suggestions")
+ if analysis['suggestions']:
+ for suggestion in analysis['suggestions']:
+ st.info(suggestion)
+ else:
+ st.success("No immediate improvements suggested!")
+
+ # Help section
+ with st.expander("How to use the Code Review"):
+ st.markdown("""
+ 1. **Paste your code** in the text area above
+ 2. Select the analysis options you want to run
+ 3. Click 'Analyze Code' to get insights
+ 4. Review the results in different tabs:
+ - Overview: Basic metrics and complexity analysis
+ - Code Smells: Potential issues in code structure
+ - Best Practices: Adherence to coding standards
+ - Suggestions: AI-powered improvement recommendations
+ """)
\ No newline at end of file
diff --git a/components/github_integration.py b/components/github_integration.py
new file mode 100644
index 0000000..61a70de
--- /dev/null
+++ b/components/github_integration.py
@@ -0,0 +1,331 @@
+import streamlit as st
+import os
+from typing import Dict, List, Any, Optional
+from services.github_service import GitHubService
+from services.code_analysis_service import CodeAnalysisService
+
+def render_github_integration():
+ """Render the GitHub integration interface"""
+ # Create a header with logo and title
+ col1, col2 = st.columns([1, 3])
+ with col1:
+ logo_path = os.path.join(os.getcwd(), "Logo.png")
+ st.image(logo_path, width=100)
+ with col2:
+ st.markdown("GitHub Integration ", unsafe_allow_html=True)
+ st.markdown("", unsafe_allow_html=True)
+
+ # Initialize GitHub service if not exists
+ if 'github_service' not in st.session_state:
+ st.session_state.github_service = GitHubService()
+
+ # Authentication section
+ st.subheader("GitHub Authentication")
+
+ # Check if token is already set
+ token_set = bool(st.session_state.github_service.token)
+
+ if not token_set:
+ with st.form("github_auth_form"):
+ github_token = st.text_input(
+ "GitHub Personal Access Token",
+ type="password",
+ help="Create a token with 'repo' scope at https://github.com/settings/tokens"
+ )
+ submit_button = st.form_submit_button("Connect to GitHub")
+
+ if submit_button and github_token:
+ st.session_state.github_service.set_token(github_token)
+ st.success("Successfully connected to GitHub!")
+ st.rerun()
+ else:
+ st.success("✅ Connected to GitHub")
+ if st.button("Disconnect"):
+ st.session_state.github_service.set_token(None)
+ st.rerun()
+
+ # Repository section
+ st.subheader("Repository Access")
+
+ # Repository input
+ col1, col2 = st.columns([3, 1])
+ with col1:
+ repo_url = st.text_input(
+ "GitHub Repository URL",
+ help="Example: https://github.com/username/repo"
+ )
+ with col2:
+ connect_button = st.button("Connect to Repository")
+
+ if connect_button and repo_url:
+ # Parse owner and repo from URL
+ try:
+ parts = repo_url.strip("/").split("/")
+ owner = parts[-2]
+ repo = parts[-1]
+
+ # Get repository info
+ repo_info = st.session_state.github_service.get_repository(owner, repo)
+
+ if "error" in repo_info:
+ st.error(f"Error connecting to repository: {repo_info['error']}")
+ else:
+ st.session_state.current_repo = {
+ "owner": owner,
+ "name": repo,
+ "info": repo_info
+ }
+ st.success(f"Connected to {owner}/{repo}")
+ st.rerun()
+ except Exception as e:
+ st.error(f"Invalid repository URL: {str(e)}")
+
+ # If repository is connected, show repository explorer
+ if "current_repo" in st.session_state:
+ render_repository_explorer()
+
+def render_repository_explorer():
+ """Render the repository explorer section"""
+ owner = st.session_state.current_repo["owner"]
+ repo = st.session_state.current_repo["name"]
+
+ st.subheader(f"Repository: {owner}/{repo}")
+
+ # Repository tabs
+ tab1, tab2, tab3 = st.tabs(["Files", "Issues", "Pull Requests"])
+
+ # Files tab
+ with tab1:
+ # Get repository structure
+ structure = st.session_state.github_service.get_repository_structure(
+ owner, repo
+ )
+
+ if isinstance(structure, list) and "error" in structure[0]:
+ st.error(f"Error fetching repository structure: {structure[0]['error']}")
+ else:
+ # Display file tree
+ st.write("Select a file to analyze:")
+
+ # Create a simple file browser
+ for item in structure:
+ if item["type"] == "file" and item["name"].endswith((".py", ".js", ".java", ".cpp", ".c", ".go", ".rb")):
+ if st.button(f"📄 {item['name']}", key=f"file_{item['path']}"):
+ file_content = st.session_state.github_service.get_file_contents(
+ owner, repo, item["path"]
+ )
+
+ if "error" in file_content:
+ st.error(f"Error fetching file: {file_content['error']}")
+ else:
+ st.session_state.current_file = {
+ "name": item["name"],
+ "path": item["path"],
+ "content": file_content.get("decoded_content", "")
+ }
+ st.rerun()
+
+ # If a file is selected, show file content and analysis
+ if "current_file" in st.session_state:
+ render_file_analysis()
+
+ # Issues tab
+ with tab2:
+ # Get issues
+ issues = st.session_state.github_service.list_issues(owner, repo)
+
+ if isinstance(issues, list) and issues and "error" in issues[0]:
+ st.error(f"Error fetching issues: {issues[0]['error']}")
+ else:
+ # Create new issue section
+ with st.expander("Create New Issue"):
+ with st.form("new_issue_form"):
+ issue_title = st.text_input("Issue Title")
+ issue_body = st.text_area("Issue Description")
+ issue_labels = st.multiselect(
+ "Labels",
+ ["bug", "enhancement", "documentation", "question"]
+ )
+
+ submit_issue = st.form_submit_button("Create Issue")
+
+ if submit_issue and issue_title and issue_body:
+ result = st.session_state.github_service.create_issue(
+ owner, repo, issue_title, issue_body, issue_labels
+ )
+
+ if "error" in result:
+ st.error(f"Error creating issue: {result['error']}")
+ else:
+ st.success("Issue created successfully!")
+ st.rerun()
+
+ # Display existing issues
+ st.subheader("Existing Issues")
+
+ if not issues:
+ st.info("No issues found in this repository.")
+ else:
+ for issue in issues:
+ with st.expander(f"#{issue['number']} - {issue['title']}"):
+ st.write(f"**Status:** {issue['state']}")
+ st.write(f"**Created by:** {issue['user']['login']}")
+ st.write(f"**Created at:** {issue['created_at']}")
+ st.markdown(issue['body'])
+
+ # Display labels
+ if issue['labels']:
+ st.write("**Labels:**")
+ for label in issue['labels']:
+ st.markdown(f"- {label['name']}")
+
+ # Pull Requests tab
+ with tab3:
+ # Get pull requests
+ pull_requests = st.session_state.github_service.get_pull_requests(owner, repo)
+
+ if isinstance(pull_requests, list) and pull_requests and "error" in pull_requests[0]:
+ st.error(f"Error fetching pull requests: {pull_requests[0]['error']}")
+ else:
+ st.subheader("Pull Requests")
+
+ if not pull_requests:
+ st.info("No pull requests found in this repository.")
+ else:
+ for pr in pull_requests:
+ with st.expander(f"#{pr['number']} - {pr['title']}"):
+ st.write(f"**Status:** {pr['state']}")
+ st.write(f"**Created by:** {pr['user']['login']}")
+ st.write(f"**Created at:** {pr['created_at']}")
+ st.markdown(pr['body'])
+
+def render_file_analysis():
+ """Render file content and analysis"""
+ file = st.session_state.current_file
+
+ st.subheader(f"File: {file['name']}")
+
+ # Display file content
+ with st.expander("File Content", expanded=True):
+ st.code(file['content'], language=get_language_from_filename(file['name']))
+
+ # Analyze button
+ if st.button("Analyze Code", type="primary"):
+ with st.spinner("Analyzing code..."):
+ # Initialize code analysis service if not exists
+ if 'code_analysis_service' not in st.session_state:
+ st.session_state.code_analysis_service = CodeAnalysisService()
+
+ # Get analysis results
+ analysis = st.session_state.code_analysis_service.analyze_code(file['content'])
+ metrics = st.session_state.code_analysis_service.get_code_metrics(file['content'])
+
+ # Display results in tabs
+ tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Code Smells", "Best Practices", "Suggestions"])
+
+ # Overview tab
+ with tab1:
+ st.subheader("Code Metrics")
+ col1, col2, col3, col4 = st.columns(4)
+
+ with col1:
+ st.metric("Lines of Code", metrics['loc'])
+ with col2:
+ st.metric("Classes", metrics['classes'])
+ with col3:
+ st.metric("Functions", metrics['functions'])
+ with col4:
+ st.metric("Imports", metrics['imports'])
+
+ st.subheader("Complexity Analysis")
+ st.progress(min(analysis['complexity_score'] / 20, 1.0))
+ st.write(f"Complexity Score: {analysis['complexity_score']}")
+
+ # Code Smells tab
+ with tab2:
+ st.subheader("Detected Code Smells")
+ if analysis['code_smells']:
+ for smell in analysis['code_smells']:
+ with st.expander(f"{smell['type']} at line {smell['location']}"):
+ st.write(smell['message'])
+
+ # Add option to create issue from code smell
+ if st.button(f"Create Issue for this Code Smell", key=f"issue_{smell['location']}"):
+ owner = st.session_state.current_repo["owner"]
+ repo = st.session_state.current_repo["name"]
+
+ issue_title = f"Code Smell: {smell['type']} in {file['name']}"
+ issue_body = f"**File:** {file['path']}\n\n**Location:** Line {smell['location']}\n\n**Issue:** {smell['message']}"
+
+ result = st.session_state.github_service.create_issue(
+ owner, repo, issue_title, issue_body, ["bug", "code-quality"]
+ )
+
+ if "error" in result:
+ st.error(f"Error creating issue: {result['error']}")
+ else:
+ st.success("Issue created successfully!")
+ else:
+ st.success("No code smells detected!")
+
+ # Best Practices tab
+ with tab3:
+ st.subheader("Best Practices Analysis")
+ if analysis['best_practices']:
+ for violation in analysis['best_practices']:
+ with st.expander(f"Issue at line {violation['location']}"):
+ st.write(violation['message'])
+
+ # Add option to create issue from best practice violation
+ if st.button(f"Create Issue for this Violation", key=f"violation_{violation['location']}"):
+ owner = st.session_state.current_repo["owner"]
+ repo = st.session_state.current_repo["name"]
+
+ issue_title = f"Best Practice Violation in {file['name']}"
+ issue_body = f"**File:** {file['path']}\n\n**Location:** Line {violation['location']}\n\n**Issue:** {violation['message']}"
+
+ result = st.session_state.github_service.create_issue(
+ owner, repo, issue_title, issue_body, ["enhancement", "code-quality"]
+ )
+
+ if "error" in result:
+ st.error(f"Error creating issue: {result['error']}")
+ else:
+ st.success("Issue created successfully!")
+ else:
+ st.success("All best practices are followed!")
+
+ # Suggestions tab
+ with tab4:
+ st.subheader("Improvement Suggestions")
+ if analysis['suggestions']:
+ for suggestion in analysis['suggestions']:
+ st.info(suggestion)
+ else:
+ st.success("No immediate improvements suggested!")
+
+def get_language_from_filename(filename: str) -> str:
+ """Get the programming language based on file extension"""
+ extension = filename.split('.')[-1].lower()
+
+ language_map = {
+ 'py': 'python',
+ 'js': 'javascript',
+ 'ts': 'typescript',
+ 'html': 'html',
+ 'css': 'css',
+ 'java': 'java',
+ 'c': 'c',
+ 'cpp': 'cpp',
+ 'cs': 'csharp',
+ 'go': 'go',
+ 'rb': 'ruby',
+ 'php': 'php',
+ 'swift': 'swift',
+ 'kt': 'kotlin',
+ 'rs': 'rust',
+ 'sh': 'bash',
+ 'md': 'markdown',
+ 'json': 'json',
+ 'xml': 'xml'
+ }
\ No newline at end of file
diff --git a/components/helpmate_bridge.py b/components/helpmate_bridge.py
new file mode 100644
index 0000000..dcf073d
--- /dev/null
+++ b/components/helpmate_bridge.py
@@ -0,0 +1,82 @@
+import os
+import subprocess
+import threading
+import streamlit as st
+from flask import Flask, send_from_directory, jsonify, request
+from flask_cors import CORS
+
+app = Flask(__name__, static_folder='../Helpmate-AI/dist')
+# Configure CORS to allow embedding in iframe from any origin
+CORS(app, resources={r"/*": {"origins": "*", "allow_headers": "*", "expose_headers": "*"}})
+
+@app.route('/')
+def serve_helpmate():
+ """Serve the Helpmate-AI React frontend"""
+ return send_from_directory(app.static_folder, 'index.html')
+
+@app.route('/')
+def serve_static(path):
+ """Serve static files from the React app's build directory"""
+ return send_from_directory(app.static_folder, path)
+
+@app.route('/api/chat', methods=['POST'])
+def chat():
+ """Handle chat requests and integrate with coderAI's backend services"""
+ data = request.json
+ question = data.get('question')
+
+ # Here we'll integrate with coderAI's model service
+ try:
+ # Access the model service from the imported st session state
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
+ if get_script_run_ctx():
+ model_service = st.session_state.model_service
+ response = model_service.generate_response(question)
+ return jsonify({'answer': response})
+ else:
+ return jsonify({'answer': 'Unable to access Streamlit session state. Please try again.'}), 500
+ except Exception as e:
+ return jsonify({'error': str(e)}), 500
+
+def check_helpmate_build():
+ """Check if Helpmate-AI is built, and build it if not"""
+ if not os.path.exists(app.static_folder):
+ # Build the React app
+ try:
+ print("Building Helpmate-AI React app...")
+ subprocess.run(
+ ["npm", "run", "build"],
+ cwd=os.path.join(os.getcwd(), "../Helpmate-AI"),
+ check=True
+ )
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to build Helpmate-AI: {str(e)}")
+ return False
+ return True
+
+def run_flask_app():
+ """Run the Flask app in a separate thread"""
+ # Set Flask server headers to allow iframe embedding
+ @app.after_request
+ def add_header(response):
+ response.headers['X-Frame-Options'] = 'ALLOW-FROM *'
+ response.headers['Content-Security-Policy'] = "frame-ancestors *"
+ return response
+
+ app.run(host='0.0.0.0', port=5173, debug=False, use_reloader=False)
+
+def init_helpmate_bridge():
+ """Initialize the Helpmate-AI bridge"""
+ # Ensure the static folder exists or build it
+ if not os.path.exists(app.static_folder):
+ if not check_helpmate_build():
+ raise Exception('Helpmate-AI build directory not found and build failed. Please build the React app manually.')
+
+ # Start the Flask app in a separate thread
+ flask_thread = threading.Thread(target=run_flask_app)
+ flask_thread.daemon = True
+ flask_thread.start()
+
+ print("Helpmate-AI bridge initialized on port 5173")
+ return flask_thread
\ No newline at end of file
diff --git a/components/main_panel.py b/components/main_panel.py
index 07be793..672e2cc 100644
--- a/components/main_panel.py
+++ b/components/main_panel.py
@@ -3,9 +3,39 @@
import plotly.express as px
from datetime import datetime
import os
+from components.helpmate_bridge import init_helpmate_bridge
def render_main_panel():
- """Render the main panel of the application"""
+ """Render the main panel of the CoderAI application"""
+
+ # Initialize Helpmate-AI bridge if not already initialized
+ if "helpmate_initialized" not in st.session_state:
+ st.session_state.helpmate_initialized = False
+
+ if not st.session_state.helpmate_initialized:
+ try:
+ st.session_state.helpmate_thread = init_helpmate_bridge()
+ st.session_state.helpmate_initialized = True
+ except Exception as e:
+ st.error(f"Failed to initialize Helpmate-AI integration: {str(e)}")
+
+ # Main panel tabs
+ tab1, tab2, tab3, tab4 = st.tabs(["Dashboard", "Chat", "Analytics", "Settings"])
+
+ with tab1:
+ render_dashboard()
+
+ with tab2:
+ render_chat_interface()
+
+ with tab3:
+ render_analytics()
+
+ with tab4:
+ render_settings()
+
+def render_dashboard():
+ """Render the dashboard section"""
# Create a header with logo and title
col1, col2 = st.columns([1, 3])
with col1:
@@ -18,23 +48,49 @@ def render_main_panel():
# Dashboard metrics
col1, col2, col3 = st.columns(3)
with col1:
- st.metric(label="Active Providers", value=sum(1 for p in st.session_state.providers.values() if p["active"]))
+ st.metric(label="Active Providers", value=sum(1 for p in st.session_state.providers.values() if p.get("active", False)))
with col2:
st.metric(label="Documents Processed", value=len(st.session_state.get("documents", [])))
with col3:
st.metric(label="AI Queries", value=len(st.session_state.get("chat_history", [])))
- # Main tabs
- tab1, tab2, tab3 = st.tabs(["Document Processing", "Chat Interface", "Analytics"])
+ # Initialize chatbot state if not already initialized
+ if "chatbot_open" not in st.session_state:
+ st.session_state.chatbot_open = False
- with tab1:
- render_document_processing()
+ # Create a button that will toggle the chatbot state
+ chatbot_col1, chatbot_col2 = st.columns([6, 1])
+ with chatbot_col2:
+ if st.button("Chat 💬", key="dashboard_chat_button"):
+ st.session_state.chatbot_open = not st.session_state.chatbot_open
- with tab2:
- render_chat_interface()
+ # Display the chatbot if it's open
+ if st.session_state.chatbot_open:
+ st.markdown("""
+
+
+ Helpmate AI
+ ✕
+
+
+
+ """, unsafe_allow_html=True)
- with tab3:
- render_analytics()
+ # Main dashboard content
+ st.subheader("Recent Activity")
+
+ # Sample data for recent activity
+ activity_data = {
+ "Timestamp": ["2025-03-08 19:45", "2025-03-08 18:30", "2025-03-08 17:15", "2025-03-08 16:00"],
+ "Activity": ["Code Review", "AI Query", "Document Processing", "GitHub Integration"],
+ "Details": ["Reviewed login.py", "Generated API documentation", "Processed requirements.txt", "Synced with repository"]
+ }
+
+ activity_df = pd.DataFrame(activity_data)
+ st.dataframe(activity_df, use_container_width=True)
def render_document_processing():
"""Render the document processing section"""
@@ -108,85 +164,97 @@ def render_document_processing():
def render_chat_interface():
"""Render the chat interface section"""
- st.subheader("Chat with AI")
-
- # Model selection
- col1, col2 = st.columns(2)
-
- with col1:
- # Provider selection
- active_providers = [p for p, c in st.session_state.providers.items() if c["active"]]
- if not active_providers:
- st.warning("No active providers. Please configure a provider in the settings.")
- return
-
- selected_provider = st.selectbox("Select Provider", active_providers)
-
- with col2:
- # Model selection based on provider
- if selected_provider == "ollama":
- models = st.session_state.config.provider_configs["ollama"]["available_models"]
- default_model = st.session_state.config.default_ollama_model
- elif selected_provider == "openai":
- models = st.session_state.config.provider_configs["openai"]["available_models"]
- default_model = models[0]
- elif selected_provider == "anthropic":
- models = st.session_state.config.provider_configs["anthropic"]["available_models"]
- default_model = models[0]
- else:
- models = ["No models available"]
- default_model = models[0]
-
- selected_model = st.selectbox(
- "Select Model",
- models,
- index=models.index(default_model) if default_model in models else 0
- )
+ st.subheader("Chat Interface")
- # Store current model and provider in session state
- st.session_state.current_model = selected_model
- st.session_state.current_provider = selected_provider
+ # Initialize chatbot state if not already initialized
+ if "chatbot_open" not in st.session_state:
+ st.session_state.chatbot_open = False
- # Chat history
- st.subheader("Chat History")
+ # Add a button to open the chatbot
+ if st.button("Open Helpmate AI Assistant", key="chat_tab_button"):
+ st.session_state.chatbot_open = not st.session_state.chatbot_open
- for message in st.session_state.get("chat_history", []):
- with st.chat_message(message["role"]):
- st.write(message["content"])
+ # Display the chatbot if it's open
+ if st.session_state.chatbot_open:
+ st.markdown("""
+
+
+ Helpmate AI
+ ✕
+
+
+
+ """, unsafe_allow_html=True)
- # Chat input
- user_input = st.chat_input("Ask something...")
+ # Native chat interface
+ st.subheader("Native Chat Interface")
- if user_input:
- # Add user message to chat history
- if "chat_history" not in st.session_state:
- st.session_state.chat_history = []
-
- st.session_state.chat_history.append({
- "role": "user",
- "content": user_input
- })
+ # Message input
+ with st.form("chat_form"):
+ user_input = st.text_area("Your message:", height=100)
- # Display user message
- with st.chat_message("user"):
- st.write(user_input)
+ col1, col2 = st.columns([4, 1])
+ with col2:
+ submit_button = st.form_submit_button("Send")
- # Generate AI response
- with st.chat_message("assistant"):
- with st.spinner("Thinking..."):
- # In a real implementation, we would:
- # 1. Send the query to the selected model via the appropriate provider
- # 2. Get the response and display it
+ if submit_button and user_input:
+ # Add user message to chat history
+ if "chat_history" not in st.session_state:
+ st.session_state.chat_history = []
+
+ st.session_state.chat_history.append({"role": "user", "content": user_input})
+
+ # Generate AI response
+ try:
+ model_service = st.session_state.model_service
- # Simulate AI response
- ai_response = f"This is a simulated response from {selected_model} via {selected_provider}. In a real implementation, this would be generated by the AI model."
- st.write(ai_response)
-
- # Add AI response to chat history
- st.session_state.chat_history.append({
- "role": "assistant",
- "content": ai_response
- })
+ # Find active provider
+ active_provider = None
+ active_model = None
+
+ if st.session_state.providers.get("ollama", {}).get("active", False):
+ active_provider = "ollama"
+ active_model = "llama2"
+ elif st.session_state.providers.get("openai", {}).get("active", False):
+ active_provider = "openai"
+ active_model = "gpt-3.5-turbo"
+ elif st.session_state.providers.get("anthropic", {}).get("active", False):
+ active_provider = "anthropic"
+ active_model = "claude-3-haiku"
+
+ if active_provider:
+ with st.spinner("Generating response..."):
+ response = model_service.query_model(
+ prompt=user_input,
+ provider=active_provider,
+ model=active_model
+ )
+
+ ai_response = response.get("text", "Sorry, I couldn't generate a response.")
+ st.session_state.chat_history.append({"role": "assistant", "content": ai_response})
+ else:
+ st.error("No active AI providers found. Please configure a provider in the settings.")
+ except Exception as e:
+ st.error(f"Error generating response: {str(e)}")
+
+ # Display chat history
+ if "chat_history" in st.session_state and st.session_state.chat_history:
+ for message in st.session_state.chat_history:
+ if message["role"] == "user":
+ st.markdown(f"""
+
+ You: {message["content"]}
+
+ """, unsafe_allow_html=True)
+ else:
+ st.markdown(f"""
+
+ AI: {message["content"]}
+
+ """, unsafe_allow_html=True)
def render_analytics():
"""Render the analytics section"""
@@ -257,3 +325,8 @@ def render_analytics():
for metric, value in metrics_data.items():
st.metric(label=metric, value=value)
+
+def render_settings():
+ """Render the settings section"""
+ st.subheader("Settings")
+ # Add settings UI here
diff --git a/components/project_management.py b/components/project_management.py
new file mode 100644
index 0000000..8139508
--- /dev/null
+++ b/components/project_management.py
@@ -0,0 +1,121 @@
+import streamlit as st
+from dataclasses import dataclass
+from typing import List, Optional
+from datetime import datetime
+
+@dataclass
+class Project:
+ name: str
+ description: str
+ created_at: datetime
+ status: str
+ tasks: List[dict]
+ collaborators: List[str]
+ repository_url: Optional[str] = None
+
+class ProjectManagement:
+ def __init__(self):
+ if 'projects' not in st.session_state:
+ st.session_state.projects = []
+
+ def create_project(self, name: str, description: str, repository_url: str = None):
+ """Create a new project"""
+ project = Project(
+ name=name,
+ description=description,
+ created_at=datetime.now(),
+ status='active',
+ tasks=[],
+ collaborators=[],
+ repository_url=repository_url
+ )
+ st.session_state.projects.append(project)
+ return project
+
+ def add_task(self, project_name: str, task: dict):
+ """Add a task to a project"""
+ for project in st.session_state.projects:
+ if project.name == project_name:
+ project.tasks.append(task)
+ return True
+ return False
+
+ def add_collaborator(self, project_name: str, collaborator: str):
+ """Add a collaborator to a project"""
+ for project in st.session_state.projects:
+ if project.name == project_name:
+ project.collaborators.append(collaborator)
+ return True
+ return False
+
+ def update_project_status(self, project_name: str, status: str):
+ """Update project status"""
+ for project in st.session_state.projects:
+ if project.name == project_name:
+ project.status = status
+ return True
+ return False
+
+ def render_project_dashboard(self):
+ """Render the project management dashboard"""
+ st.title('Project Management Dashboard')
+
+ # Create new project section
+ with st.expander('Create New Project'):
+ project_name = st.text_input('Project Name')
+ project_desc = st.text_area('Project Description')
+ repo_url = st.text_input('Repository URL (optional)')
+
+ if st.button('Create Project'):
+ if project_name and project_desc:
+ self.create_project(project_name, project_desc, repo_url)
+ st.success(f'Project {project_name} created successfully!')
+ else:
+ st.error('Please fill in required fields')
+
+ # Display existing projects
+ if st.session_state.projects:
+ for project in st.session_state.projects:
+ with st.expander(f'Project: {project.name}'):
+ st.write(f'Description: {project.description}')
+ st.write(f'Status: {project.status}')
+ st.write(f'Created: {project.created_at.strftime("%Y-%m-%d %H:%M")}')
+
+ # Tasks section
+ st.subheader('Tasks')
+ for task in project.tasks:
+ st.write(f"- {task['title']}: {task['status']}")
+
+ # Add new task
+ task_title = st.text_input('New Task Title', key=f'task_{project.name}')
+ if st.button('Add Task', key=f'add_task_{project.name}'):
+ if task_title:
+ self.add_task(project.name, {
+ 'title': task_title,
+ 'status': 'pending',
+ 'created_at': datetime.now()
+ })
+ st.success('Task added successfully!')
+
+ # Collaborators section
+ st.subheader('Collaborators')
+ st.write(', '.join(project.collaborators) if project.collaborators else 'No collaborators yet')
+
+ # Add collaborator
+ new_collaborator = st.text_input('Add Collaborator', key=f'collab_{project.name}')
+ if st.button('Add', key=f'add_collab_{project.name}'):
+ if new_collaborator:
+ self.add_collaborator(project.name, new_collaborator)
+ st.success(f'Added {new_collaborator} to the project')
+
+ # Update project status
+ new_status = st.selectbox(
+ 'Update Status',
+ ['active', 'on hold', 'completed'],
+ key=f'status_{project.name}'
+ )
+ if st.button('Update Status', key=f'update_status_{project.name}'):
+ self.update_project_status(project.name, new_status)
+ st.success('Project status updated successfully!')
+ else:
+ st.info('No projects created yet. Create your first project above!')
\ No newline at end of file
diff --git a/components/sidebar.py b/components/sidebar.py
index 6f73f05..d1bac53 100644
--- a/components/sidebar.py
+++ b/components/sidebar.py
@@ -21,6 +21,14 @@ def render_sidebar():
if st.button("⚙️ Provider Configuration", use_container_width=True):
st.session_state.current_page = "Provider Configuration"
st.rerun()
+
+ if st.button("🔍 Code Review", use_container_width=True):
+ st.session_state.current_page = "Code Review"
+ st.rerun()
+
+ if st.button("🐙 GitHub Integration", use_container_width=True):
+ st.session_state.current_page = "GitHub Integration"
+ st.rerun()
st.divider()
diff --git a/config.py b/config.py
index b138f92..0d2583f 100644
--- a/config.py
+++ b/config.py
@@ -4,16 +4,16 @@
class AppConfig(BaseModel):
"""Application configuration settings"""
# Default models
- default_ollama_model: str = "llama2"
+ default_ollama_model: str = "llama3.2:1b"
default_embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
# UI settings
theme: str = "light"
- max_chat_history: int = 100
+ max_chat_history: int = 500
# File handling
allowed_extensions: List[str] = ["pdf", "txt", "docx", "md"]
- max_file_size_mb: int = 10
+ max_file_size_mb: int = 100
# Vector database settings
vector_db_path: str = "./data/vector_db"
@@ -28,7 +28,7 @@ class AppConfig(BaseModel):
"ollama": {
"base_url": "http://localhost:11434",
"available_models": [
- "llama2", "mistral", "gemma", "phi", "codellama"
+ "llama3.2:1b", "mistral", "gemma", "phi", "codellama"
]
},
"openai": {
@@ -38,7 +38,7 @@ class AppConfig(BaseModel):
},
"anthropic": {
"available_models": [
- "claude-3-opus", "claude-3-sonnet", "claude-3-haiku"
+ "claude-3-haiku-20240307 1", "claude-3-opus-20240229", "claude-3-5-sonnet-20240620", "claude-3-5-haiku-20241022", "claude-3-5-sonnet-20241022", "claude-3-7-sonnet-20250219"
]
},
"huggingface": {
diff --git a/langflow/.env.example b/langflow/.env.example
new file mode 100644
index 0000000..e947537
--- /dev/null
+++ b/langflow/.env.example
@@ -0,0 +1,110 @@
+# Description: Example of .env file
+# Usage: Copy this file to .env and change the values
+# according to your needs
+# Do not commit .env file to git
+# Do not change .env.example file
+
+# Config directory
+# Directory where files, logs and database will be stored
+# Example: LANGFLOW_CONFIG_DIR=~/.langflow
+LANGFLOW_CONFIG_DIR=
+
+# Save database in the config directory
+# Values: true, false
+# If false, the database will be saved in Langflow's root directory
+# This means that the database will be deleted when Langflow is uninstalled
+# and that the database will not be shared between different virtual environments
+# Example: LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
+LANGFLOW_SAVE_DB_IN_CONFIG_DIR=
+
+# Database URL
+# Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow
+# SQLite example:
+LANGFLOW_DATABASE_URL=sqlite:///./langflow.db
+
+# Database connection retry
+# Values: true, false
+# If true, the database will retry to connect to the database if it fails
+# Example: LANGFLOW_DATABASE_CONNECTION_RETRY=true
+LANGFLOW_DATABASE_CONNECTION_RETRY=false
+
+# Cache type
+LANGFLOW_LANGCHAIN_CACHE=SQLiteCache
+
+# Server host
+# Example: LANGFLOW_HOST=127.0.0.1
+LANGFLOW_HOST=
+
+# Worker processes
+# Example: LANGFLOW_WORKERS=1
+LANGFLOW_WORKERS=
+
+# Server port
+# Example: LANGFLOW_PORT=7860
+LANGFLOW_PORT=
+
+# Logging level
+# Example: LANGFLOW_LOG_LEVEL=critical
+LANGFLOW_LOG_LEVEL=
+
+# Path to the log file
+# Example: LANGFLOW_LOG_FILE=logs/langflow.log
+LANGFLOW_LOG_FILE=
+
+# Path to the frontend directory containing build files
+# Example: LANGFLOW_FRONTEND_PATH=/path/to/frontend/build/files
+LANGFLOW_FRONTEND_PATH=
+
+# Whether to open the browser after starting the server
+# Values: true, false
+# Example: LANGFLOW_OPEN_BROWSER=true
+LANGFLOW_OPEN_BROWSER=
+
+# Whether to remove API keys from the projects saved in the database
+# Values: true, false
+# Example: LANGFLOW_REMOVE_API_KEYS=false
+LANGFLOW_REMOVE_API_KEYS=
+
+# Whether to use RedisCache or ThreadingInMemoryCache or AsyncInMemoryCache
+# Values: async, memory, redis
+# Example: LANGFLOW_CACHE_TYPE=memory
+# If you want to use redis then the following environment variables must be set:
+# LANGFLOW_REDIS_HOST (default: localhost)
+# LANGFLOW_REDIS_PORT (default: 6379)
+# LANGFLOW_REDIS_DB (default: 0)
+# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
+LANGFLOW_CACHE_TYPE=
+
+# Set AUTO_LOGIN to false if you want to disable auto login
+# and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
+# must be set if AUTO_LOGIN is set to false
+# Values: true, false
+LANGFLOW_AUTO_LOGIN=
+
+# Superuser username
+# Example: LANGFLOW_SUPERUSER=admin
+LANGFLOW_SUPERUSER=
+
+# Superuser password
+# Example: LANGFLOW_SUPERUSER_PASSWORD=123456
+LANGFLOW_SUPERUSER_PASSWORD=
+
+# Should store environment variables in the database
+# Values: true, false
+LANGFLOW_STORE_ENVIRONMENT_VARIABLES=
+
+# STORE_URL
+# Example: LANGFLOW_STORE_URL=https://api.langflow.store
+# LANGFLOW_STORE_URL=
+
+# DOWNLOAD_WEBHOOK_URL
+#
+# LANGFLOW_DOWNLOAD_WEBHOOK_URL=
+
+# LIKE_WEBHOOK_URL
+#
+# LANGFLOW_LIKE_WEBHOOK_URL=
+
+# Value must finish with slash /
+#BACKEND_URL=http://localhost:7860/
+BACKEND_URL=
\ No newline at end of file
diff --git a/langflow/.eslintrc.json b/langflow/.eslintrc.json
new file mode 100644
index 0000000..fc12c18
--- /dev/null
+++ b/langflow/.eslintrc.json
@@ -0,0 +1,90 @@
+{
+ "extends": [
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:prettier/recommended"
+ ],
+ "plugins": [
+ "react",
+ "import-helpers",
+ "prettier"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "project": [
+ "./tsconfig.node.json",
+ "./tsconfig.json"
+ ],
+ "extraFileExtensions:": [
+ ".mdx"
+ ],
+ "extensions:": [
+ ".mdx"
+ ]
+ },
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "settings": {
+ "react": {
+ "version": "detect"
+ }
+ },
+ "rules": {
+ "no-console": "warn",
+ "no-self-assign": "warn",
+ "no-self-compare": "warn",
+ "complexity": [
+ "error",
+ {
+ "max": 15
+ }
+ ],
+ "indent": [
+ "error",
+ 2,
+ {
+ "SwitchCase": 1
+ }
+ ],
+ "no-dupe-keys": "error",
+ "no-invalid-regexp": "error",
+ "no-undef": "error",
+ "no-return-assign": "error",
+ "no-redeclare": "error",
+ "no-empty": "error",
+ "no-await-in-loop": "error",
+ "react/react-in-jsx-scope": 0,
+ "node/exports-style": [
+ "error",
+ "module.exports"
+ ],
+ "node/file-extension-in-import": [
+ "error",
+ "always"
+ ],
+ "node/prefer-global/buffer": [
+ "error",
+ "always"
+ ],
+ "node/prefer-global/console": [
+ "error",
+ "always"
+ ],
+ "node/prefer-global/process": [
+ "error",
+ "always"
+ ],
+ "node/prefer-global/url-search-params": [
+ "error",
+ "always"
+ ],
+ "node/prefer-global/url": [
+ "error",
+ "always"
+ ],
+ "node/prefer-promises/dns": "error",
+ "node/prefer-promises/fs": "error"
+ }
+}
diff --git a/langflow/.gitattributes b/langflow/.gitattributes
new file mode 100644
index 0000000..c79a33b
--- /dev/null
+++ b/langflow/.gitattributes
@@ -0,0 +1,35 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text eol=lf
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.c text
+*.h text
+*.py text
+*.js text
+*.jsx text
+*.ts text
+*.tsx text
+*.md text
+*.mdx text
+*.yml text
+*.yaml text
+*.xml text
+*.csv text
+*.json text
+*.sh text
+*.Dockerfile text
+Dockerfile text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+*.ico binary
+*.gif binary
+*.mp4 binary
+*.svg binary
+*.csv binary
+*.wav binary
diff --git a/langflow/.github/ISSUE_TEMPLATE/bug-report.yaml b/langflow/.github/ISSUE_TEMPLATE/bug-report.yaml
new file mode 100644
index 0000000..94419e3
--- /dev/null
+++ b/langflow/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -0,0 +1,120 @@
+name: "🐛 Bug Report"
+description: Submit a bug report to help us improve Langflow
+labels: [ "bug" ]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Bug Description
+ description: A clear and concise description of what the bug is
+ placeholder: Tell us what you see!
+ validations:
+ required: true
+
+ - type: textarea
+ id: reproduction
+ validations:
+ required: true
+ attributes:
+ label: Reproduction
+ description: |
+ Please provide a code sample that reproduces the problem you ran into. It can be a Colab link or just a code snippet.
+ If you have code snippets, error messages, or stack traces please provide them here as well.
+ Important! Use code tags to format your code correctly. See https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting
+ Do not use screenshots, as they are hard to read, and (more importantly) don't allow others to copy-and-paste your code.
+ placeholder: |
+ Steps to reproduce the behavior:
+
+ 1.
+ 2.
+ 3.
+
+ - type: textarea
+ id: expected-behavior
+ validations:
+ required: true
+ attributes:
+ label: Expected behavior
+ description: "A clear and concise description of what you would expect to happen."
+
+ - type: textarea
+ id: who-can-help
+ attributes:
+ label: Who can help?
+ description: |
+ Your issue will be replied to more quickly if you can figure out the right person to tag with @.
+ If you know the relevant code owner, please tag them. Otherwise, leave this blank and a core maintainer will direct the issue accordingly.
+
+ Please tag fewer than 3 people.
+
+ Specific Areas:
+
+ Frontend:
+ - @anovazzi1
+ - @Cristhianzl
+ - @lucaseduoli
+ - @igorrCarvalho
+
+ Backend:
+ - @italojohnny
+
+ Full Stack:
+ - @ogabrielluiz
+ - @nicoloboschi
+ - @zzzming
+ - @jordanrfrazier
+ - @mfortman11
+ - @NadirJ
+
+ placeholder: "@Username ..."
+
+ - type: markdown
+ attributes:
+ value: '## Environment'
+
+ - type: input
+ id: os
+ attributes:
+ label: Operating System
+ placeholder: ex. Ubuntu Linux 22.04
+ validations:
+ required: true
+
+ - type: input
+ id: langflow-version
+ attributes:
+ label: Langflow Version
+ placeholder: ex. 1.0.9
+ validations:
+ required: true
+
+ - type: dropdown
+ id: python-version
+ attributes:
+ label: "Python Version"
+ description: |
+
+ **Langflow requires Python version 3.10 or greater.**
+ options:
+ - "3.12"
+ - "3.11"
+ - "3.10"
+
+ - type: textarea
+ id: screenshot
+ attributes:
+ label: Screenshot
+ description: "If applicable, add screenshots to help explain your problem."
+ placeholder: "Paste your screenshot here."
+
+ - type: textarea
+ id: flow-file
+ attributes:
+ label: Flow File
+ description: "Add your flow if applicable to help replicate the problem."
+ placeholder: "Add your flow link here."
diff --git a/langflow/.github/ISSUE_TEMPLATE/feature-request.yaml b/langflow/.github/ISSUE_TEMPLATE/feature-request.yaml
new file mode 100644
index 0000000..1a6862a
--- /dev/null
+++ b/langflow/.github/ISSUE_TEMPLATE/feature-request.yaml
@@ -0,0 +1,28 @@
+name: "🚀 Feature Request"
+description: Submit a proposal/request for a new Langflow feature
+labels: [ "enhancement" ]
+body:
+ - type: textarea
+ id: feature-request
+ validations:
+ required: true
+ attributes:
+ label: Feature Request
+ description: |
+ A clear and concise description of the feature proposal. Please provide any relevant links to papers, code, or other resources that support your proposal.
+
+ - type: textarea
+ id: motivation
+ validations:
+ required: true
+ attributes:
+ label: Motivation
+ description: |
+ Please outline the motivation for the proposal. Is your feature request related to a problem? e.g., I'm always frustrated when [...]. If this is related to another GitHub issue, please link it here as well.
+
+ - type: textarea
+ id: contribution
+ attributes:
+ label: Your Contribution
+ description: |
+ Is there any way that you could help, e.g., by submitting a PR? Make sure to read the CONTRIBUTING.md guidelines for Langflow before proceeding.
diff --git a/langflow/.github/ISSUE_TEMPLATE/work-in-progress.yaml b/langflow/.github/ISSUE_TEMPLATE/work-in-progress.yaml
new file mode 100644
index 0000000..213cbe8
--- /dev/null
+++ b/langflow/.github/ISSUE_TEMPLATE/work-in-progress.yaml
@@ -0,0 +1,58 @@
+name: Work in Progress
+description: Use this template to describe the new feature or improvement you are currently working on.
+labels: [enhancement]
+
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## Work in Progress
+
+ Thank you for contributing to our project! Please fill out the sections below to describe the new feature or improvement you are currently working on.
+
+ - type: input
+ id: title
+ attributes:
+ label: Title
+ description: Provide a concise title for your feature or improvement.
+ placeholder: "Short and descriptive title"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: type
+ attributes:
+ label: Type
+ description: Is this a new feature or an improvement?
+ options:
+ - New Feature
+ - Improvement
+ validations:
+ required: true
+
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Provide a detailed description of the feature or improvement.
+ placeholder: "Explain the feature or improvement in detail"
+ validations:
+ required: true
+
+ - type: textarea
+ id: use-case
+ attributes:
+ label: Use Case
+ description: Describe the use case or user story that this feature or improvement addresses.
+ placeholder: "As a [user], I want to [do something] so that [benefit]."
+ validations:
+ required: false
+
+ - type: textarea
+ id: implementation
+ attributes:
+ label: Implementation Plan
+ description: Outline your plan for implementing this feature or improvement.
+ placeholder: "Describe how you plan to implement this feature or improvement."
+ validations:
+ required: false
diff --git a/langflow/.github/actions/install-playwright/action.yml b/langflow/.github/actions/install-playwright/action.yml
new file mode 100644
index 0000000..8f3d7be
--- /dev/null
+++ b/langflow/.github/actions/install-playwright/action.yml
@@ -0,0 +1,76 @@
+name: Install Playwright
+description: Install Playwright and dependencies with cache
+
+# https://github.com/microsoft/playwright/issues/7249
+
+inputs:
+ working-directory:
+ description: Where to install Playwright
+ default: ./
+ browsers:
+ description: Browsers to install
+ default: chromium webkit firefox
+
+outputs:
+ version:
+ description: Installed version of Playwright
+ value: ${{ steps.version.outputs.version }}
+ cache-hit:
+ description: Whether cache for Playwright was found
+ value: ${{ steps.cache.outputs.cache-hit }}
+
+runs:
+ using: composite
+ steps:
+ - name: Get Playwright version
+ uses: actions/github-script@v7
+ id: version
+ with:
+ script: |
+ const fs = require('fs');
+ const path = require('path');
+
+ // Get working directory
+ const workingDirectory = "${{ inputs.working-directory }}";
+ console.debug("Specified working directory:", workingDirectory);
+ if (workingDirectory) process.chdir(workingDirectory);
+ console.debug("Actual working directory:", process.cwd());
+
+ // Read package.json
+ let version = "";
+ try {
+ const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
+ version = (
+ packageJson.devDependencies?.['@playwright/test'] ||
+ packageJson.dependencies?.['@playwright/test'] ||
+ packageJson.dependencies?.['playwright'] ||
+ packageJson.devDependencies?.['playwright']
+ )?.replace(/[\^~]/g, '');
+ } catch (error) {
+ console.log(error.message);
+ }
+
+ console.debug("Version:", version);
+ if (version) {
+ core.exportVariable("PLAYWRIGHT_VERSION", version);
+ core.setOutput("version", version);
+ } else core.setFailed("Couldn't get Playwright version");
+
+ - name: Cache Playwright
+ id: cache
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-${{ env.PLAYWRIGHT_VERSION }}
+
+ - name: Install Playwright and its dependencies
+ shell: bash
+ if: steps.cache.outputs.cache-hit != 'true'
+ working-directory: ${{ inputs.working-directory }}
+ run: npx playwright install ${{ inputs.browsers }} --with-deps
+
+ - name: Install just Playwright's dependencies
+ shell: bash
+ if: steps.cache.outputs.cache-hit == 'true'
+ working-directory: ${{ inputs.working-directory }}
+ run: npx playwright install-deps
diff --git a/langflow/.github/actions/poetry_caching/action.yml b/langflow/.github/actions/poetry_caching/action.yml
new file mode 100644
index 0000000..4bb6415
--- /dev/null
+++ b/langflow/.github/actions/poetry_caching/action.yml
@@ -0,0 +1,99 @@
+# An action for setting up poetry install with caching.
+# Using a custom action since the default action does not
+# take poetry install groups into account.
+# Action code from:
+# https://github.com/actions/setup-python/issues/505#issuecomment-1273013236
+# Copy of https://github.com/langchain-ai/langchain/blob/2f8dd1a1619f25daa4737df4d378b1acd6ff83c4/.github/actions/poetry_setup/action.yml
+name: poetry-install-with-caching
+description: Poetry install with support for caching of dependency groups.
+
+inputs:
+ python-version:
+ description: Python version, supporting MAJOR.MINOR only
+ required: true
+
+ poetry-version:
+ description: Poetry version
+ required: true
+
+ cache-key:
+ description: Cache key to use for manual handling of caching
+ required: true
+
+ working-directory:
+ description: Directory whose poetry.lock file should be cached
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - uses: actions/setup-python@v5
+ name: Setup python ${{ inputs.python-version }}
+ id: setup-python
+ with:
+ python-version: ${{ inputs.python-version }}
+
+ - uses: actions/cache@v4
+ id: cache-bin-poetry
+ name: Cache Poetry binary - Python ${{ inputs.python-version }}
+ env:
+ SEGMENT_DOWNLOAD_TIMEOUT_MIN: "1"
+ with:
+ path: |
+ /opt/pipx/venvs/poetry
+ # This step caches the poetry installation, so make sure it's keyed on the poetry version as well.
+ key: bin-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-${{ inputs.poetry-version }}
+
+ - name: Refresh shell hashtable and fixup softlinks
+ if: steps.cache-bin-poetry.outputs.cache-hit == 'true'
+ shell: bash
+ env:
+ POETRY_VERSION: ${{ inputs.poetry-version }}
+ PYTHON_VERSION: ${{ inputs.python-version }}
+ run: |
+ set -eux
+
+ # Refresh the shell hashtable, to ensure correct `which` output.
+ hash -r
+
+ # `actions/cache@v3` doesn't always seem able to correctly unpack softlinks.
+ # Delete and recreate the softlinks pipx expects to have.
+ rm /opt/pipx/venvs/poetry/bin/python
+ cd /opt/pipx/venvs/poetry/bin
+ ln -s "$(which "python$PYTHON_VERSION")" python
+ chmod +x python
+ cd /opt/pipx_bin/
+ ln -s /opt/pipx/venvs/poetry/bin/poetry poetry
+ chmod +x poetry
+
+ # Ensure everything got set up correctly.
+ /opt/pipx/venvs/poetry/bin/python --version
+ /opt/pipx_bin/poetry --version
+
+ - name: Install poetry
+ if: steps.cache-bin-poetry.outputs.cache-hit != 'true'
+ shell: bash
+ env:
+ POETRY_VERSION: ${{ inputs.poetry-version || env.POETRY_VERSION }}
+ PYTHON_VERSION: ${{ inputs.python-version }}
+ # Install poetry using the python version installed by setup-python step.
+ run: |
+ pipx install "poetry==$POETRY_VERSION" --python '${{ steps.setup-python.outputs.python-path }}' --verbose
+ pipx ensurepath
+ # Ensure the poetry binary is available in the PATH.
+ # Test that the poetry binary is available.
+ poetry --version
+
+ - name: Restore pip and poetry cached dependencies
+ uses: actions/cache@v4
+ env:
+ SEGMENT_DOWNLOAD_TIMEOUT_MIN: "4"
+ WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }}
+ with:
+ path: |
+ ~/.cache/pip
+ ~/.cache/pypoetry/virtualenvs
+ ~/.cache/pypoetry/cache
+ ~/.cache/pypoetry/artifacts
+ ${{ env.WORKDIR }}/.venv
+ key: py-deps-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ inputs.cache-key }}-${{ hashFiles(format('{0}/**/poetry.lock', env.WORKDIR)) }}
diff --git a/langflow/.github/actions/setup-uv/action.yml b/langflow/.github/actions/setup-uv/action.yml
new file mode 100644
index 0000000..e0bc862
--- /dev/null
+++ b/langflow/.github/actions/setup-uv/action.yml
@@ -0,0 +1,30 @@
+name: "Setup uv"
+description: "Checks out code, installs uv, and sets up Python environment"
+
+inputs:
+ python-version:
+ description: "Python version to use"
+ default: "3.13"
+
+runs:
+ using: "composite"
+ steps:
+ - name: Install uv
+ uses: astral-sh/setup-uv@v3
+ with:
+ enable-cache: true
+ cache-dependency-glob: "uv.lock"
+
+ - name: "Set up Python"
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ inputs.python-version }}
+
+ - name: Restore uv cache
+ uses: actions/cache@v4
+ with:
+ path: /tmp/.uv-cache
+ key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
+ restore-keys: |
+ uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
+ uv-${{ runner.os }}
diff --git a/langflow/.github/changes-filter.yaml b/langflow/.github/changes-filter.yaml
new file mode 100644
index 0000000..f0d3611
--- /dev/null
+++ b/langflow/.github/changes-filter.yaml
@@ -0,0 +1,83 @@
+# https://github.com/dorny/paths-filter
+python:
+ - "src/backend/**"
+ - "src/backend/**.py"
+ - "pyproject.toml"
+ - "uv.lock"
+ - "src/backend/base/pyproject.toml"
+ - "src/backend/base/uv.lock"
+ - "**/python_test.yml"
+components-changes:
+ - "src/backend/base/langflow/components/**"
+starter-projects-changes:
+ - "src/backend/base/langflow/initial_setup/**"
+frontend-tests:
+ - "src/frontend/tests/**"
+frontend:
+ - "src/frontend/**"
+ - "**/typescript_test.yml"
+docs:
+ - "docs/**"
+
+# Test categories and their associated paths
+starter-projects:
+ - "src/backend/base/langflow/initial_setup/**"
+ - "src/backend/base/langflow/components/**"
+ - "src/backend/base/langflow/services/**"
+ - "src/backend/base/langflow/custom/**"
+ - "src/backend/base/langflow/api/v1/chat.py"
+ - "src/frontend/src/pages/MainPage/**"
+ - "src/frontend/src/utils/reactflowUtils.ts"
+ - "src/frontend/tests/extended/features/**"
+ - "src/backend/base/langflow/custom/**"
+ - "src/backend/base/langflow/graph/**"
+
+components:
+ - "src/frontend/src/components/**"
+ - "src/frontend/src/modals/**"
+ - "src/frontend/src/pages/FlowPage/**"
+ - "src/frontend/src/shared/**"
+ - "src/frontend/src/hooks/**"
+ - "src/frontend/src/CustomNodes/**"
+ - "src/frontend/src/style/**"
+ - "src/frontend/src/utils/styleUtils.ts"
+ - "src/frontend/tests/core/features/**"
+ - "src/frontend/tests/core/integrations/**"
+ - "src/frontend/tests/core/regression/**"
+ - "src/frontend/tests/extended/integrations/**"
+ - "src/frontend/tests/extended/features/**"
+ - "src/frontend/tests/extended/regression/**"
+ - "src/backend/base/langflow/custom/**"
+ - "src/backend/base/langflow/schema/**"
+ - "src/backend/base/langflow/graph/**"
+ - "src/backend/base/langflow/utils/**"
+ - "src/backend/base/langflow/custom/**"
+ - "src/backend/base/langflow/components/**"
+
+workspace:
+ - "src/backend/base/langflow/inputs/**"
+ - "src/frontend/src/components/core/parameterRenderComponent/**"
+ - "src/frontend/src/CustomNodes/**"
+ - "src/frontend/src/modals/**"
+ - "src/frontend/src/style/**"
+ - "src/frontend/src/CustomEdges/**"
+ - "src/frontend/src/utils/reactflowUtils.ts"
+ - "src/frontend/src/utils/buildUtils.ts"
+ - "src/frontend/tests/core/features/**"
+ - "src/frontend/tests/core/unit/**"
+ - "src/frontend/tests/extended/features/**"
+ - "src/frontend/tests/core/regression/**"
+
+api:
+ - "src/backend/base/langflow/api/**"
+ - "src/frontend/src/controllers/**"
+ - "src/frontend/tests/core/features/**"
+ - "src/frontend/tests/extended/features/**"
+ - "src/frontend/tests/extended/regression/**"
+
+database:
+ - "src/backend/base/langflow/services/database/**"
+ - "src/backend/base/langflow/alembic/**"
+ - "src/frontend/src/controllers/**"
+ - "src/frontend/tests/core/features/**"
+ - "src/frontend/tests/extended/features/**"
diff --git a/langflow/.github/dependabot.yml b/langflow/.github/dependabot.yml
new file mode 100644
index 0000000..ecf2b5a
--- /dev/null
+++ b/langflow/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ commit-message:
+ prefix: "build(deps):"
+ include: scope
diff --git a/langflow/.github/release.yml b/langflow/.github/release.yml
new file mode 100644
index 0000000..fd78aad
--- /dev/null
+++ b/langflow/.github/release.yml
@@ -0,0 +1,35 @@
+changelog:
+ categories:
+ - title: 🚨 Breaking Changes
+ description: Changes that break existing functionality
+ labels:
+ - breaking
+ - title: ✨ New Features
+ description: New features and enhancements
+ labels:
+ - enhancement
+ - title: 🐛 Bug Fixes
+ description: Bug fixes and patches
+ labels:
+ - fix
+ - bug
+ - title: 📝 Documentation Updates
+ description: Changes to documentation
+ labels:
+ - documentation
+ - title: 🛠 Maintenance Tasks
+ description: Maintenance tasks and housekeeping
+ labels:
+ - chore
+ - refactor
+ - style
+ - performance
+ - build
+ - title: ✅ Tests
+ description: Changes to tests
+ labels:
+ - test
+ - title: Others
+ description: Other changes
+ labels:
+ - "*"
diff --git a/langflow/.github/semantic.yml b/langflow/.github/semantic.yml
new file mode 100644
index 0000000..efa3b02
--- /dev/null
+++ b/langflow/.github/semantic.yml
@@ -0,0 +1,2 @@
+titleOnly: true
+targetUrl: https://www.conventionalcommits.org/en/v1.0.0/#summary
diff --git a/langflow/.github/workflows/add-labels.yml b/langflow/.github/workflows/add-labels.yml
new file mode 100644
index 0000000..06dcde2
--- /dev/null
+++ b/langflow/.github/workflows/add-labels.yml
@@ -0,0 +1,65 @@
+name: Manage Review Labels
+
+on:
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ label-on-review:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ issues: write
+ steps:
+ - name: Manage LGTM Review Label
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const LGTM_LABEL = 'lgtm';
+
+ // Extract review details
+ const { state: reviewState } = context.payload.review;
+ const pullRequestNumber = context.payload.pull_request.number;
+ const repoDetails = {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: pullRequestNumber
+ };
+
+ // Log review information
+ console.log(`Processing review for PR #${pullRequestNumber}`);
+ console.log(`Review state: ${reviewState}`);
+
+ // Helper function to check for LGTM label
+ async function hasLgtmLabel() {
+ const { data: labels } = await github.rest.issues.listLabelsOnIssue(repoDetails);
+ return labels.some(label => label.name === LGTM_LABEL);
+ }
+
+ if (reviewState === 'approved') {
+ const lgtmExists = await hasLgtmLabel();
+
+ if (!lgtmExists) {
+ console.log(`Adding ${LGTM_LABEL} label to PR #${pullRequestNumber}`);
+ await github.rest.issues.addLabels({
+ ...repoDetails,
+ labels: [LGTM_LABEL]
+ });
+ console.log('Label added successfully');
+ } else {
+ console.log(`${LGTM_LABEL} label already exists`);
+ }
+ } else if (reviewState === 'changes_requested') {
+ const lgtmExists = await hasLgtmLabel();
+
+ if (lgtmExists) {
+ console.log(`Removing ${LGTM_LABEL} label from PR #${pullRequestNumber}`);
+ await github.rest.issues.removeLabel({
+ ...repoDetails,
+ name: LGTM_LABEL
+ });
+ console.log('Label removed successfully');
+ } else {
+ console.log(`No ${LGTM_LABEL} label to remove`);
+ }
+ }
diff --git a/langflow/.github/workflows/auto-update.yml b/langflow/.github/workflows/auto-update.yml
new file mode 100644
index 0000000..1d46fa1
--- /dev/null
+++ b/langflow/.github/workflows/auto-update.yml
@@ -0,0 +1,13 @@
+name: Auto-update
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ Auto:
+ name: Auto-update
+ runs-on: ubuntu-latest
+ steps:
+ - uses: tibdex/auto-update@v2
diff --git a/langflow/.github/workflows/ci.yml b/langflow/.github/workflows/ci.yml
new file mode 100644
index 0000000..2404d26
--- /dev/null
+++ b/langflow/.github/workflows/ci.yml
@@ -0,0 +1,184 @@
+name: CI
+
+on:
+ workflow_call:
+ inputs:
+ python-versions:
+ description: "Python Versions"
+ required: false
+ type: string
+ default: "['3.10']"
+ frontend-tests-folder:
+ description: "Frontend Tests Folder"
+ required: false
+ type: string
+ default: "tests/core"
+ release:
+ description: "Release"
+ required: false
+ type: boolean
+ default: false
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "(Optional) Branch to checkout"
+ required: false
+ type: string
+ openai_api_key:
+ description: "OpenAI API Key"
+ required: false
+ type: string
+ store_api_key:
+ description: "Store API Key"
+ required: false
+ type: string
+ python-versions:
+ description: "Python Versions"
+ required: false
+ type: string
+ default: "['3.10']"
+ pull_request:
+ types: [synchronize, labeled]
+ merge_group:
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ check-nightly-status:
+ name: Check Nightly Status
+ runs-on: ubuntu-latest
+ outputs:
+ should-proceed: ${{ steps.check-workflow.outputs.success }}
+ steps:
+ - name: Check nightly workflow status
+ id: check-workflow
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const workflow_name = 'nightly_build.yml';
+ const today = new Date();
+ today.setHours(0, 0, 0, 0); // Set to beginning of day
+
+ const { data: runs } = await github.rest.actions.listWorkflowRuns({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ workflow_id: workflow_name,
+ created: `>=${today.toISOString()}`,
+ per_page: 100, // Get more runs to check
+ status: 'completed'
+ });
+
+ if (runs.workflow_runs.length === 0) {
+ console.log('No completed workflow runs found today');
+ return core.setOutput('success', 'true');
+ }
+
+ // Check if any runs today were successful
+ const successfulTodayRuns = runs.workflow_runs.filter(run => run.conclusion === 'success');
+ const hasSuccessfulRunToday = successfulTodayRuns.length > 0;
+
+ console.log(`Found ${runs.workflow_runs.length} completed runs today, ${successfulTodayRuns.length} successful`);
+ core.setOutput('success', hasSuccessfulRunToday.toString());
+
+ set-ci-condition:
+ needs: check-nightly-status
+ name: Should Run CI
+ runs-on: ubuntu-latest
+ outputs:
+ should-run-ci: ${{ (needs.check-nightly-status.outputs.should-proceed == 'true' || github.event_name == 'workflow_dispatch') && ((contains( github.event.pull_request.labels.*.name, 'lgtm') && github.event.pull_request.draft == false) || (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'merge_group')) }}
+ steps:
+ # Do anything just to make the job run
+ - run: echo "Debug CI Condition"
+ - run: echo "Labels -> ${{ join(github.event.pull_request.labels.*.name, ',') }}"
+ - run: echo "IsDraft -> ${{ github.event.pull_request.draft }}"
+ - run: echo "Event name -> ${{ github.event_name }}"
+ - run: echo "Nightly build status -> ${{ needs.check-nightly-status.outputs.should-proceed }}"
+
+ path-filter:
+ needs: set-ci-condition
+ if: ${{ needs.set-ci-condition.outputs.should-run-ci == 'true' }}
+ name: Filter Paths
+ runs-on: ubuntu-latest
+ outputs:
+ python: ${{ steps.filter.outputs.python }}
+ frontend: ${{ steps.filter.outputs.frontend }}
+ docs: ${{ steps.filter.outputs.docs }}
+ frontend-tests: ${{ steps.filter.outputs.frontend-tests }}
+ components-changes: ${{ steps.filter.outputs.components-changes }}
+ starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }}
+ starter-projects: ${{ steps.filter.outputs.starter-projects }}
+ components: ${{ steps.filter.outputs.components }}
+ workspace: ${{ steps.filter.outputs.workspace }}
+ api: ${{ steps.filter.outputs.api }}
+ database: ${{ steps.filter.outputs.database }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.branch || github.ref }}
+ - name: Filter Paths
+ id: filter
+ uses: dorny/paths-filter@v3
+ with:
+ filters: ./.github/changes-filter.yaml
+
+ test-backend:
+ needs: path-filter
+ name: Run Backend Tests
+ if: ${{ needs.path-filter.outputs.python == 'true'}}
+ uses: ./.github/workflows/python_test.yml
+ with:
+ python-versions: ${{ inputs.python-versions || '["3.10"]' }}
+ test-frontend:
+ needs: path-filter
+ name: Run Frontend Tests
+ if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' || needs.path-filter.outputs.starter-projects == 'true' || needs.path-filter.outputs.components == 'true' || needs.path-filter.outputs.workspace == 'true' || needs.path-filter.outputs.api == 'true' || needs.path-filter.outputs.database == 'true' }}
+ uses: ./.github/workflows/typescript_test.yml
+ with:
+ tests_folder: ${{ inputs.frontend-tests-folder }}
+ release: ${{ inputs.release || false }}
+ secrets:
+ OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}"
+ STORE_API_KEY: "${{ secrets.STORE_API_KEY }}"
+ ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
+ TAVILY_API_KEY: "${{ secrets.TAVILY_API_KEY }}"
+
+ lint-backend:
+ needs: path-filter
+ if: ${{ needs.path-filter.outputs.python == 'true'}}
+ name: Lint Backend
+ uses: ./.github/workflows/lint-py.yml
+
+ test-docs-build:
+ needs: path-filter
+ if: ${{ needs.path-filter.outputs.docs == 'true' }}
+ name: Test Docs Build
+ uses: ./.github/workflows/docs_test.yml
+
+ # https://github.com/langchain-ai/langchain/blob/master/.github/workflows/check_diffs.yml
+ ci_success:
+ name: "CI Success"
+ needs:
+ [
+ test-backend,
+ test-frontend,
+ lint-backend,
+ test-docs-build,
+ set-ci-condition,
+ ]
+
+ if: always()
+ runs-on: ubuntu-latest
+ env:
+ JOBS_JSON: ${{ toJSON(needs) }}
+ RESULTS_JSON: ${{ toJSON(needs.*.result) }}
+ EXIT_CODE: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && needs.set-ci-condition.outputs.should-run-ci == 'true' && '0' || '1'}}
+ steps:
+ - name: "CI Success"
+ run: |
+ echo $JOBS_JSON
+ echo $RESULTS_JSON
+ echo "Exiting with $EXIT_CODE"
+ exit $EXIT_CODE
diff --git a/langflow/.github/workflows/codeflash.yml b/langflow/.github/workflows/codeflash.yml
new file mode 100644
index 0000000..843c37e
--- /dev/null
+++ b/langflow/.github/workflows/codeflash.yml
@@ -0,0 +1,33 @@
+name: Codeflash
+
+on:
+ pull_request:
+ paths:
+ - "src/backend/base/langflow/**"
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ optimize:
+ name: Optimize new Python code in this PR
+ if: ${{ github.actor != 'codeflash-ai[bot]' }}
+ runs-on: ubuntu-latest
+ env:
+ CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
+ CODEFLASH_PR_NUMBER: ${{ github.event.number }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - run: uv sync
+ - name: Run Codeflash Optimizer
+ working-directory: ./src/backend/base
+ continue-on-error: true
+ run: uv run codeflash
+ - name: Minimize uv cache
+ run: uv cache prune --ci
diff --git a/langflow/.github/workflows/codeql.yml b/langflow/.github/workflows/codeql.yml
new file mode 100644
index 0000000..c797583
--- /dev/null
+++ b/langflow/.github/workflows/codeql.yml
@@ -0,0 +1,66 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ 'dev', 'main' ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ 'dev' ]
+ schedule:
+ - cron: '17 2 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python', 'javascript' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Use only 'java' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/langflow/.github/workflows/codspeed.yml b/langflow/.github/workflows/codspeed.yml
new file mode 100644
index 0000000..96ff870
--- /dev/null
+++ b/langflow/.github/workflows/codspeed.yml
@@ -0,0 +1,47 @@
+name: Run benchmarks
+
+on:
+ push:
+ paths:
+ - "src/backend/base/**"
+ - "src/backend/tests/performance/**"
+ branches:
+ - "main" # or "master"
+ pull_request:
+ paths:
+ - "src/backend/base/**"
+ - "src/backend/tests/performance/**"
+ - "!src/backend/base/langflow/components/**"
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ codspeed:
+ name: Run benchmarks
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version:
+ - "3.12"
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Run benchmarks
+ uses: CodSpeedHQ/action@v3
+ with:
+ token: ${{ secrets.CODSPEED_TOKEN }}
+ run: |
+ uv run pytest src/backend/tests \
+ --ignore=src/backend/tests/integration \
+ --codspeed \
+ -m "not api_key_required" \
+ -n auto
+ - name: Minimize uv cache
+ run: uv cache prune --ci
diff --git a/langflow/.github/workflows/conventional-labels.yml b/langflow/.github/workflows/conventional-labels.yml
new file mode 100644
index 0000000..93137e0
--- /dev/null
+++ b/langflow/.github/workflows/conventional-labels.yml
@@ -0,0 +1,29 @@
+# Warning, do not check out untrusted code with
+# the pull_request_target event.
+name: Label PRs with Conventional Commits
+on:
+ pull_request_target:
+ types: [opened, edited, synchronize]
+ merge_group:
+
+jobs:
+ validate-pr:
+ name: Validate PR
+ runs-on: ubuntu-latest
+ steps:
+ - name: Validate the pull request
+ id: validate
+ uses: Namchee/conventional-pr@v0.15.6
+ with:
+ access_token: ${{ secrets.GITHUB_TOKEN }}
+ issue: false
+
+ label:
+ needs: validate-pr
+ name: Label PR
+ runs-on: ubuntu-latest
+ if: ${{ github.event.pull_request.user.type != 'Bot'}}
+ steps:
+ - uses: bcoe/conventional-release-labels@v1
+ with:
+ type_labels: '{"feat": "enhancement","fix": "bug","docs": "documentation","style": "style","refactor": "refactor","perf": "performance","test": "test","chore": "chore","build": "build"}'
diff --git a/langflow/.github/workflows/create-release.yml b/langflow/.github/workflows/create-release.yml
new file mode 100644
index 0000000..98329a3
--- /dev/null
+++ b/langflow/.github/workflows/create-release.yml
@@ -0,0 +1,36 @@
+name: Create Release
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Version to release"
+ required: true
+ type: string
+ ref:
+ description: "Commit to tag the release"
+ required: true
+ type: string
+ pre_release:
+ description: "Pre-release tag"
+ required: true
+ type: boolean
+
+jobs:
+ create_release:
+ name: Create Release Job
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ name: dist-main
+ path: dist
+ - name: Create Release Notes
+ uses: ncipollo/release-action@v1
+ with:
+ artifacts: "dist/*"
+ token: ${{ secrets.GITHUB_TOKEN }}
+ draft: false
+ generateReleaseNotes: true
+ prerelease: ${{ inputs.pre_release }}
+ tag: v${{ inputs.version }}
+ commit: ${{ inputs.ref }}
\ No newline at end of file
diff --git a/langflow/.github/workflows/deploy_gh-pages.yml b/langflow/.github/workflows/deploy_gh-pages.yml
new file mode 100644
index 0000000..9c38195
--- /dev/null
+++ b/langflow/.github/workflows/deploy_gh-pages.yml
@@ -0,0 +1,41 @@
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'docs/**'
+ # Review gh actions docs if you want to further define triggers, paths, etc
+ # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
+
+jobs:
+ deploy:
+ name: Deploy to GitHub Pages
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: yarn
+ cache-dependency-path: ./docs/yarn.lock
+
+ - name: Install dependencies
+ run: cd docs && yarn install
+ - name: Build website
+ run: cd docs && yarn build
+
+ # Popular action to deploy to GitHub Pages:
+ # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ # Build output to publish to the `gh-pages` branch:
+ publish_dir: ./docs/build
+ # The following lines assign commit authorship to the official
+ # GH-Actions bot for deploys to `gh-pages` branch:
+ # https://github.com/actions/checkout/issues/13#issuecomment-724415212
+ # The GH actions bot is used by default if you didn't specify the two fields.
+ # You can swap them out with your own user credentials.
diff --git a/langflow/.github/workflows/docker-build.yml b/langflow/.github/workflows/docker-build.yml
new file mode 100644
index 0000000..9565585
--- /dev/null
+++ b/langflow/.github/workflows/docker-build.yml
@@ -0,0 +1,331 @@
+name: Docker Build and Push
+run-name: Docker Build and Push @${{ inputs.release_type }} by @${{ github.actor }}
+on:
+ workflow_call:
+ inputs:
+ main_version:
+ required: true
+ type: string
+ description: "Main version to tag images with. Required for both main and base releases."
+ base_version:
+ required: false
+ type: string
+ description: "Base version to tag images with. Required for base release type."
+ release_type:
+ required: true
+ type: string
+ description: "Release type. One of 'main', 'main-ep', 'base', 'nightly-main', 'nightly-base'."
+ pre_release:
+ required: false
+ type: boolean
+ default: false
+ ref:
+ required: false
+ type: string
+ description: "Ref to check out. If not specified, will default to the main version or current branch."
+
+ workflow_dispatch:
+ inputs:
+ main_version:
+ description: "Main version to tag images with. Required for both main and base releases."
+ required: false
+ type: string
+ base_version:
+ description: "Base version to tag images with. Required for base release type."
+ required: false
+ type: string
+ release_type:
+ description: "Type of release. One of 'main', 'main-ep', 'base', 'nightly-main', 'nightly-base'."
+ required: true
+ type: string
+ pre_release:
+ required: false
+ type: boolean
+ default: false
+ ref:
+ required: false
+ type: string
+ description: "Ref to check out. If not specified, will default to the main version or current branch."
+
+
+env:
+ POETRY_VERSION: "1.8.2"
+ TEST_TAG: "langflowai/langflow:test"
+
+jobs:
+ get-version:
+ name: Get Version
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.get-version-input.outputs.version || steps.get-version-base.outputs.version || steps.get-version-main.outputs.version }}
+ steps:
+ - name: Verify a main version exists
+ if: ${{ inputs.main_version == '' }}
+ run: |
+ # due to our how we split packages, we need to have a main version to check out.
+ echo "Must specify a main version to check out."
+ exit 1
+
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || inputs.main_version || github.ref }}
+ persist-credentials: true
+
+ - name: Get Version to Tag
+ if: ${{ inputs.main_version != '' }}
+ id: get-version-input
+ run: |
+ # Produces the versions we will use to tag the docker images with.
+
+ if [[ "${{ inputs.release_type }}" == "base" && "${{ inputs.base_version }}" == '' ]]; then
+ echo "Must specify a base version for base release type."
+ exit 1
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "nightly-base" && "${{ inputs.base_version }}" == '' ]]; then
+ echo "Must specify a base version for nightly-base release type."
+ exit 1
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "main" && "${{ inputs.main_version }}" == '' ]]; then
+ echo "Must specify a main version for main release type."
+ exit 1
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "main-ep" && "${{ inputs.main_version }}" == '' ]]; then
+ echo "Must specify a main version for main-ep release type."
+ exit 1
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "nightly-main" && "${{ inputs.main_version }}" == '' ]]; then
+ echo "Must specify a main version for nightly-main release type."
+ exit 1
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
+ version=${{ inputs.base_version }}
+ echo "base version=${{ inputs.base_version }}"
+ echo version=$version
+ echo version=$version >> $GITHUB_OUTPUT
+ elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "main-ep" || "${{ inputs.release_type }}" == "nightly-main" ]]; then
+ version=${{ inputs.main_version }}
+ echo version=$version
+ echo version=$version >> $GITHUB_OUTPUT
+ else
+ echo "No version or ref specified. Exiting the workflow."
+ exit 1
+ fi
+ - name: Get Version Base
+ if: ${{ inputs.base_version == '' && (inputs.release_type == 'base' || inputs.release_type == 'nightly-base') }}
+ id: get-version-base
+ run: |
+ version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//')
+ if [ -z "$version" ]; then
+ echo "Failed to extract version from uv tree output"
+ exit 1
+ fi
+ echo version=$version
+ echo version=$version >> $GITHUB_OUTPUT
+ - name: Get Version Main
+ if: ${{ inputs.main_version == '' && (inputs.release_type == 'main' || inputs.release_type == 'main-ep' || inputs.release_type == 'nightly-main') }}
+ id: get-version-main
+ run: |
+ version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//')
+ echo version=$version
+ echo version=$version >> $GITHUB_OUTPUT
+ setup:
+ runs-on: ubuntu-latest
+ needs: get-version
+ outputs:
+ docker_tags: ${{ steps.set-vars.outputs.docker_tags }}
+ ghcr_tags: ${{ steps.set-vars.outputs.ghcr_tags }}
+ file: ${{ steps.set-vars.outputs.file }}
+ steps:
+ - name: Set Dockerfile and Tags
+ id: set-vars
+ run: |
+ nightly_suffix=''
+ if [[ "${{ inputs.release_type }}" == "nightly-base" || "${{ inputs.release_type }}" == "nightly-main" ]]; then
+ nightly_suffix="-nightly"
+ fi
+
+ if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
+ # LANGFLOW-BASE RELEASE
+ echo "docker_tags=langflowai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT
+ echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:base-${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:base-latest" >> $GITHUB_OUTPUT
+ echo "file=./docker/build_and_push_base.Dockerfile" >> $GITHUB_OUTPUT
+ else
+ if [[ "${{ inputs.pre_release }}" == "true" ]]; then
+ # LANGFLOW-MAIN PRE-RELEASE
+ echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT
+ echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }}" >> $GITHUB_OUTPUT
+ echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT
+ elif [[ "${{ inputs.release_type }}" == "main-ep" ]]; then
+ # LANGFLOW-MAIN (ENTRYPOINT) RELEASE
+ echo "docker_tags=langflowai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT
+ echo "ghcr_tags=ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-ep${nightly_suffix}:latest" >> $GITHUB_OUTPUT
+ echo "file=./docker/build_and_push_ep.Dockerfile" >> $GITHUB_OUTPUT
+ elif [[ "${{ inputs.release_type }}" == "main" || "${{ inputs.release_type }}" == "nightly-main" ]]; then
+ # LANGFLOW-MAIN RELEASE
+ echo "docker_tags=langflowai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},langflowai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT
+ echo "ghcr_tags=ghcr.io/langflow-ai/langflow${nightly_suffix}:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow${nightly_suffix}:latest" >> $GITHUB_OUTPUT
+ echo "file=./docker/build_and_push.Dockerfile" >> $GITHUB_OUTPUT
+ else
+ echo "Invalid release type. Exiting the workflow."
+ exit 1
+ fi
+ fi
+ build:
+ runs-on: ubuntu-latest
+ needs: [get-version, setup]
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || inputs.main_version || github.ref }}
+ persist-credentials: true
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: |
+ if [[ "${{ inputs.release_type }}" == "base" || "${{ inputs.release_type }}" == "nightly-base" ]]; then
+ uv sync --directory src/backend/base --no-dev --no-sources
+ else
+ uv sync --no-dev --no-sources
+ fi
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and Push to Docker Hub
+ uses: Wandalen/wretry.action@master
+ with:
+ action: docker/build-push-action@v6
+ with: |
+ context: .
+ push: true
+ file: ${{ needs.setup.outputs.file }}
+ tags: ${{ needs.setup.outputs.docker_tags }}
+ platforms: linux/amd64,linux/arm64
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Login to Github Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.TEMP_GHCR_TOKEN}}
+
+ - name: Build and push to Github Container Registry
+ uses: Wandalen/wretry.action@master
+ with:
+ action: docker/build-push-action@v6
+ with: |
+ context: .
+ push: true
+ file: ${{ needs.setup.outputs.file }}
+ tags: ${{ needs.setup.outputs.ghcr_tags }}
+ platforms: linux/amd64,linux/arm64
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ build_components:
+ if: ${{ inputs.release_type == 'main' }}
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ needs: [build, get-version]
+ strategy:
+ matrix:
+ component: [docker-backend, docker-frontend, ghcr-backend, ghcr-frontend]
+ include:
+ - component: docker-backend
+ dockerfile: ./docker/build_and_push_backend.Dockerfile
+ tags: langflowai/langflow-backend:${{ needs.get-version.outputs.version }},langflowai/langflow-backend:latest
+ langflow_image: langflowai/langflow:${{ needs.get-version.outputs.version }}
+ - component: docker-frontend
+ dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile
+ tags: langflowai/langflow-frontend:${{ needs.get-version.outputs.version }},langflowai/langflow-frontend:latest
+ langflow_image: langflowai/langflow:${{ needs.get-version.outputs.version }}
+ - component: ghcr-backend
+ dockerfile: ./docker/build_and_push_backend.Dockerfile
+ tags: ghcr.io/langflow-ai/langflow-backend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-backend:latest
+ langflow_image: ghcr.io/langflow-ai/langflow:${{ needs.get-version.outputs.version }}
+ - component: ghcr-frontend
+ dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile
+ tags: ghcr.io/langflow-ai/langflow-frontend:${{ needs.get-version.outputs.version }},ghcr.io/langflow-ai/langflow-frontend:latest
+ langflow_image: ghcr.io/langflow-ai/langflow:${{ needs.get-version.outputs.version }}
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || inputs.main_version || github.ref }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to Docker Hub
+ if: ${{ matrix.component == 'docker-backend' }} || ${{ matrix.component == 'docker-frontend' }}
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Login to Github Container Registry
+ if: ${{ matrix.component == 'ghcr-backend' }} || ${{ matrix.component == 'ghcr-frontend' }}
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.TEMP_GHCR_TOKEN}}
+
+ - name: Wait for propagation (for backend)
+ run: sleep 120
+
+ - name: Build and push ${{ matrix.component }}
+ uses: Wandalen/wretry.action@master
+ with:
+ action: docker/build-push-action@v6
+ with: |
+ context: .
+ push: true
+ build-args: |
+ LANGFLOW_IMAGE=${{ matrix.langflow_image }}
+ file: ${{ matrix.dockerfile }}
+ tags: ${{ matrix.tags }}
+ # provenance: false will result in a single manifest for all platforms which makes the image pullable from arm64 machines via the emulation (e.g. Apple Silicon machines)
+ provenance: false
+
+ restart-space:
+ name: Restart HuggingFace Spaces
+ if: ${{ inputs.release_type == 'main' }}
+ runs-on: ubuntu-latest
+ needs: [build, get-version]
+ strategy:
+ matrix:
+ python-version:
+ - "3.13"
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || inputs.main_version || github.ref }}
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Restart HuggingFace Spaces Build
+ run: |
+ uv run ./scripts/factory_restart_space.py --space "Langflow/Langflow" --token ${{ secrets.HUGGINGFACE_API_TOKEN }}
+
+
+
diff --git a/langflow/.github/workflows/docker_test.yml b/langflow/.github/workflows/docker_test.yml
new file mode 100644
index 0000000..e18756f
--- /dev/null
+++ b/langflow/.github/workflows/docker_test.yml
@@ -0,0 +1,65 @@
+name: Test Docker images
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "docker/**"
+ - "poetry.lock"
+ - "pyproject.toml"
+ - "src/backend/**"
+ - ".github/workflows/docker_test.yml"
+ pull_request:
+ branches: [dev]
+ paths:
+ - "docker/**"
+ - "poetry.lock"
+ - "pyproject.toml"
+ - "src/**"
+ - ".github/workflows/docker_test.yml"
+ workflow_dispatch:
+
+env:
+ POETRY_VERSION: "1.8.2"
+
+jobs:
+ test-docker:
+ runs-on: ubuntu-latest
+ name: Test docker images
+ steps:
+ - uses: actions/checkout@v4
+ - name: Build image
+ run: |
+ docker build -t langflowai/langflow:latest-dev \
+ -f docker/build_and_push.Dockerfile \
+ .
+ - name: Test image
+ run: |
+ expected_version=$(cat pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2)
+ version=$(docker run --rm --entrypoint bash langflowai/langflow:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'")
+ if [ "$expected_version" != "$version" ]; then
+ echo "Expected version: $expected_version"
+ echo "Actual version: $version"
+ exit 1
+ fi
+
+ - name: Build backend image
+ run: |
+ docker build -t langflowai/langflow-backend:latest-dev \
+ --build-arg LANGFLOW_IMAGE=langflowai/langflow:latest-dev \
+ -f docker/build_and_push_backend.Dockerfile \
+ .
+ - name: Test backend image
+ run: |
+ expected_version=$(cat pyproject.toml | grep version | head -n 1 | cut -d '"' -f 2)
+ version=$(docker run --rm --entrypoint bash langflowai/langflow-backend:latest-dev -c "python -c 'from langflow.utils.version import get_version_info; print(get_version_info()[\"version\"])'")
+ if [ "$expected_version" != "$version" ]; then
+ echo "Expected version: $expected_version"
+ echo "Actual version: $version"
+ exit 1
+ fi
+ - name: Build frontend image
+ run: |
+ docker build -t langflowai/langflow-frontend:latest-dev \
+ -f docker/frontend/build_and_push_frontend.Dockerfile \
+ .
diff --git a/langflow/.github/workflows/docs_test.yml b/langflow/.github/workflows/docs_test.yml
new file mode 100644
index 0000000..0876520
--- /dev/null
+++ b/langflow/.github/workflows/docs_test.yml
@@ -0,0 +1,38 @@
+name: Test Docs Build
+
+on:
+ workflow_call:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "(Optional) Branch to checkout"
+ required: false
+ type: string
+
+env:
+ NODE_VERSION: "21"
+
+jobs:
+ test-docs-build:
+ name: Test Docs Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.branch || github.ref }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: yarn
+ cache-dependency-path: ./docs/yarn.lock
+
+ - name: Install dependencies
+ run: cd docs && yarn install --frozen-lockfile
+
+ - name: Build docs
+ run: cd docs && yarn build
diff --git a/langflow/.github/workflows/fetch_docs_notion.yml b/langflow/.github/workflows/fetch_docs_notion.yml
new file mode 100644
index 0000000..c054f79
--- /dev/null
+++ b/langflow/.github/workflows/fetch_docs_notion.yml
@@ -0,0 +1,54 @@
+name: Fetch Docs from Notion
+
+on:
+ workflow_dispatch:
+
+env:
+ NODE_VERSION: "21"
+
+jobs:
+ fetch-docs:
+ name: Fetch Docs from Notion
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Get current date
+ id: date
+ run: echo "DATE=$(date +'%Y%m%d%H%M%S')" >> "$GITHUB_OUTPUT"
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: yarn
+ cache-dependency-path: ./docs/yarn.lock
+
+ - name: Install Node.js dependencies
+ run: |
+ cd docs
+ yarn install --frozen-lockfile
+ if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
+
+ - name: Fetch Docs from Notion
+ run: |
+ cd docs
+ yarn pull
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
+ NOTION_DOCS_ROOT_PAGE_ID: ${{ secrets.NOTION_DOCS_ROOT_PAGE_ID }}
+
+ - name: Create Pull Request
+ id: create_pr
+ uses: peter-evans/create-pull-request@v7
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ commit-message: Update docs from Notion
+ branch: update-docs-${{ steps.date.outputs.DATE }}
+ base: main
+ title: "docs: update docs from notion"
+ body: This PR updates the documentation from Notion.
+ labels: documentation
diff --git a/langflow/.github/workflows/integration_tests.yml b/langflow/.github/workflows/integration_tests.yml
new file mode 100644
index 0000000..4121b17
--- /dev/null
+++ b/langflow/.github/workflows/integration_tests.yml
@@ -0,0 +1,53 @@
+name: Integration Tests
+
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: "(Optional) ref to checkout"
+ required: false
+ type: string
+ workflow_call:
+ inputs:
+ python-versions:
+ description: "(Optional) Python versions to test"
+ required: true
+ type: string
+ default: "['3.10', '3.11', '3.12', '3.13']"
+ ref:
+ description: "(Optional) ref to checkout"
+ required: false
+ type: string
+
+env:
+ POETRY_VERSION: "1.8.2"
+
+jobs:
+ integration-tests:
+ name: Run Integration Tests
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 1 # Currently, we can only run at a time for collection-per-db-constraints
+ matrix:
+ python-version:
+ - "3.13"
+ - "3.12"
+ - "3.11"
+ - "3.10"
+ env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
+ ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Run integration tests with api keys
+ timeout-minutes: 20
+ run: |
+ make integration_tests_api_keys
diff --git a/langflow/.github/workflows/js_autofix.yml b/langflow/.github/workflows/js_autofix.yml
new file mode 100644
index 0000000..ab18e01
--- /dev/null
+++ b/langflow/.github/workflows/js_autofix.yml
@@ -0,0 +1,45 @@
+name: autofix.ci
+
+on:
+ pull_request:
+ paths:
+ - "src/frontend/**"
+
+permissions:
+ contents: read
+
+env:
+ NODE_VERSION: "21"
+jobs:
+ autofix:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Cache Node.js dependencies
+ uses: actions/cache@v4
+ id: npm-cache
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('src/frontend/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install Node.js dependencies
+ run: |
+ cd src/frontend
+ npm ci
+ if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
+ - name: Run Prettier
+ run: |
+ cd src/frontend
+ npm run format
+
+ - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
diff --git a/langflow/.github/workflows/lint-js.yml b/langflow/.github/workflows/lint-js.yml
new file mode 100644
index 0000000..8923473
--- /dev/null
+++ b/langflow/.github/workflows/lint-js.yml
@@ -0,0 +1,53 @@
+name: Lint Frontend
+
+on:
+ workflow_call:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "(Optional) Branch to checkout"
+ required: false
+ type: string
+
+
+env:
+ NODE_VERSION: "21"
+
+jobs:
+ run-linters:
+ name: Run Prettier
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.branch || github.ref }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Cache Node.js dependencies
+ uses: actions/cache@v4
+ id: npm-cache
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('src/frontend/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Install Node.js dependencies
+ run: |
+ cd src/frontend
+ npm install
+ if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
+
+ - name: Run Prettier
+ run: |
+ cd src/frontend
+ npm run check-format
diff --git a/langflow/.github/workflows/lint-py.yml b/langflow/.github/workflows/lint-py.yml
new file mode 100644
index 0000000..471dc30
--- /dev/null
+++ b/langflow/.github/workflows/lint-py.yml
@@ -0,0 +1,44 @@
+name: Lint Python
+
+on:
+ workflow_call:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "(Optional) Branch to checkout"
+ required: false
+ type: string
+env:
+ POETRY_VERSION: "1.8.2"
+
+
+jobs:
+ lint:
+ name: Run Mypy
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version:
+ - "3.13"
+ - "3.12"
+ - "3.11"
+ - "3.10"
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.branch || github.ref }}
+ persist-credentials: true
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install the project
+ run: uv sync
+ - name: Run Mypy
+ run: |
+ uv run mypy --namespace-packages -p "langflow"
+ env:
+ GITHUB_TOKEN: ${{ secrets.github_token }}
+ - name: Minimize uv cache
+ run: uv cache prune --ci
diff --git a/langflow/.github/workflows/matchers/ruff.json b/langflow/.github/workflows/matchers/ruff.json
new file mode 100644
index 0000000..53b21be
--- /dev/null
+++ b/langflow/.github/workflows/matchers/ruff.json
@@ -0,0 +1,14 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "ruff",
+ "pattern": [
+ {
+ "regexp": "^(Would reformat): (.+)$",
+ "message": 1,
+ "file": 2
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/langflow/.github/workflows/nightly_build.yml b/langflow/.github/workflows/nightly_build.yml
new file mode 100644
index 0000000..e037ffd
--- /dev/null
+++ b/langflow/.github/workflows/nightly_build.yml
@@ -0,0 +1,198 @@
+name: Nightly Build
+
+on:
+ workflow_dispatch:
+ schedule:
+ # Run job at 6:30 UTC, 10.30pm PST, or 11.30pm PDT
+ - cron: "30 6 * * *"
+
+env:
+ POETRY_VERSION: "1.8.3"
+ PYTHON_VERSION: "3.13"
+
+jobs:
+ create-nightly-tag:
+ if: github.repository == 'langflow-ai/langflow'
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash -ex -o pipefail {0}
+ permissions:
+ # Required to create tag
+ contents: write
+ outputs:
+ main_tag: ${{ steps.generate_main_tag.outputs.main_tag }}
+ base_tag: ${{ steps.set_base_tag.outputs.base_tag }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: true
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+
+ - name: Generate main nightly tag
+ id: generate_main_tag
+ run: |
+ # NOTE: This outputs the tag with the `v` prefix.
+ MAIN_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py main)"
+ echo "main_tag=$MAIN_TAG" >> $GITHUB_OUTPUT
+ echo "main_tag=$MAIN_TAG"
+
+ - name: Delete existing tag if it exists
+ id: check_main_tag
+ run: |
+ git fetch --tags
+ git tag -d ${{ steps.generate_main_tag.outputs.main_tag }} || true
+ git push --delete origin ${{ steps.generate_main_tag.outputs.main_tag }} || true
+ echo "main_tag_exists=false" >> $GITHUB_OUTPUT
+
+ - name: Generate base nightly tag
+ id: generate_base_tag
+ run: |
+ # NOTE: This outputs the tag with the `v` prefix.
+ BASE_TAG="$(uv run ./scripts/ci/pypi_nightly_tag.py base)"
+ echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT
+ echo "base_tag=$BASE_TAG"
+
+ - name: Commit tag
+ id: commit_tag
+ run: |
+ # If the main tag does not exist in GH, we create the base tag from the existing codebase.
+
+ git config --global user.email "bot-nightly-builds@langflow.org"
+ git config --global user.name "Langflow Bot"
+
+ MAIN_TAG="${{ steps.generate_main_tag.outputs.main_tag }}"
+ BASE_TAG="${{ steps.generate_base_tag.outputs.base_tag }}"
+ echo "Updating base project version to $BASE_TAG and updating main project version to $MAIN_TAG"
+ uv run ./scripts/ci/update_pyproject_combined.py main $MAIN_TAG $BASE_TAG
+
+ uv lock
+ cd src/backend/base && uv lock && cd ../../..
+
+ git add pyproject.toml src/backend/base/pyproject.toml uv.lock src/backend/base/uv.lock
+ git commit -m "Update version and project name"
+
+ echo "Tagging main with $MAIN_TAG"
+ if ! git tag -a $MAIN_TAG -m "Langflow nightly $MAIN_TAG"; then
+ echo "Tag creation failed. Exiting the workflow."
+ exit 1
+ fi
+
+ echo "Pushing main tag $MAIN_TAG"
+ if ! git push origin $MAIN_TAG; then
+ echo "Tag push failed. Check if the tag already exists. Exiting the workflow."
+ exit 1
+ fi
+ # TODO: notify on failure
+
+ - name: Checkout main nightly tag
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ steps.generate_main_tag.outputs.main_tag }}
+
+ - name: Retrieve Base Tag
+ id: retrieve_base_tag
+ working-directory: src/backend/base
+ run: |
+ # If the main tag already exists, we need to retrieve the base version from the main tag codebase.
+ version=$(uv tree | grep 'langflow-base' | awk '{print $3}')
+ echo "base_tag=$version" >> $GITHUB_OUTPUT
+ echo "base_tag=$version"
+
+ - name: Set Base Tag
+ id: set_base_tag
+ run: |
+ if [ "${{ steps.retrieve_base_tag.conclusion }}" != "skipped" ] && [ "${{ steps.retrieve_base_tag.outputs.base_tag }}" ]; then
+ BASE_TAG="${{ steps.retrieve_base_tag.outputs.base_tag }}"
+ echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT
+ echo "base_tag=$BASE_TAG"
+ elif [ "${{ steps.commit_tag.conclusion }}" != "skipped" ] && [ "${{ steps.generate_base_tag.outputs.base_tag }}" ]; then
+ BASE_TAG="${{ steps.generate_base_tag.outputs.base_tag }}"
+ echo "base_tag=$BASE_TAG" >> $GITHUB_OUTPUT
+ echo "base_tag=$BASE_TAG"
+ else
+ echo "No base tag found. Exiting the workflow."
+ exit 1
+ fi
+
+ frontend-tests:
+ if: github.repository == 'langflow-ai/langflow'
+ name: Run Frontend Tests
+ needs: create-nightly-tag
+ uses: ./.github/workflows/typescript_test.yml
+ with:
+ tests_folder: "tests"
+ release: true
+ secrets:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ STORE_API_KEY: ${{ secrets.STORE_API_KEY }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
+
+ backend-unit-tests:
+ if: github.repository == 'langflow-ai/langflow'
+ name: Run Backend Unit Tests
+ needs: create-nightly-tag
+ uses: ./.github/workflows/python_test.yml
+ with:
+ python-versions: '["3.10", "3.11", "3.12", "3.13"]'
+
+ # Not making nightly builds dependent on integration test success
+ # due to inherent flakiness of 3rd party integrations
+ # Revisit when https://github.com/langflow-ai/langflow/pull/3607 is merged.
+ # backend-integration-tests:
+ # name: Run Backend Integration Tests
+ # needs: create-nightly-tag
+ # uses: ./.github/workflows/integration_tests.yml
+ # with:
+ # python-versions: '["3.10", "3.11", "3.12", "3.13"]'
+ # ref: ${{ needs.create-nightly-tag.outputs.tag }}
+
+ release-nightly-build:
+ if: github.repository == 'langflow-ai/langflow'
+ name: Run Nightly Langflow Build
+ needs: [frontend-tests, backend-unit-tests, create-nightly-tag]
+ uses: ./.github/workflows/release_nightly.yml
+ with:
+ build_docker_base: true
+ build_docker_main: true
+ nightly_tag_main: ${{ needs.create-nightly-tag.outputs.main_tag }}
+ nightly_tag_base: ${{ needs.create-nightly-tag.outputs.base_tag }}
+ secrets: inherit
+
+ # slack-notification:
+ # name: Send Slack Notification
+ # needs: run-nightly-build
+ # runs-on: ubuntu-latest
+ # steps:
+ # - name: Send success notification to Slack
+ # if: success()
+ # uses: slackapi/slack-github-action@v1.26.0
+ # with:
+ # payload: |
+ # {
+ # "channel": "#langflow-nightly-builds",
+ # "username": "GitHub Actions",
+ # "text": "Nightly Build Successful :white_check_mark:",
+ # "icon_emoji": ":rocket:"
+ # }
+ # env:
+ # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+ # - name: Send failure notification to Slack
+ # if: failure()
+ # uses: slackapi/slack-github-action@v1.26.0
+ # with:
+ # payload: |
+ # {
+ # "channel": "#langflow-nightly-builds",
+ # "username": "GitHub Actions",
+ # "text": "Nightly Build Failed :x:",
+ # "icon_emoji": ":warning:"
+ # }
+ # env:
+ # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/langflow/.github/workflows/py_autofix.yml b/langflow/.github/workflows/py_autofix.yml
new file mode 100644
index 0000000..453b781
--- /dev/null
+++ b/langflow/.github/workflows/py_autofix.yml
@@ -0,0 +1,43 @@
+name: autofix.ci
+on:
+ pull_request:
+ paths:
+ - "**/*.py"
+env:
+ POETRY_VERSION: "1.8.2"
+
+jobs:
+ lint:
+ name: Run Ruff Check and Format
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - run: uv run ruff check --fix-only .
+ - run: uv run ruff format . --config pyproject.toml
+ - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
+ - name: Minimize uv cache
+ run: uv cache prune --ci
+
+ update-starter-projects:
+ name: Update Starter Projects
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+
+ - name: "Install dependencies"
+ run: |
+ uv sync
+ uv pip install -e .
+
+ - name: Run starter projects update
+ run: uv run python scripts/ci/update_starter_projects.py
+
+ - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
+
+ - name: Minimize uv cache
+ run: uv cache prune --ci
+
diff --git a/langflow/.github/workflows/python_test.yml b/langflow/.github/workflows/python_test.yml
new file mode 100644
index 0000000..26f5621
--- /dev/null
+++ b/langflow/.github/workflows/python_test.yml
@@ -0,0 +1,151 @@
+name: Python tests
+
+on:
+ workflow_call:
+ inputs:
+ python-versions:
+ description: "(Optional) Python versions to test"
+ required: true
+ type: string
+ default: "['3.10', '3.11', '3.12', '3.13']"
+ ref:
+ description: "(Optional) ref to checkout"
+ required: false
+ type: string
+ nightly:
+ description: "Whether run is from the nightly build"
+ required: false
+ type: boolean
+ default: false
+ workflow_dispatch:
+ inputs:
+ python-versions:
+ description: "(Optional) Python versions to test"
+ required: true
+ type: string
+ default: "['3.10', '3.11', '3.12', '3.13']"
+env:
+ POETRY_VERSION: "1.8.2"
+ NODE_VERSION: "21"
+ PYTEST_RUN_PATH: "src/backend/tests"
+
+jobs:
+ build:
+ name: Unit Tests - Python ${{ matrix.python-version }} - Group ${{ matrix.group }}
+ runs-on: ubuntu-latest
+ env:
+ UV_CACHE_DIR: /tmp/.uv-cache
+ strategy:
+ matrix:
+ python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
+ splitCount: [5]
+ group: [1, 2, 3, 4, 5]
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install the project
+ run: uv sync
+ - name: Run unit tests
+ uses: nick-fields/retry@v3
+ with:
+ timeout_minutes: 12
+ max_attempts: 2
+ command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5"
+ - name: Minimize uv cache
+ run: uv cache prune --ci
+ integration-tests:
+ name: Integration Tests - Python ${{ matrix.python-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]' ) }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install the project
+ run: uv sync
+ - name: Run integration tests
+ run: make integration_tests_no_api_keys
+ - name: Minimize uv cache
+ run: uv cache prune --ci
+ test-cli:
+ name: Test CLI - Python ${{ matrix.python-version }}
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ${{ fromJson(inputs.python-versions || '["3.10", "3.11", "3.12", "3.13"]') }}
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Check Version
+ id: check-version
+ # We need to print $3 because langflow-base is a dependency of langflow
+ # For langlow we'd use print $2
+ run: |
+ version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//')
+ url="https://pypi.org/pypi/langflow-base/json"
+ if [ ${{ inputs.nightly }} == true ]; then
+ url="https://pypi.org/pypi/langflow-base-nightly/json"
+ fi
+
+ last_released_version=$(curl -s $url | jq -r '.releases | keys | .[]' | sort -V | tail -n 1)
+ if [ "$version" != "$last_released_version" ]; then
+ echo "Version $version has not been released yet. Skipping the rest of the job."
+ echo skipped=true >> $GITHUB_OUTPUT
+ exit 0
+ else
+ echo version=$version >> $GITHUB_OUTPUT
+ echo skipped=false >> $GITHUB_OUTPUT
+ fi
+ - name: Build wheel
+ if: steps.check-version.outputs.skipped == 'false'
+ run: |
+ make build main=true
+ - name: Install wheel and Test CLI
+ if: steps.check-version.outputs.skipped == 'false'
+ run: |
+ uv venv new-venv
+ source new-venv/bin/activate
+ uv pip install dist/*.whl
+ - name: Test CLI
+ if: steps.check-version.outputs.skipped == 'false'
+ run: |
+ source new-venv/bin/activate
+ python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
+ SERVER_PID=$!
+ # Wait for the server to start
+ timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 5; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
+ # Terminate the server
+ kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
+ sleep 20 # give the server some time to terminate
+ # Check if the server is still running
+ if kill -0 $SERVER_PID 2>/dev/null; then
+ echo "Failed to terminate the server"
+ exit 0
+ else
+ echo "Server terminated successfully"
+ fi
+ - name: Minimize uv cache
+ run: uv cache prune --ci
diff --git a/langflow/.github/workflows/release.yml b/langflow/.github/workflows/release.yml
new file mode 100644
index 0000000..3e4b4cb
--- /dev/null
+++ b/langflow/.github/workflows/release.yml
@@ -0,0 +1,243 @@
+name: Langflow Release
+run-name: Langflow Release by @${{ github.actor }}
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_package_base:
+ description: "Release Langflow Base"
+ required: true
+ type: boolean
+ default: false
+ release_package_main:
+ description: "Release Langflow"
+ required: true
+ type: boolean
+ default: false
+ build_docker_base:
+ description: "Build Docker Image for Langflow Base"
+ required: true
+ type: boolean
+ default: false
+ build_docker_main:
+ description: "Build Docker Image for Langflow"
+ required: true
+ type: boolean
+ default: false
+ build_docker_ep:
+ description: "Build Docker Image for Langflow with Entrypoint"
+ required: false
+ type: boolean
+ default: false
+ pre_release:
+ description: "Pre-release"
+ required: false
+ type: boolean
+ default: false
+ create_release:
+ description: "Whether to create a gh release"
+ required: false
+ type: boolean
+ default: true
+
+
+jobs:
+ ci:
+ if: ${{ github.event.inputs.release_package_base == 'true' || github.event.inputs.release_package_main == 'true' }}
+ name: CI
+ uses: ./.github/workflows/ci.yml
+ with:
+ python-versions: "['3.10', '3.11', '3.12', '3.13']"
+ frontend-tests-folder: "tests"
+ release: true
+
+ release-base:
+ name: Release Langflow Base
+ needs: [ci]
+ if: inputs.release_package_base == true
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.check-version.outputs.version }}
+ skipped: ${{ steps.check-version.outputs.skipped }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Setup Environment
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+ - name: Check Version
+ id: check-version
+ run: |
+ version=$(uv tree | grep 'langflow-base' | awk '{print $3}' | sed 's/^v//')
+ last_released_version=$(curl -s "https://pypi.org/pypi/langflow-base/json" | jq -r '.releases | keys | .[]' | sort -V | tail -n 1)
+ if [ "$version" = "$last_released_version" ]; then
+ echo "Version $version is already released. Skipping release."
+ echo skipped=true >> $GITHUB_OUTPUT
+ exit 0
+ else
+ echo version=$version >> $GITHUB_OUTPUT
+ echo skipped=false >> $GITHUB_OUTPUT
+ fi
+ - name: Build project for distribution
+ if: steps.check-version.outputs.skipped == 'false'
+ run: make build base=true args="--wheel"
+ - name: Test CLI
+ if: steps.check-version.outputs.skipped == 'false'
+ run: |
+ # TODO: Unsure why the whl is not built in src/backend/base/dist
+ mkdir src/backend/base/dist
+ mv dist/*.whl src/backend/base/dist
+ uv pip install src/backend/base/dist/*.whl
+ uv run python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
+ SERVER_PID=$!
+ # Wait for the server to start
+ timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
+ # Terminate the server
+ kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
+ sleep 20 # give the server some time to terminate
+ # Check if the server is still running
+ if kill -0 $SERVER_PID 2>/dev/null; then
+ echo "Failed to terminate the server"
+ exit 0
+ else
+ echo "Server terminated successfully"
+ fi
+ - name: Publish to PyPI
+ if: steps.check-version.outputs.skipped == 'false'
+ env:
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+ run: |
+ make publish base=true
+ - name: Upload Artifact
+ if: steps.check-version.outputs.skipped == 'false'
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-base
+ path: src/backend/base/dist
+
+ release-main:
+ name: Release Langflow Main
+ if: inputs.release_package_main == true
+ needs: [release-base]
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.check-version.outputs.version }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Setup Environment
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+
+ # If pre-release is true, we need to check if ["a", "b", "rc", "dev", "post"] is in the version string
+ # if the version string is incorrect, we need to exit the workflow
+ - name: Check if pre-release
+ if: inputs.pre_release == 'true'
+ run: |
+ version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//')
+ if [[ "${version}" =~ ^([0-9]+\.)?([0-9]+\.)?[0-9]+((a|b|rc|dev|post)([0-9]+))$ ]]; then
+ echo "Pre-release version detected. Continuing with the release."
+ else
+ echo "Invalid pre-release version detected. Exiting the workflow."
+ exit 1
+ fi
+ - name: Check Version
+ id: check-version
+ run: |
+ version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}' | sed 's/^v//')
+ last_released_version=$(curl -s "https://pypi.org/pypi/langflow/json" | jq -r '.releases | keys | .[]' | sort -V | tail -n 1)
+ if [ "$version" = "$last_released_version" ]; then
+ echo "Version $version is already released. Skipping release."
+ exit 1
+ else
+ echo version=$version >> $GITHUB_OUTPUT
+ fi
+ - name: Wait for PyPI Propagation
+ if: needs.release-base.outputs.skipped == 'false'
+ run: sleep 300 # wait for 5 minutes to ensure PyPI propagation
+
+ - name: Build project for distribution
+ run: make build main=true args="--no-sources --wheel"
+ - name: Test CLI
+ run: |
+ uv pip install dist/*.whl
+ uv run python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
+ SERVER_PID=$!
+ # Wait for the server to start
+ timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/health_check; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
+ # Terminate the server
+ kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
+ sleep 20 # give the server some time to terminate
+ # Check if the server is still running
+ if kill -0 $SERVER_PID 2>/dev/null; then
+ echo "Failed to terminate the server"
+ exit 0
+ else
+ echo "Server terminated successfully"
+ fi
+ - name: Publish to PyPI
+ env:
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+ run: |
+ make publish main=true
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-main
+ path: dist
+
+ call_docker_build_base:
+ name: Call Docker Build Workflow for Langflow Base
+ if: inputs.build_docker_base == true
+ needs: [release-base, release-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ base_version: ${{ needs.release-base.outputs.version }}
+ main_version: ${{ needs.release-main.outputs.version }}
+ release_type: base
+ pre_release: ${{ inputs.pre_release }}
+ secrets: inherit
+
+ call_docker_build_main:
+ name: Call Docker Build Workflow for Langflow
+ if: inputs.build_docker_main == true
+ needs: [release-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ main_version: ${{ needs.release-main.outputs.version }}
+ release_type: main
+ pre_release: ${{ inputs.pre_release }}
+ secrets: inherit
+
+ call_docker_build_main_ep:
+ name: Call Docker Build Workflow for Langflow with Entrypoint
+ if: inputs.build_docker_ep == true
+ needs: [release-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ main_version: ${{ needs.release-main.outputs.version }}
+ release_type: main-ep
+ pre_release: False
+ secrets: inherit
+
+ create_release:
+ name: Create Release
+ runs-on: ubuntu-latest
+ needs: release-main
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ name: dist-main
+ path: dist
+ - name: Create Release
+ uses: ncipollo/release-action@v1
+ with:
+ artifacts: "dist/*"
+ token: ${{ secrets.GITHUB_TOKEN }}
+ draft: false
+ generateReleaseNotes: true
+ prerelease: ${{ inputs.pre_release }}
+ tag: ${{ needs.release-main.outputs.version }}
+ commit: ${{ github.ref }}
diff --git a/langflow/.github/workflows/release_nightly.yml b/langflow/.github/workflows/release_nightly.yml
new file mode 100644
index 0000000..277ca6c
--- /dev/null
+++ b/langflow/.github/workflows/release_nightly.yml
@@ -0,0 +1,236 @@
+name: Langflow Nightly Build
+run-name: Langflow Nightly Release by @${{ github.actor }}
+
+on:
+ workflow_dispatch:
+ inputs:
+ build_docker_base:
+ description: "Build Docker Image for Langflow Nightly Base"
+ required: true
+ type: boolean
+ default: false
+ build_docker_main:
+ description: "Build Docker Image for Langflow Nightly"
+ required: true
+ type: boolean
+ default: false
+ build_docker_ep:
+ description: "Build Docker Image for Langflow Nightly with Entrypoint"
+ required: false
+ type: boolean
+ default: false
+ nightly_tag_main:
+ description: "Tag for the nightly main build"
+ required: true
+ type: string
+ nightly_tag_base:
+ description: "Tag for the nightly base build"
+ required: true
+ type: string
+ workflow_call:
+ inputs:
+ build_docker_base:
+ description: "Build Docker Image for Langflow Nightly Base"
+ required: true
+ type: boolean
+ default: false
+ build_docker_main:
+ description: "Build Docker Image for Langflow Nightly"
+ required: true
+ type: boolean
+ default: false
+ build_docker_ep:
+ description: "Build Docker Image for Langflow Nightly with Entrypoint"
+ required: false
+ type: boolean
+ default: false
+ nightly_tag_main:
+ description: "Tag for the nightly main build"
+ required: true
+ type: string
+ nightly_tag_base:
+ description: "Tag for the nightly base build"
+ required: true
+ type: string
+
+env:
+ POETRY_VERSION: "1.8.3"
+ PYTHON_VERSION: "3.13"
+
+jobs:
+ release-nightly-base:
+ name: Release Langflow Nightly Base
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ outputs:
+ version: ${{ steps.verify.outputs.version }}
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.nightly_tag_main }}
+ persist-credentials: true
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+
+ - name: Verify Nightly Name and Version
+ id: verify
+ run: |
+ name=$(uv tree | grep 'langflow-base' | awk '{print $2}')
+ version=$(uv tree | grep 'langflow-base' | awk '{print $3}')
+ if [ "$name" != "langflow-base-nightly" ]; then
+ echo "Name $name does not match langflow-base-nightly. Exiting the workflow."
+ exit 1
+ fi
+ if [ "$version" != "${{ inputs.nightly_tag_base }}" ]; then
+ echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_base }}. Exiting the workflow."
+ exit 1
+ fi
+ # Strip the leading `v` from the version
+ version=$(echo $version | sed 's/^v//')
+ echo "version=$version" >> $GITHUB_OUTPUT
+
+ - name: Build project for distribution
+ run: |
+ rm -rf src/backend/base/dist
+ rm -rf dist
+ make build base=true args="--wheel"
+
+ - name: Test CLI
+ run: |
+ # TODO: Unsure why the whl is not built in src/backend/base/dist
+ mkdir src/backend/base/dist
+ mv dist/*.whl src/backend/base/dist/
+ python -m pip install src/backend/base/dist/*.whl
+ python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
+ SERVER_PID=$!
+ # Wait for the server to start
+ timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
+ # Terminate the server
+ kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
+ sleep 20 # give the server some time to terminate
+ # Check if the server is still running
+ if kill -0 $SERVER_PID 2>/dev/null; then
+ echo "Failed to terminate the server"
+ exit 0
+ else
+ echo "Server terminated successfully"
+ fi
+
+ - name: Publish to PyPI
+ env:
+ POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+ run: |
+ make publish base=true
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-base
+ path: src/backend/base/dist
+
+ release-nightly-main:
+ name: Release Langflow Nightly Main
+ needs: [release-nightly-base]
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.verify.outputs.version }}
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.nightly_tag_main}}
+ persist-credentials: true
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+
+ - name: Verify Nightly Name and Version
+ id: verify
+ run: |
+ name=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $1}')
+ version=$(uv tree | grep 'langflow' | grep -v 'langflow-base' | awk '{print $2}')
+ if [ "$name" != "langflow-nightly" ]; then
+ echo "Name $name does not match langflow-nightly. Exiting the workflow."
+ exit 1
+ fi
+ if [ "$version" != "${{ inputs.nightly_tag_main }}" ]; then
+ echo "Version $version does not match nightly tag ${{ inputs.nightly_tag_main }}. Exiting the workflow."
+ exit 1
+ fi
+ # Strip the leading `v` from the version
+ version=$(echo $version | sed 's/^v//')
+ echo "version=$version" >> $GITHUB_OUTPUT
+ - name: Wait for PyPI Propagation
+ run: sleep 300 # wait for 5 minutes to ensure PyPI propagation of base
+
+ - name: Build project for distribution
+ run: make build main=true args="--no-sources --wheel"
+ - name: Test CLI
+ run: |
+ uv pip install dist/*.whl
+ uv run python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
+ SERVER_PID=$!
+ # Wait for the server to start
+ timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/health_check; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
+ # Terminate the server
+ kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
+ sleep 20 # give the server some time to terminate
+ # Check if the server is still running
+ if kill -0 $SERVER_PID 2>/dev/null; then
+ echo "Failed to terminate the server"
+ exit 0
+ else
+ echo "Server terminated successfully"
+ fi
+ - name: Publish to PyPI
+ env:
+ POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
+ run: |
+ make publish main=true
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-main
+ path: dist
+
+ call_docker_build_base:
+ name: Call Docker Build Workflow for Langflow Base
+ if: always() && ${{ inputs.build_docker_base == 'true' }}
+ needs: [release-nightly-base, release-nightly-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ release_type: nightly-base
+ base_version: ${{ inputs.nightly_tag_base }}
+ main_version: ${{ inputs.nightly_tag_main }}
+ secrets: inherit
+
+ call_docker_build_main:
+ name: Call Docker Build Workflow for Langflow
+ if: always() && ${{ inputs.build_docker_main == 'true' }}
+ needs: [release-nightly-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ release_type: nightly-main
+ main_version: ${{ inputs.nightly_tag_main }}
+ secrets: inherit
+
+ call_docker_build_main_ep:
+ name: Call Docker Build Workflow for Langflow with Entrypoint
+ if: always() && ${{ inputs.build_docker_ep == 'true' }}
+ needs: [release-nightly-main]
+ uses: ./.github/workflows/docker-build.yml
+ with:
+ release_type: main-ep
+ main_version: ${{ inputs.nightly_tag_main }}
+ secrets: inherit
diff --git a/langflow/.github/workflows/store_pytest_durations.yml b/langflow/.github/workflows/store_pytest_durations.yml
new file mode 100644
index 0000000..8fe3512
--- /dev/null
+++ b/langflow/.github/workflows/store_pytest_durations.yml
@@ -0,0 +1,73 @@
+name: Store pytest durations
+
+on:
+ workflow_dispatch:
+ schedule:
+ # Run job at 6:30 UTC every Monday (10.30pm PST/11.30pm PDT Sunday night)
+ - cron: "30 6 * * 1"
+
+env:
+ PYTEST_RUN_PATH: "src/backend/tests"
+
+jobs:
+ build:
+ name: Run pytest and store durations
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
+ ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
+ UV_CACHE_DIR: /tmp/.uv-cache
+ steps:
+ - uses: actions/checkout@v4
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ - name: Install the project
+ run: uv sync
+ - name: Run unit tests
+ id: run_tests
+ continue-on-error: true
+ run: uv run pytest src/backend/tests/unit --durations-path src/backend/tests/.test_durations --splitting-algorithm least_duration --store-durations
+ - name: Minimize uv cache
+ run: uv cache prune --ci
+
+ - name: Close existing PRs
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { data: pulls } = await github.rest.pulls.list({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open'
+ });
+
+ for (const pull of pulls) {
+ if (pull.title === "chore: update test durations") {
+ await github.rest.pulls.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pull.number,
+ state: 'closed'
+ });
+ }
+ }
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v7
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ branch-token: ${{ secrets.GITHUB_TOKEN }}
+ commit-message: "chore: update test durations"
+ title: "chore: update test durations"
+ body: |
+ Automated PR to update test durations file.
+
+ This PR was automatically created by the store_pytest_durations workflow.
+ branch: update-test-durations
+ branch-suffix: timestamp
+ delete-branch: true
+ maintainer-can-modify: true
diff --git a/langflow/.github/workflows/style-check-py.yml b/langflow/.github/workflows/style-check-py.yml
new file mode 100644
index 0000000..e997e07
--- /dev/null
+++ b/langflow/.github/workflows/style-check-py.yml
@@ -0,0 +1,29 @@
+name: Ruff Style Check
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, auto_merge_enabled]
+ paths:
+ - "**/*.py"
+
+jobs:
+ lint:
+ name: Ruff Style Check
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version:
+ - "3.13"
+ steps:
+ - name: Check out the code at a specific ref
+ uses: actions/checkout@v4
+ - name: "Setup Environment"
+ uses: ./.github/actions/setup-uv
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Register problem matcher
+ run: echo "::add-matcher::.github/workflows/matchers/ruff.json"
+ - name: Run Ruff Check
+ run: uv run --only-dev ruff check --output-format=github .
+ - name: Minimize uv cache
+ run: uv cache prune --ci
diff --git a/langflow/.github/workflows/typescript_test.yml b/langflow/.github/workflows/typescript_test.yml
new file mode 100644
index 0000000..92d8302
--- /dev/null
+++ b/langflow/.github/workflows/typescript_test.yml
@@ -0,0 +1,359 @@
+name: Run Frontend Tests
+
+on:
+ workflow_call:
+ secrets:
+ OPENAI_API_KEY:
+ required: true
+ STORE_API_KEY:
+ required: true
+ ANTHROPIC_API_KEY:
+ required: true
+ TAVILY_API_KEY:
+ required: true
+ inputs:
+ suites:
+ description: "Test suites to run (JSON array)"
+ required: false
+ type: string
+ default: '[]'
+ release:
+ description: "Whether this is a release build"
+ required: false
+ type: boolean
+ default: false
+ tests_folder:
+ description: "(Optional) Tests to run"
+ required: false
+ type: string
+ default: "tests"
+ ref:
+ description: "(Optional) ref to checkout"
+ required: false
+ type: string
+ workflow_dispatch:
+ inputs:
+ suites:
+ description: "Test suites to run (JSON array)"
+ required: false
+ type: string
+ default: '[]'
+ release:
+ description: "Whether this is a release build"
+ required: false
+ type: boolean
+ default: false
+ tests_folder:
+ description: "(Optional) Tests to run"
+ required: false
+ type: string
+ default: "tests"
+
+env:
+ NODE_VERSION: "21"
+ PYTHON_VERSION: "3.13"
+ # Define the directory where Playwright browsers will be installed.
+ # This path is used for caching across workflows
+ PLAYWRIGHT_BROWSERS_PATH: "ms-playwright"
+ PLAYWRIGHT_VERSION: "1.49.1" # Add explicit version for cache key
+
+jobs:
+ determine-test-suite:
+ name: Determine Test Suites and Shard Distribution
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.setup-matrix.outputs.matrix }}
+ test_grep: ${{ steps.set-matrix.outputs.test_grep }}
+ suites: ${{ steps.set-matrix.outputs.suites }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+ fetch-depth: 0
+
+ - name: Paths Filter
+ id: filter
+ uses: dorny/paths-filter@v3
+ with:
+ filters: .github/changes-filter.yaml
+
+ - name: Determine Test Suites from Changes
+ id: set-matrix
+ run: |
+ # Start with input suites if provided, otherwise empty array
+ echo "Changes filter output: $(echo '${{ toJSON(steps.filter.outputs) }}')"
+ SUITES='${{ inputs.suites }}'
+ echo "Initial suites: $SUITES"
+ TEST_GREP=""
+ echo "Inputs Release: ${{ inputs.release }}"
+ RELEASE="${{ inputs.release || 'false' }}"
+ echo "Release build: $RELEASE"
+
+ # Only set to release if it's explicitly a release build
+ if [[ "$RELEASE" == "true" ]]; then
+ SUITES='["release"]'
+ echo "Release build detected - setting suites to: $SUITES"
+ # grep pattern for release is the @release tag - run all tests
+ TEST_GREP="--grep=\"@release\""
+ else
+ # If input suites were not provided, determine based on changes
+ if [[ "$SUITES" == "[]" ]]; then
+ echo "No input suites provided - determining from changes"
+ TAGS=()
+ # Add suites and tags based on changed files
+ if [[ "${{ steps.filter.outputs.components }}" == "true" ]]; then
+ SUITES=$(echo $SUITES | jq -c '. += ["components"]')
+ TAGS+=("@components")
+ echo "Added components suite"
+ fi
+ if [[ "${{ steps.filter.outputs.starter-projects }}" == "true" ]]; then
+ SUITES=$(echo $SUITES | jq -c '. += ["starter-projects"]')
+ TAGS+=("@starter-projects")
+ echo "Added starter-projects suite"
+ fi
+ if [[ "${{ steps.filter.outputs.workspace }}" == "true" ]]; then
+ SUITES=$(echo $SUITES | jq -c '. += ["workspace"]')
+ TAGS+=("@workspace")
+ echo "Added workspace suite"
+ fi
+ if [[ "${{ steps.filter.outputs.api }}" == "true" ]]; then
+ SUITES=$(echo $SUITES | jq -c '. += ["api"]')
+ TAGS+=("@api")
+ echo "Added api suite"
+ fi
+ if [[ "${{ steps.filter.outputs.database }}" == "true" ]]; then
+ SUITES=$(echo $SUITES | jq -c '. += ["database"]')
+ TAGS+=("@database")
+ echo "Added database suite"
+ fi
+
+ # Create grep pattern if we have tags
+ if [ ${#TAGS[@]} -gt 0 ]; then
+ # Join tags with | for OR logic
+ REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
+ TEST_GREP="--grep=\"${REGEX_PATTERN}\""
+ fi
+ else
+ # Process input suites to tags
+ TAGS=()
+ if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then
+ TAGS+=("@components")
+ fi
+ if echo "$SUITES" | jq -e 'contains(["starter-projects"])' > /dev/null; then
+ TAGS+=("@starter-projects")
+ fi
+ if echo "$SUITES" | jq -e 'contains(["workspace"])' > /dev/null; then
+ TAGS+=("@workspace")
+ fi
+ if echo "$SUITES" | jq -e 'contains(["api"])' > /dev/null; then
+ TAGS+=("@api")
+ fi
+ if echo "$SUITES" | jq -e 'contains(["database"])' > /dev/null; then
+ TAGS+=("@database")
+ fi
+
+ if [ ${#TAGS[@]} -gt 0 ]; then
+ # Join tags with | for OR logic
+ REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
+ TEST_GREP="--grep \"${REGEX_PATTERN}\""
+ fi
+ fi
+ fi
+
+ # Ensure compact JSON output
+ SUITES=$(echo "$SUITES" | jq -c '.')
+
+ echo "Final test suites to run: $SUITES"
+ echo "Test grep pattern: $TEST_GREP"
+ # Ensure proper JSON formatting for matrix output
+ echo "matrix=$(echo $SUITES | jq -c .)" >> $GITHUB_OUTPUT
+ echo "test_grep=$TEST_GREP" >> $GITHUB_OUTPUT
+
+ - name: Setup Node ${{ env.NODE_VERSION }}
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: "npm"
+ cache-dependency-path: ./src/frontend/package-lock.json
+
+ - name: Install Frontend Dependencies
+ run: npm ci
+ working-directory: ./src/frontend
+
+ - name: Calculate Test Shards Distribution
+ id: setup-matrix
+ run: |
+ cd src/frontend
+
+ # Get the test count using playwright's built-in grep
+ if [ -n "${{ steps.set-matrix.outputs.test_grep }}" ]; then
+ TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} ${{ steps.set-matrix.outputs.test_grep }} --list | wc -l)
+ else
+ TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} --list | wc -l)
+ fi
+
+ echo "Total tests to run: $TEST_COUNT"
+
+ # Calculate optimal shard count - 1 shard per 5 tests, min 1, max 10
+ SHARD_COUNT=$(( (TEST_COUNT + 4) / 5 ))
+ if [ $SHARD_COUNT -lt 1 ]; then
+ SHARD_COUNT=1
+ elif [ $SHARD_COUNT -gt 10 ]; then
+ SHARD_COUNT=10
+ fi
+
+ # Create the matrix combinations string
+ MATRIX_COMBINATIONS=""
+ for i in $(seq 1 $SHARD_COUNT); do
+ if [ $i -gt 1 ]; then
+ MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS,"
+ fi
+ MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS{\"shardIndex\": $i, \"shardTotal\": $SHARD_COUNT}"
+ done
+
+ echo "matrix={\"include\":[$MATRIX_COMBINATIONS]}" >> "$GITHUB_OUTPUT"
+
+ setup-and-test:
+ name: Playwright Tests - Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
+ runs-on: ubuntu-latest
+ if: ${{ needs.determine-test-suite.outputs.test_grep != '' }}
+ needs: determine-test-suite
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.determine-test-suite.outputs.matrix) }}
+ env:
+ OPENAI_API_KEY: ${{ inputs.openai_api_key || secrets.OPENAI_API_KEY }}
+ STORE_API_KEY: ${{ inputs.store_api_key || secrets.STORE_API_KEY }}
+ SEARCH_API_KEY: "${{ secrets.SEARCH_API_KEY }}"
+ ASTRA_DB_APPLICATION_TOKEN: "${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}"
+ ASTRA_DB_API_ENDPOINT: "${{ secrets.ASTRA_DB_API_ENDPOINT }}"
+ ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
+ TAVILY_API_KEY: "${{ secrets.TAVILY_API_KEY }}"
+ LANGFLOW_DEACTIVE_TRACING: "true"
+ UV_CACHE_DIR: /tmp/.uv-cache
+ outputs:
+ failed: ${{ steps.check-failure.outputs.failed }}
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref || github.ref }}
+
+ - name: Setup Node.js Environment
+ uses: actions/setup-node@v4
+ id: setup-node
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: "npm"
+ cache-dependency-path: ./src/frontend/package-lock.json
+
+ - name: Install Frontend Dependencies
+ run: npm ci
+ working-directory: ./src/frontend
+
+ # Cache Playwright browsers using a composite key
+ - name: Cache Playwright Browsers
+ id: cache-playwright
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
+ key: playwright-${{ env.PLAYWRIGHT_VERSION }}-chromium-${{ runner.os }}
+ restore-keys: |
+ playwright-${{ env.PLAYWRIGHT_VERSION }}-chromium-${{ runner.os }}
+
+ - name: Install Playwright Browser Dependencies
+ if: steps.cache-playwright.outputs.cache-hit != 'true'
+ run: |
+ cd ./src/frontend
+ npx playwright install --with-deps chromium
+
+ - name: Setup Python Environment with UV
+ uses: ./.github/actions/setup-uv
+
+ - name: Install Python Dependencies
+ run: uv sync
+
+ - name: Configure Environment Variables
+ run: |
+ touch .env
+ echo "${{ secrets.ENV_VARS }}" > .env
+
+ - name: Execute Playwright Tests
+ uses: nick-fields/retry@v3
+ with:
+ timeout_minutes: 12
+ max_attempts: 2
+ command: |
+ cd src/frontend
+ echo 'Running tests with pattern: ${{ needs.determine-test-suite.outputs.test_grep }}'
+ npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
+ # echo command before running
+ echo "npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2"
+
+ npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2
+
+ - name: Upload Test Results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: blob-report-${{ matrix.shardIndex }}
+ path: src/frontend/blob-report
+ retention-days: 1
+
+ - name: Cleanup UV Cache
+ run: uv cache prune --ci
+
+ merge-reports:
+ # We need to repeat the condition at every step
+ # https://github.com/actions/runner/issues/662
+ needs: setup-and-test
+ runs-on: ubuntu-latest
+ if: always()
+ env:
+ EXIT_CODE: ${{!contains(needs.setup-and-test.result, 'failure') && !contains(needs.setup-and-test.result, 'cancelled') && '0' || '1'}}
+ steps:
+ - name: "Should Merge Reports"
+ # If the CI was successful, we don't need to merge the reports
+ # so we can skip all the steps below
+ id: should_merge_reports
+ run: |
+ if [ "$EXIT_CODE" == "0" ]; then
+ echo "should_merge_reports=false" >> $GITHUB_OUTPUT
+ else
+ echo "should_merge_reports=true" >> $GITHUB_OUTPUT
+ fi
+ - name: Checkout code
+ if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+
+ if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Download blob reports from GitHub Actions Artifacts
+
+ if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
+ uses: actions/download-artifact@v4
+ with:
+ path: all-blob-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge into HTML Report
+
+ if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
+ run: |
+ npx playwright merge-reports --reporter html ./all-blob-reports
+
+ - name: Upload HTML report
+
+ if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: html-report--attempt-${{ github.run_attempt }}
+ path: playwright-report
+ retention-days: 14
diff --git a/langflow/.gitignore b/langflow/.gitignore
new file mode 100644
index 0000000..9be9589
--- /dev/null
+++ b/langflow/.gitignore
@@ -0,0 +1,279 @@
+# This is to avoid Opencommit hook from getting pushed
+prepare-commit-msg
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+qdrant_storage
+
+.dspy_cache
+# Mac
+.DS_Store
+
+# VSCode
+.vscode/settings.json
+.chroma
+.ruff_cache
+
+# PyCharm
+.idea/
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+notebooks
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+.testmondata*
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+*.db-shm
+*.db-wal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Poetry
+.testenv/*
+langflow.db
+
+
+.githooks/prepare-commit-msg
+.langchain.db
+
+# docusaurus
+.docusaurus/
+
+/tmp/*
+src/backend/langflow/frontend/
+src/backend/base/langflow/frontend/
+.docker
+scratchpad*
+chroma*/*
+stuff/*
+src/frontend/playwright-report/index.html
+*.bak
+prof/*
+
+src/frontend/temp
+*-shm
+*-wal
+.history
+
+.dspy_cache/
+*.db
diff --git a/langflow/.pre-commit-config.yaml b/langflow/.pre-commit-config.yaml
new file mode 100644
index 0000000..2f3b2b3
--- /dev/null
+++ b/langflow/.pre-commit-config.yaml
@@ -0,0 +1,23 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
+ - id: check-case-conflict
+ - id: end-of-file-fixer
+ # python, js and ts only
+ files: \.(py|js|ts)$
+ - id: mixed-line-ending
+ files: \.(py|js|ts)$
+ args:
+ - --fix=lf
+ - id: trailing-whitespace
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.8.3
+ hooks:
+ - id: ruff
+ name: ruff check
+ types_or: [python, pyi]
+ args: [--fix]
+ - id: ruff-format
+ types_or: [python, pyi]
+ args: [--config, pyproject.toml]
diff --git a/langflow/CONTRIBUTING.md b/langflow/CONTRIBUTING.md
new file mode 100644
index 0000000..bf15333
--- /dev/null
+++ b/langflow/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+# Contributing to Langflow
+
+This guide is intended to help you get started contributing to Langflow.
+As an open-source project in a rapidly developing field, we are extremely open
+to contributions, whether it be in the form of a new feature, improved infra, or better documentation.
+
+To contribute to this project, please follow the [fork and pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
+
+## Reporting bugs or suggesting improvements
+
+Our [GitHub issues](https://github.com/langflow-ai/langflow/issues) page is kept up to date
+with bugs, improvements, and feature requests. There is a taxonomy of labels to help
+with sorting and discovery of issues of interest. [See this page](https://github.com/langflow-ai/langflow/labels) for an overview of
+the system we use to tag our issues and pull requests.
+
+If you're looking for help with your code, consider posting a question on the
+[GitHub Discussions board](https://github.com/langflow-ai/langflow/discussions). Please
+understand that we won't be able to provide individual support via email. We
+also believe that help is much more valuable if it's **shared publicly**,
+so that more people can benefit from it.
+
+- **Describing your issue:** Try to provide as many details as possible. What
+ exactly goes wrong? _How_ is it failing? Is there an error?
+ "XY doesn't work" usually isn't that helpful for tracking down problems. Always
+ remember to include the code you ran and if possible, extract only the relevant
+ parts and don't just dump your entire script. This will make it easier for us to
+ reproduce the error.
+
+- **Sharing long blocks of code or logs:** If you need to include long code,
+ logs or tracebacks, you can wrap them in `` and ` `. This
+ [collapses the content](https://developer.mozilla.org/en/docs/Web/HTML/Element/details)
+ so it only becomes visible on click, making the issue easier to read and follow.
+
+## Contributing code and documentation
+
+You can develop Langflow locally and contribute to the Project!
+
+See [DEVELOPMENT.md](DEVELOPMENT.md) for instructions on setting up and using a development environment.
+
+## Opening a pull request
+
+Once you wrote and manually tested your change, you can start sending the patch to the main repository.
+
+- Open a new GitHub pull request with the patch against the `main` branch.
+- Ensure the PR title follows semantic commits conventions.
+ - For example, `feat: add new feature`, `fix: correct issue with X`.
+- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
diff --git a/langflow/DEVELOPMENT.md b/langflow/DEVELOPMENT.md
new file mode 100644
index 0000000..4cf3924
--- /dev/null
+++ b/langflow/DEVELOPMENT.md
@@ -0,0 +1,239 @@
+# Setting up a Development Environment
+
+This document details how to set up a local development environment that will allow you to contribute changes to the project!
+
+## Base Requirements
+
+* The project is hosted on GitHub, so you need an account there (and if you are reading this, you likely do!)
+* An IDE such as Microsoft VS Code IDE https://code.visualstudio.com/
+
+## Set up Git Repository Fork
+
+You will push changes to a fork of the Langflow repository, and from there create a Pull Request into the project repository.
+
+Fork the [Langflow GitHub repository](https://github.com/langflow-ai/langflow/fork), and follow the instructions to create a new fork.
+
+On your new fork, click the "<> Code" button to get a URL to [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) using your preferred method, and clone the repository; for example using `https`:
+
+```bash
+git clone https://github.com//langflow.git
+```
+
+Finally, add the Project repository as `upstream`:
+
+```bash
+cd langflow
+git remote add upstream https://github.com/langflow-ai/langflow.git
+git remote set-url --push upstream no_push
+```
+
+> [!TIP]
+> **Windows/WSL Users**: You may find that files "change", specifically the file mode e.g. "changed file mode 100755 → 100644". You can workaround this problem with `git config core.filemode false`.
+
+## Set up Environment
+
+There are two options available to you: the 'easy' and recommended option is to use a Development Container ("[Dev Container](https://containers.dev/)"), or you can choose to use your own OS / environment.
+
+### Option 1 (Preferred): Use a Dev Container
+
+Open this repository as a Dev Container per your IDEs instructions.
+
+#### Microsoft VS Code
+
+* See [Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)
+* You may also find it helpful to [share `git` credentials](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials) with the container
+
+
+### Option 2: Use Your Own Environment
+
+Install Pre-Requisites:
+
+* **Operating System**: macOS or Linux; Windows users ***MUST*** develop under WSL.
+* **`git`**: The project uses the ubiquitous `git` tool for change control.
+* **`make`**: The project uses `make` to coordidinate packaging.
+* **`uv`**: This project uses `uv` (`>=0.4`), a Python package and project manager from Astral. Install instructions at https://docs.astral.sh/uv/getting-started/installation/.
+* **`npm`**: The frontend files are built with Node.js (`v22.12 LTS`) and `npm` (`v10.9`). Install instructions at https://nodejs.org/en/download/package-manager.
+ - Windows (WSL) users: ensure `npm` is installed within WSL environment; `which npm` should resolve to a Linux location, not a Windows location.
+
+### Initial Environment Validation
+
+Setup and validate the initial environment by running:
+
+```bash
+make init
+```
+
+This will set up the development environment by installing backend and frontend dependencies, building the frontend static files, and initializing the project. It runs `make install_backend`, `make install_frontend`, `make build_frontend`, and finally `uv run langflow run` to start the application.
+
+Once the application is running, the command output should look similar to:
+
+```
+╭───────────────────────────────────────────────────────────────────╮
+│ Welcome to ⛓ Langflow │
+│ │
+│ │
+│ Collaborate, and contribute at our GitHub Repo 🌟 │
+│ │
+│ We collect anonymous usage data to improve Langflow. │
+│ You can opt-out by setting DO_NOT_TRACK=true in your environment. │
+│ │
+│ Access http://127.0.0.1:7860 │
+╰───────────────────────────────────────────────────────────────────╯
+```
+
+At this point, validate you can access the UI by opening the URL shown.
+
+This is how the application would normally run: the (static) front-end pages are compiled, and then this "frontend" is served by the FastAPI server; the "backend" APIs are also serviced by the FastAPI server.
+
+However, as a developer, you will want to proceed to the next step. Shutdown Langflow by hitting `Control (or Command)-C`.
+
+## Completing Development environment Setup
+
+There are some other steps to consider before you are ready to begin development.
+
+### Optional pre-commit hooks
+
+Pre-commit hooks will help keep your changes clean and well-formatted.
+
+> [!NOTE]
+> With these installed, the `git commit` command needs to run within the Python environment; your syntax needs to change to `uv run git commit`.
+
+ Install pre-commit hooks by running the following commands:
+
+```bash
+uv sync
+uv run pre-commit install
+```
+
+## Run Langflow in "Development" Mode
+
+With the above validation, you can now run the backend (FastAPI) and frontend (Node) services in a way that will "hot-reload" your changes. In this mode, the FastAPI server requires a Node.js server to serve the frontend pages rather than serving them directly.
+
+> [!NOTE]
+> You will likely have multiple terminal sessions active in the normal development workflow. These will be annotated as *Backend Terminal*, *Frontend Terminal*, *Documentation Terminal*, and *Build Terminal*.
+
+### Debug Mode
+
+A debug configuration is provided for VS Code users: this can be launched from the Debug tab (the backend debug mode can be launched directly via the F5 key). You may prefer to start services in this mode. You may still want to read the following subsections to understand expected console output and service readiness.
+
+### Start the Backend Service
+
+The backend service runs as a FastAPI service on Python, and is responsible for servicing API requests. In the *Backend Terminal*, start the backend service:
+
+```bash
+make backend
+```
+
+You will get output similar to:
+
+```
+INFO: Will watch for changes in these directories: ['/home/phil/git/langflow']
+INFO: Loading environment from '.env'
+INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
+INFO: Started reloader process [22330] using WatchFiles
+Starting Langflow ...
+```
+
+At which point you can check http://localhost:7860/health in a browser; when the backend service is ready it will return a document like:
+
+```json
+{"status":"ok"}
+```
+
+### Start the Frontend Service
+
+The frontend (User Interface) is, in shipped code (i.e. via `langflow run`), statically-compiled files that the backend FastAPI service provides to clients via port `7860`. In development mode, these are served by a Node.js service on port `3000`. In the *Frontend Terminal*, start the frontend service:
+
+```bash
+make frontend
+```
+
+You will get output similar to:
+
+```
+ VITE v5.4.11 ready in 552 ms
+
+ ➜ Local: http://localhost:3000/
+ ➜ Network: use --host to expose
+ ➜ press h + enter to show help
+```
+
+At this point, you can navigate to http://localhost:3000/ in a browser and access the Langflow User Interface.
+
+### Build and Display Documentation
+
+If you are contributing changes to documentation (always welcome!), these are built (using [Docusaurus](https://docusaurus.io/)) and served separately, also using Node.js.
+
+In the *Documentation Terminal* (from the project root directory), run the following:
+
+```bash
+cd docs
+yarn install
+yarn start
+```
+
+If the frontend service is running on port `3000` you might be prompted `Would you like to run the app on another port instead?`, in which case answer "yes". You will get output similar to:
+
+```
+[SUCCESS] Docusaurus website is running at: http://localhost:3001/
+```
+
+At which point you can navigate to http://localhost:3001/ in a browser and view the documentation. Documentation updates will be visible as they are saved, though sometimes the browser page will also need to be refreshed.
+
+## Adding or Modifying a Component
+
+Components reside in folders under `src/backend/base/langflow`, and their unit tests under `src/backend/base/tests/unit/components`.
+
+### Adding a Component
+
+Add the component to the appropriate subdirectory, and add the component to the `__init__.py` file (alphabetical ordering on the `import` and the `__all__` list). Assuming the backend and frontend services are running, the backend service will restart as these files are changed. The new component will be visible after the backend is restarted, _*and*_ after you hit "refresh" in the browser.
+
+> [!TIP]
+> It is faster to copy-paste the component code from your editor into the UI *without* saving in the source code in the editor, and once you are satisfied it is working you can save (restarting the backend) and refresh the browser to confirm it is present.
+
+You should try to add a unit test for your component, though templates and best practices for this is a work in progress. At the very least, please create a Markdown file in the unit test subdirectory associated with your component (create the directory if not present), with the same filename as the component but with a `.md` extension. Within this should be the steps you have taken to manually test the component.
+
+### Modifying a Component
+
+Modifying a component is much the same as adding a component: it is generally easier to make changes in the UI and then save the file in the repository. Please be sure to review and modify unit tests; if there is not a unit test for the component, the addition of one that at least covers your changes would be much appreciated!
+
+> [!NOTE]
+> If you have an old version of the component on the canvas when changes are saved and the backend service restarts, that component should show "Updates Available" when the canvas is reloaded (i.e. a browser refresh). [Issue 5179](https://github.com/langflow-ai/langflow/issues/5179) indicates this behavior is not consistent, at least in a development setting.
+
+## Building and Testing Changes
+
+When you are ready to commit, and before you commit, you should consider the following:
+
+* `make lint`
+* `make format_backend` and `make format_frontend` will run code formatters on their respective codebases
+* `make unit_tests` runs the (backend) unit tests (see "Quirks" below for more about testing).
+
+Once these changes are ready, it is helpful to rebase your changes on top of `upstream`'s `main` branch, to ensure you have the latest code version! Of course if you have had to merge changes into your component you may want to re-lint/format/unit_test.
+
+As a final validation, stop the backend and frontend services and run `make init`; this will do a clean build and the UI should be available in port `7860` (as it has invoked `langflow run`). Open a **new** browser tab to this service and do a final check of your changes by adding your new/modified component onto the canvas from the Components list.
+
+## Committing, Pushing, and Pull Requests
+
+Once you are happy your changes are complete, commit them and push the changes to your own fork (this will be `origin` if you followed the above instructions). You can then raise a Pull Request into the Project repository on the GitHub interface or within your IDE.
+
+> [!TIP]
+> Remember that if you have pre-commit hooks enabled, you need to run the `git` command as `uv run git` to activate the necessary Python environment!
+
+## Some Quirks!
+
+You may observe some quirky things:
+
+### Testing
+
+* Backend test `src/backend/tests/unit/test_database.py` can fail when running with `make tests` but passes when running manually
+ * You can validate this by running the test cases sequentially: `uv run pytest src/backend/tests/unit/test_database.py`
+* There are some other test targets: `integration_tests`, `coverage`, `tests_frontend` but these require additional setup not covered in this document.
+
+### Files That Change
+
+There are some files that change without you having made changes:
+
+* Files in `src/backend/base/langflow/initial_setup/starter_projects` modify after `langflow run`; these are formatting changes. Feel free to commit (or ignore) them.
+* `uv.lock` and `src/frontend/package-lock.json` files can be modified by `make` targets; changes should not be committed by individual contributors.
+ * You can exclude these from consideration in git: `git update-index --assume-unchanged uv.lock src/frontend/package-lock.json`
+ * You can re-include these from consideration in git: `git update-index --no-assume-unchanged uv.lock src/frontend/package-lock.json`
diff --git a/langflow/LICENSE b/langflow/LICENSE
new file mode 100644
index 0000000..ee3d6be
--- /dev/null
+++ b/langflow/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Langflow
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/langflow/Makefile b/langflow/Makefile
new file mode 100644
index 0000000..657258a
--- /dev/null
+++ b/langflow/Makefile
@@ -0,0 +1,513 @@
+.PHONY: all init format_backend format_frontend format lint build build_frontend install_frontend run_frontend run_backend dev help tests coverage clean_python_cache clean_npm_cache clean_all
+
+# Configurations
+VERSION=$(shell grep "^version" pyproject.toml | sed 's/.*\"\(.*\)\"$$/\1/')
+DOCKERFILE=docker/build_and_push.Dockerfile
+DOCKERFILE_BACKEND=docker/build_and_push_backend.Dockerfile
+DOCKERFILE_FRONTEND=docker/frontend/build_and_push_frontend.Dockerfile
+DOCKER_COMPOSE=docker_example/docker-compose.yml
+PYTHON_REQUIRED=$(shell grep '^requires-python[[:space:]]*=' pyproject.toml | sed -n 's/.*"\([^"]*\)".*/\1/p')
+RED=\033[0;31m
+NC=\033[0m # No Color
+GREEN=\033[0;32m
+
+log_level ?= debug
+host ?= 0.0.0.0
+port ?= 7860
+env ?= .env
+open_browser ?= true
+path = src/backend/base/langflow/frontend
+workers ?= 1
+async ?= true
+lf ?= false
+ff ?= true
+all: help
+
+######################
+# UTILITIES
+######################
+
+# Some directories may be mount points as in devcontainer, so we need to clear their
+# contents rather than remove the entire directory. But we must also be mindful that
+# we are not running in a devcontainer, so need to ensure the directories exist.
+# See https://code.visualstudio.com/remote/advancedcontainers/improve-performance
+CLEAR_DIRS = $(foreach dir,$1,$(shell mkdir -p $(dir) && find $(dir) -mindepth 1 -delete))
+
+# increment the patch version of the current package
+patch: ## bump the version in langflow and langflow-base
+ @echo 'Patching the version'
+ @poetry version patch
+ @echo 'Patching the version in langflow-base'
+ @cd src/backend/base && poetry version patch
+ @make lock
+
+# check for required tools
+check_tools:
+ @command -v uv >/dev/null 2>&1 || { echo >&2 "$(RED)uv is not installed. Aborting.$(NC)"; exit 1; }
+ @command -v npm >/dev/null 2>&1 || { echo >&2 "$(RED)NPM is not installed. Aborting.$(NC)"; exit 1; }
+ @echo "$(GREEN)All required tools are installed.$(NC)"
+
+
+help: ## show this help message
+ @echo '----'
+ @grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | \
+ awk -F ':.*##' '{printf "\033[36mmake %s\033[0m: %s\n", $$1, $$2}' | \
+ column -c2 -t -s :
+ @echo '----'
+
+######################
+# INSTALL PROJECT
+######################
+
+reinstall_backend: ## forces reinstall all dependencies (no caching)
+ @echo 'Installing backend dependencies'
+ @uv sync -n --reinstall --frozen
+
+install_backend: ## install the backend dependencies
+ @echo 'Installing backend dependencies'
+ @uv sync --frozen $(EXTRA_ARGS)
+
+install_frontend: ## install the frontend dependencies
+ @echo 'Installing frontend dependencies'
+ @cd src/frontend && npm install > /dev/null 2>&1
+
+build_frontend: ## build the frontend static files
+ @echo '==== Starting frontend build ===='
+ @echo 'Current directory: $$(pwd)'
+ @echo 'Checking if src/frontend exists...'
+ @ls -la src/frontend || true
+ @echo 'Building frontend static files...'
+ @cd src/frontend && CI='' npm run build 2>&1 || { echo "\nBuild failed! Error output above ☝️"; exit 1; }
+ @echo 'Clearing destination directory...'
+ $(call CLEAR_DIRS,src/backend/base/langflow/frontend)
+ @echo 'Copying build files...'
+ @cp -r src/frontend/build/. src/backend/base/langflow/frontend
+ @echo '==== Frontend build complete ===='
+
+init: check_tools clean_python_cache clean_npm_cache ## initialize the project
+ @make install_backend
+ @make install_frontend
+ @make build_frontend
+ @echo "$(GREEN)All requirements are installed.$(NC)"
+ @uv run langflow run
+
+######################
+# CLEAN PROJECT
+######################
+
+clean_python_cache:
+ @echo "Cleaning Python cache..."
+ find . -type d -name '__pycache__' -exec rm -r {} +
+ find . -type f -name '*.py[cod]' -exec rm -f {} +
+ find . -type f -name '*~' -exec rm -f {} +
+ find . -type f -name '.*~' -exec rm -f {} +
+ $(call CLEAR_DIRS,.mypy_cache )
+ @echo "$(GREEN)Python cache cleaned.$(NC)"
+
+clean_npm_cache:
+ @echo "Cleaning npm cache..."
+ cd src/frontend && npm cache clean --force
+ $(call CLEAR_DIRS,src/frontend/node_modules src/frontend/build src/backend/base/langflow/frontend)
+ rm -f src/frontend/package-lock.json
+ @echo "$(GREEN)NPM cache and frontend directories cleaned.$(NC)"
+
+clean_all: clean_python_cache clean_npm_cache # clean all caches and temporary directories
+ @echo "$(GREEN)All caches and temporary directories cleaned.$(NC)"
+
+setup_uv: ## install poetry using pipx
+ pipx install uv
+
+add:
+ @echo 'Adding dependencies'
+ifdef devel
+ @cd src/backend/base && uv add --group dev $(devel)
+endif
+
+ifdef main
+ @uv add $(main)
+endif
+
+ifdef base
+ @cd src/backend/base && uv add $(base)
+endif
+
+
+
+######################
+# CODE TESTS
+######################
+
+coverage: ## run the tests and generate a coverage report
+ @uv run coverage run
+ @uv run coverage erase
+
+unit_tests: ## run unit tests
+ @uv sync --frozen
+ @EXTRA_ARGS=""
+ @if [ "$(async)" = "true" ]; then \
+ EXTRA_ARGS="$$EXTRA_ARGS --instafail -n auto"; \
+ fi; \
+ if [ "$(lf)" = "true" ]; then \
+ EXTRA_ARGS="$$EXTRA_ARGS --lf"; \
+ fi; \
+ if [ "$(ff)" = "true" ]; then \
+ EXTRA_ARGS="$$EXTRA_ARGS --ff"; \
+ fi; \
+ uv run pytest src/backend/tests/unit \
+ --ignore=src/backend/tests/integration $$EXTRA_ARGS \
+ --instafail -ra -m 'not api_key_required' \
+ --durations-path src/backend/tests/.test_durations \
+ --splitting-algorithm least_duration $(args)
+
+unit_tests_looponfail:
+ @make unit_tests args="-f"
+
+integration_tests:
+ uv run pytest src/backend/tests/integration \
+ --instafail -ra \
+ $(args)
+
+integration_tests_no_api_keys:
+ uv run pytest src/backend/tests/integration \
+ --instafail -ra -m "not api_key_required" \
+ $(args)
+
+integration_tests_api_keys:
+ uv run pytest src/backend/tests/integration \
+ --instafail -ra -m "api_key_required" \
+ $(args)
+
+tests: ## run unit, integration, coverage tests
+ @echo 'Running Unit Tests...'
+ make unit_tests
+ @echo 'Running Integration Tests...'
+ make integration_tests
+ @echo 'Running Coverage Tests...'
+ make coverage
+
+######################
+# CODE QUALITY
+######################
+
+codespell: ## run codespell to check spelling
+ @poetry install --with spelling
+ poetry run codespell --toml pyproject.toml
+
+fix_codespell: ## run codespell to fix spelling errors
+ @poetry install --with spelling
+ poetry run codespell --toml pyproject.toml --write
+
+format_backend: ## backend code formatters
+ @uv run ruff check . --fix
+ @uv run ruff format . --config pyproject.toml
+
+format_frontend: ## frontend code formatters
+ @cd src/frontend && npm run format
+
+format: format_backend format_frontend ## run code formatters
+
+unsafe_fix:
+ @uv run ruff check . --fix --unsafe-fixes
+
+lint: install_backend ## run linters
+ @uv run mypy --namespace-packages -p "langflow"
+
+install_frontendci:
+ @cd src/frontend && npm ci > /dev/null 2>&1
+
+install_frontendc:
+ @cd src/frontend && $(call CLEAR_DIRS,node_modules) && rm -f package-lock.json && npm install > /dev/null 2>&1
+
+run_frontend: ## run the frontend
+ @-kill -9 `lsof -t -i:3000`
+ @cd src/frontend && npm start $(if $(FRONTEND_START_FLAGS),-- $(FRONTEND_START_FLAGS))
+
+tests_frontend: ## run frontend tests
+ifeq ($(UI), true)
+ @cd src/frontend && npx playwright test --ui --project=chromium
+else
+ @cd src/frontend && npx playwright test --project=chromium
+endif
+
+run_cli: install_frontend install_backend build_frontend ## run the CLI
+ @echo 'Running the CLI'
+ @uv run langflow run \
+ --frontend-path $(path) \
+ --log-level $(log_level) \
+ --host $(host) \
+ --port $(port) \
+ $(if $(env),--env-file $(env),) \
+ $(if $(filter false,$(open_browser)),--no-open-browser)
+
+run_cli_debug:
+ @echo 'Running the CLI in debug mode'
+ @make install_frontend > /dev/null
+ @echo 'Building the frontend'
+ @make build_frontend > /dev/null
+ @echo 'Install backend dependencies'
+ @make install_backend > /dev/null
+ifdef env
+ @make start env=$(env) host=$(host) port=$(port) log_level=debug
+else
+ @make start host=$(host) port=$(port) log_level=debug
+endif
+
+
+setup_devcontainer: ## set up the development container
+ make install_backend
+ make install_frontend
+ make build_frontend
+ uv run langflow --frontend-path src/frontend/build
+
+setup_env: ## set up the environment
+ @sh ./scripts/setup/setup_env.sh
+
+frontend: install_frontend ## run the frontend in development mode
+ make run_frontend
+
+frontendc: install_frontendc
+ make run_frontend
+
+
+backend: setup_env install_backend ## run the backend in development mode
+ @-kill -9 $$(lsof -t -i:7860) || true
+ifdef login
+ @echo "Running backend autologin is $(login)";
+ LANGFLOW_AUTO_LOGIN=$(login) uv run uvicorn \
+ --factory langflow.main:create_app \
+ --host 0.0.0.0 \
+ --port $(port) \
+ $(if $(filter-out 1,$(workers)),, --reload) \
+ --env-file $(env) \
+ --loop asyncio \
+ $(if $(workers),--workers $(workers),)
+else
+ @echo "Running backend respecting the $(env) file";
+ uv run uvicorn \
+ --factory langflow.main:create_app \
+ --host 0.0.0.0 \
+ --port $(port) \
+ $(if $(filter-out 1,$(workers)),, --reload) \
+ --env-file $(env) \
+ --loop asyncio \
+ $(if $(workers),--workers $(workers),)
+endif
+
+build_and_run: setup_env ## build the project and run it
+ $(call CLEAR_DIRS,dist src/backend/base/dist)
+ make build
+ uv run pip install dist/*.tar.gz
+ uv run langflow run
+
+build_and_install: ## build the project and install it
+ @echo 'Removing dist folder'
+ $(call CLEAR_DIRS,dist src/backend/base/dist)
+ make build && uv run pip install dist/*.whl && pip install src/backend/base/dist/*.whl --force-reinstall
+
+build: setup_env ## build the frontend static files and package the project
+ifdef base
+ make install_frontendci
+ make build_frontend
+ make build_langflow_base args="$(args)"
+endif
+
+ifdef main
+ make install_frontendci
+ make build_frontend
+ make build_langflow_base args="$(args)"
+ make build_langflow args="$(args)"
+endif
+
+build_langflow_base:
+ cd src/backend/base && uv build $(args)
+
+build_langflow_backup:
+ uv lock && uv build
+
+build_langflow:
+ uv lock --no-upgrade
+ uv build $(args)
+ifdef restore
+ mv pyproject.toml.bak pyproject.toml
+ mv uv.lock.bak uv.lock
+endif
+
+
+docker_build: dockerfile_build clear_dockerimage ## build DockerFile
+
+docker_build_backend: dockerfile_build_be clear_dockerimage ## build Backend DockerFile
+
+docker_build_frontend: dockerfile_build_fe clear_dockerimage ## build Frontend Dockerfile
+
+dockerfile_build:
+ @echo 'BUILDING DOCKER IMAGE: ${DOCKERFILE}'
+ @docker build --rm \
+ -f ${DOCKERFILE} \
+ -t langflow:${VERSION} .
+
+dockerfile_build_be: dockerfile_build
+ @echo 'BUILDING DOCKER IMAGE BACKEND: ${DOCKERFILE_BACKEND}'
+ @docker build --rm \
+ --build-arg LANGFLOW_IMAGE=langflow:${VERSION} \
+ -f ${DOCKERFILE_BACKEND} \
+ -t langflow_backend:${VERSION} .
+
+dockerfile_build_fe: dockerfile_build
+ @echo 'BUILDING DOCKER IMAGE FRONTEND: ${DOCKERFILE_FRONTEND}'
+ @docker build --rm \
+ --build-arg LANGFLOW_IMAGE=langflow:${VERSION} \
+ -f ${DOCKERFILE_FRONTEND} \
+ -t langflow_frontend:${VERSION} .
+
+clear_dockerimage:
+ @echo 'Clearing the docker build'
+ @if docker images -f "dangling=true" -q | grep -q '.*'; then \
+ docker rmi $$(docker images -f "dangling=true" -q); \
+ fi
+
+docker_compose_up: docker_build docker_compose_down
+ @echo 'Running docker compose up'
+ docker compose -f $(DOCKER_COMPOSE) up --remove-orphans
+
+docker_compose_down:
+ @echo 'Running docker compose down'
+ docker compose -f $(DOCKER_COMPOSE) down || true
+
+dcdev_up:
+ @echo 'Running docker compose up'
+ docker compose -f docker/dev.docker-compose.yml down || true
+ docker compose -f docker/dev.docker-compose.yml up --remove-orphans
+
+lock_base:
+ cd src/backend/base && uv lock
+
+lock_langflow:
+ uv lock
+
+lock: ## lock dependencies
+ @echo 'Locking dependencies'
+ cd src/backend/base && uv lock
+ uv lock
+
+update: ## update dependencies
+ @echo 'Updating dependencies'
+ cd src/backend/base && uv sync --upgrade
+ uv sync --upgrade
+
+publish_base:
+ cd src/backend/base && uv publish
+
+publish_langflow:
+ uv publish
+
+publish_base_testpypi:
+ # TODO: update this to use the test-pypi repository
+ cd src/backend/base && uv publish -r test-pypi
+
+publish_langflow_testpypi:
+ # TODO: update this to use the test-pypi repository
+ uv publish -r test-pypi
+
+publish: ## build the frontend static files and package the project and publish it to PyPI
+ @echo 'Publishing the project'
+ifdef base
+ make publish_base
+endif
+
+ifdef main
+ make publish_langflow
+endif
+
+publish_testpypi: ## build the frontend static files and package the project and publish it to PyPI
+ @echo 'Publishing the project'
+
+ifdef base
+ #TODO: replace with uvx twine upload dist/*
+ poetry config repositories.test-pypi https://test.pypi.org/legacy/
+ make publish_base_testpypi
+endif
+
+ifdef main
+ #TODO: replace with uvx twine upload dist/*
+ poetry config repositories.test-pypi https://test.pypi.org/legacy/
+ make publish_langflow_testpypi
+endif
+
+
+# example make alembic-revision message="Add user table"
+alembic-revision: ## generate a new migration
+ @echo 'Generating a new Alembic revision'
+ cd src/backend/base/langflow/ && uv run alembic revision --autogenerate -m "$(message)"
+
+
+alembic-upgrade: ## upgrade database to the latest version
+ @echo 'Upgrading database to the latest version'
+ cd src/backend/base/langflow/ && uv run alembic upgrade head
+
+alembic-downgrade: ## downgrade database by one version
+ @echo 'Downgrading database by one version'
+ cd src/backend/base/langflow/ && uv run alembic downgrade -1
+
+alembic-current: ## show current revision
+ @echo 'Showing current Alembic revision'
+ cd src/backend/base/langflow/ && uv run alembic current
+
+alembic-history: ## show migration history
+ @echo 'Showing Alembic migration history'
+ cd src/backend/base/langflow/ && uv run alembic history --verbose
+
+alembic-check: ## check migration status
+ @echo 'Running alembic check'
+ cd src/backend/base/langflow/ && uv run alembic check
+
+alembic-stamp: ## stamp the database with a specific revision
+ @echo 'Stamping the database with revision $(revision)'
+ cd src/backend/base/langflow/ && uv run alembic stamp $(revision)
+
+######################
+# LOAD TESTING
+######################
+
+# Default values for locust configuration
+locust_users ?= 10
+locust_spawn_rate ?= 1
+locust_host ?= http://localhost:7860
+locust_headless ?= true
+locust_time ?= 300s
+locust_api_key ?= your-api-key
+locust_flow_id ?= your-flow-id
+locust_file ?= src/backend/tests/locust/locustfile.py
+locust_min_wait ?= 2000
+locust_max_wait ?= 5000
+locust_request_timeout ?= 30.0
+
+locust: ## run locust load tests (options: locust_users=10 locust_spawn_rate=1 locust_host=http://localhost:7860 locust_headless=true locust_time=300s locust_api_key=your-api-key locust_flow_id=your-flow-id locust_file=src/backend/tests/locust/locustfile.py locust_min_wait=2000 locust_max_wait=5000 locust_request_timeout=30.0)
+ @if [ ! -f "$(locust_file)" ]; then \
+ echo "$(RED)Error: Locustfile not found at $(locust_file)$(NC)"; \
+ exit 1; \
+ fi
+ @echo "Starting Locust with $(locust_users) users, spawn rate of $(locust_spawn_rate)"
+ @echo "Testing host: $(locust_host)"
+ @echo "Using locustfile: $(locust_file)"
+ @export API_KEY=$(locust_api_key) && \
+ export FLOW_ID=$(locust_flow_id) && \
+ export LANGFLOW_HOST=$(locust_host) && \
+ export MIN_WAIT=$(locust_min_wait) && \
+ export MAX_WAIT=$(locust_max_wait) && \
+ export REQUEST_TIMEOUT=$(locust_request_timeout) && \
+ cd $$(dirname "$(locust_file)") && \
+ if [ "$(locust_headless)" = "true" ]; then \
+ uv run locust \
+ --headless \
+ -u $(locust_users) \
+ -r $(locust_spawn_rate) \
+ --run-time $(locust_time) \
+ --host $(locust_host) \
+ -f $$(basename "$(locust_file)"); \
+ else \
+ uv run locust \
+ -u $(locust_users) \
+ -r $(locust_spawn_rate) \
+ --host $(locust_host) \
+ -f $$(basename "$(locust_file)"); \
+ fi
diff --git a/langflow/README.ES.md b/langflow/README.ES.md
new file mode 100644
index 0000000..b90625e
--- /dev/null
+++ b/langflow/README.ES.md
@@ -0,0 +1,175 @@
+# [](https://www.langflow.org)
+
+
+ Un Framework visual para crear aplicaciones de agentes autónomos y RAG
+
+
+ De código abierto, construido en Python, totalmente personalizable, agnóstico en cuanto a modelos y bases de datos
+
+
+
+ Documentación -
+ Únete a nuestro Discord -
+ Síguenos en X -
+ Demostración
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 📝 Contenido
+
+- [📝 Contenido](#-contenido)
+- [📦 Introducción](#-introducción)
+- [🎨 Crear Flujos](#-crear-flujos)
+- [Despliegue](#despliegue)
+ - [Despliegue usando Google Cloud Platform](#despliegue-usando-google-cloud-platform)
+ - [Despliegue en Railway](#despliegue-en-railway)
+ - [Despliegue en Render](#despliegue-en-render)
+- [🖥️ Interfaz de Línea de Comandos (CLI)](#️-interfaz-de-línea-de-comandos-cli)
+ - [Uso](#uso)
+ - [Variables de Entorno](#variables-de-entorno)
+- [👋 Contribuir](#-contribuir)
+- [🌟 Contribuidores](#-contribuidores)
+- [📄 Licencia](#-licencia)
+
+# 📦 Introducción
+
+Puedes instalar Langflow con pip:
+
+```shell
+# Asegúrate de tener >=Python 3.10 instalado en tu sistema.
+# Instala la versión pre-release (recomendada para las actualizaciones más recientes)
+python -m pip install langflow --pre --force-reinstall
+
+# o versión estable
+python -m pip install langflow -U
+```
+
+Luego, ejecuta Langflow con:
+
+```shell
+python -m langflow run
+```
+
+También puedes ver Langflow en [HuggingFace Spaces](https://huggingface.co/spaces/Langflow/Langflow). [Clona el Space usando este enlace](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true) para crear tu propio espacio de trabajo de Langflow en minutos.
+
+# 🎨 Crear Flujos
+
+Crear flujos con Langflow es fácil. Simplemente arrastra los componentes desde la barra lateral al espacio de trabajo y conéctalos para comenzar a construir tu aplicación.
+
+Explora editando los parámetros del prompt, agrupando componentes y construyendo tus propios componentes personalizados (Custom Components).
+
+Cuando hayas terminado, puedes exportar tu flujo como un archivo JSON.
+
+Carga el flujo con:
+
+```python
+from langflow.load import run_flow_from_json
+
+results = run_flow_from_json("ruta/al/flujo.json", input_value="¡Hola, Mundo!")
+```
+
+# Despliegue
+
+## Despliegue usando Google Cloud Platform
+
+Sigue nuestra guía paso a paso para desplegar Langflow en Google Cloud Platform (GCP) usando Google Cloud Shell. La guía está disponible en el documento [**Langflow en Google Cloud Platform**](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/deployment/gcp-deployment.md).
+
+Alternativamente, haz clic en el botón **"Abrir en Cloud Shell"** a continuación para iniciar Google Cloud Shell, clonar el repositorio de Langflow y comenzar un **tutorial interactivo** que te guiará a través del proceso de configuración de los recursos necesarios y despliegue de Langflow en tu proyecto GCP.
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+## Despliegue en Railway
+
+Usa esta plantilla para desplegar Langflow 1.0 Preview en Railway:
+
+[](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
+
+O esta para desplegar Langflow 0.6.x:
+
+[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+## Despliegue en Render
+
+
+
+
+
+# 🖥️ Interfaz de Línea de Comandos (CLI)
+
+Langflow proporciona una interfaz de línea de comandos (CLI) para una fácil gestión y configuración.
+
+## Uso
+
+Puedes ejecutar Langflow usando el siguiente comando:
+
+```shell
+langflow run [OPCIONES]
+```
+
+Cada opción se detalla a continuación:
+
+- `--help`: Muestra todas las opciones disponibles.
+- `--host`: Establece el host al que vincular el servidor. Se puede configurar usando la variable de entorno `LANGFLOW_HOST`. El valor predeterminado es `127.0.0.1`.
+- `--workers`: Establece el número de procesos. Se puede configurar usando la variable de entorno `LANGFLOW_WORKERS`. El valor predeterminado es `1`.
+- `--worker-timeout`: Establece el tiempo de espera del worker en segundos. El valor predeterminado es `60`.
+- `--port`: Establece el puerto en el que escuchar. Se puede configurar usando la variable de entorno `LANGFLOW_PORT`. El valor predeterminado es `7860`.
+- `--env-file`: Especifica la ruta al archivo .env que contiene variables de entorno. El valor predeterminado es `.env`.
+- `--log-level`: Establece el nivel de registro. Se puede configurar usando la variable de entorno `LANGFLOW_LOG_LEVEL`. El valor predeterminado es `critical`.
+- `--components-path`: Especifica la ruta al directorio que contiene componentes personalizados. Se puede configurar usando la variable de entorno `LANGFLOW_COMPONENTS_PATH`. El valor predeterminado es `langflow/components`.
+- `--log-file`: Especifica la ruta al archivo de registro. Se puede configurar usando la variable de entorno `LANGFLOW_LOG_FILE`. El valor predeterminado es `logs/langflow.log`.
+- `--cache`: Selecciona el tipo de caché a utilizar. Las opciones son `InMemoryCache` y `SQLiteCache`. Se puede configurar usando la variable de entorno `LANGFLOW_LANGCHAIN_CACHE`. El valor predeterminado es `SQLiteCache`.
+- `--dev/--no-dev`: Alterna el modo de desarrollo. El valor predeterminado es `no-dev`.
+- `--path`: Especifica la ruta al directorio frontend que contiene los archivos de compilación. Esta opción es solo para fines de desarrollo. Se puede configurar usando la variable de entorno `LANGFLOW_FRONTEND_PATH`.
+- `--open-browser/--no-open-browser`: Alterna la opción de abrir el navegador después de iniciar el servidor. Se puede configurar usando la variable de entorno `LANGFLOW_OPEN_BROWSER`. El valor predeterminado es `open-browser`.
+- `--remove-api-keys/--no-remove-api-keys`: Alterna la opción de eliminar las claves API de los proyectos guardados en la base de datos. Se puede configurar usando la variable de entorno `LANGFLOW_REMOVE_API_KEYS`. El valor predeterminado es `no-remove-api-keys`.
+- `--install-completion [bash|zsh|fish|powershell|pwsh]`: Instala la función de autocompletar para el shell especificado.
+- `--show-completion [bash|zsh|fish|powershell|pwsh]`: Muestra el código para la función de autocompletar para el shell especificado, permitiéndote copiar o personalizar la instalación.
+- `--backend-only`: Este parámetro, con valor predeterminado `False`, permite ejecutar solo el servidor backend sin el frontend. También se puede configurar usando la variable de entorno `LANGFLOW_BACKEND_ONLY`.
+- `--store`: Este parámetro, con valor predeterminado `True`, activa las funciones de la tienda, usa `--no-store` para desactivarlas. Se puede configurar usando la variable de entorno `LANGFLOW_STORE`.
+
+Estos parámetros son importantes para los usuarios que necesitan personalizar el comportamiento de Langflow, especialmente en escenarios de desarrollo o despliegue especializado.
+
+### Variables de Entorno
+
+Puedes configurar muchas de las opciones de CLI usando variables de entorno. Estas se pueden exportar en tu sistema operativo o agregar a un archivo `.env` y cargarse usando la opción `--env-file`.
+
+Se incluye un archivo de ejemplo `.env` llamado `.env.example` en el proyecto. Copia este archivo a un nuevo archivo llamado `.env` y reemplaza los valores de ejemplo con tus configuraciones reales. Si estás estableciendo valores tanto en tu sistema operativo como en el archivo `.env`, las configuraciones del `.env` tendrán prioridad.
+
+# 👋 Contribuir
+
+Aceptamos contribuciones de desarrolladores de todos los niveles para nuestro proyecto de código abierto en GitHub. Si deseas contribuir, por favor, consulta nuestras [directrices de contribución](./CONTRIBUTING.md) y ayuda a hacer que Langflow sea más accesible.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+# 🌟 Contribuidores
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
+# 📄 Licencia
+
+Langflow se publica bajo la licencia MIT. Consulta el archivo [LICENSE](LICENSE) para más detalles.
diff --git a/langflow/README.FR.md b/langflow/README.FR.md
new file mode 100644
index 0000000..3337777
--- /dev/null
+++ b/langflow/README.FR.md
@@ -0,0 +1,77 @@
+> [!WARNING]
+> Tous les liens externes sont susceptibles d'être en anglais.
+
+
+
+
+ Langflow est un générateur d'applications low-code pour les applications RAG et IA multi-agents. Il est basé sur Python et indépendant de tout modèle, API ou base de données.
+
+
+
+ Documentation -
+ Service cloud gratuit -
+ Autogéré
+
+
+
+
+## ✨ Caractéristiques principales
+
+1. **Basé sur Python** et indépendant des modèles, API, sources de données ou bases de données.
+2. **IDE visuel** pour la création et le test de flux de travail par glisser-déposer.
+3. **Playground** pour tester et itérer immédiatement les flux de travail avec un contrôle étape par étape.
+4. **Orchestration multi-agent** et gestion et récupération des conversations.
+5. **Service cloud gratuit** pour démarrer en quelques minutes sans configuration.
+6. **Publier en tant qu'API** ou exporter en tant qu'application Python.
+7. **Observabilité** avec l'intégration de LangSmith, LangFuse ou LangWatch.
+8. **Sécurité et évolutivité de niveau entreprise** avec le service cloud gratuit DataStax Langflow.
+9. **Personnalisez les flux de travail** ou créez des flux entièrement à l'aide de Python.
+10. **Intégrations d'écosystèmes** en tant que composants réutilisables pour tout modèle, API ou base de données.
+
+
+
+## 📦 Démarrage
+
+- **Installer avec pip** (Python 3.10 à 3.12):
+
+```shell
+pip install langflow
+```
+
+- **Installer avec uv** (Python 3.10 à 3.12):
+
+```shell
+uv pip install langflow
+```
+
+- **Cloud :** DataStax Langflow est un environnement hébergé sans configuration. [Inscrivez-vous pour un compte gratuit.](https://astra.datastax.com/signup?type=langflow)
+- **Autogéré :** exécutez Langflow dans votre environnement. [Installez Langflow](https://docs.langflow.org/get-started-installation) pour exécuter un serveur Langflow local, puis utilisez le guide [Démarrage rapide](https://docs.langflow.org/get-started-quickstart) pour créer et exécuter un flux.
+- **Hugging Face :** [Clonez l'espace à l'aide de ce lien](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true) pour créer un espace de travail Langflow.
+
+[](https://www.youtube.com/watch?v=kinngWhaUKM)
+
+## ⭐ Restez à jour
+
+Ajoute une étoile à Langflow sur GitHub pour être instantanément informé des nouvelles versions.
+
+
+
+## 👋 Contribuer
+
+Nous acceptons les contributions des développeurs de tous niveaux. Si vous souhaitez contribuer, veuillez consulter nos [consignes de contribution](./CONTRIBUTING.md) et contribuez à rendre Langflow plus accessible.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+## ❤️ Contributeurs
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
diff --git a/langflow/README.KR.md b/langflow/README.KR.md
new file mode 100644
index 0000000..082f947
--- /dev/null
+++ b/langflow/README.KR.md
@@ -0,0 +1,197 @@
+
+
Langflow 1.0 이 출시되었습니다! 🎉
+
여기 를 눌러 자세히 알아보기!
+
+
+
+
+# [](https://www.langflow.org)
+
+
+ 다중 에이전트 및 RAG 애플리케이션 구축을 위한 시각적 프레임워크
+
+
+ 오픈소스, Python-기반, 전체 커스텀, LLM과 Vector store를 몰라도 사용 가능
+
+
+
+ 문서 -
+ Discord에 참여하기 -
+ X에서 팔로우하기 -
+ 실시간 데모
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 📝 목차
+
+- [📝 목차](#-content)
+- [📦 시작하기](#-get-started)
+- [🎨 플로우 만들기](#-create-flows)
+- [배포](#deploy)
+ - [DataStax Langflow](#datastax-langflow)
+ - [Hugging Face Spaces에 Langflow 배포하기](#deploy-langflow-on-hugging-face-spaces)
+ - [Google Cloud Platform에 Langflow 배포하기](#deploy-langflow-on-google-cloud-platform)
+ - [Railway에 배포하기](#deploy-on-railway)
+ - [Render에 배포하기](#deploy-on-render)
+ - [Kubernetes에 배포하기](#deploy-on-kubernetes)
+- [🖥️ 명령줄 인터페이스 (CLI)](#️-command-line-interface-cli)
+ - [사용법](#usage)
+ - [환경 변수](#environment-variables)
+- [👋 기여](#-contribute)
+- [🌟 기여자](#-contributors)
+- [📄 라이선스](#-license)
+
+# 📦 시작하기
+
+pip으로 Langflow 다운로드:
+
+```shell
+# >=Python 3.10 이 시스템에 미리 설치되어 있어야 합니다.
+python -m pip install langflow -U
+```
+
+혹은
+
+복제된 Repo에서 설치하려면 다음과 같이 Langflow의 프론트엔드와 백엔드를 구축하고 설치할 수 있습니다:
+
+```shell
+make install_frontend && make build_frontend && make install_backend
+```
+
+Langflow 실행하기:
+
+```shell
+python -m langflow run
+```
+
+# 🎨 플로우 만들기
+
+플로우(Flow)는 전체적인 작업의 `흐름`을 표현하는것으로, 별도의 코딩작업을 최소화 하고, 시각적으로 수정/확인이 가능한 일련의 그룹을 말합니다.
+
+Langflow를 사용하여 플로우를 만드는 것은 쉽습니다. 사이드바의 구성 요소를 작업 공간으로 끌어다가 연결하기만 하면 응용 프로그램을 구축할 수 있습니다.
+
+프롬프트 매개 변수를 편집하고 구성 요소를 하나의 상위 수준 구성 요소로 그룹화하고 사용자 정의 구성 요소를 구축하여 탐색합니다.
+
+작업이 완료되면 플로우를 JSON 파일로 내보낼 수 있습니다.
+
+플로우 로드하기:
+
+```python
+from langflow.load import run_flow_from_json
+
+results = run_flow_from_json("path/to/flow.json", input_value="Hello, World!")
+```
+
+# 배포
+
+## DataStax Langflow
+
+DataStax Langflow는 [AstraDB](https://www.datastax.com/products/datastax-astra) 와 통합된 Langflow의 호스팅된 버전입니다. 별도의 설치나 설정이 필요하지 않고 몇 분 안에 실행됩니다. [무료로 가입하기](https://astra.datastax.com/signup?type=langflow).
+
+## Hugging Face Spaces에 Langflow 배포하기
+
+[Hugging Face Spaces](https://huggingface.co/spaces/Langflow/Langflow) 에서 Langflow를 미리 볼 수 있습니다. [space 복제하기](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true) 에서 몇 분 안에 자신만의 Langflow 작업 공간을 만들 수 있습니다.
+
+## Google Cloud Platform에 Langflow 배포하기
+
+Google Cloud Shell을 사용하여 Google Cloud Platform(GCP)에 Langflow를 배포하려면 단계별 가이드를 따르십시오. 가이드는 [**Langflow in Google Cloud Platform**](/docs/docs/Deployment/deployment-gcp.md) 문서에서 확인할 수 있습니다.
+
+또는 아래의 **"Cloud Shell에서 열기"** 버튼을 클릭하여 Google Cloud Shell을 시작하고 Langflow 저장소를 복제한 후 필요한 리소스를 설정하고 GCP 프로젝트에 Langflow를 배포하는 과정을 안내하는 **대화형 튜토리얼**을 시작합니다.
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+## Railway에 배포하기
+
+이 템플릿을 사용하여 Railway에 Langflow 1.0을 배포합니다:
+
+[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+## Render에 배포하기
+
+
+
+
+
+## Kubernetes에 배포하기
+
+[Langflow on Kubernetes](./docs/docs/Deployment/deployment-kubernetes.md)의 가이드를 따르세요.
+
+# 🖥️ 명령줄 인터페이스 (CLI)
+
+Langflow는 쉬운 관리 및 구성을 위한 명령줄 인터페이스(CLI)를 제공합니다.
+
+## 사용법
+
+다음 명령을 사용하여 Langflow를 실행할 수 있습니다:
+
+```shell
+langflow run [OPTIONS]
+```
+
+각 옵션의 자세한 내용은 아래와 같습니다:
+
+- `--help`: 사용 가능한 모든 옵션을 표시합니다.
+- `--host`: 서버를 바인딩할 호스트를 정의합니다. `LANGFLOW_HOST` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `127.0.0.1`입니다.
+- `--workers`: 작업자 프로세스 수를 설정합니다. `LANGFLOW_WORKERS` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `1`입니다.
+- `--worker-timeout`: 작업자 시간 제한을 초 단위로 설정합니다. 기본 값은 `60`입니다.
+- `--port`: 수신할 포트를 설정합니다. `LANGFLOW_PORT` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `7860`입니다.
+- `--env-file`: 환경 변수가 포함된 .env 파일의 경로를 지정합니다. 기본 값은 `.env`입니다.
+- `--log-level`: 로깅 수준을 정의합니다. `LANGFLOW_LOG_LEVEL` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `critical`입니다.
+- `--components-path`: 사용자 지정 구성 요소가 포함된 디렉토리 경로를 지정합니다. `LANGFLOW_COMPONENTS_PATH` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `langflow/components`입니다.
+- `--log-file`: 로그 파일 경로를 지정합니다. `LANGFLOW_LOG_FILE` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `logs/langflow.log`입니다.
+- `--cache`: 사용할 캐시 유형을 선택합니다. 옵션은 `InMemoryCache` 와 `SQLiteCache`입니다. `LANGFLOW_LANGCHAIN_CACHE` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `SQLiteCache`입니다.
+- `--dev/--no-dev`: 개발 모드를 전환합니다. 기본 값은 `no-dev`입니다.
+- `--path`: 빌드 파일이 포함된 프런트엔드 디렉토리 경로를 지정합니다. 이 옵션은 개발 목적으로만 사용됩니다. `LANGFLOW_FRONTEND_PATH` 환경 변수를 사용하여 설정할 수 있습니다.
+- `--open-browser/--no-open-browser`: 서버를 시작한 후 브라우저를 여는 옵션을 토글합니다. `LANGFLOW_OPEN_BROWSER` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `open-browser`입니다.
+- `--remove-api-keys/--no-remove-api-keys`: 데이터베이스에 저장된 프로젝트에서 API 키를 제거하는 옵션을 토글합니다. `LANGFLOW_REMOVE_API_KEYS` 환경 변수를 사용하여 설정할 수 있습니다. 기본 값은 `no-remove-api-keys`입니다.
+- `--install-completion [bash|zsh|fish|powershell|pwsh]`: 지정된 셸에 대해 설치합니다.
+- `--show-completion [bash|zsh|fish|powershell|pwsh]`: 지정된 셸의 완료를 표시하여 셸을 복사하거나 설치를 사용자 정의할 수 있습니다.
+- `--backend-only`: 이 파라미터는 기본 값이 `False`이며, 프론트엔드 없이 백엔드 서버만 실행할 수 있도록 합니다. `LANGFLOW_BACKEND_ONLY` 환경 변수를 사용하여 설정할 수 있습니다.
+- `--store`: 이 파라미터는 기본 값이 `True`이며, 스토어 기능을 활성화하고, `--no-store`를 사용하여 비활성화할 수 있습니다. `LANGFLOW_STORE` 환경 변수를 사용하여 설정할 수 있습니다.
+
+These parameters are important for users who need to customize the behavior of Langflow, especially in development or specialized deployment scenarios.
+
+### 환경 변수
+
+환경 변수를 사용하여 많은 CLI 옵션을 구성할 수 있습니다. 이러한 옵션은 운영 체제에서 내보내거나 `.env` 파일에 추가 하고 `--env-file` 옵션을 사용하여 로드할 수 있습니다.
+
+예제 `.env` 파일은 `.env.example` 프로젝트에 포함되어 있습니다. 이 파일을 복사하고 `.env` 파일로 이름을 바꾸어 실제 설정을 바꾸세요. OS와 `.env` 파일 모두에서 값을 설정하는 경우, `.env` 파일 설정이 우선시 됩니다.
+
+# 👋 기여
+
+모든 레벨의 개발자가 GitHub의 오픈소스 프로젝트에 기여하는 것을 환영합니다. 기여하고 싶으시다면 [기여 지침](./CONTRIBUTING.md)을 확인 하고 Langflow를 더 접근하기 쉽게 만드는 데 도움을 주세요.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+# 🌟 기여자
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
+# 📄 라이선스
+
+Langflow는 MIT 라이선스에 따라 출시됩니다. 자세한 내용은 [라이선스](LICENSE) 파일을 확인하세요.
diff --git a/langflow/README.PT.md b/langflow/README.PT.md
new file mode 100644
index 0000000..881aa9b
--- /dev/null
+++ b/langflow/README.PT.md
@@ -0,0 +1,177 @@
+
+
+# [](https://www.langflow.org)
+
+
+ Um framework visual para criar apps de agentes autônomos e RAG
+
+
+ Open-source, construído em Python, totalmente personalizável, agnóstico em relação a modelos e databases
+
+
+
+ Docs -
+ Junte-se ao nosso Discord -
+ Siga-nos no X -
+ Demonstração
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 📝 Conteúdo
+
+- [📝 Conteúdo](#-conteúdo)
+- [📦 Introdução](#-introdução)
+- [🎨 Criar Fluxos](#-criar-fluxos)
+- [Deploy](#deploy)
+ - [Deploy usando Google Cloud Platform](#deploy-usando-google-cloud-platform)
+ - [Deploy on Railway](#deploy-on-railway)
+ - [Deploy on Render](#deploy-on-render)
+- [🖥️ Interface de Linha de Comando (CLI)](#️-interface-de-linha-de-comando-cli)
+ - [Uso](#uso)
+ - [Variáveis de Ambiente](#variáveis-de-ambiente)
+- [👋 Contribuir](#-contribuir)
+- [🌟 Contribuidores](#-contribuidores)
+- [📄 Licença](#-licença)
+
+# 📦 Introdução
+
+Você pode instalar o Langflow com pip:
+
+```shell
+# Certifique-se de ter >=Python 3.10 instalado no seu sistema.
+# Instale a versão pré-lançamento (recomendada para as atualizações mais recentes)
+python -m pip install langflow --pre --force-reinstall
+
+# ou versão estável
+python -m pip install langflow -U
+```
+
+Então, execute o Langflow com:
+
+```shell
+python -m langflow run
+```
+
+Você também pode visualizar o Langflow no [HuggingFace Spaces](https://huggingface.co/spaces/Langflow/Langflow). [Clone o Space usando este link](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true) para criar seu próprio workspace do Langflow em minutos.
+
+# 🎨 Criar Fluxos
+
+Criar fluxos com Langflow é fácil. Basta arrastar componentes da barra lateral para o workspace e conectá-los para começar a construir sua aplicação.
+
+Explore editando os parâmetros do prompt, agrupando componentes e construindo seus próprios componentes personalizados (Custom Components).
+
+Quando terminar, você pode exportar seu fluxo como um arquivo JSON.
+
+Carregue o fluxo com:
+
+```python
+from langflow.load import run_flow_from_json
+
+results = run_flow_from_json("path/to/flow.json", input_value="Hello, World!")
+```
+
+# Deploy
+
+## Deploy usando Google Cloud Platform
+
+Siga nosso passo a passo para fazer deploy do Langflow no Google Cloud Platform (GCP) usando o Google Cloud Shell. O guia está disponível no documento [**Langflow on Google Cloud Platform**](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/deployment/gcp-deployment.md).
+
+Alternativamente, clique no botão **"Open in Cloud Shell"** abaixo para iniciar o Google Cloud Shell, clonar o repositório do Langflow e começar um **tutorial interativo** que o guiará pelo processo de configuração dos recursos necessários e deploy do Langflow no seu projeto GCP.
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+## Deploy on Railway
+
+Use este template para implantar o Langflow 1.0 Preview no Railway:
+
+[](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
+
+Ou este para implantar o Langflow 0.6.x:
+
+[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+## Deploy on Render
+
+
+
+
+
+# 🖥️ Interface de Linha de Comando (CLI)
+
+O Langflow fornece uma interface de linha de comando (CLI) para fácil gerenciamento e configuração.
+
+## Uso
+
+Você pode executar o Langflow usando o seguinte comando:
+
+```shell
+langflow run [OPTIONS]
+```
+
+Cada opção é detalhada abaixo:
+
+- `--help`: Exibe todas as opções disponíveis.
+- `--host`: Define o host para vincular o servidor. Pode ser configurado usando a variável de ambiente `LANGFLOW_HOST`. O padrão é `127.0.0.1`.
+- `--workers`: Define o número de processos. Pode ser configurado usando a variável de ambiente `LANGFLOW_WORKERS`. O padrão é `1`.
+- `--worker-timeout`: Define o tempo limite do worker em segundos. O padrão é `60`.
+- `--port`: Define a porta para escutar. Pode ser configurado usando a variável de ambiente `LANGFLOW_PORT`. O padrão é `7860`.
+- `--env-file`: Especifica o caminho para o arquivo .env contendo variáveis de ambiente. O padrão é `.env`.
+- `--log-level`: Define o nível de log. Pode ser configurado usando a variável de ambiente `LANGFLOW_LOG_LEVEL`. O padrão é `critical`.
+- `--components-path`: Especifica o caminho para o diretório contendo componentes personalizados. Pode ser configurado usando a variável de ambiente `LANGFLOW_COMPONENTS_PATH`. O padrão é `langflow/components`.
+- `--log-file`: Especifica o caminho para o arquivo de log. Pode ser configurado usando a variável de ambiente `LANGFLOW_LOG_FILE`. O padrão é `logs/langflow.log`.
+- `--cache`: Seleciona o tipo de cache a ser usado. As opções são `InMemoryCache` e `SQLiteCache`. Pode ser configurado usando a variável de ambiente `LANGFLOW_LANGCHAIN_CACHE`. O padrão é `SQLiteCache`.
+- `--dev/--no-dev`: Alterna o modo de desenvolvimento. O padrão é `no-dev`.
+- `--path`: Especifica o caminho para o diretório frontend contendo os arquivos de build. Esta opção é apenas para fins de desenvolvimento. Pode ser configurado usando a variável de ambiente `LANGFLOW_FRONTEND_PATH`.
+- `--open-browser/--no-open-browser`: Alterna a opção de abrir o navegador após iniciar o servidor. Pode ser configurado usando a variável de ambiente `LANGFLOW_OPEN_BROWSER`. O padrão é `open-browser`.
+- `--remove-api-keys/--no-remove-api-keys`: Alterna a opção de remover as chaves de API dos projetos salvos no banco de dados. Pode ser configurado usando a variável de ambiente `LANGFLOW_REMOVE_API_KEYS`. O padrão é `no-remove-api-keys`.
+- `--install-completion [bash|zsh|fish|powershell|pwsh]`: Instala a conclusão para o shell especificado.
+- `--show-completion [bash|zsh|fish|powershell|pwsh]`: Exibe a conclusão para o shell especificado, permitindo que você copie ou personalize a instalação.
+- `--backend-only`: Este parâmetro, com valor padrão `False`, permite executar apenas o servidor backend sem o frontend. Também pode ser configurado usando a variável de ambiente `LANGFLOW_BACKEND_ONLY`.
+- `--store`: Este parâmetro, com valor padrão `True`, ativa os recursos da loja, use `--no-store` para desativá-los. Pode ser configurado usando a variável de ambiente `LANGFLOW_STORE`.
+
+Esses parâmetros são importantes para usuários que precisam personalizar o comportamento do Langflow, especialmente em cenários de desenvolvimento ou deploy especializado.
+
+### Variáveis de Ambiente
+
+Você pode configurar muitas das opções de CLI usando variáveis de ambiente. Estas podem ser exportadas no seu sistema operacional ou adicionadas a um arquivo `.env` e carregadas usando a opção `--env-file`.
+
+Um arquivo de exemplo `.env` chamado `.env.example` está incluído no projeto. Copie este arquivo para um novo arquivo chamado `.env` e substitua os valores de exemplo pelas suas configurações reais. Se você estiver definindo valores tanto no seu sistema operacional quanto no arquivo `.env`, as configurações do `.env` terão precedência.
+
+# 👋 Contribuir
+
+Aceitamos contribuições de desenvolvedores de todos os níveis para nosso projeto open-source no GitHub. Se você deseja contribuir, por favor, confira nossas [diretrizes de contribuição](./CONTRIBUTING.md) e ajude a tornar o Langflow mais acessível.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+# 🌟 Contribuidores
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
+# 📄 Licença
+
+O Langflow é lançado sob a licença MIT. Veja o arquivo [LICENSE](LICENSE) para detalhes.
diff --git a/langflow/README.RU.md b/langflow/README.RU.md
new file mode 100644
index 0000000..9e33d6f
--- /dev/null
+++ b/langflow/README.RU.md
@@ -0,0 +1,78 @@
+
+
+
+
+
+ Langflow — это инструмент для создания приложений с низким уровнем кода для RAG и многоагентных ИИ-приложений. Он основан на Python и не зависит от конкретных моделей, API или баз данных.
+
+
+
+ Документация -
+ Бесплатный облачный сервис -
+ Самостоятельное развертывание
+
+
+
+
+
+## ✨ Основные возможности
+
+1. **Основан на Python** и не зависит от моделей, API, источников данных или баз данных.
+2. **Визуальная IDE** для построения и тестирования рабочих процессов методом drag-and-drop.
+3. **Песочница** для мгновенного тестирования и итерации процессов с поэтапным контролем.
+4. **Оркестрация многоагентных систем** с управлением диалогами и извлечением информации.
+5. **Бесплатный облачный сервис**, позволяющий начать работу за считанные минуты без настройки.
+6. **Публикация в виде API** или экспорт в виде Python-приложения.
+7. **Наблюдаемость** с интеграцией LangSmith, LangFuse или LangWatch.
+8. **Безопасность и масштабируемость уровня предприятия** с бесплатным облачным сервисом DataStax Langflow.
+9. **Настройка рабочих процессов** или создание потоков исключительно на Python.
+10. **Интеграция с экосистемами** через повторно используемые компоненты для любых моделей, API или баз данных.
+
+
+
+## 📦 Быстрый старт
+
+- **Установка через uv (рекомендуется)** (Python 3.10–3.12):
+
+```shell
+uv pip install langflow
+```
+
+- **Установка через pip** (Python 3.10–3.12):
+
+```shell
+pip install langflow
+```
+
+- **Облако:** DataStax Langflow — это управляемая среда без необходимости настройки. [Зарегистрируйтесь бесплатно.](https://astra.datastax.com/signup?type=langflow)
+- **Самостоятельное развертывание:** Запустите Langflow в своей среде. [Установите Langflow](https://docs.langflow.org/get-started-installation), чтобы запустить локальный сервер Langflow, а затем воспользуйтесь [руководством по быстрому старту](https://docs.langflow.org/get-started-quickstart) для создания и выполнения потока.
+- **Hugging Face:** [Клонируйте пространство по этой ссылке](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true), чтобы создать рабочее пространство Langflow.
+
+[](https://www.youtube.com/watch?v=kinngWhaUKM)
+
+## ⭐ Будьте в курсе обновлений
+
+Добавьте Langflow в избранное на GitHub, чтобы мгновенно узнавать о новых релизах.
+
+
+
+## 👋 Внесите свой вклад
+
+Мы приветствуем вклад разработчиков любого уровня. Если хотите помочь в развитии проекта, ознакомьтесь с нашими [руководящими принципами для участников](./CONTRIBUTING.md) и сделайте Langflow еще доступнее.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+## ❤️ Соавторы
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
diff --git a/langflow/README.ja.md b/langflow/README.ja.md
new file mode 100644
index 0000000..2c2b3d0
--- /dev/null
+++ b/langflow/README.ja.md
@@ -0,0 +1,195 @@
+
+
Langflow 1.0 がリリースされました! 🎉
+
詳細は こちら をご覧ください!
+
+
+
+
+# [](https://www.langflow.org)
+
+
+ マルチエージェントおよびRAGアプリケーションを構築するためのビジュアルフレームワーク
+
+
+ オープンソース、Python駆動、完全にカスタマイズ可能、LLMおよびベクトルストアに依存しない
+
+
+
+ ドキュメント -
+ Discordに参加 -
+ Xでフォロー -
+ ライブデモ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 📝 目次
+
+- [📝 目次](#-目次)
+- [📦 始めに](#-始めに)
+- [🎨 フローの作成](#-フローの作成)
+- [デプロイ](#デプロイ)
+ - [DataStax Langflow](#datastax-langflow)
+ - [Hugging Face Spaces に Langflow をデプロイ](#hugging-face-spacesにlangflowをデプロイ)
+ - [Google Cloud Platform に Langflow をデプロイ](#google-cloud-platformにlangflowをデプロイ)
+ - [Railway にデプロイ](#railwayにデプロイ)
+ - [Render にデプロイ](#renderにデプロイ)
+ - [Kubernetes にデプロイ](#kubernetesにデプロイ)
+- [🖥️ コマンドラインインターフェース (CLI)](#️-コマンドラインインターフェース-cli)
+ - [使用方法](#使用方法)
+ - [環境変数](#環境変数)
+- [👋 貢献](#-貢献)
+- [🌟 貢献者](#-貢献者)
+- [📄 ライセンス](#-ライセンス)
+
+# 📦 始めに
+
+Langflow を pip でインストールできます:
+
+```shell
+# システムに>=Python 3.10がインストールされていることを確認してください。
+python -m pip install langflow -U
+```
+
+または
+
+クローンしたリポジトリからインストールしたい場合は、以下のコマンドで Langflow のフロントエンドとバックエンドをビルドしてインストールできます:
+
+```shell
+make install_frontend && make build_frontend && make install_backend
+```
+
+その後、以下のコマンドで Langflow を実行します:
+
+```shell
+python -m langflow run
+```
+
+# 🎨 フローの作成
+
+Langflow を使ってフローを作成するのは簡単です。サイドバーからコンポーネントをワークスペースにドラッグして接続するだけで、アプリケーションの構築を開始できます。
+
+プロンプトパラメータを編集したり、コンポーネントを単一の高レベルコンポーネントにグループ化したり、独自のカスタムコンポーネントを作成したりして探索してください。
+
+完了したら、フローを JSON ファイルとしてエクスポートできます。
+
+以下のスクリプトを使用してフローを読み込みます:
+
+```python
+from langflow.load import run_flow_from_json
+
+results = run_flow_from_json("path/to/flow.json", input_value="Hello, World!")
+```
+
+# デプロイ
+
+## DataStax Langflow
+
+DataStax Langflow は、[AstraDB](https://www.datastax.com/products/datastax-astra)と統合された Langflow のホストバージョンです。インストールや設定なしで数分で稼働できます。[無料でサインアップ](https://astra.datastax.com/signup?type=langflow)してください。
+
+## Hugging Face Spaces に Langflow をデプロイ
+
+[HuggingFace Spaces](https://huggingface.co/spaces/Langflow/Langflow-Preview)で Langflow をプレビューすることもできます。[このリンクを使用してスペースをクローン](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true)して、数分で独自の Langflow ワークスペースを作成できます。
+
+## Google Cloud Platform に Langflow をデプロイ
+
+Google Cloud Shell を使用して Google Cloud Platform(GCP)に Langflow をデプロイする手順については、[**Langflow in Google Cloud Platform**](./docs/docs/Deployment/deployment-gcp.md)ドキュメントをご覧ください。
+
+または、以下の**「Open in Cloud Shell」**ボタンをクリックして Google Cloud Shell を起動し、Langflow リポジトリをクローンして、GCP プロジェクトに必要なリソースを設定し、Langflow をデプロイするプロセスをガイドする**インタラクティブチュートリアル**を開始します。
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+## Railway にデプロイ
+
+このテンプレートを使用して Langflow 1.0 を Railway にデプロイします:
+
+[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+## Render にデプロイ
+
+
+
+
+
+## Kubernetes にデプロイ
+
+[Kubernetes に Langflow をデプロイ](./docs/docs/Deployment/deployment-kubernetes.md)する手順については、ステップバイステップガイドをご覧ください。
+
+# 🖥️ コマンドラインインターフェース (CLI)
+
+Langflow は、簡単な管理と設定のためのコマンドラインインターフェース(CLI)を提供します。
+
+## 使用方法
+
+以下のコマンドを使用して Langflow を実行できます:
+
+```shell
+langflow run [OPTIONS]
+```
+
+各オプションの詳細は以下の通りです:
+
+- `--help`: 利用可能なすべてのオプションを表示します。
+- `--host`: サーバーをバインドするホストを定義します。`LANGFLOW_HOST`環境変数を使用して設定できます。デフォルトは`127.0.0.1`です。
+- `--workers`: ワーカープロセスの数を設定します。`LANGFLOW_WORKERS`環境変数を使用して設定できます。デフォルトは`1`です。
+- `--worker-timeout`: ワーカーのタイムアウトを秒単位で設定します。デフォルトは`60`です。
+- `--port`: リッスンするポートを設定します。`LANGFLOW_PORT`環境変数を使用して設定できます。デフォルトは`7860`です。
+- `--env-file`: 環境変数を含む.env ファイルのパスを指定します。デフォルトは`.env`です。
+- `--log-level`: ログレベルを定義します。`LANGFLOW_LOG_LEVEL`環境変数を使用して設定できます。デフォルトは`critical`です。
+- `--components-path`: カスタムコンポーネントを含むディレクトリのパスを指定します。`LANGFLOW_COMPONENTS_PATH`環境変数を使用して設定できます。デフォルトは`langflow/components`です。
+- `--log-file`: ログファイルのパスを指定します。`LANGFLOW_LOG_FILE`環境変数を使用して設定できます。デフォルトは`logs/langflow.log`です。
+- `--cache`: 使用するキャッシュの種類を選択します。オプションは`InMemoryCache`と`SQLiteCache`です。`LANGFLOW_LANGCHAIN_CACHE`環境変数を使用して設定できます。デフォルトは`SQLiteCache`です。
+- `--dev/--no-dev`: 開発モードを切り替えます。デフォルトは`no-dev`です。
+- `--path`: ビルドファイルを含むフロントエンドディレクトリのパスを指定します。このオプションは開発目的のみに使用されます。`LANGFLOW_FRONTEND_PATH`環境変数を使用して設定できます。
+- `--open-browser/--no-open-browser`: サーバー起動後にブラウザを開くオプションを切り替えます。`LANGFLOW_OPEN_BROWSER`環境変数を使用して設定できます。デフォルトは`open-browser`です。
+- `--remove-api-keys/--no-remove-api-keys`: データベースに保存されたプロジェクトから API キーを削除するオプションを切り替えます。`LANGFLOW_REMOVE_API_KEYS`環境変数を使用して設定できます。デフォルトは`no-remove-api-keys`です。
+- `--install-completion [bash|zsh|fish|powershell|pwsh]`: 指定されたシェルの補完をインストールします。
+- `--show-completion [bash|zsh|fish|powershell|pwsh]`: 指定されたシェルの補完を表示し、コピーまたはインストールをカスタマイズできます。
+- `--backend-only`: デフォルト値が`False`のこのパラメータは、フロントエンドなしでバックエンドサーバーのみを実行することを許可します。`LANGFLOW_BACKEND_ONLY`環境変数を使用して設定できます。
+- `--store`: デフォルト値が`True`のこのパラメータは、ストア機能を有効にします。無効にするには`--no-store`を使用します。`LANGFLOW_STORE`環境変数を使用して設定できます。
+
+これらのパラメータは、特に開発や特殊なデプロイメントシナリオで Langflow の動作をカスタマイズする必要があるユーザーにとって重要です。
+
+### 環境変数
+
+多くの CLI オプションは環境変数を使用して構成できます。これらの変数は、オペレーティングシステムにエクスポートするか、`.env`ファイルに追加して`--env-file`オプションを使用してロードできます。
+
+プロジェクトには、`.env.example`という名前のサンプル`.env`ファイルが含まれています。このファイルを新しいファイル`.env`にコピーし、サンプル値を実際の設定に置き換えます。OS と`.env`ファイルの両方に値を設定している場合、`.env`の設定が優先されます。
+
+# 👋 貢献
+
+私たちは、すべてのレベルの開発者が GitHub のオープンソースプロジェクトに貢献することを歓迎します。貢献したい場合は、[貢献ガイドライン](./CONTRIBUTING.md)を確認し、Langflow をよりアクセスしやすくするのにご協力ください。
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+# 🌟 貢献者
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
+# 📄 ライセンス
+
+Langflow は MIT ライセンスの下でリリースされています。詳細については、[LICENSE](LICENSE)ファイルを参照してください。
diff --git a/langflow/README.md b/langflow/README.md
new file mode 100644
index 0000000..3499b0c
--- /dev/null
+++ b/langflow/README.md
@@ -0,0 +1,79 @@
+
+
+
+
+
+ Langflow is a low-code app builder for RAG and multi-agent AI applications. It’s Python-based and agnostic to any model, API, or database.
+
+
+
+ Docs -
+ Free Cloud Service -
+ Self Managed
+
+
+
+
+
+## ✨ Core features
+
+1. **Python-based** and agnostic to models, APIs, data sources, or databases.
+2. **Visual IDE** for drag-and-drop building and testing of workflows.
+3. **Playground** to immediately test and iterate workflows with step-by-step control.
+4. **Multi-agent** orchestration and conversation management and retrieval.
+5. **Free cloud service** to get started in minutes with no setup.
+6. **Publish as an API** or export as a Python application.
+7. **Observability** with LangSmith, LangFuse, or LangWatch integration.
+8. **Enterprise-grade** security and scalability with free DataStax Langflow cloud service.
+9. **Customize workflows** or create flows entirely just using Python.
+10. **Ecosystem integrations** as reusable components for any model, API or database.
+
+
+
+## 📦 Quickstart
+
+- **Install with uv (recommended)** (Python 3.10 to 3.12):
+
+```shell
+uv pip install langflow
+```
+
+- **Install with pip** (Python 3.10 to 3.12):
+
+```shell
+pip install langflow
+```
+
+- **Cloud:** DataStax Langflow is a hosted environment with zero setup. [Sign up for a free account.](https://astra.datastax.com/signup?type=langflow)
+- **Self-managed:** Run Langflow in your environment. [Install Langflow](https://docs.langflow.org/get-started-installation) to run a local Langflow server, and then use the [Quickstart](https://docs.langflow.org/get-started-quickstart) guide to create and execute a flow.
+- **Hugging Face:** [Clone the space using this link](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true) to create a Langflow workspace.
+
+[](https://www.youtube.com/watch?v=kinngWhaUKM)
+
+## ⭐ Stay up-to-date
+
+Star Langflow on GitHub to be instantly notified of new releases.
+
+
+
+## 👋 Contribute
+
+We welcome contributions from developers of all levels. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+## ❤️ Contributors
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
diff --git a/langflow/README.zh_CN.md b/langflow/README.zh_CN.md
new file mode 100644
index 0000000..82cf2e6
--- /dev/null
+++ b/langflow/README.zh_CN.md
@@ -0,0 +1,178 @@
+
+
+# [](https://www.langflow.org)
+
+
+ 一种用于构建多智能体和RAG应用的可视化框架
+
+
+ 开源、Python驱动、完全可定制、大模型且不依赖于特定的向量存储
+
+
+
+ 文档 -
+ 加入我们的Discord社区 -
+ 在X上关注我们 -
+ 在线体验
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 📝 目录
+
+- [📝 目录](#-目录)
+- [📦 快速开始](#-快速开始)
+- [🎨 创建工作流](#-创建工作流)
+- [部署](#部署)
+ - [在 Google Cloud Platform 上部署 Langflow](#在google-cloud-platform上部署langflow)
+ - [在 Railway 上部署](#在railway上部署)
+ - [在 Render 上部署](#在render上部署)
+- [🖥️ 命令行界面 (CLI)](#️-命令行界面-cli)
+ - [用法](#用法)
+ - [环境变量](#环境变量)
+- [👋 贡献](#-贡献)
+- [🌟 贡献者](#-贡献者)
+- [📄 许可证](#-许可证)
+
+# 📦 快速开始
+
+使用 pip 安装 Langflow:
+
+```shell
+# 确保您的系统已经安装上>=Python 3.10
+# 安装Langflow预发布版本
+python -m pip install langflow --pre --force-reinstall
+
+# 安装Langflow稳定版本
+python -m pip install langflow -U
+```
+
+然后运行 Langflow:
+
+```shell
+python -m langflow run
+```
+
+您可以在[HuggingFace Spaces](https://huggingface.co/spaces/Langflow/Langflow-Preview)中在线体验 Langflow,也可以使用该链接[克隆空间](https://huggingface.co/spaces/Langflow/Langflow-Preview?duplicate=true),在几分钟内创建您自己的 Langflow 运行工作空间。
+
+# 🎨 创建工作流
+
+使用 Langflow 来创建工作流非常简单。只需从侧边栏拖动组件到画布上,然后连接组件即可开始构建应用程序。
+
+您可以通过编辑提示参数、将组件分组到单个高级组件中以及构建您自己的自定义组件来展开探索。
+
+完成后,可以将工作流导出为 JSON 文件。
+
+然后使用以下脚本加载工作流:
+
+```python
+from langflow.load import run_flow_from_json
+
+results = run_flow_from_json("path/to/flow.json", input_value="Hello, World!")
+```
+
+# 部署
+
+## 在 Google Cloud Platform 上部署 Langflow
+
+请按照我们的分步指南使用 Google Cloud Shell 在 Google Cloud Platform (GCP) 上部署 Langflow。该指南在 [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) 文档中提供。
+
+或者,点击下面的 "Open in Cloud Shell" 按钮,启动 Google Cloud Shell,克隆 Langflow 仓库,并开始一个互动教程,该教程将指导您设置必要的资源并在 GCP 项目中部署 Langflow。
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+## 在 Railway 上部署
+
+使用此模板在 Railway 上部署 Langflow 1.0 预览版:
+
+[](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
+
+或者使用此模板部署 Langflow 0.6.x:
+
+[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+## 在 Render 上部署
+
+
+
+
+
+# 🖥️ 命令行界面 (CLI)
+
+Langflow 提供了一个命令行界面以便于平台的管理和配置。
+
+## 用法
+
+您可以使用以下命令运行 Langflow:
+
+```shell
+langflow run [OPTIONS]
+```
+
+命令行参数的详细说明:
+
+- `--help`: 显示所有可用参数。
+- `--host`: 定义绑定服务器的主机 host 参数,可以使用 LANGFLOW_HOST 环境变量设置,默认值为 127.0.0.1。
+- `--workers`: 设置工作进程的数量,可以使用 LANGFLOW_WORKERS 环境变量设置,默认值为 1。
+- `--worker-timeout`: 设置工作进程的超时时间(秒),默认值为 60。
+- `--port`: 设置服务监听的端口,可以使用 LANGFLOW_PORT 环境变量设置,默认值为 7860。
+- `--config`: 定义配置文件的路径,默认值为 config.yaml。
+- `--env-file`: 指定包含环境变量的 .env 文件路径,默认值为 .env。
+- `--log-level`: 定义日志记录级别,可以使用 LANGFLOW_LOG_LEVEL 环境变量设置,默认值为 critical。
+- `--components-path`: 指定包含自定义组件的目录路径,可以使用 LANGFLOW_COMPONENTS_PATH 环境变量设置,默认值为 langflow/components。
+- `--log-file`: 指定日志文件的路径,可以使用 LANGFLOW_LOG_FILE 环境变量设置,默认值为 logs/langflow.log。
+- `--cache`: 选择要使用的缓存类型,可选项为 InMemoryCache 和 SQLiteCache,可以使用 LANGFLOW_LANGCHAIN_CACHE 环境变量设置,默认值为 SQLiteCache。
+- `--dev/--no-dev`: 切换开发/非开发模式,默认值为 no-dev 即非开发模式。
+- `--path`: 指定包含前端构建文件的目录路径,此参数仅用于开发目的,可以使用 LANGFLOW_FRONTEND_PATH 环境变量设置。
+- `--open-browser/--no-open-browser`: 切换启动服务器后是否打开浏览器,可以使用 LANGFLOW_OPEN_BROWSER 环境变量设置,默认值为 open-browser 即启动后打开浏览器。
+- `--remove-api-keys/--no-remove-api-keys`: 切换是否从数据库中保存的项目中移除 API 密钥,可以使用 LANGFLOW_REMOVE_API_KEYS 环境变量设置,默认值为 no-remove-api-keys。
+- `--install-completion [bash|zsh|fish|powershell|pwsh]`: 为指定的 shell 安装自动补全。
+- `--show-completion [bash|zsh|fish|powershell|pwsh]`: 显示指定 shell 的自动补全,使您可以复制或自定义安装。
+- `--backend-only`: 此参数默认为 False,允许仅运行后端服务器而不运行前端,也可以使用 LANGFLOW_BACKEND_ONLY 环境变量设置。
+- `--store`: 此参数默认为 True,启用存储功能,使用 --no-store 可禁用它,可以使用 LANGFLOW_STORE 环境变量配置。
+
+这些参数对于需要定制 Langflow 行为的用户尤其重要,特别是在开发或者特殊部署场景中。
+
+### 环境变量
+
+您可以使用环境变量配置许多 CLI 参数选项。这些变量可以在操作系统中导出,或添加到 .env 文件中,并使用 --env-file 参数加载。
+
+项目中包含一个名为 .env.example 的示例 .env 文件。将此文件复制为新文件 .env,并用实际设置值替换示例值。如果同时在操作系统和 .env 文件中设置值,则 .env 设置优先。
+
+# 👋 贡献
+
+我们欢迎各级开发者为我们的 GitHub 开源项目做出贡献,并帮助 Langflow 更加易用,如果您想参与贡献,请查看我们的贡献指南 [contributing guidelines](./CONTRIBUTING.md) 。
+
+---
+
+[](https://star-history.com/#langflow-ai/langflow&Date)
+
+# 🌟 贡献者
+
+[](https://github.com/langflow-ai/langflow/graphs/contributors)
+
+# 📄 许可证
+
+Langflow 以 MIT 许可证发布。有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
diff --git a/langflow/deploy/.env.example b/langflow/deploy/.env.example
new file mode 100644
index 0000000..a0b80bd
--- /dev/null
+++ b/langflow/deploy/.env.example
@@ -0,0 +1,52 @@
+DOMAIN=localhost
+STACK_NAME=langflow-stack
+
+TRAEFIK_PUBLIC_NETWORK=traefik-public
+TRAEFIK_TAG=langflow-traefik
+TRAEFIK_PUBLIC_TAG=traefik-public
+
+
+# Langflow backend configuration
+LANGFLOW_LOG_LEVEL=debug
+LANGFLOW_SUPERUSER=superuser
+LANGFLOW_SUPERUSER_PASSWORD=superuser
+LANGFLOW_NEW_USER_IS_ACTIVE=False
+
+
+# Langflow frontend configuration
+BACKEND_URL=http://backend:7860
+
+# RabbitMQ configuration
+RABBITMQ_DEFAULT_USER=langflow
+RABBITMQ_DEFAULT_PASS=langflow
+
+# Database configuration
+DB_USER=langflow
+DB_PASSWORD=langflow
+DB_HOST=db
+DB_PORT=5432
+DB_NAME=langflow
+
+# DB configuration
+POSTGRES_USER=langflow
+POSTGRES_PASSWORD=langflow
+POSTGRES_DB=langflow
+POSTGRES_PORT=5432
+
+# Flower configuration
+# Disable until https://github.com/langflow-ai/langflow/pull/2655 gets released
+#LANGFLOW_CACHE_TYPE=redis
+LANGFLOW_REDIS_HOST=result_backend
+LANGFLOW_REDIS_PORT=6379
+LANGFLOW_REDIS_DB=0
+LANGFLOW_REDIS_EXPIRE=3600
+LANGFLOW_REDIS_PASSWORD=
+FLOWER_UNAUTHENTICATED_API=True
+BROKER_URL=amqp://langflow:langflow@broker:5672
+RESULT_BACKEND=redis://result_backend:6379/0
+C_FORCE_ROOT="true"
+
+
+# PGAdmin configuration
+PGADMIN_DEFAULT_EMAIL=admin@admin.com
+PGADMIN_DEFAULT_PASSWORD=admin
diff --git a/langflow/deploy/.gitignore b/langflow/deploy/.gitignore
new file mode 100644
index 0000000..fdbcefe
--- /dev/null
+++ b/langflow/deploy/.gitignore
@@ -0,0 +1 @@
+pgadmin
\ No newline at end of file
diff --git a/langflow/deploy/README.md b/langflow/deploy/README.md
new file mode 100644
index 0000000..5456713
--- /dev/null
+++ b/langflow/deploy/README.md
@@ -0,0 +1,21 @@
+# Run Langflow
+
+
+## Docker compose
+To run Langflow with Docker compose, you need to have Docker and Docker compose installed on your machine. You can install Docker and Docker compose by following the instructions on the [official Docker documentation](https://docs.docker.com/get-docker/).
+
+The docker-compose file uses `latest` tag; it's recommended to pull the latest version of the images before running the docker-compose file.
+
+```bash
+docker compose pull
+```
+
+To start the Langflow services, run the following command:
+
+```bash
+docker compose up
+```
+
+After running the command, you can access the Langflow services at the following url: http://localhost:80.
+
+Edit the `.env` file to change the port or other configurations.
\ No newline at end of file
diff --git a/langflow/deploy/docker-compose.override.yml b/langflow/deploy/docker-compose.override.yml
new file mode 100644
index 0000000..0bff922
--- /dev/null
+++ b/langflow/deploy/docker-compose.override.yml
@@ -0,0 +1,65 @@
+services:
+ proxy:
+ ports:
+ - "80:80"
+ - "8090:8080"
+ command:
+ # Enable Docker in Traefik, so that it reads labels from Docker services
+ - --providers.docker
+ # Add a constraint to only use services with the label for this stack
+ # from the env var TRAEFIK_TAG
+ - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`)
+ # Do not expose all Docker services, only the ones explicitly exposed
+ - --providers.docker.exposedbydefault=false
+ # Disable Docker Swarm mode for local development
+ # - --providers.docker.swarmmode
+ # Enable the access log, with HTTP requests
+ - --accesslog
+ # Enable the Traefik log, for configurations and errors
+ - --log
+ # Enable the Dashboard and API
+ - --api
+ # Enable the Dashboard and API in insecure mode for local development
+ - --api.insecure=true
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80
+
+ result_backend:
+ ports:
+ - "6379:6379"
+
+ pgadmin:
+ ports:
+ - "5050:5050"
+
+ flower:
+ ports:
+ - "5555:5555"
+
+ backend:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=7860
+
+ frontend:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
+
+ celeryworker:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-celeryworker-http.rule=PathPrefix(`/api/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-celeryworker.loadbalancer.server.port=7860
+
+networks:
+ traefik-public:
+ # For local dev, don't expect an external Traefik network
+ external: false
diff --git a/langflow/deploy/docker-compose.yml b/langflow/deploy/docker-compose.yml
new file mode 100644
index 0000000..d4c00fd
--- /dev/null
+++ b/langflow/deploy/docker-compose.yml
@@ -0,0 +1,237 @@
+services:
+ proxy:
+ image: traefik:v3.0
+ env_file:
+ - .env
+ networks:
+ - ${TRAEFIK_PUBLIC_NETWORK?Variable not set}
+ - default
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ command:
+ # Enable Docker in Traefik, so that it reads labels from Docker services
+ - --providers.docker
+ # Add a constraint to only use services with the label for this stack
+ # from the env var TRAEFIK_TAG
+ - --providers.docker.constraints=Label(`traefik.constraint-label-stack`, `${TRAEFIK_TAG?Variable not set}`)
+ # Do not expose all Docker services, only the ones explicitly exposed
+ - --providers.docker.exposedbydefault=false
+ # Enable the access log, with HTTP requests
+ - --accesslog
+ # Enable the Traefik log, for configurations and errors
+ - --log
+ # Enable the Dashboard and API
+ - --api
+ deploy:
+ placement:
+ constraints:
+ - node.role == manager
+ labels:
+ # Enable Traefik for this service, to make it available in the public network
+ - traefik.enable=true
+ # Use the traefik-public network (declared below)
+ - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set}
+ # Use the custom label "traefik.constraint-label=traefik-public"
+ # This public Traefik will only use services with this label
+ - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set}
+ # traefik-http set up only to use the middleware to redirect to https
+ - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.scheme=https
+ - traefik.http.middlewares.${STACK_NAME?Variable not set}-https-redirect.redirectscheme.permanent=true
+ # Handle host with and without "www" to redirect to only one of them
+ # Uses environment variable DOMAIN
+ # To disable www redirection remove the Host() you want to discard, here and
+ # below for HTTPS
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.entrypoints=http
+ # traefik-https the actual router using HTTPS
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.rule=Host(`${DOMAIN?Variable not set}`) || Host(`www.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.entrypoints=https
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls=true
+ # Use the "le" (Let's Encrypt) resolver created below
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.tls.certresolver=le
+ # Define the port inside of the Docker service to use
+ - traefik.http.services.${STACK_NAME?Variable not set}-proxy.loadbalancer.server.port=80
+ # Handle domain with and without "www" to redirect to only one
+ # To disable www redirection remove the next line
+ - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^https?://(www.)?(${DOMAIN?Variable not set})/(.*)
+ # Redirect a domain with www to non-www
+ # To disable it remove the next line
+ - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=https://${DOMAIN?Variable not set}/$${3}
+ # Redirect a domain without www to www
+ # To enable it remove the previous line and uncomment the next
+ # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3}
+ # Middleware to redirect www, to disable it remove the next line
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect
+ # Middleware to redirect www, and redirect HTTP to HTTPS
+ # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect,
+ - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-http.middlewares=${STACK_NAME?Variable not set}-www-redirect,${STACK_NAME?Variable not set}-https-redirect
+
+ backend: &backend
+ image: "langflowai/langflow-backend:latest"
+ depends_on:
+ - db
+ - broker
+ - result_backend
+ env_file:
+ - .env
+ healthcheck:
+ test: "exit 0"
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api/v1`) || PathPrefix(`/docs`) || PathPrefix(`/health`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=7860
+
+ db:
+ image: postgres:15.4
+ volumes:
+ - app-db-data:/var/lib/postgresql/data/pgdata
+ environment:
+ - PGDATA=/var/lib/postgresql/data/pgdata
+ deploy:
+ placement:
+ constraints:
+ - node.labels.app-db-data == true
+ healthcheck:
+ test: "exit 0"
+ env_file:
+ - .env
+
+ pgadmin:
+ image: dpage/pgadmin4
+ networks:
+ - ${TRAEFIK_PUBLIC_NETWORK?Variable not set}
+ - default
+ volumes:
+ - pgadmin-data:/var/lib/pgadmin
+ env_file:
+ - .env
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set}
+ - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.rule=Host(`pgadmin.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.entrypoints=http
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-http.middlewares=${STACK_NAME?Variable not set}-https-redirect
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.rule=Host(`pgadmin.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.entrypoints=https
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls=true
+ - traefik.http.routers.${STACK_NAME?Variable not set}-pgadmin-https.tls.certresolver=le
+ - traefik.http.services.${STACK_NAME?Variable not set}-pgadmin.loadbalancer.server.port=5050
+
+ result_backend:
+ image: redis:6.2.5
+ env_file:
+ - .env
+ ports:
+ - 6379:6379
+ healthcheck:
+ test: "exit 0"
+
+ celeryworker:
+ <<: *backend
+ env_file:
+ - .env
+ command: /bin/sh -c "python -m celery -A langflow.worker.celery_app worker --loglevel=INFO --concurrency=1 -n lf-worker@%h -P eventlet"
+ healthcheck:
+ test: "exit 0"
+ deploy:
+ replicas: 1
+
+ flower:
+ <<: *backend
+ env_file:
+ - .env
+ networks:
+ - default
+ environment:
+ - FLOWER_PORT=5555
+
+ command: /bin/sh -c "python -m celery -A langflow.worker.celery_app --broker=${BROKER_URL?Variable not set} flower --port=5555"
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.docker.network=${TRAEFIK_PUBLIC_NETWORK?Variable not set}
+ - traefik.constraint-label=${TRAEFIK_PUBLIC_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.rule=Host(`flower.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.entrypoints=http
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-http.middlewares=${STACK_NAME?Variable not set}-https-redirect
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.rule=Host(`flower.${DOMAIN?Variable not set}`)
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.entrypoints=https
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true
+ - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls.certresolver=le
+ - traefik.http.services.${STACK_NAME?Variable not set}-flower.loadbalancer.server.port=5555
+
+ frontend:
+ image: "langflowai/langflow-frontend:latest"
+ env_file:
+ - .env
+ restart: on-failure
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
+
+ broker:
+ # RabbitMQ management console
+ image: rabbitmq:3-management
+ environment:
+ - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER:-admin}
+ - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS:-admin}
+ volumes:
+ - rabbitmq_data:/etc/rabbitmq/
+ - rabbitmq_data:/var/lib/rabbitmq/
+ - rabbitmq_log:/var/log/rabbitmq/
+ ports:
+ - 5672:5672
+ - 15672:15672
+
+ prometheus:
+ image: prom/prometheus:v2.37.9
+ env_file:
+ - .env
+ volumes:
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
+ command:
+ - "--config.file=/etc/prometheus/prometheus.yml"
+ # ports:
+ # - 9090:9090
+ healthcheck:
+ test: "exit 0"
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-prometheus-http.rule=PathPrefix(`/metrics`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-prometheus.loadbalancer.server.port=9090
+
+ grafana:
+ image: grafana/grafana:8.2.6
+ env_file:
+ - .env
+ # ports:
+ # - 3000:3000
+ volumes:
+ - grafana_data:/var/lib/grafana
+ deploy:
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set}
+ - traefik.http.routers.${STACK_NAME?Variable not set}-grafana-http.rule=PathPrefix(`/grafana`)
+ - traefik.http.services.${STACK_NAME?Variable not set}-grafana.loadbalancer.server.port=3000
+
+volumes:
+ grafana_data:
+ app-db-data:
+ rabbitmq_data:
+ rabbitmq_log:
+ pgadmin-data:
+
+networks:
+ traefik-public:
+ # Allow setting it to false for testing
+ external: false # ${TRAEFIK_PUBLIC_NETWORK_IS_EXTERNAL-true}
diff --git a/langflow/deploy/prometheus.yml b/langflow/deploy/prometheus.yml
new file mode 100644
index 0000000..1d53d5b
--- /dev/null
+++ b/langflow/deploy/prometheus.yml
@@ -0,0 +1,11 @@
+global:
+ scrape_interval: 15s
+ evaluation_interval: 15s
+
+scrape_configs:
+ - job_name: prometheus
+ static_configs:
+ - targets: ["prometheus:9090"]
+ - job_name: flower
+ static_configs:
+ - targets: ["flower:5555"]
diff --git a/langflow/docker/.dockerignore b/langflow/docker/.dockerignore
new file mode 100644
index 0000000..737244f
--- /dev/null
+++ b/langflow/docker/.dockerignore
@@ -0,0 +1,9 @@
+.venv/
+**/aws
+node_modules
+**/node_modules/
+dist/
+**/build/
+src/backend/langflow/frontend
+**/langflow-pre.db
+**/langflow.db
\ No newline at end of file
diff --git a/langflow/docker/build_and_push.Dockerfile b/langflow/docker/build_and_push.Dockerfile
new file mode 100644
index 0000000..3cfc134
--- /dev/null
+++ b/langflow/docker/build_and_push.Dockerfile
@@ -0,0 +1,94 @@
+# syntax=docker/dockerfile:1
+# Keep this syntax directive! It's used to enable Docker BuildKit
+
+################################
+# BUILDER-BASE
+# Used to build deps + create our virtual environment
+################################
+
+# 1. use python:3.12.3-slim as the base image until https://github.com/pydantic/pydantic-core/issues/1292 gets resolved
+# 2. do not add --platform=$BUILDPLATFORM because the pydantic binaries must be resolved for the final architecture
+# Use a Python image with uv pre-installed
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
+
+# Install the project into `/app`
+WORKDIR /app
+
+# Enable bytecode compilation
+ENV UV_COMPILE_BYTECODE=1
+
+# Copy from the cache instead of linking since it's a mounted volume
+ENV UV_LINK_MODE=copy
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install --no-install-recommends -y \
+ # deps for building python deps
+ build-essential \
+ git \
+ # npm
+ npm \
+ # gcc
+ gcc \
+
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN --mount=type=cache,target=/root/.cache/uv \
+ --mount=type=bind,source=uv.lock,target=uv.lock \
+ --mount=type=bind,source=README.md,target=README.md \
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
+ --mount=type=bind,source=src/backend/base/README.md,target=src/backend/base/README.md \
+ --mount=type=bind,source=src/backend/base/uv.lock,target=src/backend/base/uv.lock \
+ --mount=type=bind,source=src/backend/base/pyproject.toml,target=src/backend/base/pyproject.toml \
+ uv sync --frozen --no-install-project --no-editable --extra postgresql
+
+COPY ./src /app/src
+
+COPY src/frontend /tmp/src/frontend
+WORKDIR /tmp/src/frontend
+RUN --mount=type=cache,target=/root/.npm \
+ npm ci \
+ && npm run build \
+ && cp -r build /app/src/backend/langflow/frontend \
+ && rm -rf /tmp/src/frontend
+
+WORKDIR /app
+COPY ./pyproject.toml /app/pyproject.toml
+COPY ./uv.lock /app/uv.lock
+COPY ./README.md /app/README.md
+
+RUN --mount=type=cache,target=/root/.cache/uv \
+ uv sync --frozen --no-editable --extra postgresql
+
+################################
+# RUNTIME
+# Setup user, utilities and copy the virtual environment only
+################################
+FROM python:3.12.3-slim AS runtime
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install git -y \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* \
+ && useradd user -u 1000 -g 0 --no-create-home --home-dir /app/data
+
+COPY --from=builder --chown=1000 /app/.venv /app/.venv
+
+# Place executables in the environment at the front of the path
+ENV PATH="/app/.venv/bin:$PATH"
+
+LABEL org.opencontainers.image.title=langflow
+LABEL org.opencontainers.image.authors=['Langflow']
+LABEL org.opencontainers.image.licenses=MIT
+LABEL org.opencontainers.image.url=https://github.com/langflow-ai/langflow
+LABEL org.opencontainers.image.source=https://github.com/langflow-ai/langflow
+
+USER user
+WORKDIR /app
+
+ENV LANGFLOW_HOST=0.0.0.0
+ENV LANGFLOW_PORT=7860
+
+CMD ["langflow", "run"]
diff --git a/langflow/docker/build_and_push_backend.Dockerfile b/langflow/docker/build_and_push_backend.Dockerfile
new file mode 100644
index 0000000..66cf652
--- /dev/null
+++ b/langflow/docker/build_and_push_backend.Dockerfile
@@ -0,0 +1,9 @@
+# syntax=docker/dockerfile:1
+# Keep this syntax directive! It's used to enable Docker BuildKit
+
+ARG LANGFLOW_IMAGE
+FROM $LANGFLOW_IMAGE
+
+RUN rm -rf /app/.venv/langflow/frontend
+
+CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860", "--backend-only"]
diff --git a/langflow/docker/build_and_push_base.Dockerfile b/langflow/docker/build_and_push_base.Dockerfile
new file mode 100644
index 0000000..4d40eb1
--- /dev/null
+++ b/langflow/docker/build_and_push_base.Dockerfile
@@ -0,0 +1,98 @@
+# syntax=docker/dockerfile:1
+# Keep this syntax directive! It's used to enable Docker BuildKit
+
+
+################################
+# BUILDER-BASE
+# Used to build deps + create our virtual environment
+################################
+
+# 1. use python:3.12.3-slim as the base image until https://github.com/pydantic/pydantic-core/issues/1292 gets resolved
+# 2. do not add --platform=$BUILDPLATFORM because the pydantic binaries must be resolved for the final architecture
+# Use a Python image with uv pre-installed
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
+
+# Install the project into `/app`
+WORKDIR /app
+
+# Enable bytecode compilation
+ENV UV_COMPILE_BYTECODE=1
+
+# Copy from the cache instead of linking since it's a mounted volume
+ENV UV_LINK_MODE=copy
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install --no-install-recommends -y \
+ # deps for building python deps
+ build-essential \
+ git \
+ # npm
+ npm \
+ # gcc
+ gcc \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install the project's dependencies using the lockfile and settings
+# We need to mount the root uv.lock and pyproject.toml to build the base with uv because we're still using uv workspaces
+RUN --mount=type=cache,target=/root/.cache/uv \
+ --mount=type=bind,source=src/backend/base/README.md,target=src/backend/base/README.md \
+ --mount=type=bind,source=src/backend/base/uv.lock,target=src/backend/base/uv.lock \
+ --mount=type=bind,source=src/backend/base/pyproject.toml,target=src/backend/base/pyproject.toml \
+ --mount=type=bind,source=uv.lock,target=uv.lock \
+ --mount=type=bind,source=README.md,target=README.md \
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
+ cd src/backend/base && uv sync --frozen --no-install-project --no-dev --no-editable --extra postgresql
+
+COPY ./src /app/src
+
+COPY src/frontend /tmp/src/frontend
+WORKDIR /tmp/src/frontend
+RUN npm install \
+ && npm run build \
+ && cp -r build /app/src/backend/base/langflow/frontend \
+ && rm -rf /tmp/src/frontend
+
+COPY ./src/backend/base /app/src/backend/base
+WORKDIR /app/src/backend/base
+# again we need these because of workspaces
+COPY ./pyproject.toml /app/pyproject.toml
+COPY ./uv.lock /app/uv.lock
+COPY ./src/backend/base/pyproject.toml /app/src/backend/base/pyproject.toml
+COPY ./src/backend/base/uv.lock /app/src/backend/base/uv.lock
+COPY ./src/backend/base/README.md /app/src/backend/base/README.md
+RUN --mount=type=cache,target=/root/.cache/uv \
+ uv sync --frozen --no-dev --no-editable --extra postgresql
+
+################################
+# RUNTIME
+# Setup user, utilities and copy the virtual environment only
+################################
+FROM python:3.12.3-slim AS runtime
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install git -y \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* \
+ && useradd user -u 1000 -g 0 --no-create-home --home-dir /app/data
+# and we use the venv at the root because workspaces
+COPY --from=builder --chown=1000 /app/.venv /app/.venv
+
+# Place executables in the environment at the front of the path
+ENV PATH="/app/.venv/bin:$PATH"
+
+LABEL org.opencontainers.image.title=langflow
+LABEL org.opencontainers.image.authors=['Langflow']
+LABEL org.opencontainers.image.licenses=MIT
+LABEL org.opencontainers.image.url=https://github.com/langflow-ai/langflow
+LABEL org.opencontainers.image.source=https://github.com/langflow-ai/langflow
+
+USER user
+WORKDIR /app
+
+ENV LANGFLOW_HOST=0.0.0.0
+ENV LANGFLOW_PORT=7860
+
+CMD ["langflow-base", "run"]
diff --git a/langflow/docker/build_and_push_ep.Dockerfile b/langflow/docker/build_and_push_ep.Dockerfile
new file mode 100644
index 0000000..3473b01
--- /dev/null
+++ b/langflow/docker/build_and_push_ep.Dockerfile
@@ -0,0 +1,97 @@
+# syntax=docker/dockerfile:1
+# Keep this syntax directive! It's used to enable Docker BuildKit
+
+################################
+# BUILDER-BASE
+# Used to build deps + create our virtual environment
+################################
+
+# 1. use python:3.12.3-slim as the base image until https://github.com/pydantic/pydantic-core/issues/1292 gets resolved
+# 2. do not add --platform=$BUILDPLATFORM because the pydantic binaries must be resolved for the final architecture
+# Use a Python image with uv pre-installed
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
+
+# Install the project into `/app`
+WORKDIR /app
+
+# Enable bytecode compilation
+ENV UV_COMPILE_BYTECODE=1
+
+# Copy from the cache instead of linking since it's a mounted volume
+ENV UV_LINK_MODE=copy
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install --no-install-recommends -y \
+ # deps for building python deps
+ build-essential \
+ git \
+ # npm
+ npm \
+ # gcc
+ gcc \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN --mount=type=cache,target=/root/.cache/uv \
+ --mount=type=bind,source=uv.lock,target=uv.lock \
+ --mount=type=bind,source=README.md,target=README.md \
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
+ --mount=type=bind,source=src/backend/base/README.md,target=src/backend/base/README.md \
+ --mount=type=bind,source=src/backend/base/uv.lock,target=src/backend/base/uv.lock \
+ --mount=type=bind,source=src/backend/base/pyproject.toml,target=src/backend/base/pyproject.toml \
+ uv sync --frozen --no-install-project --no-editable --extra nv-ingest --extra postgresql
+
+COPY ./src /app/src
+
+COPY src/frontend /tmp/src/frontend
+WORKDIR /tmp/src/frontend
+RUN --mount=type=cache,target=/root/.npm \
+ npm ci \
+ && npm run build \
+ && cp -r build /app/src/backend/langflow/frontend \
+ && rm -rf /tmp/src/frontend
+
+WORKDIR /app
+COPY ./pyproject.toml /app/pyproject.toml
+COPY ./uv.lock /app/uv.lock
+COPY ./README.md /app/README.md
+
+RUN --mount=type=cache,target=/root/.cache/uv \
+ uv sync --frozen --no-editable --extra nv-ingest --extra postgresql
+
+################################
+# RUNTIME
+# Setup user, utilities and copy the virtual environment only
+################################
+FROM python:3.12.3-slim AS runtime
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install -y curl git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/* \
+ && useradd user -u 1000 -g 0 --no-create-home --home-dir /app/data \
+ && mkdir /data && chown -R 1000:0 /data
+
+COPY --from=builder --chown=1000 /app/.venv /app/.venv
+
+# curl is required for langflow health checks
+RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
+
+# Place executables in the environment at the front of the path
+ENV PATH="/app/.venv/bin:$PATH"
+
+LABEL org.opencontainers.image.title=langflow
+LABEL org.opencontainers.image.authors=['Langflow']
+LABEL org.opencontainers.image.licenses=MIT
+LABEL org.opencontainers.image.url=https://github.com/langflow-ai/langflow
+LABEL org.opencontainers.image.source=https://github.com/langflow-ai/langflow
+
+WORKDIR /app
+
+ENV LANGFLOW_HOST=0.0.0.0
+ENV LANGFLOW_PORT=7860
+
+USER 1000
+CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--backend-only"]
diff --git a/langflow/docker/cdk-docker-compose.yml b/langflow/docker/cdk-docker-compose.yml
new file mode 100644
index 0000000..987d198
--- /dev/null
+++ b/langflow/docker/cdk-docker-compose.yml
@@ -0,0 +1,37 @@
+version: "3"
+networks:
+ langflow:
+
+services:
+ backend:
+ build:
+ context: ./
+ dockerfile: ./dev.Dockerfile
+ env_file:
+ - .env
+ ports:
+ - "7860:7860"
+ volumes:
+ - ./:/app
+ command: bash -c "uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --loop asyncio"
+ networks:
+ - langflow
+ frontend:
+ build:
+ context: ./src/frontend
+ dockerfile: ./cdk.Dockerfile
+ args:
+ - BACKEND_URL=http://backend:7860
+ depends_on:
+ - backend
+ environment:
+ - VITE_PROXY_TARGET=http://backend:7860
+ ports:
+ - "8080:3000"
+ volumes:
+ - ./src/frontend/public:/home/node/app/public
+ - ./src/frontend/src:/home/node/app/src
+ - ./src/frontend/package.json:/home/node/app/package.json
+ restart: on-failure
+ networks:
+ - langflow
diff --git a/langflow/docker/cdk.Dockerfile b/langflow/docker/cdk.Dockerfile
new file mode 100644
index 0000000..0c2669a
--- /dev/null
+++ b/langflow/docker/cdk.Dockerfile
@@ -0,0 +1,25 @@
+FROM --platform=linux/amd64 python:3.10-slim
+
+WORKDIR /app
+
+# Install Poetry
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install gcc g++ curl build-essential postgresql-server-dev-all -y \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+RUN curl -sSL https://install.python-poetry.org | python3 -
+# # Add Poetry to PATH
+ENV PATH="${PATH}:/root/.local/bin"
+# # Copy the pyproject.toml and poetry.lock files
+COPY poetry.lock pyproject.toml ./
+# Copy the rest of the application codes
+COPY ./ ./
+
+# Install dependencies
+RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
+
+RUN poetry add botocore
+RUN poetry add pymysql
+
+CMD ["sh", "./container-cmd-cdk.sh"]
diff --git a/langflow/docker/container-cmd-cdk.sh b/langflow/docker/container-cmd-cdk.sh
new file mode 100644
index 0000000..dbab8f4
--- /dev/null
+++ b/langflow/docker/container-cmd-cdk.sh
@@ -0,0 +1,5 @@
+export LANGFLOW_DATABASE_URL="mysql+pymysql://${username}:${password}@${host}:3306/${dbname}"
+# echo $LANGFLOW_DATABASE_URL
+uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --log-level debug --loop asyncio
+
+# python -m langflow run --host 0.0.0.0 --port 7860
\ No newline at end of file
diff --git a/langflow/docker/dev.Dockerfile b/langflow/docker/dev.Dockerfile
new file mode 100644
index 0000000..56c37e9
--- /dev/null
+++ b/langflow/docker/dev.Dockerfile
@@ -0,0 +1,31 @@
+FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
+ENV TZ=UTC
+
+WORKDIR /app
+
+RUN apt-get update \
+ && apt-get upgrade -y \
+ && apt-get install -y \
+ build-essential \
+ curl \
+ npm \
+ git \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY . /app
+
+# Install dependencies using uv
+RUN --mount=type=cache,target=/root/.cache/uv \
+ --mount=type=bind,source=uv.lock,target=uv.lock \
+ --mount=type=bind,source=README.md,target=README.md \
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
+ --mount=type=bind,source=src/backend/base/README.md,target=src/backend/base/README.md \
+ --mount=type=bind,source=src/backend/base/uv.lock,target=src/backend/base/uv.lock \
+ --mount=type=bind,source=src/backend/base/pyproject.toml,target=src/backend/base/pyproject.toml \
+ uv sync --frozen --no-install-project --no-dev --extra postgresql
+
+EXPOSE 7860
+EXPOSE 3000
+
+CMD ["./docker/dev.start.sh"]
diff --git a/langflow/docker/dev.docker-compose.yml b/langflow/docker/dev.docker-compose.yml
new file mode 100644
index 0000000..5cad3f5
--- /dev/null
+++ b/langflow/docker/dev.docker-compose.yml
@@ -0,0 +1,42 @@
+networks:
+ dev-langflow:
+
+services:
+ langflow:
+ build:
+ context: ..
+ dockerfile: docker/dev.Dockerfile
+ image: dev-langflow
+ container_name: dev-langflow
+ restart: always
+ ports:
+ - "7860:7860"
+ - "3000:3000"
+ environment:
+ - PYTHONDONTWRITEBYTECODE=1
+ - LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
+ - LANGFLOW_SUPERUSER=langflow
+ - LANGFLOW_SUPERUSER_PASSWORD=langflow
+ - LANGFLOW_CONFIG_DIR=/var/lib/langflow
+ env_file:
+ - ../.env
+ volumes:
+ - ../:/app
+ depends_on:
+ - postgres
+ networks:
+ - dev-langflow
+
+
+ postgres:
+ container_name: postgres
+ image: pgvector/pgvector:pg16
+ environment:
+ POSTGRES_USER: langflow
+ POSTGRES_PASSWORD: langflow
+ POSTGRES_DB: langflow
+ ports:
+ - "5432:5432"
+ networks:
+ - dev-langflow
+
diff --git a/langflow/docker/dev.start.sh b/langflow/docker/dev.start.sh
new file mode 100644
index 0000000..defa6c5
--- /dev/null
+++ b/langflow/docker/dev.start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+cd src/frontend \
+ && rm -rf node_modules \
+ && npm install \
+ && npm run dev:docker &
+make backend
diff --git a/langflow/docker/frontend/build_and_push_frontend.Dockerfile b/langflow/docker/frontend/build_and_push_frontend.Dockerfile
new file mode 100644
index 0000000..5ca674d
--- /dev/null
+++ b/langflow/docker/frontend/build_and_push_frontend.Dockerfile
@@ -0,0 +1,29 @@
+# syntax=docker/dockerfile:1
+# Keep this syntax directive! It's used to enable Docker BuildKit
+
+################################
+# BUILDER-BASE
+################################
+
+# 1. force platform to the current architecture to increase build speed time on multi-platform builds
+FROM --platform=$BUILDPLATFORM node:lts-bookworm-slim AS builder-base
+COPY src/frontend /frontend
+
+RUN cd /frontend && npm install && npm run build
+
+################################
+# RUNTIME
+################################
+FROM nginxinc/nginx-unprivileged:stable-bookworm-perl AS runtime
+
+LABEL org.opencontainers.image.title=langflow-frontend
+LABEL org.opencontainers.image.authors=['Langflow']
+LABEL org.opencontainers.image.licenses=MIT
+LABEL org.opencontainers.image.url=https://github.com/langflow-ai/langflow
+LABEL org.opencontainers.image.source=https://github.com/langflow-ai/langflow
+
+COPY --from=builder-base --chown=nginx /frontend/build /usr/share/nginx/html
+COPY --chown=nginx ./docker/frontend/start-nginx.sh /start-nginx.sh
+COPY --chown=nginx ./docker/frontend/default.conf.template /etc/nginx/conf.d/default.conf.template
+RUN chmod +x /start-nginx.sh
+ENTRYPOINT ["/start-nginx.sh"]
diff --git a/langflow/docker/frontend/default.conf.template b/langflow/docker/frontend/default.conf.template
new file mode 100644
index 0000000..76864de
--- /dev/null
+++ b/langflow/docker/frontend/default.conf.template
@@ -0,0 +1,43 @@
+worker_processes auto;
+pid /tmp/nginx.pid;
+events {}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type text/plain;
+
+ types {
+ text/html html;
+ text/css css;
+ application/javascript js;
+ }
+
+ server {
+ gzip on;
+ gzip_comp_level 2;
+ gzip_min_length 1000;
+ gzip_types text/xml text/css;
+ gzip_http_version 1.1;
+ gzip_vary on;
+ gzip_disable "MSIE [4-6] \.";
+
+ listen ${FRONTEND_PORT};
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri $uri/ /index.html =404;
+ }
+ location /api {
+ proxy_pass ${BACKEND_URL};
+ }
+ location /health_check {
+ proxy_pass ${BACKEND_URL};
+ }
+ location /health {
+ proxy_pass ${BACKEND_URL};
+ }
+
+ include /etc/nginx/extra-conf.d/*.conf;
+ }
+}
\ No newline at end of file
diff --git a/langflow/docker/frontend/nginx.conf b/langflow/docker/frontend/nginx.conf
new file mode 100644
index 0000000..b064a5a
--- /dev/null
+++ b/langflow/docker/frontend/nginx.conf
@@ -0,0 +1,28 @@
+server {
+ gzip on;
+ gzip_comp_level 2;
+ gzip_min_length 1000;
+ gzip_types text/xml text/css;
+ gzip_http_version 1.1;
+ gzip_vary on;
+ gzip_disable "MSIE [4-6] \.";
+
+ listen __FRONTEND_PORT__;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri $uri/ /index.html =404;
+ }
+ location /api {
+ proxy_pass __BACKEND_URL__;
+ }
+ location /health_check {
+ proxy_pass __BACKEND_URL__;
+ }
+ location /health {
+ proxy_pass __BACKEND_URL__;
+ }
+
+ include /etc/nginx/extra-conf.d/*.conf;
+}
diff --git a/langflow/docker/frontend/start-nginx.sh b/langflow/docker/frontend/start-nginx.sh
new file mode 100644
index 0000000..891075e
--- /dev/null
+++ b/langflow/docker/frontend/start-nginx.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+set -e
+
+# Define writable directory for the final config
+CONFIG_DIR="/tmp/nginx"
+mkdir -p $CONFIG_DIR
+
+# Check and set environment variables
+if [ -z "$BACKEND_URL" ]; then
+ BACKEND_URL="$1"
+fi
+if [ -z "$FRONTEND_PORT" ]; then
+ FRONTEND_PORT="$2"
+fi
+if [ -z "$FRONTEND_PORT" ]; then
+ FRONTEND_PORT="80"
+fi
+if [ -z "$BACKEND_URL" ]; then
+ echo "BACKEND_URL must be set as an environment variable or as first parameter. (e.g. http://localhost:7860)"
+ exit 1
+fi
+
+# Export variables for envsubst
+export BACKEND_URL FRONTEND_PORT
+
+# Use envsubst to substitute environment variables in the template
+envsubst '${BACKEND_URL} ${FRONTEND_PORT}' < /etc/nginx/conf.d/default.conf.template > $CONFIG_DIR/default.conf
+
+# Start nginx with the new configuration
+exec nginx -c $CONFIG_DIR/default.conf -g 'daemon off;'
\ No newline at end of file
diff --git a/langflow/docker/render.Dockerfile b/langflow/docker/render.Dockerfile
new file mode 100644
index 0000000..e8462ab
--- /dev/null
+++ b/langflow/docker/render.Dockerfile
@@ -0,0 +1,3 @@
+FROM langflowai/langflow:latest
+
+ENTRYPOINT ["python", "-m", "langflow", "run"]
diff --git a/langflow/docker/render.pre-release.Dockerfile b/langflow/docker/render.pre-release.Dockerfile
new file mode 100644
index 0000000..d3aa9cb
--- /dev/null
+++ b/langflow/docker/render.pre-release.Dockerfile
@@ -0,0 +1 @@
+FROM langflowai/langflow:1.0-alpha
diff --git a/langflow/docker_example/Dockerfile b/langflow/docker_example/Dockerfile
new file mode 100644
index 0000000..e7f0b42
--- /dev/null
+++ b/langflow/docker_example/Dockerfile
@@ -0,0 +1,3 @@
+FROM langflowai/langflow:latest
+
+CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
diff --git a/langflow/docker_example/README.md b/langflow/docker_example/README.md
new file mode 100644
index 0000000..622f7af
--- /dev/null
+++ b/langflow/docker_example/README.md
@@ -0,0 +1,65 @@
+# Running LangFlow with Docker
+
+This guide will help you get LangFlow up and running using Docker and Docker Compose.
+
+## Prerequisites
+
+- Docker
+- Docker Compose
+
+## Steps
+
+1. Clone the LangFlow repository:
+
+ ```sh
+ git clone https://github.com/langflow-ai/langflow.git
+ ```
+
+2. Navigate to the `docker_example` directory:
+
+ ```sh
+ cd langflow/docker_example
+ ```
+
+3. Run the Docker Compose file:
+
+ ```sh
+ docker compose up
+ ```
+
+LangFlow will now be accessible at [http://localhost:7860/](http://localhost:7860/).
+
+## Docker Compose Configuration
+
+The Docker Compose configuration spins up two services: `langflow` and `postgres`.
+
+### LangFlow Service
+
+The `langflow` service uses the `langflowai/langflow:latest` Docker image and exposes port 7860. It depends on the `postgres` service.
+
+Environment variables:
+
+- `LANGFLOW_DATABASE_URL`: The connection string for the PostgreSQL database.
+- `LANGFLOW_CONFIG_DIR`: The directory where LangFlow stores logs, file storage, monitor data, and secret keys.
+
+Volumes:
+
+- `langflow-data`: This volume is mapped to `/app/langflow` in the container.
+
+### PostgreSQL Service
+
+The `postgres` service uses the `postgres:16` Docker image and exposes port 5432.
+
+Environment variables:
+
+- `POSTGRES_USER`: The username for the PostgreSQL database.
+- `POSTGRES_PASSWORD`: The password for the PostgreSQL database.
+- `POSTGRES_DB`: The name of the PostgreSQL database.
+
+Volumes:
+
+- `langflow-postgres`: This volume is mapped to `/var/lib/postgresql/data` in the container.
+
+## Switching to a Specific LangFlow Version
+
+If you want to use a specific version of LangFlow, you can modify the `image` field under the `langflow` service in the Docker Compose file. For example, to use version 1.0-alpha, change `langflowai/langflow:latest` to `langflowai/langflow:1.0-alpha`.
diff --git a/langflow/docker_example/docker-compose.yml b/langflow/docker_example/docker-compose.yml
new file mode 100644
index 0000000..10c8bdf
--- /dev/null
+++ b/langflow/docker_example/docker-compose.yml
@@ -0,0 +1,29 @@
+services:
+ langflow:
+ image: langflowai/langflow:latest # or another version tag on https://hub.docker.com/r/langflowai/langflow
+ pull_policy: always # set to 'always' when using 'latest' image
+ ports:
+ - "7860:7860"
+ depends_on:
+ - postgres
+ environment:
+ - LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
+ # This variable defines where the logs, file storage, monitor data and secret keys are stored.
+ - LANGFLOW_CONFIG_DIR=app/langflow
+ volumes:
+ - langflow-data:/app/langflow
+
+ postgres:
+ image: postgres:16
+ environment:
+ POSTGRES_USER: langflow
+ POSTGRES_PASSWORD: langflow
+ POSTGRES_DB: langflow
+ ports:
+ - "5432:5432"
+ volumes:
+ - langflow-postgres:/var/lib/postgresql/data
+
+volumes:
+ langflow-postgres:
+ langflow-data:
diff --git a/langflow/docker_example/pre.Dockerfile b/langflow/docker_example/pre.Dockerfile
new file mode 100644
index 0000000..a72b725
--- /dev/null
+++ b/langflow/docker_example/pre.Dockerfile
@@ -0,0 +1,3 @@
+FROM langflowai/langflow:1.0-alpha
+
+CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
diff --git a/langflow/docker_example/pre.docker-compose.yml b/langflow/docker_example/pre.docker-compose.yml
new file mode 100644
index 0000000..3df573d
--- /dev/null
+++ b/langflow/docker_example/pre.docker-compose.yml
@@ -0,0 +1,30 @@
+version: "3.8"
+
+services:
+ langflow:
+ image: langflowai/langflow:1.0-alpha
+ ports:
+ - "7860:7860"
+ depends_on:
+ - postgres
+ environment:
+ - LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
+ # This variable defines where the logs, file storage, monitor data and secret keys are stored.
+ - LANGFLOW_CONFIG_DIR=app/langflow
+ volumes:
+ - langflow-data:/app/langflow
+
+ postgres:
+ image: postgres:16
+ environment:
+ POSTGRES_USER: langflow
+ POSTGRES_PASSWORD: langflow
+ POSTGRES_DB: langflow
+ ports:
+ - "5432:5432"
+ volumes:
+ - langflow-postgres:/var/lib/postgresql/data
+
+volumes:
+ langflow-postgres:
+ langflow-data:
diff --git a/langflow/docs/.gitignore b/langflow/docs/.gitignore
new file mode 100644
index 0000000..c775dd6
--- /dev/null
+++ b/langflow/docs/.gitignore
@@ -0,0 +1,30 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Yarn
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+*.orig
diff --git a/langflow/docs/.yarnrc.yml b/langflow/docs/.yarnrc.yml
new file mode 100644
index 0000000..789221e
--- /dev/null
+++ b/langflow/docs/.yarnrc.yml
@@ -0,0 +1 @@
+nodeLinker: node-modules
\ No newline at end of file
diff --git a/langflow/docs/README.md b/langflow/docs/README.md
new file mode 100644
index 0000000..8fa5a68
--- /dev/null
+++ b/langflow/docs/README.md
@@ -0,0 +1,41 @@
+# Website
+
+This website is built using [Docusaurus 3](https://docusaurus.io/), a modern static website generator.
+
+### Installation
+
+```
+$ yarn install
+```
+
+### Local Development
+
+```
+$ yarn start
+```
+
+This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
+
+### Build
+
+```
+$ yarn build
+```
+
+This command generates static content into the `build` directory and can be served using any static contents hosting service, including `yarn serve`.
+
+### Deployment
+
+Using SSH:
+
+```
+$ USE_SSH=true yarn deploy
+```
+
+Not using SSH:
+
+```
+$ GIT_USER= yarn deploy
+```
+
+If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
diff --git a/langflow/docs/babel.config.js b/langflow/docs/babel.config.js
new file mode 100644
index 0000000..e00595d
--- /dev/null
+++ b/langflow/docs/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
+};
diff --git a/langflow/docs/css/custom.css b/langflow/docs/css/custom.css
new file mode 100644
index 0000000..107fbc6
--- /dev/null
+++ b/langflow/docs/css/custom.css
@@ -0,0 +1,278 @@
+/**
+ * Any CSS included here will be global. The classic template
+ * bundles Infima by default. Infima is a CSS framework designed to
+ * work well for content-centric websites.
+ */
+:root {
+ --ifm-background-color: var(--token-primary-bg-c);
+ --ifm-color-primary: hsla(330, 81%, 60%, 1);
+ --ifm-navbar-link-hover-color: initial;
+ --ifm-navbar-padding-vertical: 0;
+ --ifm-navbar-item-padding-vertical: 0;
+ --ifm-font-family-base: Inter, -apple-system, BlinkMacSystemFont, Helvetica,
+ Arial, sans-serif, "Apple Color Emoji", "Segoe UI emoji";
+ --ifm-font-family-monospace: "SFMono-Regular", "Roboto Mono", Consolas,
+ "Liberation Mono", Menlo, Courier, monospace;
+}
+
+.theme-doc-sidebar-item-category.menu__list-item:not(:first-child) {
+ margin-top: 1.5rem !important;
+}
+
+.docusaurus-highlight-code-line {
+ background-color: rgba(0, 0, 0, 0.1);
+ display: block;
+ margin: 0 calc(-1 * var(--ifm-pre-padding));
+ padding: 0 var(--ifm-pre-padding);
+}
+
+.diagonal-box {
+ transform: skewY(-6deg);
+}
+
+.diagonal-content {
+ transform: skewY(6deg);
+}
+
+[class^="announcementBar"] {
+ z-index: 10;
+}
+
+.showcase {
+ background-color: #fff;
+}
+
+.showcase-border {
+ border-color: rgba(243, 244, 246, 1);
+}
+
+.text-description {
+ color: rgba(107, 114, 128, 1);
+}
+
+p {
+ text-align: justify;
+}
+
+/* apply */
+#hero-apply {
+ z-index: -1;
+ background-image: linear-gradient(
+ var(--ifm-footer-background-color),
+ var(--ifm-navbar-background-color)
+ );
+}
+
+.apply-form {
+ background-image: linear-gradient(#fff, #f5f5fa);
+ max-width: 600px;
+}
+
+.apply-text {
+ color: #36395a;
+}
+
+/* index */
+#hero {
+ background-image: linear-gradient(
+ var(--ifm-footer-background-color),
+ var(--ifm-navbar-background-color)
+ );
+}
+
+/**
+ * Hero component title overrides to match other heading styles
+ */
+.hero-title {
+ color: rgb(28, 30, 33);
+ font-family: var(--ifm-heading-font-family);
+}
+h1 {
+ font-size: 26px;
+}
+h2 {
+ font-size: 22px;
+}
+h3 {
+ font-size: 18px;
+}
+
+body {
+ font-size: 16px;
+}
+
+.docsearch-logo {
+ color: #21243d;
+}
+
+.apply-button:hover {
+ color: #fff;
+}
+
+/* GitHub */
+.header-github-link:hover {
+ opacity: 0.6;
+}
+
+.header-github-link:before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/gitLight.svg") no-repeat;
+}
+
+[data-theme="dark"] .header-github-link:before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/gitDark.svg") no-repeat;
+}
+
+/* Twitter */
+.header-twitter-link:hover {
+ opacity: 0.6;
+}
+
+.header-twitter-link::before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/xLight.svg");
+ background-size: contain;
+}
+
+[data-theme="dark"] .header-twitter-link::before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/xDark.svg");
+ background-size: contain;
+}
+
+/* Discord */
+
+.header-discord-link {
+ margin-right: 0.5rem;
+}
+
+.header-discord-link:hover {
+ opacity: 0.6;
+}
+
+[data-theme="dark"] .header-discord-link::before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/discordDark.svg");
+ background-size: contain;
+}
+
+.header-discord-link::before {
+ content: "";
+ width: 24px;
+ height: 24px;
+ display: flex;
+ background: url("/logos/discordLight.svg");
+ background-size: contain;
+}
+
+/* Images */
+.image-rendering-crisp {
+ image-rendering: crisp-edges;
+
+ /* alias for google chrome */
+ image-rendering: -webkit-optimize-contrast;
+}
+
+.image-rendering-pixel {
+ image-rendering: pixelated;
+}
+
+.img-center {
+ display: flex;
+ justify-content: center;
+ width: 100%;
+}
+
+/* Add comprehensive image control */
+.markdown img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ margin: 1rem auto;
+ max-height: 600px;
+ object-fit: contain;
+}
+
+/* Default size for most images */
+.markdown img:not(.resized-image) {
+ max-width: 600px;
+ width: auto;
+}
+
+.resized-image {
+ width: 300px;
+ max-height: 500px;
+}
+
+/* Responsive images on mobile devices */
+@media (max-width: 768px) {
+ .markdown img:not(.resized-image) {
+ max-width: 100%;
+ max-height: 500px;
+ }
+
+ .resized-image {
+ width: 100%;
+ max-width: 300px;
+ max-height: 400px;
+ }
+}
+
+/* Reduce width on mobile for Mendable Search */
+@media (max-width: 767px) {
+ .mendable-search {
+ width: 200px;
+ }
+}
+
+@media (max-width: 500px) {
+ .mendable-search {
+ width: 150px;
+ }
+}
+
+@media (max-width: 380px) {
+ .mendable-search {
+ width: 140px;
+ }
+}
+/*
+.ch-scrollycoding {
+ gap: 10rem !important;
+} */
+
+.ch-scrollycoding-content {
+ max-width: 55% !important;
+ min-width: 40% !important;
+}
+
+.ch-scrollycoding-sticker {
+ max-width: 60% !important;
+ min-width: 45% !important;
+}
+
+.ch-scrollycoding-step-content {
+ min-height: 70px;
+}
+
+.theme-doc-sidebar-item-category.theme-doc-sidebar-item-category-level-2.menu__list-item:not(
+ :first-child
+ ) {
+ margin-top: 0.25rem !important;
+}
diff --git a/langflow/docs/css/docu-notion-styles.css b/langflow/docs/css/docu-notion-styles.css
new file mode 100644
index 0000000..6332fd2
--- /dev/null
+++ b/langflow/docs/css/docu-notion-styles.css
@@ -0,0 +1,60 @@
+/* This should be added to the docusaurus.config.js in order to show some notion things correctly.
+See the option: --css-output-directory
+See the docusaurus docs: https://docusaurus.io/docs/styling-layout
+See the use in the docu-notion-sample-site: https://github.com/sillsdev/docu-notion-sample-site/blob/main/docusaurus.config.js
+*/
+
+/* Copied from
+ https://github1s.com/NotionX/react-notion-x/blob/master/packages/react-notion-x/src/styles.css#L934
+ and
+ https://github1s.com/NotionX/react-notion-x/blob/master/packages/react-notion-x/src/styles.css#L1063
+*/
+.notion-column {
+ display: flex;
+ flex-direction: column;
+ padding-top: 12px;
+ padding-bottom: 12px;
+}
+
+.notion-column > *:first-child {
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.notion-column > *:last-child {
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: 0;
+}
+
+.notion-row {
+ display: flex;
+ overflow: hidden;
+ width: 100%;
+ max-width: 100%;
+}
+
+@media (max-width: 640px) {
+ .notion-row {
+ flex-direction: column;
+ }
+
+ .notion-row .notion-column {
+ width: 100% !important;
+ }
+
+ .notion-row .notion-spacer {
+ display: none;
+ }
+}
+
+.notion-spacer {
+ /* This matches the value in ColumnTransformer.ts */
+ width: calc(min(32px, 4vw));
+}
+
+.notion-spacer:last-child {
+ display: none;
+}
+/* End copied from NotionX */
diff --git a/langflow/docs/css/gifplayer.css b/langflow/docs/css/gifplayer.css
new file mode 100644
index 0000000..120b9b3
--- /dev/null
+++ b/langflow/docs/css/gifplayer.css
@@ -0,0 +1,36 @@
+.gif_player {
+ display: inline-block;
+ position: relative;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-touch-callout: none;
+ -webkit-tap-highlight-color: transparent; }
+ .gif_player .play_button {
+ background-color: rgba(0, 0, 0, 0.5);
+ border: 2px dashed #fff;
+ border-radius: 50%;
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.5);
+ color: #fff;
+ cursor: pointer;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 24px;
+ left: 50%;
+ opacity: 1;
+ padding: 14px 10px;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%) scale(1) rotate(0deg);
+ transition: transform 0.4s, opacity 0.4s; }
+ .gif_player .play_button:hover {
+ background-color: rgba(0, 0, 0, 0.7);
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.7); }
+ .gif_player .play_button::after {
+ content: "GIF"; }
+ .gif_player.playing .play_button {
+ transform: translate(-50%, -50%) scale(0) rotate(180deg);
+ opacity: 0.5; }
+ .gif_player img {
+ max-width: 100%; }
+
diff --git a/langflow/docs/docs/API-Reference/api-reference-api-examples.md b/langflow/docs/docs/API-Reference/api-reference-api-examples.md
new file mode 100644
index 0000000..f3a1107
--- /dev/null
+++ b/langflow/docs/docs/API-Reference/api-reference-api-examples.md
@@ -0,0 +1,1627 @@
+---
+title: API examples
+slug: /api-reference-api-examples
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+This page provides examples and practices for managing Langflow using the Langflow API.
+
+The Langflow API's OpenAPI spec can be viewed and tested at your Langflow deployment's `docs` endpoint.
+For example, `http://127.0.0.1:7860/docs`.
+
+## Export values
+
+You might find it helpful to set the following environment variables in your terminal.
+
+The examples in this guide use environment variables for these values.
+
+* Export your Langflow URL in your terminal.
+Langflow starts by default at `http://127.0.0.1:7860`.
+```plain
+export LANGFLOW_URL="http://127.0.0.1:7860"
+```
+
+* Export the `flow-id` in your terminal.
+The `flow-id` is found in the [API pane](/concepts-api) or in the flow's URL.
+```plain
+export FLOW_ID="359cd752-07ea-46f2-9d3b-a4407ef618da"
+```
+
+* Export the `folder-id` in your terminal.
+To find your folder ID, call the Langflow [/api/v1/folders/](#read-folders) endpoint for a list of folders.
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/folders/" \
+ -H "accept: application/json"
+```
+
+
+```plain
+[
+ {
+ "name": "My Projects",
+ "description": "Manage your own projects. Download and upload folders.",
+ "id": "1415de42-8f01-4f36-bf34-539f23e47466",
+ "parent_id": null
+ }
+]
+```
+
+
+Export the `folder-id` as an environment variable.
+```plain
+export FOLDER_ID="1415de42-8f01-4f36-bf34-539f23e47466"
+```
+
+* Export the Langflow API key as an environment variable.
+To create a Langflow API key, run the following command in the Langflow CLI.
+
+
+```plain
+langflow api-key
+```
+
+
+```plain
+API Key Created Successfully:
+sk-...
+```
+
+
+Export the generated API key as an environment variable.
+```plain
+export LANGFLOW_API_KEY="sk-..."
+```
+
+## Base
+
+Use the base Langflow API to run your flow and retrieve configuration information.
+
+### Get all components
+
+This operation returns a dictionary of all Langflow components.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/all" \
+ -H "accept: application/json"
+```
+
+
+
+```result
+A dictionary of all Langflow components.
+```
+
+
+
+### Run flow
+
+Execute a specified flow by ID or name.
+The flow is executed as a batch, but LLM responses can be streamed.
+For more configuration options when building your flow, use the [`/build` endpoint](/api-reference-api-examples#build-flow) instead.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/run/$FLOW_ID?stream=false" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{}'
+```
+
+
+
+
+```result
+{"session_id":"947eaf64-bc35-4431-ae52-8b30d819915b","outputs":[{"inputs":{},"outputs":[{"results":{"message":{"text_key":"text","data":{"timestamp":"2025-02-04T21:44:02+00:00","sender":"Machine","sender_name":"AI","session_id":"947eaf64-bc35-4431-ae52-8b30d819915b","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","files":[],"error":false,"edit":false,"properties":{"text_color":"","background_color":"","edited":false,"source":{"id":"OpenAIModel-g7uMN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":false,"positive_feedback":null,"state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"dcb15280-a16c-489a-9818-bec697fd123e","flow_id":"947eaf64-bc35-4431-ae52-8b30d819915b"},"default_value":"","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"session_id":"947eaf64-bc35-4431-ae52-8b30d819915b","timestamp":"2025-02-04T21:44:02+00:00","flow_id":"947eaf64-bc35-4431-ae52-8b30d819915b","error":false,"edit":false,"properties":{"text_color":"","background_color":"","edited":false,"source":{"id":"OpenAIModel-g7uMN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":false,"positive_feedback":null,"state":"complete","targets":[]},"category":"message","content_blocks":[]}},"artifacts":{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"type":"object"},"outputs":{"message":{"message":{"timestamp":"2025-02-04T21:44:02","sender":"Machine","sender_name":"AI","session_id":"947eaf64-bc35-4431-ae52-8b30d819915b","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","files":[],"error":false,"edit":false,"properties":{"text_color":"","background_color":"","edited":false,"source":{"id":"OpenAIModel-g7uMN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":false,"positive_feedback":null,"state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"dcb15280-a16c-489a-9818-bec697fd123e","flow_id":"947eaf64-bc35-4431-ae52-8b30d819915b"},"type":"message"}},"logs":{"message":[]},"messages":[{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","session_id":"947eaf64-bc35-4431-ae52-8b30d819915b","stream_url":null,"component_id":"ChatOutput-HnFx1","files":[],"type":"message"}],"timedelta":null,"duration":null,"component_display_name":"Chat Output","component_id":"ChatOutput-HnFx1","used_frozen_result":false}]}]}
+```
+
+
+
+
+To stream LLM token responses, append the `?stream=true` query parameter to the request. LLM chat responses are streamed back as `token` events until the `end` event closes the connection.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/run/$FLOW_ID?stream=true" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -H "x-api-key: $LANGFLOW_API_KEY" \
+ -d '{"message": "Tell me something interesting!"}'
+```
+
+
+
+
+```result
+{"event": "add_message", "data": {"timestamp": "2025-02-05T14:46:24", "sender": "User", "sender_name": "User", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": null, "display_name": null, "source": null}, "icon": "", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "1d6044d6-332c-431a-a50c-dcea5deee4ab", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}}
+
+{"event": "add_message", "data": {"timestamp": "2025-02-05T14:46:24", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}}
+
+{"event": "token", "data": {"chunk": "", "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "timestamp": "2025-02-05 14:46:25 UTC"}}
+
+{"event": "token", "data": {"chunk": "Hello", "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "timestamp": "2025-02-05 14:46:25 UTC"}}
+
+{"event": "token", "data": {"chunk": "!", "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "timestamp": "2025-02-05 14:46:25 UTC"}}
+
+{"event": "token", "data": {"chunk": " \ud83c\udf1f", "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "timestamp": "2025-02-05 14:46:25 UTC"}}
+
+{"event": "end", "data": {"result": {"session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "outputs": [{"inputs": {}, "outputs": [{"results": {"message": {"text_key": "text", "data": {"timestamp": "2025-02-05T14:46:24+00:00", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "default_value": "", "text": "", "sender": "Machine", "sender_name": "AI", "files": [], "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "timestamp": "2025-02-05T14:46:24+00:00", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": []}, "text": ""}, "artifacts": {"message": "", "sender": "Machine", "sender_name": "AI", "stream_url": "/api/v1/build/947eaf64-bc35-4431-ae52-8b30d819915b/ChatOutput-HnFx1/stream", "files": [], "type": "stream"}, "outputs": {"message": {"message": {"timestamp": "2025-02-05T14:46:24", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "be9db128-eaac-49db-bfd7-ed63406ebbcf", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "type": "message"}}, "logs": {"message": []}, "messages": [], "timedelta": null, "duration": null, "component_display_name": "Chat Output", "component_id": "ChatOutput-HnFx1", "used_frozen_result": false}]}]}}}
+```
+
+
+
+
+This result is abbreviated, but illustrates where the `end` event completes the LLM's token streaming response.
+
+### Webhook run flow
+
+The webhook endpoint triggers flow execution with an HTTP POST request.
+
+When a **Webhook** component is added to the workspace, a new **Webhook cURL** tab becomes available in the **API** pane that contains an HTTP POST request for triggering the webhook component, similar to the call in this example.
+
+To test the **Webhook** component in your flow, see the [Webhook component](/components-data#webhook).
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/webhook/$FLOW_ID" \
+ -H "Content-Type: application/json" \
+ -d '{"data": "example-data"}'
+```
+
+
+
+
+```result
+{
+ {"message":"Task started in the background","status":"in progress"}
+}
+```
+
+
+
+
+### Process
+
+:::info
+This endpoint is deprecated. Use the `/run` endpoint instead.
+:::
+
+### Predict
+
+:::info
+This endpoint is deprecated. Use the `/run` endpoint instead.
+:::
+
+### Get task status
+
+Get the status of a task.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/task/TASK_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+```result
+{
+ "status": "Task status",
+ "result": "Task result if completed"
+}
+```
+
+
+
+
+### Create upload file (Deprecated)
+
+:::info
+This endpoint is deprecated. Use the `/file` endpoint instead.
+:::
+
+### Get version
+
+Get the version of the Langflow API.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/version" \
+ -H "accept: application/json"
+```
+
+
+
+
+```result
+{
+ "version": "1.1.1",
+ "main_version": "1.1.1",
+ "package": "Langflow"
+}
+```
+
+
+
+
+### Get config
+
+Retrieve the Langflow configuration information.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/config" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+{
+ "feature_flags": {
+ "mvp_components": false
+ },
+ "frontend_timeout": 0,
+ "auto_saving": true,
+ "auto_saving_interval": 1000,
+ "health_check_max_retries": 5,
+ "max_file_size_upload": 100
+}
+```
+
+
+
+
+
+## Build
+
+Use the `/build` endpoint to build vertices and flows, and execute those flows with streaming event responses.
+
+The `/build` endpoint offers additional configuration for running flows.
+
+For a simpler execution of your flows, use the [`/run` endpoint](/api-reference-api-examples#run-flow) instead.
+
+### Build flow
+
+This example builds a flow with a given `flow_id`.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/build/$FLOW_ID/flow" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -H "x-api-key: $LANGFLOW_API_KEY" \
+ -d '{"input_value": "hello, how are you doing?"}'
+```
+
+
+
+
+```plain
+{"event": "vertices_sorted", "data": {"ids": ["ChatInput-TAEvF"], "to_run": ["Prompt-2gtLN", "ChatInput-TAEvF", "ChatOutput-HnFx1", "OpenAIModel-g7uMN"]}}
+
+{"event": "add_message", "data": {"timestamp": "2025-02-05T14:45:33", "sender": "User", "sender_name": "User", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": null, "display_name": null, "source": null}, "icon": "", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "12be981b-9816-4361-b0f9-ce65d0ba6cc1", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}}
+
+{"event": "end_vertex", "data": {"build_data": {"id": "ChatInput-TAEvF", "inactivated_vertices": [], "next_vertices_ids": ["Prompt-2gtLN"], "top_level_vertices": ["Prompt-2gtLN"], "valid": true, "params": "- Files: []\n Message: Hello\n Sender: User\n Sender Name: User\n Type: object\n", "data": {"results": {"message": {"text_key": "text", "data": {"timestamp": "2025-02-05T14:45:33+00:00", "sender": "User", "sender_name": "User", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": null, "display_name": null, "source": null}, "icon": "", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "12be981b-9816-4361-b0f9-ce65d0ba6cc1", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "default_value": "", "text": "Hello", "sender": "User", "sender_name": "User", "files": [], "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "timestamp": "2025-02-05T14:45:33+00:00", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": null, "display_name": null, "source": null}, "icon": "", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": []}}, "outputs": {"message": {"message": {"timestamp": "2025-02-05T14:45:33+00:00", "sender": "User", "sender_name": "User", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": null, "display_name": null, "source": null}, "icon": "", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "12be981b-9816-4361-b0f9-ce65d0ba6cc1", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "type": "message"}}, "logs": {"message": []}, "message": {"message": "Hello", "sender": "User", "sender_name": "User", "files": [], "type": "object"}, "artifacts": {"message": "Hello", "sender": "User", "sender_name": "User", "files": [], "type": "object"}, "timedelta": 0.01409304200205952, "duration": "14 ms", "used_frozen_result": false}, "timestamp": "2025-02-05T14:45:33.486696Z"}}}
+
+{"event": "end_vertex", "data": {"build_data": {"id": "Prompt-2gtLN", "inactivated_vertices": [], "next_vertices_ids": ["OpenAIModel-g7uMN"], "top_level_vertices": ["OpenAIModel-g7uMN"], "valid": true, "params": "None", "data": {"results": {}, "outputs": {"prompt": {"message": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.", "type": "text"}}, "logs": {"prompt": []}, "message": {"prompt": {"repr": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.", "raw": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.", "type": "text"}}, "artifacts": {"prompt": {"repr": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.", "raw": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.", "type": "text"}}, "timedelta": 0.005742790992371738, "duration": "6 ms", "used_frozen_result": false}, "timestamp": "2025-02-05T14:45:33.503829Z"}}}
+
+{"event": "end_vertex", "data": {"build_data": {"id": "OpenAIModel-g7uMN", "inactivated_vertices": [], "next_vertices_ids": ["ChatOutput-HnFx1"], "top_level_vertices": ["ChatOutput-HnFx1"], "valid": true, "params": "None", "data": {"results": {}, "outputs": {"text_output": {"message": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "type": "text"}, "model_output": {"message": "", "type": "unknown"}}, "logs": {"text_output": []}, "message": {"text_output": {"repr": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "raw": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "type": "text"}}, "artifacts": {"text_output": {"repr": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "raw": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "type": "text"}}, "timedelta": 1.8065362079942133, "duration": "1.81 seconds", "used_frozen_result": false}, "timestamp": "2025-02-05T14:45:35.310876Z"}}}
+
+{"event": "add_message", "data": {"timestamp": "2025-02-05T14:45:35", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "56db271c-1dab-4a47-8464-d0058ac0943b", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}}
+
+{"event": "end_vertex", "data": {"build_data": {"id": "ChatOutput-HnFx1", "inactivated_vertices": [], "next_vertices_ids": [], "top_level_vertices": [], "valid": true, "params": "- Files: []\n Message: Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building\n something fresh! What do you have in mind? Whether it's a project, an idea, or\n a concept, let's dive in and make it happen!\n Sender: Machine\n Sender Name: AI\n Type: object\n", "data": {"results": {"message": {"text_key": "text", "data": {"timestamp": "2025-02-05T14:45:35+00:00", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "56db271c-1dab-4a47-8464-d0058ac0943b", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "default_value": "", "text": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "sender": "Machine", "sender_name": "AI", "files": [], "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "timestamp": "2025-02-05T14:45:35+00:00", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": []}}, "outputs": {"message": {"message": {"timestamp": "2025-02-05T14:45:35+00:00", "sender": "Machine", "sender_name": "AI", "session_id": "947eaf64-bc35-4431-ae52-8b30d819915b", "text": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "files": [], "error": false, "edit": false, "properties": {"text_color": "", "background_color": "", "edited": false, "source": {"id": "OpenAIModel-g7uMN", "display_name": "OpenAI", "source": "gpt-4o-mini"}, "icon": "OpenAI", "allow_markdown": false, "positive_feedback": null, "state": "complete", "targets": []}, "category": "message", "content_blocks": [], "id": "56db271c-1dab-4a47-8464-d0058ac0943b", "flow_id": "947eaf64-bc35-4431-ae52-8b30d819915b"}, "type": "message"}}, "logs": {"message": []}, "message": {"message": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "sender": "Machine", "sender_name": "AI", "files": [], "type": "object"}, "artifacts": {"message": "Hello! \ud83c\udf1f I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!", "sender": "Machine", "sender_name": "AI", "files": [], "type": "object"}, "timedelta": 0.02948787499917671, "duration": "29 ms", "used_frozen_result": false}, "timestamp": "2025-02-05T14:45:35.345598Z"}}}
+
+{"event": "end", "data": {}}
+```
+
+
+
+
+This output is abbreviated, but the order of events illustrates how Langflow runs components.
+
+1. Langflow first sorts the vertices by dependencies (edges) in the `vertices_sorted` event:
+```
+ChatInput → Prompt → OpenAIModel → ChatOutput
+```
+2. The Chat Input component receives user input in the `add_message` event.
+3. The Prompt component is built and executed with the received input in the `end_vertex` event.
+4. The Open AI model's responses stream as `token` events.
+The `token` event represents individual pieces of text as they're generated by an LLM.
+5. The clean `end` event tells you the flow executed with no errors.
+If your flow executes with errors, the `error` event handler prints the errors to the playground.
+
+### Configure the build endpoint
+
+The `/build` endpoint accepts values for `start_component_id` and `stop_component_id` to control where the flow run will start and stop.
+Setting `stop_component_id` for a component triggers the same behavior as clicking the **Play** button on that component, where all dependent components leading up to that component are also run.
+For example, to stop flow execution at the Open AI model component, run the following command:
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/build/$FLOW_ID/flow" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -H "x-api-key: $LANGFLOW_API_KEY" \
+ -d '{"stop_component_id": "OpenAIModel-Uksag"}'
+```
+
+The `/build` endpoint also accepts inputs for `data` directly, instead of using the values stored in the Langflow database.
+This is useful for running flows without having to pass custom values through the UI.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/build/$FLOW_ID/flow" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "data": {
+ "nodes": [],
+ "edges": []
+ },
+ "inputs": {
+ "input_value": "Your custom input here",
+ "session": "session_id"
+ }
+ }'
+```
+
+
+
+
+```json
+{"event": "vertices_sorted", "data": {"ids": [], "to_run": []}}
+
+{"event": "end", "data": {}}
+```
+
+
+
+
+## Files
+
+Use the `/files` endpoint to add or delete files between your local machine and Langflow.
+
+### Upload file
+
+Upload a file to an existing flow.
+
+This example uploads `the_oscar_award.csv`.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/files/upload/$FLOW_ID" \
+ -H "accept: application/json" \
+ -H "Content-Type: multipart/form-data" \
+ -F "file=@the_oscar_award.csv"
+```
+
+
+
+
+```json
+{
+ "flowId": "92f9a4c5-cfc8-4656-ae63-1f0881163c28",
+ "file_path": "92f9a4c5-cfc8-4656-ae63-1f0881163c28/2024-12-30_15-19-43_the_oscar_award.csv"
+}
+```
+
+
+
+
+#### Upload image files
+
+Send image files to the Langflow API for AI analysis.
+
+The default file limit is 100 MB. To configure this value, change the `LANGFLOW_MAX_FILE_SIZE_UPLOAD` environment variable.
+For more information, see [Supported environment variables](/environment-variables#supported-variables).
+
+1. To send an image to your flow with the API, POST the image file to the `v1/files/upload/` endpoint of your flow.
+
+```curl
+curl -X POST "$LANGFLOW_URL/api/v1/files/upload/a430cc57-06bb-4c11-be39-d3d4de68d2c4" \
+ -H "Content-Type: multipart/form-data" \
+ -F "file=@image-file.png"
+```
+
+The API returns the image file path in the format `"file_path":"/_"}`.
+
+```json
+{"flowId":"a430cc57-06bb-4c11-be39-d3d4de68d2c4","file_path":"a430cc57-06bb-4c11-be39-d3d4de68d2c4/2024-11-27_14-47-50_image-file.png"}
+```
+
+2. Post the image file to the **Chat Input** component of a **Basic prompting** flow.
+Pass the file path value as an input in the **Tweaks** section of the curl call to Langflow.
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/run/a430cc57-06bb-4c11-be39-d3d4de68d2c4?stream=false" \
+ -H 'Content-Type: application/json'\
+ -d '{
+ "output_type": "chat",
+ "input_type": "chat",
+ "tweaks": {
+ "ChatInput-b67sL": {
+ "files": "a430cc57-06bb-4c11-be39-d3d4de68d2c4/2024-11-27_14-47-50_image-file.png",
+ "input_value": "what do you see?"
+ }
+}}'
+```
+
+Your chatbot describes the image file you sent.
+
+```plain
+"text": "This flowchart appears to represent a complex system for processing financial inquiries using various AI agents and tools. Here's a breakdown of its components and how they might work together..."
+```
+
+
+### List files
+
+List all files associated with a specific flow.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/files/list/$FLOW_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+```json
+{
+ "files": [
+ "2024-12-30_15-19-43_the_oscar_award.csv"
+ ]
+}
+```
+
+
+
+
+### Download file
+
+Download a specific file for a given flow.
+
+To look up the file name in Langflow, use the `/list` endpoint.
+
+This example downloads the `2024-12-30_15-19-43_the_oscar_award.csv` file from Langflow to a file named `output-file.csv`.
+
+The `--output` flag is optional.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/files/download/$FLOW_ID/2024-12-30_15-19-43_the_oscar_award.csv" \
+ -H "accept: application/json" \
+ --output output-file.csv
+```
+
+
+
+
+```plain
+The file contents.
+```
+
+
+
+
+### Download image
+
+Download an image file for a given flow.
+
+To look up the file name in Langflow, use the `/list` endpoint.
+
+This example downloads the `2024-12-30_15-42-44_image-file.png` file from Langflow to a file named `output-image.png`.
+
+The `--output` flag is optional.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/files/images/$FLOW_ID/2024-12-30_15-42-44_image-file.png" \
+ -H "accept: application/json" \
+ --output output-image.png
+```
+
+
+
+
+```plain
+Image file content.
+```
+
+
+
+
+
+### Delete file
+
+Delete a specific file from a flow.
+
+This example deletes the `2024-12-30_15-42-44_image-file.png` file from Langflow.
+
+
+
+
+```curl
+curl -X DELETE \
+ "$LANGFLOW_URL/api/v1/files/delete/$FLOW_ID/2024-12-30_15-42-44_image-file.png" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+{
+ "message": "File 2024-12-30_15-42-44_image-file.png deleted successfully"
+}
+```
+
+
+
+
+## Flows
+
+Use the `/flows` endpoint to create, read, update, and delete flows.
+
+### Create flow
+
+Create a new flow.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/flows/" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "string2",
+ "description": "string",
+ "icon": "string",
+ "icon_bg_color": "#FF0000",
+ "gradient": "string",
+ "data": {},
+ "is_component": false,
+ "updated_at": "2024-12-30T15:48:01.519Z",
+ "webhook": false,
+ "endpoint_name": "string",
+ "tags": [
+ "string"
+ ]
+}'
+```
+
+
+
+```plain
+{"name":"string2","description":"string","icon":"string","icon_bg_color":"#FF0000","gradient":"string","data":{},"is_component":false,"updated_at":"2025-02-04T21:07:36+00:00","webhook":false,"endpoint_name":"string","tags":["string"],"locked":false,"id":"e8d81c37-714b-49ae-ba82-e61141f020ee","user_id":"f58396d4-a387-4bb8-b749-f40825c3d9f3","folder_id":"1415de42-8f01-4f36-bf34-539f23e47466"}
+```
+
+
+
+
+### Read flows
+
+Retrieve a list of flows with pagination support.
+
+
+
+
+```bash
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/flows/?remove_example_flows=false&components_only=false&get_all=true&header_flows=false&page=1&size=50" \
+ -H "accept: application/json"
+```
+
+
+
+
+
+```plain
+A JSON object containing a list of flows.
+```
+
+
+
+To retrieve only the flows from a specific folder, pass `folder_id` in the query string.
+
+
+
+
+
+```bash
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/flows/?remove_example_flows=true&components_only=false&get_all=false&folder_id=$FOLDER_ID&header_flows=false&page=1&size=1" \
+ -H "accept: application/json"
+```
+
+
+
+
+
+```plain
+A JSON object containing a list of flows.
+```
+
+
+
+
+### Read flow
+
+Read a specific flow by its ID.
+
+
+
+
+```bash
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/flows/$FLOW_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+
+```json
+{
+ "name": "Basic Prompting",
+ "description": "Perform basic prompting with an OpenAI model.",
+ "icon": "Braces",
+ "icon_bg_color": null,
+ "gradient": "2",
+ "data": {
+ "nodes": [
+ ...
+ ]
+ }
+}
+```
+
+
+
+
+### Update flow
+
+Update an existing flow by its ID.
+
+This example changes the value for `endpoint_name` from a random UUID to `my_new_endpoint_name`.
+
+
+
+
+```bash
+curl -X PATCH \
+ "$LANGFLOW_URL/api/v1/flows/$FLOW_ID" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "string",
+ "description": "string",
+ "data": {},
+ "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "endpoint_name": "my_new_endpoint_name",
+ "locked": true
+}'
+```
+
+
+
+
+```json
+{
+ "name": "string",
+ "description": "string",
+ "icon": "Braces",
+ "icon_bg_color": null,
+ "gradient": "2",
+ "data": {},
+ "is_component": false,
+ "updated_at": "2024-12-30T18:30:22+00:00",
+ "webhook": false,
+ "endpoint_name": "my_new_endpoint_name",
+ "tags": null,
+ "locked": true,
+ "id": "01ce083d-748b-4b8d-97b6-33adbb6a528a",
+ "user_id": "f58396d4-a387-4bb8-b749-f40825c3d9f3",
+ "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+}
+```
+
+
+
+
+### Delete flow
+
+Delete a specific flow by its ID.
+
+
+
+
+```bash
+curl -X DELETE \
+ "$LANGFLOW_URL/api/v1/flows/$FLOW_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+
+```json
+{
+ "message": "Flow deleted successfully"
+}
+```
+
+
+
+
+### Create flows
+
+Create multiple new flows.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/flows/batch/" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "flows": [
+ {
+ "name": "string",
+ "description": "string",
+ "icon": "string",
+ "icon_bg_color": "string",
+ "gradient": "string",
+ "data": {},
+ "is_component": false,
+ "updated_at": "2024-12-30T18:36:02.737Z",
+ "webhook": false,
+ "endpoint_name": "string",
+ "tags": [
+ "string"
+ ],
+ "locked": false,
+ "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ },
+ {
+ "name": "string",
+ "description": "string",
+ "icon": "string",
+ "icon_bg_color": "string",
+ "gradient": "string",
+ "data": {},
+ "is_component": false,
+ "updated_at": "2024-12-30T18:36:02.737Z",
+ "webhook": false,
+ "endpoint_name": "string",
+ "tags": [
+ "string"
+ ],
+ "locked": false,
+ "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ }
+ ]
+}'
+```
+
+
+
+
+```json
+[
+ {
+ // FlowRead objects
+ }
+]
+```
+
+
+
+
+### Upload flows
+
+Upload flows from a file.
+
+This example uploads a local file named `agent-with-astra-db-tool.json`.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/flows/upload/?folder_id=$FOLDER_ID" \
+ -H "accept: application/json" \
+ -H "Content-Type: multipart/form-data" \
+ -F "file=@agent-with-astra-db-tool.json;type=application/json"
+```
+
+
+
+
+```json
+[
+ {
+ "name": "agent-with-astra-db-tool",
+ "description": "",
+ "icon": null,
+ "icon_bg_color": null,
+ "gradient": null,
+ "data": {}
+ ...
+ }
+]
+```
+
+
+
+To specify a target folder for the flow, include the query parameter `folder_id`.
+The target `folder_id` must already exist before uploading a flow. Call the [/api/v1/folders/](#read-folders) endpoint for a list of available folders.
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/flows/upload/?folder_id=$FOLDER_ID" \
+ -H "accept: application/json" \
+ -H "Content-Type: multipart/form-data" \
+ -F "file=@agent-with-astra-db-tool.json;type=application/json"
+```
+
+### Download all flows
+
+Download all flows as a ZIP file.
+
+This endpoint downloads a ZIP file containing flows for all `flow-id` values listed in the command's body.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/flows/download/" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '[
+ "e1e40c77-0541-41a9-88ab-ddb3419398b5",
+ "92f9a4c5-cfc8-4656-ae63-1f0881163c28"
+]' \
+ --output langflow-flows.zip
+```
+
+
+
+
+```plain
+ % Total % Received % Xferd Average Speed Time Time Time Current
+ Dload Upload Total Spent Left Speed
+100 76437 0 76353 100 84 4516k 5088 --:--:-- --:--:-- --:--:-- 4665k
+```
+
+
+
+### Read basic examples
+
+Retrieve a list of basic example flows.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/flows/basic_examples/" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+A list of example flows.
+```
+
+
+
+
+
+## Folders
+
+Use the `/folders` endpoint to create, read, update, and delete folders.
+
+Folders store your flows and components.
+
+### Read folders
+
+Get a list of Langflow folders.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/folders/" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+[
+ {
+ "name": "My Projects",
+ "description": "Manage your own projects. Download and upload folders.",
+ "id": "1415de42-8f01-4f36-bf34-539f23e47466",
+ "parent_id": null
+ }
+]
+```
+
+
+
+
+### Create folder
+
+Create a new folder.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/folders/" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "new_folder_name",
+ "description": "string",
+ "components_list": [],
+ "flows_list": []
+}'
+```
+
+
+
+
+```plain
+{
+ "name": "new_folder_name",
+ "description": "string",
+ "id": "b408ddb9-6266-4431-9be8-e04a62758331",
+ "parent_id": null
+}
+```
+
+
+
+
+To add flows and components at folder creation, retrieve the `components_list` and `flows_list` values from the [/api/v1/store/components](#get-all-components) and [/api/v1/flows/read](#read-flows) endpoints and add them to the request body.
+
+Adding a flow to a folder moves the flow from its previous location. The flow is not copied.
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/folders/" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "new_folder_name",
+ "description": "string",
+ "components_list": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ ],
+ "flows_list": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ ]
+}'
+```
+
+### Read folder
+
+Retrieve details of a specific folder.
+
+To find the UUID of your folder, call the [read folders](#read-folders) endpoint.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/folders/$FOLDER_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+[
+ {
+ "name": "My Projects",
+ "description": "Manage your own projects. Download and upload folders.",
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "parent_id": null
+ }
+]
+```
+
+
+
+
+### Update folder
+
+Update the information of a specific folder with a `PATCH` request.
+
+Each PATCH request updates the folder with the values you send.
+Only the fields you include in your request are updated.
+If you send the same values multiple times, the update is still processed, even if the values are unchanged.
+
+
+
+
+```curl
+curl -X PATCH \
+ "$LANGFLOW_URL/api/v1/folders/b408ddb9-6266-4431-9be8-e04a62758331" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "string",
+ "description": "string",
+ "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "components": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ ],
+ "flows": [
+ "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ ]
+}'
+```
+
+
+
+
+```plain
+{
+ "name": "string",
+ "description": "string",
+ "id": "b408ddb9-6266-4431-9be8-e04a62758331",
+ "parent_id": null
+}
+```
+
+
+
+
+### Delete folder
+
+Delete a specific folder.
+
+
+
+
+```curl
+curl -X DELETE \
+ "$LANGFLOW_URL/api/v1/folders/$FOLDER_ID" \
+ -H "accept: */*"
+```
+
+
+
+
+```plain
+204 No Content
+```
+
+
+
+
+### Download folder
+
+Download all flows from a folder as a zip file.
+
+The `--output` flag is optional.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/folders/download/b408ddb9-6266-4431-9be8-e04a62758331" \
+ -H "accept: application/json" \
+ --output langflow-folder.zip
+```
+
+
+
+
+```plain
+The folder contents.
+```
+
+
+
+
+### Upload folder
+
+Upload a folder to Langflow.
+
+
+
+
+```curl
+curl -X POST \
+ "$LANGFLOW_URL/api/v1/folders/upload/" \
+ -H "accept: application/json" \
+ -H "Content-Type: multipart/form-data" \
+ -F "file=@20241230_135006_langflow_flows.zip;type=application/zip"
+```
+
+
+
+
+
+```plain
+The folder contents are uploaded to Langflow.
+```
+
+
+
+
+
+## Logs
+
+Retrieve logs for your Langflow flow.
+
+This endpoint requires log retrieval to be enabled in your Langflow application.
+
+To enable log retrieval, include these values in your `.env` file:
+
+```plain
+LANGFLOW_ENABLE_LOG_RETRIEVAL=true
+LANGFLOW_LOG_RETRIEVER_BUFFER_SIZE=10000
+LANGFLOW_LOG_LEVEL=DEBUG
+```
+
+For log retrieval to function, `LANGFLOW_LOG_RETRIEVER_BUFFER_SIZE` needs to be greater than 0. The default value is `10000`.
+
+Start Langflow with this `.env`:
+
+```plain
+uv run langflow run --env-file .env
+```
+
+### Stream logs
+
+Stream logs in real-time using Server-Sent Events (SSE).
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/logs-stream" \
+ -H "accept: text/event-stream"
+```
+
+
+
+
+```plain
+keepalive
+
+{"1736355791151": "2025-01-08T12:03:11.151218-0500 DEBUG Building Chat Input\n"}
+
+{"1736355791485": "2025-01-08T12:03:11.485380-0500 DEBUG consumed event add_message-153bcd5d-ef4d-4ece-8cc0-47c6b6a9ef92 (time in queue, 0.0000, client 0.0001)\n"}
+
+{"1736355791499": "2025-01-08T12:03:11.499704-0500 DEBUG consumed event end_vertex-3d7125cd-7b8a-44eb-9113-ed5b785e3cf3 (time in queue, 0.0056, client 0.0047)\n"}
+
+{"1736355791502": "2025-01-08T12:03:11.502510-0500 DEBUG consumed event end-40d0b363-5618-4a23-bbae-487cd0b9594d (time in queue, 0.0001, client 0.0004)\n"}
+
+{"1736355791513": "2025-01-08T12:03:11.513097-0500 DEBUG Logged vertex build: 729ff2f8-6b01-48c8-9ad0-3743c2af9e8a\n"}
+
+{"1736355791834": "2025-01-08T12:03:11.834982-0500 DEBUG Telemetry data sent successfully.\n"}
+
+{"1736355791941": "2025-01-08T12:03:11.941840-0500 DEBUG Telemetry data sent successfully.\n"}
+
+keepalive
+```
+
+
+
+
+### Retrieve logs with optional parameters
+
+Retrieve logs with optional query parameters.
+
+* `lines_before`: The number of logs before the timestamp or the last log.
+* `lines_after`: The number of logs after the timestamp.
+* `timestamp`: The timestamp to start getting logs from.
+
+The default values for all three parameters is `0`.
+With these values, the endpoint returns the last 10 lines of logs.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/logs?lines_before=0&lines_after=0×tamp=0" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+{
+ "1736354770500": "2025-01-08T11:46:10.500363-0500 DEBUG Creating starter project Document Q&A\n",
+ "1736354770511": "2025-01-08T11:46:10.511146-0500 DEBUG Creating starter project Image Sentiment Analysis\n",
+ "1736354770521": "2025-01-08T11:46:10.521018-0500 DEBUG Creating starter project SEO Keyword Generator\n",
+ "1736354770532": "2025-01-08T11:46:10.532677-0500 DEBUG Creating starter project Sequential Tasks Agents\n",
+ "1736354770544": "2025-01-08T11:46:10.544010-0500 DEBUG Creating starter project Custom Component Generator\n",
+ "1736354770555": "2025-01-08T11:46:10.555513-0500 DEBUG Creating starter project Prompt Chaining\n",
+ "1736354770588": "2025-01-08T11:46:10.588105-0500 DEBUG Create service ServiceType.CHAT_SERVICE\n",
+ "1736354771021": "2025-01-08T11:46:11.021817-0500 DEBUG Telemetry data sent successfully.\n",
+ "1736354775619": "2025-01-08T11:46:15.619545-0500 DEBUG Create service ServiceType.STORE_SERVICE\n",
+ "1736354775699": "2025-01-08T11:46:15.699661-0500 DEBUG File 046-rocket.svg retrieved successfully from flow /Users/mendon.kissling/Library/Caches/langflow/profile_pictures/Space.\n"
+}
+```
+
+
+
+
+## Monitor
+
+Use the `/monitor` endpoint to monitor and modify messages passed between Langflow components, vertex builds, and transactions.
+
+### Get Vertex builds
+
+Retrieve Vertex builds for a specific flow.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/monitor/builds?flow_id=$FLOW_ID" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+{"vertex_builds":{"ChatInput-NCmix":[{"data":{"results":{"message":{"text_key":"text","data":{"timestamp":"2024-12-23 19:10:57","sender":"User","sender_name":"User","session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","text":"Hello","files":[],"error":"False","edit":"False","properties":{"text_color":"","background_color":"","edited":"False","source":{"id":"None","display_name":"None","source":"None"},"icon":"","allow_markdown":"False","positive_feedback":"None","state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"c95bed34-f906-4aa6-84e4-68553f6db772","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"},"default_value":"","text":"Hello","sender":"User","sender_name":"User","files":[],"session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","timestamp":"2024-12-23 19:10:57+00:00","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","error":"False","edit":"False","properties":{"text_color":"","background_color":"","edited":"False","source":{"id":"None","display_name":"None","source":"None"},"icon":"","allow_markdown":"False","positive_feedback":"None","state":"complete","targets":[]},"category":"message","content_blocks":[]}},"outputs":{"message":{"message":{"timestamp":"2024-12-23T19:10:57","sender":"User","sender_name":"User","session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","text":"Hello","files":[],"error":false,"edit":false,"properties":{"text_color":"","background_color":"","edited":false,"source":{"id":null,"display_name":null,"source":null},"icon":"","allow_markdown":false,"positive_feedback":null,"state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"c95bed34-f906-4aa6-84e4-68553f6db772","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"},"type":"object"}},"logs":{"message":[]},"message":{"message":"Hello","sender":"User","sender_name":"User","files":[],"type":"object"},"artifacts":{"message":"Hello","sender":"User","sender_name":"User","files":[],"type":"object"},"timedelta":0.015060124918818474,"duration":"15 ms","used_frozen_result":false},"artifacts":{"message":"Hello","sender":"User","sender_name":"User","files":[],"type":"object"},"params":"- Files: []\n Message: Hello\n Sender: User\n Sender Name: User\n Type: object\n","valid":true,"build_id":"40aa200e-74db-4651-b698-f80301d2b26b","id":"ChatInput-NCmix","timestamp":"2024-12-23T19:10:58.772766Z","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"}],"Prompt-BEn9c":[{"data":{"results":{},"outputs":{"prompt":{"message":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","type":"text"}},"logs":{"prompt":[]},"message":{"prompt":{"repr":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","raw":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","type":"text"}},"artifacts":{"prompt":{"repr":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","raw":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","type":"text"}},"timedelta":0.0057758750626817346,"duration":"6 ms","used_frozen_result":false},"artifacts":{"prompt":{"repr":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","raw":"Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.","type":"text"}},"params":"None","valid":true,"build_id":"39bbbfde-97fd-42a5-a9ed-d42a5c5d532b","id":"Prompt-BEn9c","timestamp":"2024-12-23T19:10:58.781019Z","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"}],"OpenAIModel-7AjrN":[{"data":{"results":{},"outputs":{"text_output":{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","type":"text"},"model_output":{"message":"","type":"unknown"}},"logs":{"text_output":[]},"message":{"text_output":{"repr":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","raw":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","type":"text"}},"artifacts":{"text_output":{"repr":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","raw":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","type":"text"}},"timedelta":1.034765167045407,"duration":"1.03 seconds","used_frozen_result":false},"artifacts":{"text_output":{"repr":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","raw":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","type":"text"}},"params":"None","valid":true,"build_id":"4f0ae730-a266-4d35-b89f-7b825c620a0f","id":"OpenAIModel-7AjrN","timestamp":"2024-12-23T19:10:58.790484Z","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"}],"ChatOutput-sfUhT":[{"data":{"results":{"message":{"text_key":"text","data":{"timestamp":"2024-12-23 19:10:58","sender":"Machine","sender_name":"AI","session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","files":[],"error":"False","edit":"False","properties":{"text_color":"","background_color":"","edited":"False","source":{"id":"OpenAIModel-7AjrN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":"False","positive_feedback":"None","state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"5688356d-9f30-40ca-9907-79a7a2fc16fd","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"},"default_value":"","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","timestamp":"2024-12-23 19:10:58+00:00","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","error":"False","edit":"False","properties":{"text_color":"","background_color":"","edited":"False","source":{"id":"OpenAIModel-7AjrN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":"False","positive_feedback":"None","state":"complete","targets":[]},"category":"message","content_blocks":[]}},"outputs":{"message":{"message":{"timestamp":"2024-12-23T19:10:58","sender":"Machine","sender_name":"AI","session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","text":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","files":[],"error":false,"edit":false,"properties":{"text_color":"","background_color":"","edited":false,"source":{"id":"OpenAIModel-7AjrN","display_name":"OpenAI","source":"gpt-4o-mini"},"icon":"OpenAI","allow_markdown":false,"positive_feedback":null,"state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"5688356d-9f30-40ca-9907-79a7a2fc16fd","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"},"type":"object"}},"logs":{"message":[]},"message":{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"type":"object"},"artifacts":{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"type":"object"},"timedelta":0.017838125000707805,"duration":"18 ms","used_frozen_result":false},"artifacts":{"message":"Hello! 🌟 I'm excited to help you get started on your journey to building something fresh! What do you have in mind? Whether it's a project, an idea, or a concept, let's dive in and make it happen!","sender":"Machine","sender_name":"AI","files":[],"type":"object"},"params":"- Files: []\n Message: Hello! 🌟 I'm excited to help you get started on your journey to building\n something fresh! What do you have in mind? Whether it's a project, an idea, or\n a concept, let's dive in and make it happen!\n Sender: Machine\n Sender Name: AI\n Type: object\n","valid":true,"build_id":"1e8b908b-aba7-403b-9e9b-eca92bb78668","id":"ChatOutput-sfUhT","timestamp":"2024-12-23T19:10:58.813268Z","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"}]}}
+```
+
+
+
+
+### Delete Vertex builds
+
+Delete Vertex builds for a specific flow.
+
+
+
+
+```curl
+curl -X DELETE \
+ "$LANGFLOW_URL/api/v1/monitor/builds?flow_id=$FLOW_ID" \
+ -H "accept: */*"
+```
+
+
+
+
+```plain
+204 No Content
+```
+
+
+
+
+### Get messages
+
+Retrieve messages with optional filters.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/monitor/messages" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+A list of all messages.
+```
+
+
+
+
+You can filter messages by `flow_id`, `session_id`, `sender`, and `sender_name`.
+Results can be ordered with the `order_by` query string.
+
+This example retrieves messages sent by `Machine` and `AI` in a given chat session (`session_id`) and orders the messages by timestamp.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/monitor/messages?flow_id=$FLOW_ID&session_id=01ce083d-748b-4b8d-97b6-33adbb6a528a&sender=Machine&sender_name=AI&order_by=timestamp" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+[
+ {
+ "id": "1c1d6134-9b8b-4079-931c-84dcaddf19ba",
+ "flow_id": "01ce083d-748b-4b8d-97b6-33adbb6a528a",
+ "timestamp": "2024-12-23 19:20:11 UTC",
+ "sender": "Machine",
+ "sender_name": "AI",
+ "session_id": "01ce083d-748b-4b8d-97b6-33adbb6a528a",
+ "text": "Hello! It's great to see you here! What exciting project or idea are you thinking about diving into today? Whether it's something fresh and innovative or a classic concept with a twist, I'm here to help you get started! Let's brainstorm together!",
+ "files": "[]",
+ "edit": false,
+ "properties": {
+ "text_color": "",
+ "background_color": "",
+ "edited": false,
+ "source": {
+ "id": "OpenAIModel-7AjrN",
+ "display_name": "OpenAI",
+ "source": "gpt-4o-mini"
+ },
+ "icon": "OpenAI",
+ "allow_markdown": false,
+ "positive_feedback": null,
+ "state": "complete",
+ "targets": []
+ },
+ "category": "message",
+ "content_blocks": []
+ }
+]
+```
+
+
+
+
+### Delete messages
+
+Delete specific messages by their IDs.
+
+This example deletes the message retrieved in the previous Get messages example.
+
+
+
+
+```curl
+curl -v -X DELETE \
+ "$LANGFLOW_URL/api/v1/monitor/messages" \
+ -H "accept: */*" \
+ -H "Content-Type: application/json" \
+ -d '["MESSAGE_ID_1", "MESSAGE_ID_2"]'
+```
+
+
+
+```plain
+204 No Content
+```
+
+
+
+
+### Update message
+
+Update a specific message by its ID.
+
+This example updates the `text` value of message `3ab66cc6-c048-48f8-ab07-570f5af7b160`.
+
+
+
+
+```curl
+curl -X PUT \
+ "$LANGFLOW_URL/api/v1/monitor/messages/3ab66cc6-c048-48f8-ab07-570f5af7b160" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "text": "testing 1234"
+}'
+```
+
+
+
+
+```plain
+{"timestamp":"2024-12-23T18:49:06","sender":"string","sender_name":"string","session_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a","text":"testing 1234","files":["string"],"error":true,"edit":true,"properties":{"text_color":"string","background_color":"string","edited":false,"source":{"id":"string","display_name":"string","source":"string"},"icon":"string","allow_markdown":false,"positive_feedback":true,"state":"complete","targets":[]},"category":"message","content_blocks":[],"id":"3ab66cc6-c048-48f8-ab07-570f5af7b160","flow_id":"01ce083d-748b-4b8d-97b6-33adbb6a528a"}
+```
+
+
+
+
+
+### Update session ID
+
+Update the session ID for messages.
+
+This example updates the `session_ID` value `01ce083d-748b-4b8d-97b6-33adbb6a528a` to `different_session_id`.
+
+
+
+
+```curl
+curl -X PATCH \
+ "$LANGFLOW_URL/api/v1/monitor/messages/session/01ce083d-748b-4b8d-97b6-33adbb6a528a?new_session_id=different_session_id" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+[
+ {
+ "id": "8dd7f064-e63a-4773-b472-ca0475249dfd",
+ "flow_id": "01ce083d-748b-4b8d-97b6-33adbb6a528a",
+ "timestamp": "2024-12-23 18:49:55 UTC",
+ "sender": "User",
+ "sender_name": "User",
+ "session_id": "different_session_id",
+ "text": "message",
+ "files": "[]",
+ "edit": false,
+ "properties": {
+ "text_color": "",
+ "background_color": "",
+ "edited": false,
+ "source": {
+ "id": null,
+ "display_name": null,
+ "source": null
+ },
+ "icon": "",
+ "allow_markdown": false,
+ "positive_feedback": null,
+ "state": "complete",
+ "targets": []
+ },
+ "category": "message",
+ "content_blocks": []
+ },
+]
+```
+
+
+
+
+### Delete messages by session
+
+Delete all messages for a specific session.
+
+
+
+
+```curl
+curl -X DELETE \
+ "$LANGFLOW_URL/api/v1/monitor/messages/session/different_session_id_2" \
+ -H "accept: */*"
+```
+
+
+
+
+```plain
+HTTP/1.1 204 No Content
+```
+
+
+
+
+### Get transactions
+
+Retrieve all transactions (interactions between components) for a specific flow.
+
+
+
+
+```curl
+curl -X GET \
+ "$LANGFLOW_URL/api/v1/monitor/transactions?flow_id=$FLOW_ID&page=1&size=50" \
+ -H "accept: application/json"
+```
+
+
+
+
+```plain
+{
+ "items": [
+ {
+ "timestamp": "2024-12-23T20:05:01.061Z",
+ "vertex_id": "string",
+ "target_id": "string",
+ "inputs": {},
+ "outputs": {},
+ "status": "string",
+ "error": "string",
+ "flow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ }
+ ],
+ "total": 0,
+ "page": 1,
+ "size": 1,
+ "pages": 0
+}
+```
+
+
+
+
+
+
diff --git a/langflow/docs/docs/Agents/agent-tool-calling-agent-component.md b/langflow/docs/docs/Agents/agent-tool-calling-agent-component.md
new file mode 100644
index 0000000..b7abf8d
--- /dev/null
+++ b/langflow/docs/docs/Agents/agent-tool-calling-agent-component.md
@@ -0,0 +1,212 @@
+---
+title: Create a problem-solving agent
+slug: /agents-tool-calling-agent-component
+---
+
+Developing **agents** in Langchain is complex.
+
+The `AgentComponent` is a component for easily creating an AI agent capable of analyzing tasks using tools you provide.
+
+The component contains all of the elements you'll need for creating an agent. Instead of managing LLM models and providers, pick your model and enter your API key. Instead of connecting a **Prompt** component, enter instructions in the component's **Agent Instruction** fields.
+
+
+
+
+Learn how to build a flow starting with the **Tool calling agent** component, and see how it can help you solve problems.
+
+## Prerequisites
+
+- [An OpenAI API key](https://platform.openai.com/)
+- [A Search API key](https://www.searchapi.io/)
+
+## Create a problem-solving agent with AgentComponent
+
+Create a problem-solving agent in Langflow, starting with the **Tool calling agent**.
+
+1. Click **New Flow**, and then click **Blank Flow**.
+2. Click and drag an **Agent** component to your workspace.
+The default settings are acceptable for now, so this guide assumes you're using **Open AI** for the LLM.
+3. Add your **Open AI API Key** to the **Agent** component.
+4. Add **Chat input** and **Chat output** components to your flow, and connect them to the tool calling agent.
+
+
+
+This basic flow enables you to chat with the agent with the **Playground** after you've connected some **Tools**.
+
+5. Connect the **Search API** tool component to your agent.
+6. Add your **Search API key** to the component.
+Your agent can now query the Search API for information.
+7. Connect a **Calculator** tool for solving basic math problems.
+8. Connect an **API Request** component to the agent.
+This component is not in the **Tools** category, but the agent can still use it as a tool by enabling **Tool Mode**.
+**Tool Mode** makes a component into a tool by adding a **Toolset** port that can be connected to an agent's **Tools** port.
+To enable **Tool Mode** on the component, click **Tool Mode**.
+The component's fields change dynamically based on the mode it's in.
+
+
+
+## Solve problems with the agent
+
+Your agent now has tools for performing a web search, doing basic math, and performing API requests. You can solve many problems with just these capabilities.
+
+* Your tabletop game group cancelled, and you're stuck at home.
+Point **API Request** to an online rules document, tell your agent `You are a fun game organizer who uses the tools at your disposal`, and play a game.
+* You need to learn a new software language quickly.
+Point **API Request** to some docs, tell your agent `You are a knowledgeable software developer who uses the tools at your disposal`, and start learning.
+
+See what problems you can solve with this flow. As your problem becomes more specialized, add a tool. For example, the [math agent tutorial project](/tutorials-math-agent) adds a Python REPL component to solve math problems that are too challenging for the calculator.
+
+### Edit a tool's metadata
+
+To edit a tool's metadata, click the **Edit Tools** button in the tool to modify its `name` or `description` metadata. These fields help connected agents understand how to use the tool, without having to modify the agent's prompt instructions.
+
+For example, the [URL](/components-data#url) component has three tools available when **Tool Mode** is enabled.
+
+| Tool Name | Description |
+|-----------|-------------|
+| `URL-fetch_content` | Use this tool to fetch and retrieve raw content from a URL, including HTML and other structured data. The full response content is returned. |
+| `URL-fetch_content_text` | Use this tool to fetch and extract clean, readable text content from a webpage. Only plain text content is returned. |
+| `URL-as_dataframe` | Use this tool to fetch structured data from a URL and convert it into a tabular format. Data is returned in a structured DataFrame table format. |
+
+A connected agent will have a clear idea of each tool's capabilities based on the `name` and `description` metadata. If you think the agent is using a tool incorrectly, edit a tool's metadata to help it understand the tool better.
+
+Tool names and descriptions can be edited, but the default tool identifiers cannot be changed. If you want to change the tool identifier, create a custom component.
+
+To see which tools the agent is using and how it's using them, ask the agent, `What tools are you using to answer my questions?`
+
+## Use an agent as a tool
+
+The agent component itself also supports **Tool Mode** for creating multi-agent flows.
+
+Add an agent to your problem-solving flow that uses a different OpenAI model for more specialized problem solving.
+
+1. Click and drag an **Agent** component to your workspace.
+2. Add your **Open AI API Key** to the **Agent** component.
+3. In the **Model Name** field, select `gpt-4o`.
+4. Click **Tool Mode** to use this new agent as a tool.
+5. Connect the new agent's **Toolset** port to the previously created agent's **Tools** port.
+6. Connect **Search API** and **API Request** to the new agent.
+The new agent will use `gpt-4o` for the larger tasks of scraping and searching information that requires large context windows.
+The problem-solving agent will now use this agent as a tool, with its unique LLM and toolset.
+
+
+
+7. The new agent's metadata can be edited to help the problem-solving agent understand how to use it.
+Click **Edit Tools** to modify the new agent's `name` or `description` metadata so its usage is clear to the problem-solving agent.
+For example, the default tool name is `Agent`. Edit the name to `Agent-gpt-4o`, and edit the description to `Use the gpt-4o model for complex problem solving`. The problem-solving agent will understand that this is the `gpt-4o` agent, and will use it for tasks requiring a larger context window.
+
+## Add custom components as tools {#components-as-tools}
+
+An agent can use custom components as tools.
+
+1. To add a custom component to the problem-solving agent flow, click **New Custom Component**.
+
+2. Add custom Python code to the custom component.
+Here's an example text analyzer for sentiment analysis.
+
+```python
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+import re
+
+class TextAnalyzerComponent(Component):
+ display_name = "Text Analyzer"
+ description = "Analyzes and transforms input text."
+ documentation: str = "http://docs.langflow.org/components/custom"
+ icon = "chart-bar"
+ name = "TextAnalyzerComponent"
+
+ inputs = [
+ MessageTextInput(
+ name="input_text",
+ display_name="Input Text",
+ info="Enter text to analyze",
+ value="Hello, World!",
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Analysis Result", name="output", method="analyze_text"),
+ ]
+
+ def analyze_text(self) -> Data:
+ text = self.input_text
+
+ # Perform text analysis
+ word_count = len(text.split())
+ char_count = len(text)
+ sentence_count = len(re.findall(r'\w+[.!?]', text))
+
+ # Transform text
+ reversed_text = text[::-1]
+ uppercase_text = text.upper()
+
+ analysis_result = {
+ "original_text": text,
+ "word_count": word_count,
+ "character_count": char_count,
+ "sentence_count": sentence_count,
+ "reversed_text": reversed_text,
+ "uppercase_text": uppercase_text
+ }
+
+ data = Data(value=analysis_result)
+ self.status = data
+ return data
+```
+
+3. To enable the custom component as a tool, click **Tool Mode**.
+4. Connect the tool output to the agent's tools input.
+5. Ask the agent, `What tools are you using to answer my questions?`
+Your response will be similar to the following, and will include your custom component.
+```plain
+I have access to several tools that assist me in answering your questions, including:
+Search API: This allows me to search for recent information or results on the web.
+HTTP Requests: I can make HTTP requests to various URLs to retrieve data or interact with APIs.
+Calculator: I can evaluate basic arithmetic expressions.
+Text Analyzer: I can analyze and transform input text.
+Current Date and Time: I can retrieve the current date and time in various time zones.
+```
+
+## Make any component a tool
+
+If the component you want to use as a tool doesn't have a **Tool Mode** button, add `tool_mode=True` to one of the component's inputs, and connect the new **Toolset** output to the agent's **Tools** input.
+
+Langflow supports **Tool Mode** for the following data types:
+
+* `DataInput`
+* `DataFrameInput`
+* `PromptInput`
+* `MessageTextInput`
+* `MultilineInput`
+* `DropdownInput`
+
+For example, the [components as tools](#components-as-tools) example above adds `tool_mode=True` to the `MessageTextInput` input so the custom component can be used as a tool.
+
+```python
+inputs = [
+ MessageTextInput(
+ name="input_text",
+ display_name="Input Text",
+ info="Enter text to analyze",
+ value="Hello, World!",
+ tool_mode=True,
+ ),
+]
+```
+
+## Use the Run Flow component as a tool
+
+An agent can use flows that are saved in your workspace as tools with the [Run flow](/components-logic#run-flow) component.
+
+1. To add a **Run flow** component, click and drag a **Run flow** component to your workspace.
+2. Select the flow you want the agent to use as a tool.
+3. Enable **Tool Mode** in the component.
+4. Connect the tool output to the agent's tools input.
+5. To enable tool mode, select a **Flow** in the **Run flow** component, and then click **Tool Mode**.
+6. Ask the agent, `What tools are you using to answer my questions?`
+Your flow should be visible in the response as a tool.
+
+
diff --git a/langflow/docs/docs/Agents/agents-overview.md b/langflow/docs/docs/Agents/agents-overview.md
new file mode 100644
index 0000000..660e8b1
--- /dev/null
+++ b/langflow/docs/docs/Agents/agents-overview.md
@@ -0,0 +1,14 @@
+---
+title: Agents overview
+slug: /agents-overview
+---
+
+**Agents** are AI systems that use LLMs as a brain to analyze problems and select external tools.
+
+Instead of developers having to create logical statements to direct every possible path of a program, an agent can operate with autonomy. An agent can leverage external tools and APIs to gather information and take action, demonstrate chain-of-thought reasoning, and generate tailored text for specific purposes.
+
+To simplify the development of agents, Langflow created a custom [Tool calling agent](/components-agents#agent-component) component that simplifies configuration and lets developers focus on solving problems with agents.
+
+
+
+To get started, see [Create a problem solving agent](/agents-tool-calling-agent-component).
\ No newline at end of file
diff --git a/langflow/docs/docs/Components/components-agents.md b/langflow/docs/docs/Components/components-agents.md
new file mode 100644
index 0000000..7075742
--- /dev/null
+++ b/langflow/docs/docs/Components/components-agents.md
@@ -0,0 +1,330 @@
+---
+title: Agents
+slug: /components-agents
+---
+
+# Agent components in Langflow
+
+Agent components define the behavior and capabilities of AI agents in your flow.
+
+Agents use LLMs as a reasoning engine to decide which of the connected tool components to use to solve a problem.
+
+Tools in agentic functions are, essentially, functions that the agent can call to perform tasks or access external resources.
+A function is wrapped as a `Tool` object, with a common interface the agent understands.
+Agents become aware of tools through tool registration, where the agent is provided a list of available tools, typically at agent initialization. The `Tool` object's description tells the agent what the tool can do.
+
+The agent then uses a connected LLM to reason through the problem to decide which tool is best for the job.
+
+## Use an agent in a flow
+
+The [simple agent starter project](/starter-projects-simple-agent) uses an [agent component](#agent-component) connected to URL and Calculator tools to answer a user's questions. The OpenAI LLM acts as a brain for the agent to decide which tool to use. Tools are connected to agent components at the **Tools** port.
+
+
+
+For a multi-agent example, see [Create a problem-solving agent](/agents-tool-calling-agent-component).
+
+## Agent component {#agent-component}
+
+This component creates an agent that can use tools to answer questions and perform tasks based on given instructions.
+
+The component includes an LLM model integration, a system message prompt, and a **Tools** port to connect tools to extend its capabilities.
+
+For more information on this component, see the [tool calling agent documentation](/agents-tool-calling-agent-component).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent_llm | Dropdown | The provider of the language model that the agent will use to generate responses. Options include OpenAI and other providers, or Custom. |
+| system_prompt | String | System Prompt: Initial instructions and context provided to guide the agent's behavior. |
+| tools | List | List of tools available for the agent to use. |
+| input_value | String | The input task or question for the agent to process. |
+| add_current_date_tool | Boolean | If true, adds a tool to the agent that returns the current date. |
+| memory | Memory | Optional memory configuration for maintaining conversation history. |
+| max_iterations | Integer | Maximum number of iterations the agent can perform. |
+| handle_parsing_errors | Boolean | Whether to handle parsing errors during agent execution. |
+| verbose | Boolean | Enables verbose output for detailed logging. |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| response | Message | The agent's response to the given input task. |
+
+## CSV Agent
+
+This component creates a CSV agent from a CSV file and LLM.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| path | File | Path to the CSV file |
+| agent_type | String | Type of agent to create (zero-shot-react-description, openai-functions, or openai-tools) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | CSV agent instance |
+
+## CrewAI Agent
+
+This component represents an Agent of CrewAI, allowing for the creation of specialized AI agents with defined roles, goals, and capabilities within a crew.
+
+For more information, see the [CrewAI documentation](https://docs.crewai.com/core-concepts/Agents/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| role | Role | The role of the agent |
+| goal | Goal | The objective of the agent |
+| backstory | Backstory | The backstory of the agent |
+| tools | Tools | Tools at agent's disposal |
+| llm | Language Model | Language model that will run the agent |
+| memory | Memory | Whether the agent should have memory or not |
+| verbose | Verbose | Enables verbose output |
+| allow_delegation | Allow Delegation | Whether the agent is allowed to delegate tasks to other agents |
+| allow_code_execution | Allow Code Execution | Whether the agent is allowed to execute code |
+| kwargs | kwargs | Additional keyword arguments for the agent |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output | Agent | The constructed CrewAI Agent object |
+
+## Hierarchical Crew
+
+This component represents a group of agents, managing how they should collaborate and the tasks they should perform in a hierarchical structure. This component allows for the creation of a crew with a manager overseeing the task execution.
+
+For more information, see the [CrewAI documentation](https://docs.crewai.com/how-to/Hierarchical/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| agents | Agents | List of Agent objects representing the crew members |
+| tasks | Tasks | List of HierarchicalTask objects representing the tasks to be executed |
+| manager_llm | Manager LLM | Language model for the manager agent (optional) |
+| manager_agent | Manager Agent | Specific agent to act as the manager (optional) |
+| verbose | Verbose | Enables verbose output for detailed logging |
+| memory | Memory | Specifies the memory configuration for the crew |
+| use_cache | Use Cache | Enables caching of results |
+| max_rpm | Max RPM | Sets the maximum requests per minute |
+| share_crew | Share Crew | Determines if the crew information is shared among agents |
+| function_calling_llm | Function Calling LLM | Specifies the language model for function calling |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| crew | Crew | The constructed Crew object with hierarchical task execution |
+
+## JSON Agent
+
+This component creates a JSON agent from a JSON or YAML file and an LLM.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| path | File | Path to the JSON or YAML file |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | JSON agent instance |
+
+## OpenAI Tools Agent
+
+This component creates an OpenAI Tools Agent using LangChain.
+
+For more information, see the [LangChain documentation](https://python.langchain.com/v0.1/docs/modules/agents/agent_types/openai_tools/).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent (must be tool-enabled) |
+| system_prompt | String | System prompt for the agent |
+| user_prompt | String | User prompt template (must contain 'input' key) |
+| chat_history | List[Data] | Optional chat history for the agent |
+| tools | List[Tool] | List of tools available to the agent |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | OpenAI Tools Agent instance |
+
+## OpenAPI Agent
+
+This component creates an OpenAPI Agent to interact with APIs defined by OpenAPI specifications.
+
+For more information, see the LangChain documentation on OpenAPI Agents.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| path | File | Path to the OpenAPI specification file (JSON or YAML) |
+| allow_dangerous_requests | Boolean | Whether to allow potentially dangerous API requests |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | OpenAPI Agent instance |
+
+## SQL Agent
+
+This component creates a SQL Agent to interact with SQL databases.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| database_uri | String | URI of the SQL database to connect to |
+| extra_tools | List[Tool] | Additional tools to provide to the agent (optional) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | SQL Agent instance |
+
+## Sequential Crew
+
+This component represents a group of agents with tasks that are executed sequentially. This component allows for the creation of a crew that performs tasks in a specific order.
+
+For more information, see the [CrewAI documentation](https://docs.crewai.com/how-to/Sequential/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| tasks | Tasks | List of SequentialTask objects representing the tasks to be executed |
+| verbose | Verbose | Enables verbose output for detailed logging |
+| memory | Memory | Specifies the memory configuration for the crew |
+| use_cache | Use Cache | Enables caching of results |
+| max_rpm | Max RPM | Sets the maximum requests per minute |
+| share_crew | Share Crew | Determines if the crew information is shared among agents |
+| function_calling_llm | Function Calling LLM | Specifies the language model for function calling |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| crew | Crew | The constructed Crew object with sequential task execution |
+
+## Sequential task agent
+
+This component creates a CrewAI Task and its associated Agent, allowing for the definition of sequential tasks with specific agent roles and capabilities.
+
+For more information, see the [CrewAI documentation](https://docs.crewai.com/how-to/Sequential/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| role | Role | The role of the agent |
+| goal | Goal | The objective of the agent |
+| backstory | Backstory | The backstory of the agent |
+| tools | Tools | Tools at agent's disposal |
+| llm | Language Model | Language model that will run the agent |
+| memory | Memory | Whether the agent should have memory or not |
+| verbose | Verbose | Enables verbose output |
+| allow_delegation | Allow Delegation | Whether the agent is allowed to delegate tasks to other agents |
+| allow_code_execution | Allow Code Execution | Whether the agent is allowed to execute code |
+| agent_kwargs | Agent kwargs | Additional kwargs for the agent |
+| task_description | Task Description | Descriptive text detailing task's purpose and execution |
+| expected_output | Expected Task Output | Clear definition of expected task outcome |
+| async_execution | Async Execution | Boolean flag indicating asynchronous task execution |
+| previous_task | Previous Task | The previous task in the sequence (for chaining) |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| task_output | Sequential Task | List of SequentialTask objects representing the created task(s) |
+
+## Tool Calling Agent
+
+This component creates a Tool Calling Agent using LangChain.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| system_prompt | String | System prompt for the agent |
+| user_prompt | String | User prompt template (must contain 'input' key) |
+| chat_history | List[Data] | Optional chat history for the agent |
+| tools | List[Tool] | List of tools available to the agent |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | Tool Calling Agent instance |
+
+## Vector Store Agent
+
+This component creates a Vector Store Agent using LangChain.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| vectorstore | VectorStoreInfo | Vector store information for the agent to use |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | Vector Store Agent instance |
+
+## Vector Store Router Agent
+
+This component creates a Vector Store Router Agent using LangChain.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| vectorstores | List[VectorStoreInfo] | List of vector store information for the agent to route between |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | Vector Store Router Agent instance |
+
+## XML Agent
+
+This component creates an XML Agent using LangChain.
+
+The agent uses XML formatting for tool instructions to the Language Model.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| llm | LanguageModel | Language model to use for the agent |
+| user_prompt | String | Custom prompt template for the agent (includes XML formatting instructions) |
+| tools | List[Tool] | List of tools available to the agent |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| agent | AgentExecutor | XML Agent instance |
\ No newline at end of file
diff --git a/langflow/docs/docs/Components/components-custom-components.md b/langflow/docs/docs/Components/components-custom-components.md
new file mode 100644
index 0000000..e76fbf8
--- /dev/null
+++ b/langflow/docs/docs/Components/components-custom-components.md
@@ -0,0 +1,531 @@
+---
+title: Create custom Python components
+slug: /components-custom-components
+---
+
+Custom components are created within Langflow and extend the platform's functionality with custom, reusable Python code.
+
+Since Langflow operates with Python behind the scenes, you can implement any Python function within a Custom Component. This means you can leverage the power of libraries such as Pandas, Scikit-learn, Numpy, and thousands of other packages to create components that handle data processing in unlimited ways. You can use any type as long as the type is properly annotated in the output methods (e.g., `> list[int]`).
+
+Custom Components create reusable and configurable components to enhance the capabilities of Langflow, making it a powerful tool for developing complex processing between user and AI messages.
+
+## Directory structure requirements
+
+By default, Langflow looks for custom components in the `langflow/components` directory.
+
+If you're creating custom components in a different location using the [LANGFLOW_COMPONENTS_PATH](/environment-variables#LANGFLOW_COMPONENTS_PATH)
+`LANGFLOW_COMPONENTS_PATH` environment variable, components must be organized in a specific directory structure to be properly loaded and displayed in the UI:
+
+```
+/your/custom/components/path/ # Base directory (set by LANGFLOW_COMPONENTS_PATH)
+ └── category_name/ # Required category subfolder (determines menu name)
+ └── custom_component.py # Component file
+```
+
+Components must be placed inside **category folders**, not directly in the base directory.
+The category folder name determines where the component appears in the UI menu.
+
+For example, to add a component to the **Helpers** menu, place it in a `helpers` subfolder:
+
+```
+/app/custom_components/ # LANGFLOW_COMPONENTS_PATH
+ └── helpers/ # Shows up as "Helpers" menu
+ └── custom_component.py # Your component
+```
+
+You can have **multiple category folders** to organize components into different menus:
+```
+/app/custom_components/
+ ├── helpers/
+ │ └── helper_component.py
+ └── tools/
+ └── tool_component.py
+```
+
+This folder structure is required for Langflow to properly discover and load your custom components. Components placed directly in the base directory will not be loaded.
+
+```
+/app/custom_components/ # LANGFLOW_COMPONENTS_PATH
+ └── custom_component.py # Won't be loaded - missing category folder!
+```
+
+## Create a custom component in Langflow
+
+Creating custom components in Langflow involves creating a Python class that defines the component's functionality, inputs, and outputs.
+The default code provides a working structure for your custom component.
+```python
+# from langflow.field_typing import Data
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+
+
+class CustomComponent(Component):
+ display_name = "Custom Component"
+ description = "Use as a template to create your own component."
+ documentation: str = "https://docs.langflow.org/components-custom-components"
+ icon = "custom_components"
+ name = "CustomComponent"
+
+ inputs = [
+ MessageTextInput(name="input_value", display_name="Input Value", value="Hello, World!"),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+
+ def build_output(self) -> Data:
+ data = Data(value=self.input_value)
+ self.status = data
+ return data
+
+```
+
+You can create your class in your favorite text editor outside of Langflow and paste it in later, or just follow along in the code pane.
+
+1. In Langflow, click **+ Custom Component** to add a custom component into the workspace.
+2. Open the component's code pane.
+3. Import dependencies.
+Your custom component inherits from the langflow `Component` class so you need to include it.
+```python
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+```
+4. **Define the Class**: Start by defining a Python class that inherits from `Component`. This class will encapsulate the functionality of your custom component.
+
+```python
+class CustomComponent(Component):
+ display_name = "Custom Component"
+ description = "Use as a template to create your own component."
+ documentation: str = "https://docs.langflow.org/components-custom-components"
+ icon = "custom_components"
+ name = "CustomComponent"
+```
+5. **Specify Inputs and Outputs**: Use Langflow's input and output classes to define the inputs and outputs of your component. They should be declared as class attributes.
+```python
+ inputs = [
+ MessageTextInput(name="input_value", display_name="Input Value", value="Hello, World!"),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+```
+6. **Implement Output Methods**: Implement methods for each output, which contains the logic of your component. These methods can access input values using `self.` , return processed values and define what to be displayed in the component with the `self.status` attribute.
+```python
+ def build_output(self) -> Data:
+ data = Data(value=self.input_value)
+ self.status = data
+ return data
+```
+7. **Use Proper Annotations**: Ensure that output methods are properly annotated with their types. Langflow uses these annotations to validate and handle data correctly. For example, this method is annotated to output `Data`.
+```python
+ def build_output(self) -> Data:
+```
+8. Click **Check & Save** to confirm your component works.
+You now have an operational custom component.
+
+
+## Add inputs and modify output methods
+
+This code defines a custom component that accepts 5 inputs and outputs a Message.
+
+Copy and paste it into the Custom Component code pane and click **Check & Save.**
+
+```python
+from langflow.custom import Component
+from langflow.inputs import StrInput, MultilineInput, SecretStrInput, IntInput, DropdownInput
+from langflow.template import Output, Input
+from langflow.schema.message import Message
+
+class MyCustomComponent(Component):
+ display_name = "My Custom Component"
+ description = "An example of a custom component with various input types."
+
+ inputs = [
+ StrInput(
+ name="username",
+ display_name="Username",
+ info="Enter your username."
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="Password",
+ info="Enter your password."
+ ),
+ MessageTextInput(
+ name="special_message",
+ display_name="special_message",
+ info="Enter a special message.",
+ ),
+ IntInput(
+ name="age",
+ display_name="Age",
+ info="Enter your age."
+ ),
+ DropdownInput(
+ name="gender",
+ display_name="Gender",
+ options=["Male", "Female", "Other"],
+ info="Select your gender."
+ )
+ ]
+
+ outputs = [
+ Output(display_name="Result", name="result", method="process_inputs"),
+ ]
+
+ def process_inputs(self) -> Message:
+ """
+ Process the user inputs and return a Message object.
+
+ Returns:
+ Message: A Message object containing the processed information.
+ """
+ try:
+ processed_text = f"User {self.username} (Age: {self.age}, Gender: {self.gender}) " \
+ f"sent the following special message: {self.special_message}"
+ return Message(text=processed_text)
+ except AttributeError as e:
+ return Message(text=f"Error processing inputs: {str(e)}")
+```
+
+Since the component outputs a `Message`, you can wire it into a chat and pass messages to yourself.
+
+Your Custom Component accepts the Chat Input message through `MessageTextInput`, fills in the variables with the `process_inputs` method, and finally passes the message `User Username (Age: 49, Gender: Male) sent the following special message: Hello!` to Chat Output.
+
+By defining inputs this way, Langflow can automatically handle the validation and display of these fields in the user interface, making it easier to create robust and user-friendly custom components.
+
+All of the types detailed above derive from a general class that can also be accessed through the generic `Input` class.
+
+:::tip
+Use `MessageInput` to get the entire Message object instead of just the text.
+:::
+
+## Input Types {#3815589831f24ab792328ed233c8b00d}
+
+---
+
+
+Langflow provides several higher-level input types to simplify the creation of custom components. These input types standardize how inputs are defined, validated, and used. Here’s a guide on how to use these inputs and their primary purposes:
+
+
+### **HandleInput** {#fb06c48a326043ffa46badc1ab3ba467}
+
+
+Represents an input that has a handle to a specific type (e.g., `BaseLanguageModel`, `BaseRetriever`, etc.).
+
+- **Usage:** Useful for connecting to specific component types in a flow.
+
+### **DataInput** {#0e1dcb768e38487180d720b0884a90f5}
+
+
+Represents an input that receives a `Data` object.
+
+- **Usage:** Ideal for components that process or manipulate data objects.
+- **Input Types:** `["Data"]`
+
+### **StrInput** {#4ec6e68ad9ab4cd194e8e607bc5b3411}
+
+
+Represents a standard string input field.
+
+- **Usage:** Used for any text input where the user needs to provide a string.
+- **Input Types:** `["Text"]`
+
+### **MessageInput** {#9292ac0105e14177af5eff2131b9c71b}
+
+
+Represents an input field specifically for `Message` objects.
+
+- **Usage:** Used in components that handle or process messages.
+- **Input Types:** `["Message"]`
+
+### **MessageTextInput** {#5511f5e32b944b4e973379a6bd5405e4}
+
+
+Represents a text input for messages.
+
+- **Usage:** Suitable for components that need to extract text from message objects.
+- **Input Types:** `["Message"]`
+
+### **MultilineInput** {#e6d8315b0fb44a2fb8c62c3f3184bbe9}
+
+
+Represents a text field that supports multiple lines.
+
+- **Usage:** Ideal for longer text inputs where the user might need to write extended text.
+- **Input Types:** `["Text"]`
+- **Attributes:** `multiline=True`
+
+### **SecretStrInput** {#2283c13aa5f745b8b0009f7d40e59419}
+
+
+Represents a password input field.
+
+- **Usage:** Used for sensitive text inputs where the input should be hidden (e.g., passwords, API keys).
+- **Attributes:** `password=True`
+- **Input Types:** Does not accept input types, meaning it has no input handles for previous nodes/components to connect to it.
+
+### **IntInput** {#612680db6578451daef695bd19827a56}
+
+
+Represents an integer input field.
+
+- **Usage:** Used for numeric inputs where the value should be an integer.
+- **Input Types:** `["Integer"]`
+
+### **FloatInput** {#a15e1fdae15b49fc9bfbf38f8bd7b203}
+
+
+Represents a float input field.
+
+- **Usage:** Used for numeric inputs where the value should be a floating-point number.
+- **Input Types:** `["Float"]`
+
+### **BoolInput** {#3083671e0e7f4390a03396485114be66}
+
+
+Represents a boolean input field.
+
+- **Usage:** Used for true/false or yes/no type inputs.
+- **Input Types:** `["Boolean"]`
+
+### **NestedDictInput** {#2866fc4018e743d8a45afde53f1e57be}
+
+
+Represents an input field for nested dictionaries.
+
+- **Usage:** Used for more complex data structures where the input needs to be a dictionary.
+- **Input Types:** `["NestedDict"]`
+
+### **DictInput** {#daa2c2398f694ec199b425e2ed4bcf93}
+
+
+Represents an input field for dictionaries.
+
+- **Usage:** Suitable for inputs that require a dictionary format.
+- **Input Types:** `["Dict"]`
+
+### **DropdownInput** {#14dcdef11bab4d3f8127eaf2e36a77b9}
+
+
+Represents a dropdown input field.
+
+- **Usage:** Used where the user needs to select from a predefined list of options.
+- **Attributes:** `options` to define the list of selectable options.
+- **Input Types:** `["Text"]`
+
+### **FileInput** {#73e6377dc5f446f39517a558a1291410}
+
+
+Represents a file input field.
+
+- **Usage:** Used to upload files.
+- **Attributes:** `file_types` to specify the types of files that can be uploaded.
+- **Input Types:** `["File"]`
+
+
+### Generic Input {#278e2027493e45b68746af0a5b6c06f6}
+
+
+---
+
+
+Langflow offers native input types, but you can use any type as long as they are properly annotated in the output methods (e.g., `-> list[int]`).
+
+
+The `Input` class is highly customizable, allowing you to specify a wide range of attributes for each input field. It has several attributes that can be customized:
+
+- `field_type`: Specifies the type of field (e.g., `str`, `int`). Default is `str`.
+- `required`: Boolean indicating if the field is required. Default is `False`.
+- `placeholder`: Placeholder text for the input field. Default is an empty string.
+- `is_list`: Boolean indicating if the field should accept a list of values. Default is `False`.
+- `show`: Boolean indicating if the field should be shown. Default is `True`.
+- `multiline`: Boolean indicating if the field should allow multi-line input. Default is `False`.
+- `value`: Default value for the input field. Default is `None`.
+- `file_types`: List of accepted file types (for file inputs). Default is an empty list.
+- `file_path`: File path if the field is a file input. Default is `None`.
+- `password`: Boolean indicating if the field is a password. Default is `False`.
+- `options`: List of options for the field (for dropdowns). Default is `None`.
+- `name`: Name of the input field. Default is `None`.
+- `display_name`: Display name for the input field. Default is `None`.
+- `advanced`: Boolean indicating if the field is an advanced parameter. Default is `False`.
+- `input_types`: List of accepted input types. Default is `None`.
+- `dynamic`: Boolean indicating if the field is dynamic. Default is `False`.
+- `info`: Additional information or tooltip for the input field. Default is an empty string.
+- `real_time_refresh`: Boolean indicating if the field should refresh in real-time. Default is `None`.
+- `refresh_button`: Boolean indicating if the field should have a refresh button. Default is `None`.
+- `refresh_button_text`: Text for the refresh button. Default is `None`.
+- `range_spec`: Range specification for numeric fields. Default is `None`.
+- `load_from_db`: Boolean indicating if the field should load from the database. Default is `False`.
+- `title_case`: Boolean indicating if the display name should be in title case. Default is `True`.
+
+## Create a Custom Component with Generic Input
+
+Here is an example of how to define inputs for a component using the `Input` class.
+
+Copy and paste it into the Custom Component code pane and click **Check & Save.**
+
+```python
+from langflow.template import Input, Output
+from langflow.custom import Component
+from langflow.field_typing import Text
+from langflow.schema.message import Message
+from typing import Dict, Any
+
+class TextAnalyzerComponent(Component):
+ display_name = "Text Analyzer"
+ description = "Analyzes input text and provides basic statistics."
+
+ inputs = [
+ Input(
+ name="input_text",
+ display_name="Input Text",
+ field_type="Message",
+ required=True,
+ placeholder="Enter text to analyze",
+ multiline=True,
+ info="The text you want to analyze.",
+ input_types=["Text"]
+ ),
+ Input(
+ name="include_word_count",
+ display_name="Include Word Count",
+ field_type="bool",
+ required=False,
+ info="Whether to include word count in the analysis.",
+ ),
+ Input(
+ name="perform_sentiment_analysis",
+ display_name="Perform Sentiment Analysis",
+ field_type="bool",
+ required=False,
+ info="Whether to perform basic sentiment analysis.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Analysis Results", name="results", method="analyze_text"),
+ ]
+
+ def analyze_text(self) -> Message:
+ # Extract text from the Message object
+ if isinstance(self.input_text, Message):
+ text = self.input_text.text
+ else:
+ text = str(self.input_text)
+
+ results = {
+ "character_count": len(text),
+ "sentence_count": text.count('.') + text.count('!') + text.count('?')
+ }
+
+ if self.include_word_count:
+ results["word_count"] = len(text.split())
+
+ if self.perform_sentiment_analysis:
+ # Basic sentiment analysis
+ text_lower = text.lower()
+ if "happy" in text_lower or "good" in text_lower:
+ sentiment = "positive"
+ elif "sad" in text_lower or "bad" in text_lower:
+ sentiment = "negative"
+ else:
+ sentiment = "neutral"
+
+ results["sentiment"] = sentiment
+
+ # Convert the results dictionary to a formatted string
+ formatted_results = "\n".join([f"{key}: {value}" for key, value in results.items()])
+
+ # Return a Message object
+ return Message(text=formatted_results)
+
+# Define how to use the inputs and outputs
+component = TextAnalyzerComponent()
+```
+
+In this custom component:
+
+- The `input_text` input is a required multi-line text field that accepts a Message object or a string. It's used to provide the text for analysis.
+
+- The `include_word_count` input is an optional boolean field. When set to True, it adds a word count to the analysis results.
+
+- The `perform_sentiment_analysis` input is an optional boolean field. When set to True, it triggers a basic sentiment analysis of the input text.
+
+The component performs basic text analysis, including character count and sentence count (based on punctuation marks). If word count is enabled, it splits the text and counts the words. If sentiment analysis is enabled, it performs a simple keyword-based sentiment classification (positive, negative, or neutral).
+
+Since the component inputs and outputs a `Message`, you can wire the component into a chat and see how the basic custom component logic interacts with your input.
+
+## Create a Custom Component with Multiple Outputs {#6f225be8a142450aa19ee8e46a3b3c8c}
+
+---
+
+
+In Langflow, custom components can have multiple outputs. Each output can be associated with a specific method in the component, allowing you to define distinct behaviors for each output path. This feature is particularly useful when you want to route data based on certain conditions or process it in multiple ways.
+
+1. **Definition of Outputs**: Each output is defined in the `outputs` list of the component. Each output is associated with a display name, an internal name, and a method that gets called to generate the output.
+2. **Output Methods**: The methods associated with outputs are responsible for generating the data for that particular output. These methods are called when the component is executed, and each method can independently produce its result.
+
+This example component has two outputs:
+
+- `process_data`: Processes the input text (e.g., converts it to uppercase) and returns it.
+- `get_processing_function`: Returns the `process_data` method itself to be reused in composition.
+
+```python
+from typing import Callable
+from langflow.custom import Component
+from langflow.inputs import StrInput
+from langflow.template import Output
+from langflow.field_typing import Text
+
+class DualOutputComponent(Component):
+ display_name = "Dual Output"
+ description = "Processes input text and returns both the result and the processing function."
+ icon = "double-arrow"
+
+ inputs = [
+ StrInput(
+ name="input_text",
+ display_name="Input Text",
+ info="The text input to be processed.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Processed Data", name="processed_data", method="process_data"),
+ Output(display_name="Processing Function", name="processing_function", method="get_processing_function"),
+ ]
+
+ def process_data(self) -> Text:
+ # Process the input text (e.g., convert to uppercase)
+ processed = self.input_text.upper()
+ self.status = processed
+ return processed
+
+ def get_processing_function(self) -> Callable[[], Text]:
+ # Return the processing function itself
+ return self.process_data
+```
+
+This example shows how to define multiple outputs in a custom component. The first output returns the processed data, while the second output returns the processing function itself.
+
+The `processing_function` output can be used in scenarios where the function itself is needed for further processing or dynamic flow control. Notice how both outputs are properly annotated with their respective types, ensuring clarity and type safety.
+
+
+## Special Operations
+
+Advanced methods and attributes offer additional control and functionality. Understanding how to leverage these can enhance your custom components' capabilities.
+
+- `self.inputs`: Access all defined inputs. Useful when an output method needs to interact with multiple inputs.
+- `self.outputs`: Access all defined outputs. This is particularly useful if an output function needs to trigger another output function.
+- `self.status`: Use this to update the component's status or intermediate results. It helps track the component's internal state or store temporary data.
+- `self.graph.flow_id`: Retrieve the flow ID, useful for maintaining context or debugging.
+- `self.stop("output_name")`: Use this method within an output function to prevent data from being sent through other components. This method stops next component execution and is particularly useful for specific operations where a component should stop from running based on specific conditions.
+
+## Contribute Custom Components to Langflow
+
+See [How to Contribute](/contributing-components) to contribute your custom component to Langflow.
diff --git a/langflow/docs/docs/Components/components-data.md b/langflow/docs/docs/Components/components-data.md
new file mode 100644
index 0000000..d756fe3
--- /dev/null
+++ b/langflow/docs/docs/Components/components-data.md
@@ -0,0 +1,230 @@
+---
+title: Data
+slug: /components-data
+---
+
+# Data components in Langflow
+
+Data components load data from a source into your flow.
+
+They may perform some processing or type checking, like converting raw HTML data into text, or ensuring your loaded file is of an acceptable type.
+
+## Use a data component in a flow
+
+The **URL** data component loads content from a list of URLs.
+
+In the component's **URLs** field, enter a comma-separated list of URLs you want to load. Alternatively, connect a component that outputs the `Message` type, like the **Chat Input** component, to supply your URLs with a component.
+
+To output a `Data` type, in the **Output Format** dropdown, select **Raw HTML**.
+To output a `Message` type, in the **Output Format** dropdown, select **Text**. This option applies postprocessing with the `data_to_text` helper function.
+
+In this example of a document ingestion pipeline, the URL component outputs raw HTML to a text splitter, which splits the raw content into chunks for a vector database to ingest.
+
+
+
+## API Request
+
+This component makes HTTP requests using URLs or cURL commands.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| urls | URLs | Enter one or more URLs, separated by commas. |
+| curl | cURL | Paste a curl command to populate the dictionary fields for headers and body. |
+| method | Method | The HTTP method to use. |
+| use_curl | Use cURL | Enable cURL mode to populate fields from a cURL command. |
+| query_params | Query Parameters | The query parameters to append to the URL. |
+| body | Body | The body to send with the request as a dictionary (for `POST`, `PATCH`, `PUT`). |
+| headers | Headers | The headers to send with the request as a dictionary. |
+| timeout | Timeout | The timeout to use for the request. |
+| follow_redirects | Follow Redirects | Whether to follow http redirects. |
+| save_to_file | Save to File | Save the API response to a temporary file |
+| include_httpx_metadata | Include HTTPx Metadata | Include properties such as `headers`, `status_code`, `response_headers`, and `redirection_history` in the output. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | The result of the API requests. |
+
+
+## Directory
+
+This component recursively loads files from a directory, with options for file types, depth, and concurrency.
+
+### Inputs
+
+| Input | Type | Description |
+| ------------------ | ---------------- | -------------------------------------------------- |
+| path | MessageTextInput | Path to the directory to load files from |
+| types | MessageTextInput | File types to load (leave empty to load all types) |
+| depth | IntInput | Depth to search for files |
+| max_concurrency | IntInput | Maximum concurrency for loading files |
+| load_hidden | BoolInput | If true, hidden files are loaded |
+| recursive | BoolInput | If true, the search is recursive |
+| silent_errors | BoolInput | If true, errors do not raise an exception |
+| use_multithreading | BoolInput | If true, multithreading is used |
+
+
+### Outputs
+
+| Output | Type | Description |
+| ------ | ---------- | ----------------------------------- |
+| data | List[Data] | Loaded file data from the directory |
+
+## File
+
+The FileComponent is a class that loads and parses text files of various supported formats, converting the content into a Data object. It supports multiple file types and provides an option for silent error handling.
+
+The maximum supported file size is 100 MB.
+
+### Inputs
+
+| Name | Display Name | Info |
+| ------------- | ------------- | -------------------------------------------- |
+| path | Path | File path to load. |
+| silent_errors | Silent Errors | If true, errors do not raise an exception. |
+
+### Outputs
+
+| Name | Display Name | Info |
+| ---- | ------------ | -------------------------------------------- |
+| data | Data | Parsed content of the file as a Data object. |
+
+## Gmail Loader
+
+This component loads emails from Gmail using provided credentials and filters.
+
+For more on creating a service account JSON, see [Service Account JSON](https://developers.google.com/identity/protocols/oauth2/service-account).
+
+### Inputs
+
+| Input | Type | Description |
+| ----------- | ---------------- | ------------------------------------------------------------------------------------ |
+| json_string | SecretStrInput | JSON string containing OAuth 2.0 access token information for service account access |
+| label_ids | MessageTextInput | Comma-separated list of label IDs to filter emails |
+| max_results | MessageTextInput | Maximum number of emails to load |
+
+### Outputs
+
+| Output | Type | Description |
+| ------ | ---- | ----------------- |
+| data | Data | Loaded email data |
+
+## Google Drive Loader
+
+This component loads documents from Google Drive using provided credentials and a single document ID.
+
+For more on creating a service account JSON, see [Service Account JSON](https://developers.google.com/identity/protocols/oauth2/service-account).
+
+### Inputs
+
+| Input | Type | Description |
+| ----------- | ---------------- | ------------------------------------------------------------------------------------ |
+| json_string | SecretStrInput | JSON string containing OAuth 2.0 access token information for service account access |
+| document_id | MessageTextInput | Single Google Drive document ID |
+
+### Outputs
+
+| Output | Type | Description |
+| ------ | ---- | -------------------- |
+| docs | Data | Loaded document data |
+
+## Google Drive Search
+
+This component searches Google Drive files using provided credentials and query parameters.
+
+For more on creating a service account JSON, see [Service Account JSON](https://developers.google.com/identity/protocols/oauth2/service-account).
+
+### Inputs
+
+| Input | Type | Description |
+| -------------- | ---------------- | ------------------------------------------------------------------------------------ |
+| token_string | SecretStrInput | JSON string containing OAuth 2.0 access token information for service account access |
+| query_item | DropdownInput | The field to query |
+| valid_operator | DropdownInput | Operator to use in the query |
+| search_term | MessageTextInput | The value to search for in the specified query item |
+| query_string | MessageTextInput | The query string used for searching (can be edited manually) |
+
+### Outputs
+
+| Output | Type | Description |
+| ---------- | --------- | ----------------------------------------------- |
+| doc_urls | List[str] | URLs of the found documents |
+| doc_ids | List[str] | IDs of the found documents |
+| doc_titles | List[str] | Titles of the found documents |
+| Data | Data | Document titles and URLs in a structured format |
+
+## SQL Query
+
+This component executes SQL queries on a specified database.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| query | Query | The SQL query to execute. |
+| database_url | Database URL | The URL of the database. |
+| include_columns | Include Columns | Include columns in the result. |
+| passthrough | Passthrough | If an error occurs, return the query instead of raising an exception. |
+| add_error | Add Error | Add the error to the result. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| result | Result | The result of the SQL query execution. |
+
+## URL
+
+This component fetches content from one or more URLs, processes the content, and returns it as a list of [Data](/concepts-objects) objects.
+
+### Inputs
+
+| Name | Display Name | Info |
+| ---- | ------------ | ---------------------- |
+| urls | URLs | Enter one or more URLs |
+
+### Outputs
+
+| Name | Display Name | Info |
+| ---- | ------------ | ------------------------------------------------------------ |
+| data | Data | List of Data objects containing fetched content and metadata |
+
+## Webhook
+
+This component defines a webhook trigger that runs a flow when it receives an HTTP POST request.
+
+If the input is not valid JSON, the component wraps it in a `payload` object so that it can be processed and still trigger the flow. The component does not require an API key.
+
+When a **Webhook** component is added to the workspace, a new **Webhook cURL** tab becomes available in the **API** pane that contains an HTTP POST request for triggering the webhook component. For example:
+
+```bash
+curl -X POST \
+ "http://127.0.0.1:7860/api/v1/webhook/**YOUR_FLOW_ID**" \
+ -H 'Content-Type: application/json'\
+ -d '{"any": "data"}'
+ ```
+
+To test the webhook component:
+
+1. Add a **Webhook** component to the flow.
+2. Connect the **Webhook** component's **Data** output to the **Data** input of a [Data to Message](/components-processing#data-to-message) component.
+3. Connect the **Data to Message** component's **Message** output to the **Text** input of a [Chat Output](/components-io#chat-output) component.
+4. To send a POST request, copy the code from the **Webhook cURL** tab in the **API** pane and paste it into a terminal.
+5. Send the POST request.
+6. Open the **Playground**.
+Your JSON data is posted to the **Chat Output** component, which indicates that the webhook component is correctly triggering the flow.
+
+### Inputs
+
+| Name | Type | Description |
+| ---- | ------ | ---------------------------------------------- |
+| data | String | JSON payload for testing the webhook component |
+
+### Outputs
+
+| Name | Type | Description |
+| ----------- | ---- | ------------------------------------- |
+| output_data | Data | Processed data from the webhook input |
diff --git a/langflow/docs/docs/Components/components-embedding-models.md b/langflow/docs/docs/Components/components-embedding-models.md
new file mode 100644
index 0000000..5dfa36d
--- /dev/null
+++ b/langflow/docs/docs/Components/components-embedding-models.md
@@ -0,0 +1,399 @@
+---
+title: Embeddings
+slug: /components-embedding-models
+---
+
+# Embeddings models in Langflow
+
+Embeddings models convert text into numerical vectors. These embeddings capture semantic meaning of the input text, and allow LLMs to understand context.
+
+Refer to your specific component's documentation for more information on parameters.
+
+## Use an embeddings model component in a flow
+
+In this example of a document ingestion pipeline, the **OpenAI** embeddings model is connected to a vector database. The component converts the text chunks into vectors and stores them in the vector database. The vectorized data can be used to inform AI workloads like chatbots, similarity searches, and agents.
+
+This embeddings component uses an OpenAI API key for authentication. Refer to your specific embeddings component's documentation for more information on authentication.
+
+
+
+## AI/ML
+
+This component generates embeddings using the [AI/ML API](https://docs.aimlapi.com/api-overview/embeddings).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| model_name | String | The name of the AI/ML embedding model to use |
+| aiml_api_key | SecretString | API key for authenticating with the AI/ML service |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance of AIMLEmbeddingsImpl for generating embeddings |
+
+## Amazon Bedrock Embeddings
+
+This component is used to load embedding models from [Amazon Bedrock](https://aws.amazon.com/bedrock/).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| credentials_profile_name | String | Name of the AWS credentials profile in ~/.aws/credentials or ~/.aws/config, which has access keys or role information |
+| model_id | String | ID of the model to call, e.g., `amazon.titan-embed-text-v1`. This is equivalent to the `modelId` property in the `list-foundation-models` API |
+| endpoint_url | String | URL to set a specific service endpoint other than the default AWS endpoint |
+| region_name | String | AWS region to use, e.g., `us-west-2`. Falls back to `AWS_DEFAULT_REGION` environment variable or region specified in ~/.aws/config if not provided |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using Amazon Bedrock |
+
+## Astra DB vectorize
+
+:::important
+This component is deprecated as of Langflow version 1.1.2.
+Instead, use the [Astra DB vector store component](/components-vector-stores#astra-db-vector-store)
+:::
+
+Connect this component to the **Embeddings** port of the [Astra DB vector store component](/components-vector-stores#astra-db-vector-store) to generate embeddings.
+
+This component requires that your Astra DB database has a collection that uses a vectorize embedding provider integration.
+For more information and instructions, see [Embedding Generation](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| provider | Embedding Provider | The embedding provider to use |
+| model_name | Model Name | The embedding model to use |
+| authentication | Authentication | The name of the API key in Astra that stores your [vectorize embedding provider credentials](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html#embedding-provider-authentication). (Not required if using an [Astra-hosted embedding provider](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html#supported-embedding-providers).) |
+| provider_api_key | Provider API Key | As an alternative to `authentication`, directly provide your embedding provider credentials. |
+| model_parameters | Model Parameters | Additional model parameters |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using Astra vectorize | | |
+
+## Azure OpenAI Embeddings
+
+This component generates embeddings using Azure OpenAI models.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| Model | String | Name of the model to use (default: `text-embedding-3-small`) |
+| Azure Endpoint | String | Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/` |
+| Deployment Name | String | The name of the deployment |
+| API Version | String | The API version to use, options include various dates |
+| API Key | String | The API key to access the Azure OpenAI service |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using Azure OpenAI |
+
+## Cloudflare Workers AI Embeddings
+
+This component generates embeddings using [Cloudflare Workers AI models](https://developers.cloudflare.com/workers-ai/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| account_id | Cloudflare account ID |[Find your Cloudflare account ID](https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/#find-account-id-workers-and-pages) |
+| api_token | Cloudflare API token | [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) |
+| model_name | Model Name | [List of supported models](https://developers.cloudflare.com/workers-ai/models/#text-embeddings) |
+| strip_new_lines | Strip New Lines | Whether to strip new lines from the input text |
+| batch_size | Batch Size | Number of texts to embed in each batch |
+| api_base_url | Cloudflare API base URL | Base URL for the Cloudflare API |
+| headers | Headers | Additional request headers |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embeddings | An instance for generating embeddings using Cloudflare Workers |
+
+## Cohere Embeddings
+
+This component is used to load embedding models from [Cohere](https://cohere.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| cohere_api_key | String | API key required to authenticate with the Cohere service |
+| model | String | Language model used for embedding text documents and performing queries (default: `embed-english-v2.0`) |
+| truncate | Boolean | Whether to truncate the input text to fit within the model's constraints (default: `False`) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using Cohere |
+
+## Embedding similarity
+
+This component computes selected forms of similarity between two embedding vectors.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embedding_vectors | Embedding Vectors | A list containing exactly two data objects with embedding vectors to compare. |
+| similarity_metric | Similarity Metric | Select the similarity metric to use. Options: "Cosine Similarity", "Euclidean Distance", "Manhattan Distance". |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| similarity_data | Similarity Data | Data object containing the computed similarity score and additional information. |
+
+## Google generative AI embeddings
+
+This component connects to Google's generative AI embedding service using the GoogleGenerativeAIEmbeddings class from the `langchain-google-genai` package.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| api_key | API Key | Secret API key for accessing Google's generative AI service (required) |
+| model_name | Model Name | Name of the embedding model to use (default: "models/text-embedding-004") |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embeddings | Built GoogleGenerativeAIEmbeddings object |
+
+## Hugging Face Embeddings
+
+:::note
+This component is deprecated as of Langflow version 1.0.18.
+Instead, use the [Hugging Face Embeddings Inference component](#hugging-face-embeddings-inference).
+:::
+
+This component loads embedding models from HuggingFace.
+
+Use this component to generate embeddings using locally downloaded Hugging Face models. Ensure you have sufficient computational resources to run the models.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| Cache Folder | Cache Folder | Folder path to cache HuggingFace models |
+| Encode Kwargs | Encoding Arguments | Additional arguments for the encoding process |
+| Model Kwargs | Model Arguments | Additional arguments for the model |
+| Model Name | Model Name | Name of the HuggingFace model to use |
+| Multi Process | Multi-Process | Whether to use multiple processes |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embeddings | The generated embeddings |
+
+## Hugging Face embeddings inference
+
+This component generates embeddings using [Hugging Face Inference API models](https://huggingface.co/) and requires a [Hugging Face API token](https://huggingface.co/docs/hub/security-tokens) to authenticate. Local inference models do not require an API key.
+
+Use this component to create embeddings with Hugging Face's hosted models, or to connect to your own locally hosted models.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| API Key | API Key | The API key for accessing the Hugging Face Inference API. |
+| API URL | API URL | The URL of the Hugging Face Inference API. |
+| Model Name | Model Name | The name of the model to use for embeddings. |
+| Cache Folder | Cache Folder | The folder path to cache Hugging Face models. |
+| Encode Kwargs | Encoding Arguments | Additional arguments for the encoding process. |
+| Model Kwargs | Model Arguments | Additional arguments for the model. |
+| Multi Process | Multi-Process | Whether to use multiple processes. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embeddings | The generated embeddings. |
+
+### Connect the Hugging Face component to a local embeddings model
+
+To run an embeddings inference locally, see the [HuggingFace documentation](https://huggingface.co/docs/text-embeddings-inference/local_cpu).
+
+To connect the local Hugging Face model to the **Hugging Face embeddings inference** component and use it in a flow, follow these steps:
+
+1. Create a [Vector store RAG flow](/starter-projects-vector-store-rag).
+There are two embeddings models in this flow that you can replace with **Hugging Face** embeddings inference components.
+2. Replace both **OpenAI** embeddings model components with **Hugging Face** model components.
+3. Connect both **Hugging Face** components to the **Embeddings** ports of the **Astra DB vector store** components.
+4. In the **Hugging Face** components, set the **Inference Endpoint** field to the URL of your local inference model. **The **API Key** field is not required for local inference.**
+5. Run the flow. The local inference models generate embeddings for the input text.
+
+## LM Studio Embeddings
+
+This component generates embeddings using [LM Studio](https://lmstudio.ai/docs) models.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| model | Model | The LM Studio model to use for generating embeddings |
+| base_url | LM Studio Base URL | The base URL for the LM Studio API |
+| api_key | LM Studio API Key | API key for authentication with LM Studio |
+| temperature | Model Temperature | Temperature setting for the model |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embeddings | The generated embeddings |
+
+
+## MistralAI
+
+This component generates embeddings using [MistralAI](https://docs.mistral.ai/) models.
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| model | String | The MistralAI model to use (default: "mistral-embed") |
+| mistral_api_key | SecretString | API key for authenticating with MistralAI |
+| max_concurrent_requests | Integer | Maximum number of concurrent API requests (default: 64) |
+| max_retries | Integer | Maximum number of retry attempts for failed requests (default: 5) |
+| timeout | Integer | Request timeout in seconds (default: 120) |
+| endpoint | String | Custom API endpoint URL (default: `https://api.mistral.ai/v1/`) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | MistralAIEmbeddings instance for generating embeddings |
+
+## NVIDIA
+
+This component generates embeddings using [NVIDIA models](https://docs.nvidia.com).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| model | String | The NVIDIA model to use for embeddings (e.g., `nvidia/nv-embed-v1`) |
+| base_url | String | Base URL for the NVIDIA API (default: `https://integrate.api.nvidia.com/v1`) |
+| nvidia_api_key | SecretString | API key for authenticating with NVIDIA's service |
+| temperature | Float | Model temperature for embedding generation (default: `0.1`) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | NVIDIAEmbeddings instance for generating embeddings |
+
+## Ollama Embeddings
+
+This component generates embeddings using [Ollama models](https://ollama.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| Ollama Model | String | Name of the Ollama model to use (default: `llama2`) |
+| Ollama Base URL | String | Base URL of the Ollama API (default: `http://localhost:11434`) |
+| Model Temperature | Float | Temperature parameter for the model. Adjusts the randomness in the generated embeddings |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using Ollama |
+
+## OpenAI Embeddings
+
+This component is used to load embedding models from [OpenAI](https://openai.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| OpenAI API Key | String | The API key to use for accessing the OpenAI API |
+| Default Headers | Dict | Default headers for the HTTP requests |
+| Default Query | NestedDict | Default query parameters for the HTTP requests |
+| Allowed Special | List | Special tokens allowed for processing (default: `[]`) |
+| Disallowed Special | List | Special tokens disallowed for processing (default: `["all"]`) |
+| Chunk Size | Integer | Chunk size for processing (default: `1000`) |
+| Client | Any | HTTP client for making requests |
+| Deployment | String | Deployment name for the model (default: `text-embedding-3-small`) |
+| Embedding Context Length | Integer | Length of embedding context (default: `8191`) |
+| Max Retries | Integer | Maximum number of retries for failed requests (default: `6`) |
+| Model | String | Name of the model to use (default: `text-embedding-3-small`) |
+| Model Kwargs | NestedDict | Additional keyword arguments for the model |
+| OpenAI API Base | String | Base URL of the OpenAI API |
+| OpenAI API Type | String | Type of the OpenAI API |
+| OpenAI API Version | String | Version of the OpenAI API |
+| OpenAI Organization | String | Organization associated with the API key |
+| OpenAI Proxy | String | Proxy server for the requests |
+| Request Timeout | Float | Timeout for the HTTP requests |
+| Show Progress Bar | Boolean | Whether to show a progress bar for processing (default: `False`) |
+| Skip Empty | Boolean | Whether to skip empty inputs (default: `False`) |
+| TikToken Enable | Boolean | Whether to enable TikToken (default: `True`) |
+| TikToken Model Name | String | Name of the TikToken model |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using OpenAI |
+
+## Text embedder
+
+This component generates embeddings for a given message using a specified embedding model.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embedding_model | Embedding Model | The embedding model to use for generating embeddings. |
+| message | Message | The message for which to generate embeddings. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| embeddings | Embedding Data | Data object containing the original text and its embedding vector. |
+
+## VertexAI Embeddings
+
+This component is a wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) [Embeddings API](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| credentials | Credentials | The default custom credentials to use |
+| location | String | The default location to use when making API calls (default: `us-central1`) |
+| max_output_tokens | Integer | Token limit determines the maximum amount of text output from one prompt (default: `128`) |
+| model_name | String | The name of the Vertex AI large language model (default: `text-bison`) |
+| project | String | The default GCP project to use when making Vertex API calls |
+| request_parallelism | Integer | The amount of parallelism allowed for requests issued to VertexAI models (default: `5`) |
+| temperature | Float | Tunes the degree of randomness in text generations. Should be a non-negative value (default: `0`) |
+| top_k | Integer | How the model selects tokens for output, the next token is selected from the top `k` tokens (default: `40`) |
+| top_p | Float | Tokens are selected from the most probable to least until the sum of their probabilities exceeds the top `p` value (default: `0.95`) |
+| tuned_model_name | String | The name of a tuned model. If provided, `model_name` is ignored |
+| verbose | Boolean | This parameter controls the level of detail in the output. When set to `True`, it prints internal states of the chain to help debug (default: `False`) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| embeddings | Embeddings | An instance for generating embeddings using VertexAI |
+
diff --git a/langflow/docs/docs/Components/components-helpers.md b/langflow/docs/docs/Components/components-helpers.md
new file mode 100644
index 0000000..7a5a1b8
--- /dev/null
+++ b/langflow/docs/docs/Components/components-helpers.md
@@ -0,0 +1,177 @@
+---
+title: Helpers
+slug: /components-helpers
+---
+
+# Helper components in Langflow
+
+Helper components provide utility functions to help manage data, tasks, and other components in your flow.
+
+## Use a helper component in a flow
+
+Chat memory in Langflow is stored either in local Langflow tables with `LCBufferMemory`, or connected to an external database.
+
+The **Store Message** helper component stores chat memories as [Data](/concepts-objects) objects, and the **Message History** helper component retrieves chat messages as data objects or strings.
+
+This example flow stores and retrieves chat history from an [AstraDBChatMemory](/components-memories#astradbchatmemory-component) component with **Store Message** and **Chat Memory** components.
+
+
+
+## Batch Run Component
+
+The Batch Run component runs a language model over each row of a [DataFrame](/concepts-objects#dataframe-object) text column and returns a new DataFrame with the original text and the model's response.
+
+### Inputs
+
+| Name | Display Name | Type | Info | Required |
+|------|--------------|------|------|----------|
+| model | Language Model | HandleInput | Connect the 'Language Model' output from your LLM component here. | Yes |
+| system_message | System Message | MultilineInput | Multi-line system instruction for all rows in the DataFrame. | No |
+| df | DataFrame | DataFrameInput | The DataFrame whose column (specified by 'column_name') will be treated as text messages. | Yes |
+| column_name | Column Name | StrInput | The name of the DataFrame column to treat as text messages. Default='text'. | Yes |
+
+### Outputs
+
+| Name | Display Name | Method | Info |
+|------|--------------|--------|------|
+| batch_results | Batch Results | run_batch | A DataFrame with two columns: 'text_input' and 'model_response'. |
+
+## Create List
+
+This component dynamically creates a record with a specified number of fields.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| n_fields | Number of Fields | Number of fields to be added to the record. |
+| text_key | Text Key | Key used as text. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| list | List | The dynamically created list with the specified number of fields. |
+
+## Current date
+
+The Current Date component returns the current date and time in a selected timezone. This component provides a flexible way to obtain timezone-specific date and time information within a Langflow pipeline.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|timezone|Timezone|Select the timezone for the current date and time.
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|current_date|Current Date|The resulting current date and time in the selected timezone.
+
+## ID Generator
+
+This component generates a unique ID.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| unique_id| Value | The generated unique ID. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| id | ID | The generated unique ID. |
+
+## Message history
+
+:::info
+Prior to Langflow 1.1, this component was known as the Chat Memory component.
+:::
+
+This component retrieves and manages chat messages from Langflow tables or an external memory.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| memory | External Memory | Retrieve messages from an external memory. If empty, it will use the Langflow tables. |
+| sender | Sender Type | Filter by sender type. |
+| sender_name | Sender Name | Filter by sender name. |
+| n_messages | Number of Messages | Number of messages to retrieve. |
+| session_id | Session ID | The session ID of the chat. If empty, the current session ID parameter will be used. |
+| order | Order | Order of the messages. |
+| template | Template | The template to use for formatting the data. It can contain the keys `{text}`, `{sender}` or any other key in the message data. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| messages | Messages (Data) | Retrieved messages as Data objects. |
+| messages_text | Messages (Text) | Retrieved messages formatted as text. |
+| lc_memory | Memory | A constructed Langchain [ConversationBufferMemory](https://api.python.langchain.com/en/latest/memory/langchain.memory.buffer.ConversationBufferMemory.html) object |
+
+## Message store
+
+This component stores chat messages or text into Langflow tables or an external memory.
+
+It provides flexibility in managing message storage and retrieval within a chat system.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| message | Message | The chat message to be stored. (Required) |
+| memory | External Memory | The external memory to store the message. If empty, it will use the Langflow tables. |
+| sender | Sender | The sender of the message. Can be Machine or User. If empty, the current sender parameter will be used. |
+| sender_name | Sender Name | The name of the sender. Can be AI or User. If empty, the current sender parameter will be used. |
+| session_id | Session ID | The session ID of the chat. If empty, the current session ID parameter will be used. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| stored_messages | Stored Messages | The list of stored messages after the current message has been added. |
+
+## Structured output
+
+This component transforms LLM responses into structured data formats.
+
+In this example from the **Financial Support Parser** template, the **Structured Output** component transforms unstructured financial reports into structured data.
+
+
+
+The connected LLM model is prompted by the **Structured Output** component's `Format Instructions` parameter to extract structured output from the unstructured text. `Format Instructions` is utilized as the system prompt for the **Structured Output** component.
+
+In the **Structured Output** component, click the **Open table** button to view the `Output Schema` table.
+The `Output Schema` parameter defines the structure and data types for the model's output using a table with the following fields:
+
+* **Name**: The name of the output field.
+* **Description**: The purpose of the output field.
+* **Type**: The data type of the output field. The available types are `str`, `int`, `float`, `bool`, `list`, or `dict`. The default is `text`.
+* **Multiple**: This feature is deprecated. Currently, it is set to `True` by default if you expect multiple values for a single field. For example, a `list` of `features` is set to `True` to contain multiple values, such as `["waterproof", "durable", "lightweight"]`. Default: `True`.
+
+The **Parse DataFrame** component parses the structured output into a template for orderly presentation in chat output. The template receives the values from the `output_schema` table with curly braces.
+
+For example, the template `EBITDA: {EBITDA} , Net Income: {NET_INCOME} , GROSS_PROFIT: {GROSS_PROFIT}` presents the extracted values in the **Playground** as `EBITDA: 900 million , Net Income: 500 million , GROSS_PROFIT: 1.2 billion`.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| llm | Language Model | The language model to use to generate the structured output. |
+| input_value | Input Message | The input message to the language model. |
+| system_prompt | Format Instructions | Instructions to the language model for formatting the output. |
+| schema_name | Schema Name | The name for the output data schema. |
+| output_schema | Output Schema | Defines the structure and data types for the model's output.|
+| multiple | Generate Multiple | [Deprecated] Always set to `True`. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| structured_output | Structured Output | The structured output is a Data object based on the defined schema. |
+| structured_output_dataframe | DataFrame | The structured output converted to a [DataFrame](/concepts-objects#dataframe-object) format. |
+
diff --git a/langflow/docs/docs/Components/components-io.md b/langflow/docs/docs/Components/components-io.md
new file mode 100644
index 0000000..e937024
--- /dev/null
+++ b/langflow/docs/docs/Components/components-io.md
@@ -0,0 +1,133 @@
+---
+title: Inputs and outputs
+slug: /components-io
+---
+
+# Input and output components in Langflow
+
+Input and output components define where data enters and exits your flow.
+
+Both components accept user input and return a `Message` object, but serve different purposes.
+
+The **Text Input** component accepts a text string input and returns a `Message` object containing only the input text. The output does not appear in the **Playground**.
+
+The **Chat Input** component accepts multiple input types including text, files, and metadata, and returns a `Message` object containing the text along with sender information, session ID, and file attachments.
+
+The **Chat Input** component provides an interactive chat interface in the **Playground**.
+
+## Chat Input
+
+This component collects user input as `Text` strings from the chat and wraps it in a [Message](/concepts-objects) object that includes the input text, sender information, session ID, file attachments, and styling properties.
+
+It can optionally store the message in a chat history.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|input_value|Text|The Message to be passed as input.
+|should_store_message|Store Messages|Store the message in the history.|
+|sender|Sender Type|The type of sender.|
+|sender_name|Sender Name|The name of the sender.|
+|session_id|Session ID|The session ID of the chat. If empty, the current session ID parameter is used.|
+|files|Files|The files to be sent with the message.|
+|background_color|Background Color|The background color of the icon.|
+|chat_icon|Icon|The icon of the message.|
+|text_color|Text Color|The text color of the name.|
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|message|Message|The resulting chat message object with all specified properties.|
+
+### Message method
+
+The `ChatInput` class provides an asynchronous method to create and store a `Message` object based on the input parameters.
+The `Message` object is created in the `message_response` method of the ChatInput class using the `Message.create()` factory method.
+
+```python
+message = await Message.create(
+ text=self.input_value,
+ sender=self.sender,
+ sender_name=self.sender_name,
+ session_id=self.session_id,
+ files=self.files,
+ properties={
+ "background_color": background_color,
+ "text_color": text_color,
+ "icon": icon,
+ },
+)
+```
+
+## Text Input
+
+The **Text Input** component accepts a text string input and returns a `Message` object containing only the input text.
+
+The output does not appear in the **Playground**.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|input_value|Text|The text/content to be passed as output.|
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|text|Text|The resulting text message.|
+
+
+## Chat Output
+
+The **Chat Output** component creates a [Message](/concepts-objects#message-object) object that includes the input text, sender information, session ID, and styling properties.
+
+The component accepts the following input types.
+* [Data](/concepts-objects#data-object)
+* [DataFrame](/concepts-objects#dataframe-object)
+* [Message](/concepts-objects#message-object)
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|input_value|Text|The message to be passed as output.|
+|should_store_message|Store Messages|The flag to store the message in the history.|
+|sender|Sender Type|The type of sender.|
+|sender_name|Sender Name|The name of the sender.|
+|session_id|Session ID|The session ID of the chat. If empty, the current session ID parameter is used.|
+|data_template|Data Template|The template to convert Data to Text. If the option is left empty, it is dynamically set to the Data's text key.|
+|background_color|Background Color|The background color of the icon.|
+|chat_icon|Icon|The icon of the message.|
+|text_color|Text Color|The text color of the name.|
+|clean_data|Basic Clean Data|When enabled, `DataFrame` inputs are cleaned when converted to text. Cleaning removes empty rows, empty lines in cells, and multiple newlines.|
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|message|Message|The resulting chat message object with all specified properties.|
+
+
+## Text Output
+
+The **Text Output** takes a single input of text and returns a [Message](/concepts-objects) object containing that text.
+
+The output does not appear in the **Playground**.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|input_value|Text|The text to be passed as output.|
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|text|Text|The resulting text message.|
+
+
+
diff --git a/langflow/docs/docs/Components/components-loaders.md b/langflow/docs/docs/Components/components-loaders.md
new file mode 100644
index 0000000..67b4814
--- /dev/null
+++ b/langflow/docs/docs/Components/components-loaders.md
@@ -0,0 +1,82 @@
+---
+title: Loaders
+slug: /components-loaders
+---
+
+# Loader components in Langflow
+
+:::info
+As of Langflow 1.1, loader components are now found in the **Components** menu under **Bundles**.
+:::
+
+Loaders fetch data into Langflow from various sources, such as databases, websites, and local files.
+
+## Use a loader component in a flow
+
+This flow creates a question-and-answer chatbot for documents that are loaded into the flow.
+The [Unstructured.io](https://unstructured.io/) loader component loads files from your local machine, and then parses them into a list of structured [Data](/concepts-objects) objects.
+This loaded data informs the **Open AI** component's responses to your questions.
+
+
+
+## Confluence
+
+The Confluence component integrates with the Confluence wiki collaboration platform to load and process documents. It utilizes the ConfluenceLoader from LangChain to fetch content from a specified Confluence space.
+
+### Inputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| url | Site URL | The base URL of the Confluence Space (e.g., `https://company.atlassian.net/wiki`) |
+| username | Username | Atlassian User E-mail (e.g., `email@example.com`) |
+| api_key | API Key | Atlassian API Key (Create an API key at: [Atlassian](https://id.atlassian.com/manage-profile/security/api-tokens)) |
+| space_key | Space Key | The key of the Confluence space to access |
+| cloud | Use Cloud? | Whether to use Confluence Cloud (default: true) |
+| content_format | Content Format | Specify content format (default: STORAGE) |
+| max_pages | Max Pages | Maximum number of pages to retrieve (default: 1000) |
+
+### Outputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| data | Data | List of Data objects containing the loaded Confluence documents |
+
+## GitLoader
+
+The GitLoader component uses the GitLoader from LangChain to fetch and load documents from a specified Git repository.
+
+### Inputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| repo_path | Repository Path | The local path to the Git repository |
+| clone_url | Clone URL | The URL to clone the Git repository from (optional) |
+| branch | Branch | The branch to load files from (default: 'main') |
+| file_filter | File Filter | Patterns to filter files (e.g., '.py' to include only .py files, '!.py' to exclude .py files) |
+| content_filter | Content Filter | A regex pattern to filter files based on their content |
+
+### Outputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| data | Data | List of Data objects containing the loaded Git repository documents |
+
+## Unstructured
+
+This component uses the [Unstructured.io](https://unstructured.io/) Serverless API to load and parse files into a list of structured [Data](/concepts-objects) objects.
+
+### Inputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| file | File | The path to the file to be parsed (supported types are listed [here](https://docs.unstructured.io/api-reference/api-services/overview#supported-file-types)) |
+| api_key | API Key | Unstructured.io Serverless API Key |
+| api_url | Unstructured.io API URL | Optional URL for the Unstructured API |
+| chunking_strategy | Chunking Strategy | Strategy for chunking the document (options: "", "basic", "by_title", "by_page", "by_similarity") |
+| unstructured_args | Additional Arguments | Optional dictionary of additional arguments for the Unstructured.io API |
+
+### Outputs
+
+| Name | Display Name | Info |
+| --- | --- | --- |
+| data | Data | List of Data objects containing the parsed content from the input file |
diff --git a/langflow/docs/docs/Components/components-logic.md b/langflow/docs/docs/Components/components-logic.md
new file mode 100644
index 0000000..04ac64d
--- /dev/null
+++ b/langflow/docs/docs/Components/components-logic.md
@@ -0,0 +1,236 @@
+---
+title: Logic
+slug: /components-logic
+---
+
+# Logic components in Langflow
+
+Logic components provide functionalities for routing, conditional processing, and flow management.
+
+## Use a logic component in a flow
+
+This flow creates a summarizing "for each" loop with the [Loop](/components-logic#loop) component.
+
+The component iterates over a list of [Data](/concepts-objects#data-object) objects until it's completed, and then the **Done** loop aggregates the results.
+
+The **File** component loads text files from your local machine, and then the **Parse Data** component parses them into a list of structured `Data` objects.
+The **Loop** component passes each `Data` object to a **Prompt** to be summarized.
+
+When the **Loop** component runs out of `Data`, the **Done** loop activates, which counts the number of pages and summarizes their tone with another **Prompt**.
+This is represented in Langflow by connecting the Parse Data component's **Data List** output to the Loop component's `Data` loop input.
+
+
+
+The output will look similar to this:
+```plain
+Document Summary
+Total Pages Processed
+Total Pages: 2
+Overall Tone of Document
+Tone: Informative and Instructional
+The documentation outlines microservices architecture patterns and best practices.
+It emphasizes service isolation and inter-service communication protocols.
+The use of asynchronous messaging patterns is recommended for system scalability.
+It includes code examples of REST and gRPC implementations to demonstrate integration approaches.
+```
+
+## Conditional router (If-Else component)
+
+This component routes an input message to a corresponding output based on text comparison.
+
+The ConditionalRouterComponent routes messages based on text comparison. It evaluates a condition by comparing two text inputs using a specified operator and routes the message accordingly.
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|----------|-------------------------------------------------------------------|
+| input_text | String | The primary text input for the operation. |
+| match_text | String | The text input to compare against. |
+| operator | Dropdown | The operator to apply for comparing the texts. |
+| case_sensitive | Boolean | If true, the comparison will be case sensitive. |
+| message | Message | The message to pass through either route. |
+| max_iterations | Integer | The maximum number of iterations for the conditional router. |
+| default_route | Dropdown | The default route to take when max iterations are reached. |
+
+### Outputs
+
+| Name | Type | Description |
+|--------------|---------|--------------------------------------------|
+| true_result | Message | The output when the condition is true. |
+| false_result | Message | The output when the condition is false. |
+
+## Data conditional router
+
+This component routes `Data` objects based on a condition applied to a specified key, including boolean validation.
+
+This component is particularly useful in workflows that require conditional routing of complex data structures, enabling dynamic decision-making based on data content.
+
+### Inputs
+
+| Name | Type | Description |
+|---------------|----------|-----------------------------------------------------------------------------------|
+| data_input | Data | The data object or list of data objects to process. |
+| key_name | String | The name of the key in the data object to check. |
+| operator | Dropdown | The operator to apply for comparing the values. |
+| compare_value | String | The value to compare against (not used for boolean validator). |
+
+### Outputs
+
+| Name | Type | Description |
+|--------------|-------------|------------------------------------------------------|
+| true_output | Data/List | Output when the condition is met. |
+| false_output | Data/List | Output when the condition is not met. |
+
+
+## Flow as tool {#flow-as-tool}
+
+:::important
+This component is deprecated as of Langflow version 1.1.2.
+Instead, use the [Run flow component](/components-logic#run-flow)
+:::
+
+This component constructs a tool from a function that runs a loaded flow.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------|----------|------------------------------------------------------------|
+| flow_name | Dropdown | The name of the flow to run. |
+| tool_name | String | The name of the tool. |
+| tool_description | String | The description of the tool. |
+| return_direct | Boolean | If true, returns the result directly from the tool. |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------|----------------------------------------|
+| api_build_tool | Tool | The constructed tool from the flow. |
+
+## Listen
+
+This component listens for a notification and retrieves its associated state.
+
+### Inputs
+
+| Name | Type | Description |
+|------|--------|------------------------------------------------|
+| name | String | The name of the notification to listen for. |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|------|--------------------------------------------|
+| output | Data | The state associated with the notification. |
+
+
+## Loop
+
+This component iterates over a list of [Data](/concepts-objects#data-object) objects, outputting one item at a time and aggregating results from loop inputs.
+
+### Inputs
+
+| Name | Type | Description |
+|------|-----------|------------------------------------------------------|
+| data | Data/List | The initial list of Data objects to iterate over. |
+
+### Outputs
+
+| Name | Type | Description |
+|------|---------|-------------------------------------------------------|
+| item | Data | Outputs one item at a time from the data list. |
+| done | Data | Triggered when iteration complete, returns aggregated results. |
+
+## Notify
+
+This component generates a notification for the Listen component to use.
+
+### Inputs
+
+| Name | Type | Description |
+|--------|---------|-------------------------------------------------------------------|
+| name | String | The name of the notification. |
+| data | Data | The data to store in the notification. |
+| append | Boolean | If true, the record will be appended to the existing notification.|
+
+### Outputs
+
+| Name | Type | Description |
+|--------|------|-----------------------------------------|
+| output | Data | The data stored in the notification. |
+
+## Pass message
+
+This component forwards the input message, unchanged.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| input_message | Input Message | The message to be passed forward. |
+| ignored_message | Ignored Message | A second message to be ignored. Used as a workaround for continuity. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output_message | Output Message | The forwarded input message. |
+
+## Run flow
+
+This component allows you to run any flow stored in your Langflow database without opening the flow editor.
+
+The Run Flow component can also be used as a tool when connected to an [Agent](/components-agents). The `name` and `description` metadata that the Agent uses to register the tool are created automatically.
+
+When you select a flow, the component fetches the flow's graph structure and uses it to generate the inputs and outputs for the Run Flow component.
+
+To use the Run Flow component as a tool, do the following:
+1. Add the **Run Flow** component to the [Simple Agent](/starter-projects-simple-agent) flow.
+2. In the **Flow Name** menu, select the sub-flow you want to run.
+The appearance of the **Run Flow** component changes to reflect the inputs and outputs of the selected flow.
+3. On the **Run Flow** component, enable **Tool Mode**.
+4. Connect the **Run Flow** component to the **Toolset** input of the Agent.
+Your flow should now look like this:
+
+5. Run the flow. The Agent uses the Run Flow component as a tool to run the selected sub-flow.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------|----------|----------------------------------------------------------------|
+| flow_name_selected| Dropdown | The name of the flow to run. |
+| flow_tweak_data | Dict | Dictionary of tweaks to customize the flow's behavior. |
+| dynamic inputs | Various | Additional inputs that are generated based on the selected flow. |
+
+### Outputs
+
+| Name | Type | Description |
+|--------------|-------------|---------------------------------------------------------------|
+| run_outputs | A `List` of types `Data`, `Message,` or `DataFrame` | All outputs are generated from running the flow. |
+
+## Sub flow
+
+:::important
+This component is deprecated as of Langflow version 1.1.2.
+Instead, use the [Run flow component](/components-logic#run-flow)
+:::
+
+This `SubFlowComponent` generates a component from a flow with all of its inputs and outputs.
+
+This component can integrate entire flows as components within a larger workflow. It dynamically generates inputs based on the selected flow and executes the flow with provided parameters.
+
+### Inputs
+
+| Name | Type | Description |
+|-----------|----------|------------------------------------|
+| flow_name | Dropdown | The name of the flow to run. |
+
+### Outputs
+
+| Name | Type | Description |
+|--------------|-------------|---------------------------------------|
+| flow_outputs | List[Data] | The outputs generated from the flow. |
+
+
+
+
+
diff --git a/langflow/docs/docs/Components/components-memories.md b/langflow/docs/docs/Components/components-memories.md
new file mode 100644
index 0000000..a370273
--- /dev/null
+++ b/langflow/docs/docs/Components/components-memories.md
@@ -0,0 +1,128 @@
+---
+title: Memories
+slug: /components-memories
+---
+
+# Memory components in Langflow
+
+Memory components store and retrieve chat messages by `session_id`.
+
+They are distinct from vector store components, because they are built specifically for storing and retrieving chat messages from external databases.
+
+Memory components provide access to their respective external databases **as memory**. This allows Large Language Models (LLMs) or [agents](/components-agents) to access external memory for persistence and context retention.
+
+## Use a memory component in a flow
+
+This example flow stores and retrieves chat history from an **Astra DB Chat Memory** component with **Store Message** and **Chat Memory** components.
+
+The **Store Message** helper component stores chat memories as [Data](/concepts-objects) objects, and the **Message History** helper component retrieves chat messages as [Data](/concepts-objects) objects or strings.
+
+
+
+## AstraDBChatMemory Component
+
+This component creates an `AstraDBChatMessageHistory` instance, which stores and retrieves chat messages using Astra DB, a cloud-native database service.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------|---------------|-----------------------------------------------------------------------|
+| collection_name | String | Name of the Astra DB collection for storing messages. Required. |
+| token | SecretString | Authentication token for Astra DB access. Required. |
+| api_endpoint | SecretString | API endpoint URL for the Astra DB service. Required. |
+| namespace | String | Optional namespace within Astra DB for the collection. |
+| session_id | MessageText | Chat session ID. Uses current session ID if not provided. |
+
+### Outputs
+
+| Name | Type | Description |
+|-----------------|-------------------------|-----------------------------------------------------------|
+| message_history | BaseChatMessageHistory | An instance of AstraDBChatMessageHistory for the session. |
+
+## CassandraChatMemory Component
+
+This component creates a `CassandraChatMessageHistory` instance, enabling storage and retrieval of chat messages using Apache Cassandra or DataStax Astra DB.
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-------------------------------------------------------------------------------|
+| database_ref | MessageText | Contact points for Cassandra or Astra DB database ID. Required. |
+| username | MessageText | Username for Cassandra (leave empty for Astra DB). |
+| token | SecretString | Password for Cassandra or token for Astra DB. Required. |
+| keyspace | MessageText | Keyspace in Cassandra or namespace in Astra DB. Required. |
+| table_name | MessageText | Name of the table or collection for storing messages. Required. |
+| session_id | MessageText | Unique identifier for the chat session. Optional. |
+| cluster_kwargs | Dictionary | Additional keyword arguments for Cassandra cluster configuration. Optional. |
+
+### Outputs
+
+| Name | Type | Description |
+|-----------------|-------------------------|--------------------------------------------------------------|
+| message_history | BaseChatMessageHistory | An instance of CassandraChatMessageHistory for the session. |
+
+## Mem0 Chat Memory
+
+The Mem0 Chat Memory component retrieves and stores chat messages using Mem0 memory storage.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| mem0_config | Mem0 Configuration | Configuration dictionary for initializing Mem0 memory instance. |
+| ingest_message | Message to Ingest | The message content to be ingested into Mem0 memory. |
+| existing_memory | Existing Memory Instance | Optional existing Mem0 memory instance. |
+| user_id | User ID | Identifier for the user associated with the messages. |
+| search_query | Search Query | Input text for searching related memories in Mem0. |
+| mem0_api_key | Mem0 API Key | API key for Mem0 platform (leave empty to use the local version). |
+| metadata | Metadata | Additional metadata to associate with the ingested message. |
+| openai_api_key | OpenAI API Key | API key for OpenAI. This item is required if you use OpenAI embeddings without a provided configuration. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| memory | Mem0 Memory | The resulting Mem0 Memory object after ingesting data. |
+| search_results | Search Results | The search results from querying Mem0 memory. |
+
+
+## Redis Chat Memory
+
+This component retrieves and stores chat messages from Redis.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| host | hostname | IP address or hostname. |
+| port | port | Redis Port Number. |
+| database | database | Redis database. |
+| username | Username | The Redis user name. |
+| password | Password | The password for username. |
+| key_prefix | Key prefix | Key prefix. |
+| session_id | Session ID | Session ID for the message. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| memory | Memory | The Redis chat message history object |
+
+## ZepChatMemory Component
+
+This component creates a `ZepChatMessageHistory` instance, enabling storage and retrieval of chat messages using Zep, a memory server for Large Language Models (LLMs).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------|---------------|-----------------------------------------------------------|
+| url | MessageText | URL of the Zep instance. Required. |
+| api_key | SecretString | API Key for authentication with the Zep instance. |
+| api_base_path | Dropdown | API version to use. Options: "api/v1" or "api/v2". |
+| session_id | MessageText | Unique identifier for the chat session. Optional. |
+
+### Outputs
+
+| Name | Type | Description |
+|-----------------|-------------------------|-------------------------------------------------------|
+| message_history | BaseChatMessageHistory | An instance of ZepChatMessageHistory for the session. |
\ No newline at end of file
diff --git a/langflow/docs/docs/Components/components-models.md b/langflow/docs/docs/Components/components-models.md
new file mode 100644
index 0000000..f2f30e7
--- /dev/null
+++ b/langflow/docs/docs/Components/components-models.md
@@ -0,0 +1,546 @@
+---
+title: Models
+slug: /components-models
+---
+
+# Model components in Langflow
+
+Model components generate text using large language models.
+
+Refer to your specific component's documentation for more information on parameters.
+
+## Use a model component in a flow
+
+Model components receive inputs and prompts for generating text, and the generated text is sent to an output component.
+
+The model output can also be sent to the **Language Model** port and on to a **Parse Data** component, where the output can be parsed into structured [Data](/concepts-objects) objects.
+
+This example has the OpenAI model in a chatbot flow. For more information, see the [Basic prompting flow](/starter-projects-basic-prompting).
+
+
+
+## AI/ML API
+
+This component creates a ChatOpenAI model instance using the AIML API.
+
+For more information, see [AIML documentation](https://docs.aimlapi.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|--------------|-------------|---------------------------------------------------------------------------------------------|
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens. Range: 0-128000. |
+| model_kwargs | Dictionary | Additional keyword arguments for the model. |
+| model_name | String | The name of the AIML model to use. Options are predefined in `AIML_CHAT_MODELS`. |
+| aiml_api_base| String | The base URL of the AIML API. Defaults to `https://api.aimlapi.com`. |
+| api_key | SecretString| The AIML API Key to use for the model. |
+| temperature | Float | Controls randomness in the output. Default: `0.1`. |
+| seed | Integer | Controls reproducibility of the job. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatOpenAI configured with the specified parameters. |
+
+## Amazon Bedrock
+
+This component generates text using Amazon Bedrock LLMs.
+
+For more information, see [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock).
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|-------------------------------------------------------------------------------------|
+| model_id | String | The ID of the Amazon Bedrock model to use. Options include various models. |
+| aws_access_key | SecretString | AWS Access Key for authentication. |
+| aws_secret_key | SecretString | AWS Secret Key for authentication. |
+| credentials_profile_name | String | Name of the AWS credentials profile to use (advanced). |
+| region_name | String | AWS region name. Default: `us-east-1`. |
+| model_kwargs | Dictionary | Additional keyword arguments for the model (advanced). |
+| endpoint_url | String | Custom endpoint URL for the Bedrock service (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|-------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatBedrock configured with the specified parameters. |
+
+## Anthropic
+
+This component allows the generation of text using Anthropic Chat and Language models.
+
+For more information, see the [Anthropic documentation](https://docs.anthropic.com/en/docs/welcome).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|-------------|----------------------------------------------------------------------------------------|
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens. Default: `4096`.|
+| model | String | The name of the Anthropic model to use. Options include various Claude 3 models. |
+| anthropic_api_key | SecretString| Your Anthropic API key for authentication. |
+| temperature | Float | Controls randomness in the output. Default: `0.1`. |
+| anthropic_api_url | String | Endpoint of the Anthropic API. Defaults to `https://api.anthropic.com` if not specified (advanced). |
+| prefill | String | Prefill text to guide the model's response (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatAnthropic configured with the specified parameters. |
+
+## Azure OpenAI
+
+This component generates text using Azure OpenAI LLM.
+
+For more information, see the [Azure OpenAI documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|---------------------|---------------------|---------------------------------------------------------------------------------|
+| Model Name | Model Name | Specifies the name of the Azure OpenAI model to be used for text generation. |
+| Azure Endpoint | Azure Endpoint | Your Azure endpoint, including the resource. |
+| Deployment Name | Deployment Name | Specifies the name of the deployment. |
+| API Version | API Version | Specifies the version of the Azure OpenAI API to be used. |
+| API Key | API Key | Your Azure OpenAI API key. |
+| Temperature | Temperature | Specifies the sampling temperature. Defaults to `0.7`. |
+| Max Tokens | Max Tokens | Specifies the maximum number of tokens to generate. Defaults to `1000`. |
+| Input Value | Input Value | Specifies the input text for text generation. |
+| Stream | Stream | Specifies whether to stream the response from the model. Defaults to `False`. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of AzureOpenAI configured with the specified parameters. |
+
+## Cohere
+
+This component generates text using Cohere's language models.
+
+For more information, see the [Cohere documentation](https://cohere.ai/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|---------------------|--------------------|----------------------------------------------------------|
+| Cohere API Key | Cohere API Key | Your Cohere API key. |
+| Max Tokens | Max Tokens | Specifies the maximum number of tokens to generate. Defaults to `256`. |
+| Temperature | Temperature | Specifies the sampling temperature. Defaults to `0.75`. |
+| Input Value | Input Value | Specifies the input text for text generation. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of the Cohere model configured with the specified parameters. |
+
+## DeepSeek
+
+This component generates text using DeepSeek's language models.
+
+For more information, see the [DeepSeek documentation](https://api-docs.deepseek.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| max_tokens | Integer | Maximum number of tokens to generate. Set to `0` for unlimited. Range: `0-128000`. |
+| model_kwargs | Dictionary | Additional keyword arguments for the model. |
+| json_mode | Boolean | If `True`, outputs JSON regardless of passing a schema. |
+| model_name | String | The DeepSeek model to use. Default: `deepseek-chat`. |
+| api_base | String | Base URL for API requests. Default: `https://api.deepseek.com`. |
+| api_key | SecretString | Your DeepSeek API key for authentication. |
+| temperature | Float | Controls randomness in responses. Range: `[0.0, 2.0]`. Default: `1.0`. |
+| seed | Integer | Number initialized for random number generation. Use the same seed integer for more reproducible results, and use a different seed number for more random results. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatOpenAI configured with the specified parameters. |
+
+## Google Generative AI
+
+This component generates text using Google's Generative AI models.
+
+For more information, see the [Google Generative AI documentation](https://cloud.google.com/vertex-ai/docs/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|---------------------|--------------------|-----------------------------------------------------------------------|
+| Google API Key | Google API Key | Your Google API key to use for the Google Generative AI. |
+| Model | Model | The name of the model to use, such as `"gemini-pro"`. |
+| Max Output Tokens | Max Output Tokens | The maximum number of tokens to generate. |
+| Temperature | Temperature | Run inference with this temperature. |
+| Top K | Top K | Consider the set of top K most probable tokens. |
+| Top P | Top P | The maximum cumulative probability of tokens to consider when sampling. |
+| N | N | Number of chat completions to generate for each prompt. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatGoogleGenerativeAI configured with the specified parameters. |
+
+## Groq
+
+This component generates text using Groq's language models.
+
+For more information, see the [Groq documentation](https://groq.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| groq_api_key | SecretString | API key for the Groq API. |
+| groq_api_base | String | Base URL path for API requests. Default: `https://api.groq.com` (advanced). |
+| max_tokens | Integer | The maximum number of tokens to generate (advanced). |
+| temperature | Float | Controls randomness in the output. Range: `[0.0, 1.0]`. Default: `0.1`. |
+| n | Integer | Number of chat completions to generate for each prompt (advanced). |
+| model_name | String | The name of the Groq model to use. Options are dynamically fetched from the Groq API. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatGroq configured with the specified parameters. |
+
+## Hugging Face API
+
+This component sends requests to the Hugging Face API to generate text using the model specified in the **Model ID** field.
+
+The Hugging Face API is a hosted inference API for models hosted on Hugging Face, and requires a [Hugging Face API token](https://huggingface.co/docs/hub/security-tokens) to authenticate.
+
+In this example based on the [Basic prompting flow](/starter-projects-basic-prompting), the **Hugging Face API** model component replaces the **Open AI** model. By selecting different hosted models, you can see how different models return different results.
+
+1. Create a [Basic prompting flow](/starter-projects-basic-prompting).
+
+2. Replace the **OpenAI** model component with a **Hugging Face API** model component.
+
+3. In the **Hugging Face API** component, add your Hugging Face API token to the **API Token** field.
+
+4. Open the **Playground** and ask a question to the model, and see how it responds.
+
+5. Try different models, and see how they perform differently.
+
+For more information, see the [Hugging Face documentation](https://huggingface.co/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| model_id | String | The model ID from Hugging Face Hub. For example, "gpt2", "facebook/bart-large". |
+| huggingfacehub_api_token | SecretString | Your Hugging Face API token for authentication. |
+| temperature | Float | Controls randomness in the output. Range: [0.0, 1.0]. Default: 0.7. |
+| max_new_tokens | Integer | Maximum number of tokens to generate. Default: 512. |
+| top_p | Float | Nucleus sampling parameter. Range: [0.0, 1.0]. Default: 0.95. |
+| top_k | Integer | Top-k sampling parameter. Default: 50. |
+| model_kwargs | Dictionary | Additional keyword arguments to pass to the model. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of HuggingFaceHub configured with the specified parameters. |
+
+## LMStudio
+
+This component generates text using LM Studio's local language models.
+
+For more information, see [LM Studio documentation](https://lmstudio.ai/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| base_url | String | The URL where LM Studio is running. Default: `"http://localhost:1234"`. |
+| max_tokens | Integer | Maximum number of tokens to generate in the response. Default: `512`. |
+| temperature | Float | Controls randomness in the output. Range: `[0.0, 2.0]`. Default: `0.7`. |
+| top_p | Float | Controls diversity via nucleus sampling. Range: `[0.0, 1.0]`. Default: `1.0`. |
+| stop | List[String] | List of strings that will stop generation when encountered (advanced). |
+| stream | Boolean | Whether to stream the response. Default: `False`. |
+| presence_penalty | Float | Penalizes repeated tokens. Range: `[-2.0, 2.0]`. Default: `0.0`. |
+| frequency_penalty | Float | Penalizes frequent tokens. Range: `[-2.0, 2.0]`. Default: `0.0`. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of LMStudio configured with the specified parameters. |
+
+## Maritalk
+
+This component generates text using Maritalk LLMs.
+
+For more information, see [Maritalk documentation](https://www.maritalk.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| max_tokens | Integer | The maximum number of tokens to generate. Set to `0` for unlimited tokens. Default: `512`. |
+| model_name | String | The name of the Maritalk model to use. Options: `sabia-2-small`, `sabia-2-medium`. Default: `sabia-2-small`. |
+| api_key | SecretString | The Maritalk API Key to use for authentication. |
+| temperature | Float | Controls randomness in the output. Range: `[0.0, 1.0]`. Default: `0.5`. |
+| endpoint_url | String | The Maritalk API endpoint. Default: `https://api.maritalk.com`. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatMaritalk configured with the specified parameters. |
+
+## Mistral
+
+This component generates text using MistralAI LLMs.
+
+For more information, see [Mistral AI documentation](https://docs.mistral.ai/).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|--------------|-----------------------------------------------------------------------------------------------|
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens (advanced). |
+| model_name | String | The name of the Mistral AI model to use. Options include `open-mixtral-8x7b`, `open-mixtral-8x22b`, `mistral-small-latest`, `mistral-medium-latest`, `mistral-large-latest`, and `codestral-latest`. Default: `codestral-latest`. |
+| mistral_api_base | String | The base URL of the Mistral API. Defaults to `https://api.mistral.ai/v1` (advanced). |
+| api_key | SecretString | The Mistral API Key to use for authentication. |
+| temperature | Float | Controls randomness in the output. Default: 0.5. |
+| max_retries | Integer | Maximum number of retries for API calls. Default: 5 (advanced). |
+| timeout | Integer | Timeout for API calls in seconds. Default: 60 (advanced). |
+| max_concurrent_requests | Integer | Maximum number of concurrent API requests. Default: 3 (advanced). |
+| top_p | Float | Nucleus sampling parameter. Default: 1 (advanced). |
+| random_seed | Integer | Seed for random number generation. Default: 1 (advanced). |
+| safe_mode | Boolean | Enables safe mode for content generation (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|---------------|-----------------------------------------------------|
+| model | LanguageModel | An instance of ChatMistralAI configured with the specified parameters. |
+
+## Novita AI
+
+This component generates text using Novita AI's language models.
+
+For more information, see [Novita AI documentation](https://novita.ai/docs/model-api/reference/llm/llm.html?utm_source=github_langflow&utm_medium=github_readme&utm_campaign=link).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|---------------|------------------------------------------------------------------|
+| api_key | SecretString | Your Novita AI API Key. |
+| model | String | The id of the Novita AI model to use. |
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens. |
+| temperature | Float | Controls randomness in the output. Range: [0.0, 1.0]. Default: 0.7. |
+| top_p | Float | Controls the nucleus sampling. Range: [0.0, 1.0]. Default: 1.0. |
+| frequency_penalty | Float | Controls the frequency penalty. Range: [0.0, 2.0]. Default: 0.0. |
+| presence_penalty | Float | Controls the presence penalty. Range: [0.0, 2.0]. Default: 0.0. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of Novita AI model configured with the specified parameters. |
+
+## NVIDIA
+
+This component generates text using NVIDIA LLMs.
+
+For more information, see [NVIDIA AI documentation](https://developer.nvidia.com/generative-ai).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|--------------|-----------------------------------------------------------------------------------------------|
+| max_tokens | Integer | The maximum number of tokens to generate. Set to `0` for unlimited tokens (advanced). |
+| model_name | String | The name of the NVIDIA model to use. Default: `mistralai/mixtral-8x7b-instruct-v0.1`. |
+| base_url | String | The base URL of the NVIDIA API. Default: `https://integrate.api.nvidia.com/v1`. |
+| nvidia_api_key | SecretString | The NVIDIA API Key for authentication. |
+| temperature | Float | Controls randomness in the output. Default: `0.1`. |
+| seed | Integer | The seed controls the reproducibility of the job (advanced). Default: `1`. |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|---------------|-----------------------------------------------------|
+| model | LanguageModel | An instance of ChatNVIDIA configured with the specified parameters. |
+
+## Ollama
+
+This component generates text using Ollama's language models.
+
+For more information, see [Ollama documentation](https://ollama.com/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|---------------------|---------------|---------------------------------------------|
+| Base URL | Base URL | Endpoint of the Ollama API. |
+| Model Name | Model Name | The model name to use. |
+| Temperature | Temperature | Controls the creativity of model responses. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of an Ollama model configured with the specified parameters. |
+
+## OpenAI
+
+This component generates text using OpenAI's language models.
+
+For more information, see [OpenAI documentation](https://beta.openai.com/docs/).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|---------------|------------------------------------------------------------------|
+| api_key | SecretString | Your OpenAI API Key. |
+| model | String | The name of the OpenAI model to use. Options include "gpt-3.5-turbo" and "gpt-4". |
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens. |
+| temperature | Float | Controls randomness in the output. Range: [0.0, 1.0]. Default: 0.7. |
+| top_p | Float | Controls the nucleus sampling. Range: [0.0, 1.0]. Default: 1.0. |
+| frequency_penalty | Float | Controls the frequency penalty. Range: [0.0, 2.0]. Default: 0.0. |
+| presence_penalty | Float | Controls the presence penalty. Range: [0.0, 2.0]. Default: 0.0. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of OpenAI model configured with the specified parameters. |
+
+
+## OpenRouter
+
+This component generates text using OpenRouter's unified API for multiple AI models from different providers.
+
+For more information, see [OpenRouter documentation](https://openrouter.ai/docs).
+
+### Inputs
+
+| Name | Type | Description |
+|-------------|---------------|------------------------------------------------------------------|
+| api_key | SecretString | Your OpenRouter API key for authentication. |
+| site_url | String | Your site URL for OpenRouter rankings (advanced). |
+| app_name | String | Your app name for OpenRouter rankings (advanced). |
+| provider | String | The AI model provider to use. |
+| model_name | String | The specific model to use for chat completion. |
+| temperature | Float | Controls randomness in the output. Range: [0.0, 2.0]. Default: 0.7. |
+| max_tokens | Integer | The maximum number of tokens to generate (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatOpenAI configured with the specified parameters. |
+
+## Perplexity
+
+This component generates text using Perplexity's language models.
+
+For more information, see [Perplexity documentation](https://perplexity.ai/).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|--------------|-----------------------------------------------------------------------------------------------|
+| model_name | String | The name of the Perplexity model to use. Options include various Llama 3.1 models. |
+| max_output_tokens | Integer | The maximum number of tokens to generate. |
+| api_key | SecretString | The Perplexity API Key for authentication. |
+| temperature | Float | Controls randomness in the output. Default: 0.75. |
+| top_p | Float | The maximum cumulative probability of tokens to consider when sampling (advanced). |
+| n | Integer | Number of chat completions to generate for each prompt (advanced). |
+| top_k | Integer | Number of top tokens to consider for top-k sampling. Must be positive (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|---------------|-----------------------------------------------------|
+| model | LanguageModel | An instance of ChatPerplexity configured with the specified parameters. |
+
+
+## Qianfan
+
+This component generates text using Qianfan's language models.
+
+For more information, see [Qianfan documentation](https://github.com/baidubce/bce-qianfan-sdk).
+
+## SambaNova
+
+This component generates text using SambaNova LLMs.
+
+For more information, see [Sambanova Cloud documentation](https://cloud.sambanova.ai/).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|---------------|------------------------------------------------------------------|
+| sambanova_url | String | Base URL path for API requests. Default: `https://api.sambanova.ai/v1/chat/completions`. |
+| sambanova_api_key | SecretString | Your SambaNova API Key. |
+| model_name | String | The name of the Sambanova model to use. Options include various Llama models. |
+| max_tokens | Integer | The maximum number of tokens to generate. Set to 0 for unlimited tokens. |
+| temperature | Float | Controls randomness in the output. Range: [0.0, 1.0]. Default: 0.07. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of SambaNova model configured with the specified parameters. |
+
+## VertexAI
+
+This component generates text using Vertex AI LLMs.
+
+For more information, see [Google Vertex AI documentation](https://cloud.google.com/vertex-ai).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|--------------|-----------------------------------------------------------------------------------------------|
+| credentials | File | JSON credentials file. Leave empty to fallback to environment variables. File type: JSON. |
+| model_name | String | The name of the Vertex AI model to use. Default: "gemini-1.5-pro". |
+| project | String | The project ID (advanced). |
+| location | String | The location for the Vertex AI API. Default: "us-central1" (advanced). |
+| max_output_tokens | Integer | The maximum number of tokens to generate (advanced). |
+| max_retries | Integer | Maximum number of retries for API calls. Default: 1 (advanced). |
+| temperature | Float | Controls randomness in the output. Default: 0.0. |
+| top_k | Integer | The number of highest probability vocabulary tokens to keep for top-k-filtering (advanced). |
+| top_p | Float | The cumulative probability of parameter highest probability vocabulary tokens to keep for nucleus sampling. Default: 0.95 (advanced). |
+| verbose | Boolean | Whether to print verbose output. Default: False (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|---------------|-----------------------------------------------------|
+| model | LanguageModel | An instance of ChatVertexAI configured with the specified parameters. |
+
+## xAI
+
+This component generates text using xAI models like [Grok](https://x.ai/grok).
+
+For more information, see the [xAI documentation](https://x.ai/).
+
+### Inputs
+
+| Name | Type | Description |
+|----------------|---------------|-----------------------------------------------------------------|
+| max_tokens | Integer | Maximum number of tokens to generate. Set to `0` for unlimited. Range: `0-128000`. |
+| model_kwargs | Dictionary | Additional keyword arguments for the model. |
+| json_mode | Boolean | If `True`, outputs JSON regardless of passing a schema. |
+| model_name | String | The xAI model to use. Default: `grok-2-latest`. |
+| base_url | String | Base URL for API requests. Default: `https://api.x.ai/v1`. |
+| api_key | SecretString | Your xAI API key for authentication. |
+| temperature | Float | Controls randomness in the output. Range: `[0.0, 2.0]`. Default: `0.1`. |
+| seed | Integer | Controls reproducibility of the job. |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|---------------|------------------------------------------------------------------|
+| model | LanguageModel | An instance of ChatOpenAI configured with the specified parameters. |
+
+
diff --git a/langflow/docs/docs/Components/components-processing.md b/langflow/docs/docs/Components/components-processing.md
new file mode 100644
index 0000000..960c603
--- /dev/null
+++ b/langflow/docs/docs/Components/components-processing.md
@@ -0,0 +1,359 @@
+---
+title: Processing
+slug: /components-processing
+---
+
+# Processing components in Langflow
+
+Processing components process and transform data within a flow.
+
+## Use a processing component in a flow
+
+The **Split Text** processing component in this flow splits the incoming [Data](/concepts-objects) into chunks to be embedded into the vector store component.
+
+The component offers control over chunk size, overlap, and separator, which affect context and granularity in vector store retrieval results.
+
+
+
+## Alter metadata
+
+This component modifies metadata of input objects. It can add new metadata, update existing metadata, and remove specified metadata fields. The component works with both [Message](/concepts-objects#message-object) and [Data](/concepts-objects#data-object) objects, and can also create a new Data object from user-provided text.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| input_value | Input | Objects to which Metadata should be added |
+| text_in | User Text | Text input; the value will be in the 'text' attribute of the [Data](/concepts-objects#data-object) object. Empty text entries are ignored. |
+| metadata | Metadata | Metadata to add to each object |
+| remove_fields | Fields to Remove | Metadata Fields to Remove |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | List of Input objects, each with added metadata |
+
+## Combine text
+
+This component concatenates two text sources into a single text chunk using a specified delimiter.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| first_text | First Text | The first text input to concatenate. |
+| second_text | Second Text | The second text input to concatenate. |
+| delimiter | Delimiter | A string used to separate the two text inputs. Defaults to a space. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+|message |Message |A [Message](/concepts-objects#message-object) object containing the combined text.
+
+
+## Create data
+
+:::important
+This component is in **Legacy**, which means it is no longer in active development as of Langflow version 1.1.3.
+:::
+
+This component dynamically creates a [Data](/concepts-objects#data-object) object with a specified number of fields.
+
+### Inputs
+| Name | Display Name | Info |
+|------|--------------|------|
+| number_of_fields | Number of Fields | The number of fields to be added to the record. |
+| text_key | Text Key | Key that identifies the field to be used as the text content. |
+| text_key_validator | Text Key Validator | If enabled, checks if the given `Text Key` is present in the given `Data`. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | A [Data](/concepts-objects#data-object) object created with the specified fields and text key. |
+
+## Data combiner
+
+:::important
+Prior to Langflow version 1.1.3, this component was named **Merge Data**.
+:::
+
+This component combines multiple data sources into a single unified [Data](/concepts-objects#data-object) object.
+
+The component iterates through the input list of data objects, merging them into a single data object. If the input list is empty, it returns an empty data object. If there's only one input data object, it returns that object unchanged. The merging process uses the addition operator to combine data objects.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | A list of data objects to be merged. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| merged_data | Merged Data | A single [Data](/concepts-objects#data-object) object containing the combined information from all input data objects. |
+
+## DataFrame operations
+
+This component performs the following operations on Pandas [DataFrame](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html):
+
+| Operation | Description | Required Inputs |
+|-----------|-------------|-----------------|
+| Add Column | Adds a new column with a constant value | new_column_name, new_column_value |
+| Drop Column | Removes a specified column | column_name |
+| Filter | Filters rows based on column value | column_name, filter_value |
+| Head | Returns first n rows | num_rows |
+| Rename Column | Renames an existing column | column_name, new_column_name |
+| Replace Value | Replaces values in a column | column_name, replace_value, replacement_value |
+| Select Columns | Selects specific columns | columns_to_select |
+| Sort | Sorts DataFrame by column | column_name, ascending |
+| Tail | Returns last n rows | num_rows |
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| df | DataFrame | The input DataFrame to operate on. |
+| operation | Operation | Select the DataFrame operation to perform. Options: Add Column, Drop Column, Filter, Head, Rename Column, Replace Value, Select Columns, Sort, Tail |
+| column_name | Column Name | The column name to use for the operation. |
+| filter_value | Filter Value | The value to filter rows by. |
+| ascending | Sort Ascending | Whether to sort in ascending order. |
+| new_column_name | New Column Name | The new column name when renaming or adding a column. |
+| new_column_value | New Column Value | The value to populate the new column with. |
+| columns_to_select | Columns to Select | List of column names to select. |
+| num_rows | Number of Rows | Number of rows to return (for head/tail). Default: 5 |
+| replace_value | Value to Replace | The value to replace in the column. |
+| replacement_value | Replacement Value | The value to replace with. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output | DataFrame | The resulting DataFrame after the operation. |
+
+
+## Data to message
+
+:::important
+Prior to Langflow version 1.1.3, this component was named **Parse Data**.
+:::
+
+The ParseData component converts data objects into plain text using a specified template.
+This component transforms structured data into human-readable text formats, allowing for customizable output through the use of templates.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | The data to convert to text. |
+| template | Template | The template to use for formatting the data. It can contain the keys `{text}`, `{data}`, or any other key in the data. |
+| sep | Separator | The separator to use between multiple data items. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| text | Text | The resulting formatted text string as a [Message](/concepts-objects#message-object) object. |
+
+## Filter data
+
+:::important
+This component is in **Beta** as of Langflow version 1.1.3, and is not yet fully supported.
+:::
+
+This component filters a [Data](/concepts-objects#data-object) object based on a list of keys.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | Data object to filter. |
+| filter_criteria | Filter Criteria | List of keys to filter by. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| filtered_data | Filtered Data | A new [Data](/concepts-objects#data-object) object containing only the key-value pairs that match the filter criteria. |
+
+## Filter values
+
+:::important
+This component is in **Beta** as of Langflow version 1.1.3, and is not yet fully supported.
+:::
+
+The Filter values component filters a list of data items based on a specified key, filter value, and comparison operator.
+
+### Inputs
+| Name | Display Name | Info |
+|------|--------------|------|
+| input_data | Input data | The list of data items to filter. |
+| filter_key | Filter Key | The key to filter on, for example, 'route'. |
+| filter_value | Filter Value | The value to filter by, for example, 'CMIP'. |
+| operator | Comparison Operator | The operator to apply for comparing the values. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| filtered_data | Filtered data | The resulting list of filtered data items. |
+
+## JSON cleaner
+
+The JSON cleaner component cleans JSON strings to ensure they are fully compliant with the JSON specification.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| json_str | JSON String | The JSON string to be cleaned. This can be a raw, potentially malformed JSON string produced by language models or other sources that may not fully comply with JSON specifications. |
+| remove_control_chars | Remove Control Characters | If set to True, this option removes control characters (ASCII characters 0-31 and 127) from the JSON string. This can help eliminate invisible characters that might cause parsing issues or make the JSON invalid. |
+| normalize_unicode | Normalize Unicode | When enabled, this option normalizes Unicode characters in the JSON string to their canonical composition form (NFC). This ensures consistent representation of Unicode characters across different systems and prevents potential issues with character encoding. |
+| validate_json | Validate JSON | If set to True, this option attempts to parse the JSON string to ensure it is well-formed before applying the final repair operation. It raises a ValueError if the JSON is invalid, allowing for early detection of major structural issues in the JSON. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output | Cleaned JSON String | The resulting cleaned, repaired, and validated JSON string that fully complies with the JSON specification. |
+
+## LLM router
+
+This component routes requests to the most appropriate LLM based on OpenRouter model specifications.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| models | Language Models | List of LLMs to route between |
+| input_value | Input | The input message to be routed |
+| judge_llm | Judge LLM | LLM that will evaluate and select the most appropriate model |
+| optimization | Optimization | Optimization preference (quality/speed/cost/balanced) |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output | Output | The response from the selected model |
+| selected_model | Selected Model | Name of the chosen model |
+
+
+## Message to data
+
+This component converts [Message](/concepts-objects#message-object) objects to [Data](/concepts-objects#data-object) objects.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| message | Message | The [Message](/concepts-objects#message-object) object to convert to a [Data](/concepts-objects#data-object) object. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | The converted [Data](/concepts-objects#data-object) object. |
+
+
+## Parse DataFrame
+
+This component converts DataFrames into plain text using templates.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| df | DataFrame | The DataFrame to convert to text rows |
+| template | Template | Template for formatting (use `{column_name}` placeholders) |
+| sep | Separator | String to join rows in output |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| text | Text | All rows combined into single text |
+
+## Parse JSON
+
+:::important
+This component is in **Legacy**, which means it is no longer in active development as of Langflow version 1.1.3.
+:::
+
+This component converts and extracts JSON fields using JQ queries.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| input_value | Input | Data object to filter ([Message](/concepts-objects#message-object) or [Data](/concepts-objects#data-object)). |
+| query | JQ Query | JQ Query to filter the data |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| filtered_data | Filtered Data | Filtered data as list of [Data](/concepts-objects#data-object) objects. |
+
+## Select data
+
+:::important
+This component is in **Legacy**, which means it is no longer in active development as of Langflow version 1.1.3.
+:::
+
+This component selects a single [Data](/concepts-objects#data-object) item from a list.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data_list | Data List | List of data to select from |
+| data_index | Data Index | Index of the data to select |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| selected_data | Selected Data | The selected [Data](/concepts-objects#data-object) object. |
+
+## Split text
+
+This component splits text into chunks based on specified criteria.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data_inputs | Input Documents | The data to split.The component accepts [Data](/concepts-objects#data-object) or [DataFrame](/concepts-objects#dataframe-object) objects. |
+| chunk_overlap | Chunk Overlap | The number of characters to overlap between chunks. Default: `200`. |
+| chunk_size | Chunk Size | The maximum number of characters in each chunk. Default: `1000`. |
+| separator | Separator | The character to split on. Default: `newline`. |
+| text_key | Text Key | The key to use for the text column (advanced). Default: `text`. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| chunks | Chunks | List of split text chunks as [Data](/concepts-objects#data-object) objects. |
+| dataframe | DataFrame | List of split text chunks as [DataFrame](/concepts-objects#dataframe-object) objects. |
+
+## Update data
+
+This component dynamically updates or appends data with specified fields.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| old_data | Data | The records to update |
+| number_of_fields | Number of Fields | Number of fields to add (max 15) |
+| text_key | Text Key | Key for text content |
+| text_key_validator | Text Key Validator | Validates text key presence |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | Updated [Data](/concepts-objects#data-object) objects. |
diff --git a/langflow/docs/docs/Components/components-prompts.md b/langflow/docs/docs/Components/components-prompts.md
new file mode 100644
index 0000000..3884ccb
--- /dev/null
+++ b/langflow/docs/docs/Components/components-prompts.md
@@ -0,0 +1,64 @@
+---
+title: Prompts
+slug: /components-prompts
+---
+
+# Prompt components in Langflow
+
+A prompt is a structured input to a language model that instructs the model how to handle user inputs and variables.
+
+Prompt components create prompt templates with custom fields and dynamic variables for providing your model structured, repeatable prompts.
+
+Prompts are a combination of natural language and variables created with curly braces.
+
+## Use a prompt component in a flow
+
+An example of modifying a prompt can be found in the [Quickstart](/get-started-quickstart#run-the-chatbot-with-retrieved-context), where a basic chatbot flow is extended to include a full vector RAG pipeline.
+
+
+
+The default prompt in the **Prompt** component is `Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.`
+
+This prompt creates a "personality" for your LLM's chat interactions, but it doesn't include variables that you may find useful when templating prompts.
+
+To modify the prompt template, in the **Prompt** component, click the **Template** field. For example, the `{context}` variable gives the LLM model access to embedded vector data to return better answers.
+
+```plain
+Given the context
+{context}
+Answer the question
+{user_question}
+```
+
+When variables are added to a prompt template, new fields are automatically created in the component. These fields can be connected to receive text input from other components to automate prompting, or to output instructions to other components. An example of prompts controlling agents behavior is available in the [sequential tasks agent starter flow](/tutorials-sequential-agent).
+
+### Inputs
+
+| Name | Display Name | Info |
+|----------|--------------|-------------------------------------------------------------------|
+| template | Template | Create a prompt template with dynamic variables. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|--------|----------------|--------------------------------------------------------|
+| prompt | Prompt Message | The built prompt message returned by the `build_prompt` method. |
+
+## Langchain Hub Prompt Template
+
+This component fetches prompts from the [Langchain Hub](https://docs.smith.langchain.com/old/category/prompt-hub).
+
+When a prompt is loaded, the component generates input fields for custom variables. For example, the default prompt "efriis/my-first-prompt" generates fields for `profession` and `question`.
+
+### Inputs
+
+| Name | Display Name | Info |
+|--------------------|---------------------------|------------------------------------------|
+| langchain_api_key | Your LangChain API Key | The LangChain API Key to use. |
+| langchain_hub_prompt| LangChain Hub Prompt | The LangChain Hub prompt to use. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|--------|--------------|-------------------------------------------------------------------|
+| prompt | Build Prompt | The built prompt message returned by the `build_prompt` method. |
diff --git a/langflow/docs/docs/Components/components-tools.md b/langflow/docs/docs/Components/components-tools.md
new file mode 100644
index 0000000..418f6fc
--- /dev/null
+++ b/langflow/docs/docs/Components/components-tools.md
@@ -0,0 +1,528 @@
+---
+title: Tools
+slug: /components-tools
+---
+
+# Tool components in Langflow
+
+Tools are typically connected to agent components at the **Tools** port. Agents use LLMs as a reasoning engine to decide which of the connected tool components to use to solve a problem.
+
+Tools in agentic functions are, essentially, functions that the agent can call to perform tasks or access external resources.
+A function is wrapped as a `Tool` object, with a common interface the agent understands.
+Agents become aware of tools through tool registration, where the agent is provided a list of available tools, typically at agent initialization. The `Tool` object's description tells the agent what the tool can do.
+
+The agent then uses a connected LLM to reason through the problem to decide which tool is best for the job.
+
+## Use a tool in a flow
+
+Tools are typically connected to agent components at the **Tools** port.
+
+The [simple agent starter project](/starter-projects-simple-agent) uses URL and Calculator tools connected to an [agent component](/components-agents#agent-component) to answer a user's questions. The OpenAI LLM acts as a brain for the agent to decide which tool to use.
+
+
+
+To make a component into a tool that an agent can use, enable **Tool mode** in the component. Enabling **Tool mode** modifies a component input to accept calls from an agent.
+If the component you want to connect to an agent doesn't have a **Tool mode** option, you can modify the component's inputs to become a tool.
+For an example, see [Make any component a tool](/agents-tool-calling-agent-component#make-any-component-a-tool).
+
+## arXiv
+
+This component searches and retrieves papers from [arXiv.org](https://arXiv.org).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| search_query | Search Query | The search query for arXiv papers (for example, `quantum computing`) |
+| search_type | Search Field | The field to search in |
+| max_results | Max Results | Maximum number of results to return |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| papers | Papers | List of retrieved arXiv papers |
+
+
+## Astra DB Tool
+
+The `Astra DB Tool` allows agents to connect to and query data from Astra DB collections.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------|--------|----------------------------------------------------------------------------------------------------------------------------------|
+| Tool Name | String | The name used to reference the tool in the agent's prompt. |
+| Tool Description | String | A brief description of the tool. This helps the model decide when to use it. |
+| Collection Name | String | The name of the Astra DB collection to query. |
+| Token | SecretString | The authentication token for accessing Astra DB. |
+| API Endpoint | String | The Astra DB API endpoint. |
+| Projection Fields | String | The attributes to return, separated by commas. Default: "*". |
+| Tool Parameters | Dict | Parameters the model needs to fill to execute the tool. For required parameters, use an exclamation mark (for example, `!customer_id`). |
+| Static Filters | Dict | Attribute-value pairs used to filter query results. |
+| Limit | String | The number of documents to return. |
+
+### Outputs
+
+The Data output is primarily used when directly querying Astra DB, while the Tool output is used when integrating with LangChain agents or chains.
+
+| Name | Type | Description |
+|------|------|-------------|
+| Data | List[`Data`] | A list of [Data](/concepts-objects) objects containing the query results from Astra DB. Each `Data` object contains the document fields specified by the projection attributes. Limited by the `number_of_results` parameter. |
+| Tool | StructuredTool | A LangChain `StructuredTool` object that can be used in agent workflows. Contains the tool name, description, argument schema based on tool parameters, and the query function. |
+
+
+## Astra DB CQL Tool
+
+The `Astra DB CQL Tool` allows agents to query data from CQL tables in Astra DB.
+
+The main difference between this tool and the **Astra DB Tool** is that this tool is specifically designed for CQL tables and requires partition keys for querying, while also supporting clustering keys for more specific queries.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------|
+| Tool Name | String | The name used to reference the tool in the agent's prompt. |
+| Tool Description | String | A brief description of the tool to guide the model in using it. |
+| Keyspace | String | The name of the keyspace. |
+| Table Name | String | The name of the Astra DB CQL table to query. |
+| Token | SecretString | The authentication token for Astra DB. |
+| API Endpoint | String | The Astra DB API endpoint. |
+| Projection Fields | String | The attributes to return, separated by commas. Default: "*". |
+| Partition Keys | Dict | Required parameters that the model must fill to query the tool. |
+| Clustering Keys | Dict | Optional parameters the model can fill to refine the query. Required parameters should be marked with an exclamation mark (for example, `!customer_id`). |
+| Static Filters | Dict | Attribute-value pairs used to filter query results. |
+| Limit | String | The number of records to return. |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| Data | List[Data] | A list of [Data](/concepts-objects) objects containing the query results from the Astra DB CQL table. Each Data object contains the document fields specified by the projection fields. Limited by the `number_of_results` parameter. |
+| Tool | StructuredTool | A LangChain StructuredTool object that can be used in agent workflows. Contains the tool name, description, argument schema based on partition and clustering keys, and the query function. |
+
+## Bing Search API
+
+This component allows you to call the Bing Search API.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|---------------------------------------|
+| bing_subscription_key | SecretString | Bing API subscription key |
+| input_value | String | Search query input |
+| bing_search_url | String | Custom Bing Search URL (optional) |
+| k | Integer | Number of search results to return |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|--------------------------------------|
+| results | List[Data]| List of search results |
+| tool | Tool | Bing Search tool for use in LangChain|
+
+## Calculator Tool
+
+This component creates a tool for performing basic arithmetic operations on a given expression.
+
+### Inputs
+
+| Name | Type | Description |
+|------------|--------|--------------------------------------------------------------------|
+| expression | String | The arithmetic expression to evaluate (for example, `4*4*(33/22)+12-20`). |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|------|-------------------------------------------------|
+| result | Tool | Calculator tool for use in LangChain |
+
+This component allows you to evaluate basic arithmetic expressions. It supports addition, subtraction, multiplication, division, and exponentiation. The tool uses a secure evaluation method that prevents the execution of arbitrary Python code.
+
+## Combinatorial Reasoner
+
+This component runs Icosa's Combinatorial Reasoning (CR) pipeline on an input to create an optimized prompt with embedded reasons. Sign up for access here: https://forms.gle/oWNv2NKjBNaqqvCx6
+
+### Inputs
+
+| Name | Display Name | Description |
+|------------------------|--------------|---------------------------------------|
+| prompt | Prompt | Input to run CR on |
+| openai_api_key | OpenAI API Key | OpenAI API key for authentication |
+| username | Username | Username for Icosa API authentication |
+| password | Password | Password for Icosa API authentication |
+| model_name | Model Name | OpenAI LLM to use for reason generation|
+
+### Outputs
+
+| Name | Display Name | Description |
+|---------|-----------|--------------------------------------|
+| optimized_prompt | Optimized Prompt| A message object containing the optimized prompt |
+| reasons | Selected Reasons| A list of the selected reasons that are embedded in the optimized prompt|
+
+## DuckDuckGo search
+
+This component performs web searches using the [DuckDuckGo](https://www.duckduckgo.com) search engine with result-limiting capabilities.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| input_value | Search Query | The search query to execute with DuckDuckGo. |
+| max_results | Max Results | The maximum number of search results to return. Default: `5`. |
+| max_snippet_length | Max Snippet Length | The maximum length of each result snippet. Default: `100`.|
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | [Data](/concepts-objects#data-object) | List of search results as Data objects containing snippets and full content. |
+| text | Text | Search results formatted as a single text string. |
+
+## Exa Search
+
+This component provides an [https://exa.ai/](Exa Search) toolkit for search and content retrieval.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| metaphor_api_key | Exa Search API Key | API key for Exa Search (entered as a password) |
+| use_autoprompt | Use Autoprompt | Whether to use autoprompt feature (default: true) |
+| search_num_results | Search Number of Results | Number of results to return for search (default: 5) |
+| similar_num_results | Similar Number of Results | Number of similar results to return (default: 5) |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| tools | Tools | List of search tools provided by the toolkit |
+## Glean Search API
+
+This component allows you to call the Glean Search API.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|---------------------------------------|
+| glean_api_url | String | URL of the Glean API |
+| glean_access_token | SecretString | Access token for Glean API authentication |
+| query | String | Search query input |
+| page_size | Integer | Number of results per page (default: 10) |
+| request_options | Dict | Additional options for the API request (optional) |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|--------------------------------------|
+| results | List[Data]| List of search results |
+| tool | Tool | Glean Search tool for use in LangChain|
+
+## Google Search API
+
+This component allows you to call the Google Search API.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|---------------------------------------|
+| google_api_key | SecretString | Google API key for authentication |
+| google_cse_id | SecretString | Google Custom Search Engine ID |
+| input_value | String | Search query input |
+| k | Integer | Number of search results to return |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|--------------------------------------|
+| results | List[Data]| List of search results |
+| tool | Tool | Google Search tool for use in LangChain|
+
+## Google Serper API
+
+This component allows you to call the Serper.dev Google Search API.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|---------------------------------------|
+| serper_api_key | SecretString | API key for Serper.dev authentication |
+| input_value | String | Search query input |
+| k | Integer | Number of search results to return |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|--------------------------------------|
+| results | List[Data]| List of search results |
+| tool | Tool | Google Serper search tool for use in LangChain|
+
+## MCP Tools (stdio)
+
+This component connects to a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server over `stdio` and exposes its tools as Langflow tools to be used by an Agent component.
+
+To use the MCP stdio component, follow these steps:
+
+1. Add the MCP stdio component to your workflow, and connect it to an agent. The flow looks like this:
+
+
+
+2. In the MCP stdio component, in the **mcp command** field, enter the command to start your MCP server. For a [Fetch](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) server, the command is:
+
+```bash
+uvx mcp-server-fetch
+```
+
+3. Open the **Playground**.
+Ask the agent to summarize recent tech news. The agent calls the MCP server function `fetch` and returns the summary.
+This confirms the MCP server is connected and working.
+
+### Inputs
+
+| Name | Type | Description |
+|---------|--------|--------------------------------------------|
+| command | String | MCP command (default: `uvx mcp-sse-shim@latest`) |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|-----------|-------------------------------------------|
+| tools | List[Tool]| List of tools exposed by the MCP server |
+
+## MCP Tools (SSE)
+
+This component connects to a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server over [SSE (Server-Sent Events)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) and exposes its tools as Langflow tools to be used by an Agent component.
+
+To use the MCP SSE component, follow these steps:
+
+1. Add the MCP SSE component to your workflow, and connect it to an agent. The flow looks similar to the MCP stdio component flow.
+
+2. In the MCP SSE component, in the **url** field, enter the URL of your current Langflow server's `mcp/sse` endpoint.
+This will fetch all currently available tools from the Langflow server.
+
+### Inputs
+
+| Name | Type | Description |
+|------|--------|------------------------------------------------------|
+| url | String | SSE URL (default: `http://localhost:7860/api/v1/mcp/sse`) |
+
+### Outputs
+
+| Name | Type | Description |
+|-------|-----------|-------------------------------------------|
+| tools | List[Tool]| List of tools exposed by the MCP server |
+
+## Python Code Structured Tool
+
+This component creates a structured tool from Python code using a dataclass.
+
+The component dynamically updates its configuration based on the provided Python code, allowing for custom function arguments and descriptions.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|---------------------------------------|
+| tool_code | String | Python code for the tool's dataclass |
+| tool_name | String | Name of the tool |
+| tool_description | String | Description of the tool |
+| return_direct | Boolean | Whether to return the function output directly |
+| tool_function | String | Selected function for the tool |
+| global_variables | Dict | Global variables or data for the tool |
+
+### Outputs
+
+| Name | Type | Description |
+|-------------|-------|-----------------------------------------|
+| result_tool | Tool │ Structured tool created from the Python code |
+
+## Python REPL Tool
+
+This component creates a Python REPL (Read-Eval-Print Loop) tool for executing Python code.
+
+### Inputs
+
+| Name | Type | Description |
+|-----------------|--------------|--------------------------------------------------------|
+| name | String | The name of the tool (default: "python_repl") |
+| description | String | A description of the tool's functionality |
+| global_imports | List[String] | List of modules to import globally (default: ["math"]) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|--------------------------------------------|
+| tool | Tool | Python REPL tool for use in LangChain |
+
+## Retriever Tool
+
+This component creates a tool for interacting with a retriever in LangChain.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------|---------------|---------------------------------------------|
+| retriever | BaseRetriever | The retriever to interact with |
+| name | String | The name of the tool |
+| description | String | A description of the tool's functionality |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|--------------------------------------------|
+| tool | Tool | Retriever tool for use in LangChain |
+
+## SearXNG Search Tool
+
+This component creates a tool for searching using SearXNG, a metasearch engine.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------|--------------|---------------------------------------|
+| url | String | The URL of the SearXNG instance |
+| max_results | Integer | Maximum number of results to return |
+| categories | List[String] | Categories to search in |
+| language | String | Language for the search results |
+
+### Outputs
+
+| Name | Type | Description |
+|-------------|------|--------------------------------------------|
+| result_tool | Tool | SearXNG search tool for use in LangChain |
+
+## Search API
+
+This component calls the `searchapi.io` API. It can be used to search the web for information.
+
+For more information, see the [SearchAPI documentation](https://www.searchapi.io/docs/google).
+
+### Inputs
+
+| Name | Display Name | Info |
+|----------------|---------------------|-----------------------------------------------------|
+| engine | Engine | The search engine to use (default: "google") |
+| api_key | SearchAPI API Key | The API key for authenticating with SearchAPI |
+| input_value | Input | The search query or input for the API call |
+| search_params | Search parameters | Additional parameters for customizing the search |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|-----------------|------------------------------------------------------|
+| data | Search Results | List of Data objects containing search results |
+| tool | Search API Tool | A Tool object for use in LangChain workflows |
+
+## Serp Search API
+
+This component creates a tool for searching using the Serp API.
+
+### Inputs
+
+| Name | Type | Description |
+|------------------|--------------|---------------------------------------------|
+| serpapi_api_key | SecretString | API key for Serp API authentication |
+| input_value | String | Search query input |
+| search_params | Dict | Additional search parameters (optional) |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|---------------------------------------------|
+| results | List[Data]| List of search results |
+| tool | Tool | Serp API search tool for use in LangChain |
+
+## Tavily AI Search
+
+This component performs searches using the Tavily AI search engine, which is optimized for LLMs and RAG applications.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| api_key | Tavily API Key | Your Tavily API Key. |
+| query | Search Query | The search query you want to execute with Tavily. |
+| search_depth | Search Depth | The depth of the search. |
+| topic | Search Topic | The category of the search. |
+| max_results | Max Results | The maximum number of search results to return. |
+| include_images | Include Images | Include a list of query-related images in the response. |
+| include_answer | Include Answer | Include a short answer to original query. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | The search results as a list of Data objects. |
+| text | Text | The search results formatted as a text string. |
+
+## Wikidata
+
+This component performs a search using the Wikidata API.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| query | Query | The text query for similarity search on Wikidata. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| data | Data | The search results from Wikidata API as a list of Data objects. |
+| text | Message | The search results formatted as a text message. |
+
+
+## Wikipedia API
+
+This component creates a tool for searching and retrieving information from Wikipedia.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------------|---------|-----------------------------------------------------------|
+| input_value | String | Search query input |
+| lang | String | Language code for Wikipedia (default: "en") |
+| k | Integer | Number of results to return |
+| load_all_available_meta | Boolean | Whether to load all available metadata (advanced) |
+| doc_content_chars_max | Integer | Maximum number of characters for document content (advanced)|
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|---------------------------------------|
+| results | List[Data]| List of Wikipedia search results |
+| tool | Tool | Wikipedia search tool for use in LangChain |
+
+## Wolfram Alpha API
+
+This component creates a tool for querying the Wolfram Alpha API.
+
+### Inputs
+
+| Name | Type | Description |
+|-------------|--------------|--------------------------------|
+| input_value | String | Query input for Wolfram Alpha |
+| app_id | SecretString | Wolfram Alpha API App ID |
+
+### Outputs
+
+| Name | Type | Description |
+|---------|-----------|------------------------------------------------|
+| results | List[Data]| List containing the Wolfram Alpha API response |
+| tool | Tool | Wolfram Alpha API tool for use in LangChain |
+
+## Yahoo Finance News Tool
+
+This component creates a tool for retrieving news from Yahoo Finance.
+
+### Inputs
+
+This component does not have any input parameters.
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|----------------------------------------------|
+| tool | Tool | Yahoo Finance News tool for use in LangChain |
+
+
diff --git a/langflow/docs/docs/Components/components-vector-stores.md b/langflow/docs/docs/Components/components-vector-stores.md
new file mode 100644
index 0000000..91ba291
--- /dev/null
+++ b/langflow/docs/docs/Components/components-vector-stores.md
@@ -0,0 +1,735 @@
+---
+title: Vector stores
+slug: /components-vector-stores
+---
+
+# Vector store components in Langflow
+
+Vector databases store vector data, which backs AI workloads like chatbots and Retrieval Augmented Generation.
+
+Vector database components establish connections to existing vector databases or create in-memory vector stores for storing and retrieving vector data.
+
+Vector database components are distinct from [memory components](/components-memories), which are built specifically for storing and retrieving chat messages from external databases.
+
+## Use a vector store component in a flow
+
+This example uses the **Astra DB vector store** component. Your vector store component's parameters and authentication may be different, but the document ingestion workflow is the same. A document is loaded from a local machine and chunked. The Astra DB vector store generates embeddings with the connected [model](/components-models) component, and stores them in the connected Astra DB database.
+
+This vector data can then be retrieved for workloads like Retrieval Augmented Generation.
+
+
+
+The user's chat input is embedded and compared to the vectors embedded during document ingestion for a similarity search.
+The results are output from the vector database component as a [Data](/concepts-objects) object and parsed into text.
+This text fills the `{context}` variable in the **Prompt** component, which informs the **Open AI model** component's responses.
+
+Alternatively, connect the vector database component's **Retriever** port to a [retriever tool](components-tools#retriever-tool), and then to an [agent](/components-agents) component. This enables the agent to use your vector database as a tool and make decisions based on the available data.
+
+
+
+## Astra DB Vector Store
+
+This component implements a Vector Store using Astra DB with search capabilities.
+
+For more information, see the [DataStax documentation](https://docs.datastax.com/en/astra-db-serverless/databases/create-database.html).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| token | Astra DB Application Token | The authentication token for accessing Astra DB. |
+| environment | Environment | The environment for the Astra DB API Endpoint. For example, `dev` or `prod`. |
+| database_name | Database | The database name for the Astra DB instance. |
+| api_endpoint | Astra DB API Endpoint | The API endpoint for the Astra DB instance. This supersedes the database selection. |
+| collection_name | Collection | The name of the collection within Astra DB where the vectors are stored. |
+| keyspace | Keyspace | An optional keyspace within Astra DB to use for the collection. |
+| embedding_choice | Embedding Model or Astra Vectorize | Choose an embedding model or use Astra vectorize. |
+| embedding_model | Embedding Model | Specify the embedding model. Not required for Astra vectorize collections. |
+| number_of_results | Number of Search Results | The number of search results to return (default: `4`). |
+| search_type | Search Type | The search type to use. The options are `Similarity`, `Similarity with score threshold`, and `MMR (Max Marginal Relevance)`. |
+| search_score_threshold | Search Score Threshold | The minimum similarity score threshold for search results when using the `Similarity with score threshold` option. |
+| advanced_search_filter | Search Metadata Filter | An optional dictionary of filters to apply to the search query. |
+| autodetect_collection | Autodetect Collection | A boolean flag to determine whether to autodetect the collection. |
+| content_field | Content Field | A field to use as the text content field for the vector store. |
+| deletion_field | Deletion Based On Field | When provided, documents in the target collection with metadata field values matching the input metadata field value are deleted before new data is loaded. |
+| ignore_invalid_documents | Ignore Invalid Documents | A boolean flag to determine whether to ignore invalid documents at runtime. |
+| astradb_vectorstore_kwargs | AstraDBVectorStore Parameters | An optional dictionary of additional parameters for the AstraDBVectorStore. |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| vector_store | Vector Store | Astra DB vector store instance configured with the specified parameters. |
+| search_results | Search Results | The results of the similarity search as a list of [Data](/concepts-objects#data-object) objects. |
+
+### Generate embeddings
+
+The **Astra DB Vector Store** component offers two methods for generating embeddings.
+
+1. **Embedding Model**: Use your own embedding model by connecting an [Embeddings](/components-embedding-models) component in Langflow.
+
+2. **Astra Vectorize**: Use Astra DB's built-in embedding generation service. When creating a new collection, choose the embeddings provider and models, including NVIDIA's `NV-Embed-QA` model hosted by Datastax.
+
+:::important
+The embedding model selection is made when creating a new collection and cannot be changed later.
+:::
+
+For an example of using the **Astra DB Vector Store** component with an embedding model, see the [Vector Store RAG starter project](/starter-projects-vector-store-rag).
+
+For more information, see the [Astra DB Serverless documentation](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html).
+
+## AstraDB Graph vector store
+
+This component implements a Vector Store using AstraDB with graph capabilities.
+For more information, see the [Astra DB Serverless documentation](https://docs.datastax.com/en/astra-db-serverless/tutorials/graph-rag.html).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| collection_name | Collection Name | The name of the collection within AstraDB where the vectors will be stored (required) |
+| token | Astra DB Application Token | Authentication token for accessing AstraDB (required) |
+| api_endpoint | API Endpoint | API endpoint URL for the AstraDB service (required) |
+| search_input | Search Input | Query string for similarity search |
+| ingest_data | Ingest Data | Data to be ingested into the vector store |
+| namespace | Namespace | Optional namespace within AstraDB to use for the collection |
+| embedding | Embedding Model | Embedding model to use |
+| metric | Metric | Distance metric for vector comparisons (options: "cosine", "euclidean", "dot_product") |
+| setup_mode | Setup Mode | Configuration mode for setting up the vector store (options: "Sync", "Async", "Off") |
+| pre_delete_collection | Pre Delete Collection | Boolean flag to determine whether to delete the collection before creating a new one |
+| number_of_results | Number of Results | Number of results to return in similarity search (default: 4) |
+| search_type | Search Type | Search type to use (options: "Similarity", "Graph Traversal", "Hybrid") |
+| traversal_depth | Traversal Depth | Maximum depth for graph traversal searches (default: 1) |
+| search_score_threshold | Search Score Threshold | Minimum similarity score threshold for search results |
+| search_filter | Search Metadata Filter | Optional dictionary of filters to apply to the search query |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| vector_store | Vector Store | Astra DB graph vector store instance configured with the specified parameters. |
+| search_results | Search Results | The results of the similarity search as a list of `Data` objects. |
+
+
+## Cassandra
+
+This component creates a Cassandra Vector Store with search capabilities.
+For more information, see the [Cassandra documentation](https://cassandra.apache.org/doc/latest/cassandra/vector-search/overview.html).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| database_ref | String | Contact points for the database or AstraDB database ID |
+| username | String | Username for the database (leave empty for AstraDB) |
+| token | SecretString | User password for the database or AstraDB token |
+| keyspace | String | Table Keyspace or AstraDB namespace |
+| table_name | String | Name of the table or AstraDB collection |
+| ttl_seconds | Integer | Time-to-live for added texts |
+| batch_size | Integer | Number of data to process in a single batch |
+| setup_mode | String | Configuration mode for setting up the Cassandra table |
+| cluster_kwargs | Dict | Additional keyword arguments for the Cassandra cluster |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+| search_type | String | Type of search to perform |
+| search_score_threshold | Float | Minimum similarity score for search results |
+| search_filter | Dict | Metadata filters for search query |
+| body_search | String | Document textual search terms |
+| enable_body_search | Boolean | Flag to enable body search |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| vector_store | Cassandra | A Cassandra vector store instance configured with the specified parameters. |
+| search_results | List[Data] | The results of the similarity search as a list of `Data` objects. |
+
+## Cassandra Graph Vector Store
+
+This component implements a Cassandra Graph Vector Store with search capabilities.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| database_ref | Contact Points / Astra Database ID | Contact points for the database or AstraDB database ID (required) |
+| username | Username | Username for the database (leave empty for AstraDB) |
+| token | Password / AstraDB Token | User password for the database or AstraDB token (required) |
+| keyspace | Keyspace | Table Keyspace or AstraDB namespace (required) |
+| table_name | Table Name | The name of the table or AstraDB collection where vectors will be stored (required) |
+| setup_mode | Setup Mode | Configuration mode for setting up the Cassandra table (options: "Sync", "Off", default: "Sync") |
+| cluster_kwargs | Cluster arguments | Optional dictionary of additional keyword arguments for the Cassandra cluster |
+| search_query | Search Query | Query string for similarity search |
+| ingest_data | Ingest Data | Data to be ingested into the vector store (list of Data objects) |
+| embedding | Embedding | Embedding model to use |
+| number_of_results | Number of Results | Number of results to return in similarity search (default: 4) |
+| search_type | Search Type | Search type to use (options: "Traversal", "MMR traversal", "Similarity", "Similarity with score threshold", "MMR (Max Marginal Relevance)", default: "Traversal") |
+| depth | Depth of traversal | The maximum depth of edges to traverse (for "Traversal" or "MMR traversal" search types, default: 1) |
+| search_score_threshold | Search Score Threshold | Minimum similarity score threshold for search results (for "Similarity with score threshold" search type) |
+| search_filter | Search Metadata Filter | Optional dictionary of filters to apply to the search query |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| vector_store | Vector Store | A Cassandra Graph vector store instance configured with the specified parameters. |
+| search_results | Search Results | The results of the similarity search as a list of `Data` objects. |
+
+## Chroma DB
+
+This component creates a Chroma Vector Store with search capabilities.
+For more information, see the [Chroma documentation](https://docs.trychroma.com/).
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------------|---------------|--------------------------------------------------|
+| collection_name | String | The name of the Chroma collection. Default: "langflow". |
+| persist_directory | String | The directory to persist the Chroma database. |
+| search_query | String | The query to search for in the vector store. |
+| ingest_data | Data | The data to ingest into the vector store (list of Data objects). |
+| embedding | Embeddings | The embedding function to use for the vector store. |
+| chroma_server_cors_allow_origins | String | CORS allow origins for the Chroma server. |
+| chroma_server_host | String | Host for the Chroma server. |
+| chroma_server_http_port | Integer | HTTP port for the Chroma server. |
+| chroma_server_grpc_port | Integer | gRPC port for the Chroma server. |
+| chroma_server_ssl_enabled | Boolean | Enable SSL for the Chroma server. |
+| allow_duplicates | Boolean | Allow duplicate documents in the vector store. |
+| search_type | String | Type of search to perform: "Similarity" or "MMR". |
+| number_of_results | Integer | Number of results to return from the search. Default: 10. |
+| limit | Integer | Limit the number of records to compare when Allow Duplicates is False. |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|---------------|--------------------------------|
+| vector_store | Chroma | Chroma vector store instance |
+| search_results | List[Data] | Results of similarity search |
+
+## Clickhouse
+
+This component implements a Clickhouse Vector Store with search capabilities.
+For more information, see the [CLickhouse Documentation](https://clickhouse.com/docs/en/intro).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| host | hostname | Clickhouse server hostname (required, default: "localhost") |
+| port | port | Clickhouse server port (required, default: 8123) |
+| database | database | Clickhouse database name (required) |
+| table | Table name | Clickhouse table name (required) |
+| username | The ClickHouse user name. | Username for authentication (required) |
+| password | The password for username. | Password for authentication (required) |
+| index_type | index_type | Type of the index (options: "annoy", "vector_similarity", default: "annoy") |
+| metric | metric | Metric to compute distance (options: "angular", "euclidean", "manhattan", "hamming", "dot", default: "angular") |
+| secure | Use https/TLS | Overrides inferred values from the interface or port arguments (default: false) |
+| index_param | Param of the index | Index parameters (default: "'L2Distance',100") |
+| index_query_params | index query params | Additional index query parameters |
+| search_query | Search Query | Query string for similarity search |
+| ingest_data | Ingest Data | Data to be ingested into the vector store |
+| embedding | Embedding | Embedding model to use |
+| number_of_results | Number of Results | Number of results to return in similarity search (default: 4) |
+| score_threshold | Score threshold | Threshold for similarity scores |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| vector_store | Vector Store | Built Clickhouse vector store |
+| search_results | Search Results | Results of the similarity search as a list of Data objects |
+
+## Couchbase
+
+This component creates a Couchbase Vector Store with search capabilities.
+For more information, see the [Couchbase documentation](https://docs.couchbase.com/home/index.html).
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------------|---------------|--------------------------------------------------|
+| couchbase_connection_string | SecretString | Couchbase Cluster connection string (required). |
+| couchbase_username | String | Couchbase username (required). |
+| couchbase_password | SecretString | Couchbase password (required). |
+| bucket_name | String | Name of the Couchbase bucket (required). |
+| scope_name | String | Name of the Couchbase scope (required). |
+| collection_name | String | Name of the Couchbase collection (required). |
+| index_name | String | Name of the Couchbase index (required). |
+| search_query | String | The query to search for in the vector store. |
+| ingest_data | Data | The data to ingest into the vector store (list of Data objects). |
+| embedding | Embeddings | The embedding function to use for the vector store. |
+| number_of_results | Integer | Number of results to return from the search. Default: 4 (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------------------------|--------------------------------|
+| vector_store | CouchbaseVectorStore | A Couchbase vector store instance configured with the specified parameters. |
+
+
+## Elasticsearch
+
+This component creates an Elasticsearch Vector Store with search capabilities.
+For more information, see the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html).
+
+### Inputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| es_url | String | Elasticsearch server URL |
+| es_user | String | Username for Elasticsearch authentication |
+| es_password | SecretString | Password for Elasticsearch authentication |
+| index_name | String | Name of the Elasticsearch index |
+| strategy | String | Strategy for vector search ("approximate_k_nearest_neighbors" or "script_scoring") |
+| distance_strategy | String | Strategy for distance calculation ("COSINE", "EUCLIDEAN_DISTANCE", "DOT_PRODUCT") |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search (default: 4) |
+
+### Outputs
+
+| Name | Type | Description |
+|------|------|-------------|
+| vector_store | ElasticsearchStore | Elasticsearch vector store instance |
+| search_results | List[Data] | Results of similarity search |
+
+## FAISS
+
+This component creates a FAISS Vector Store with search capabilities.
+For more information, see the [FAISS documentation](https://faiss.ai/index.html).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------------|---------------|--------------------------------------------------|
+| index_name | String | The name of the FAISS index. Default: "langflow_index". |
+| persist_directory | String | Path to save the FAISS index. It will be relative to where Langflow is running. |
+| search_query | String | The query to search for in the vector store. |
+| ingest_data | Data | The data to ingest into the vector store (list of Data objects or documents). |
+| allow_dangerous_deserialization | Boolean | Set to True to allow loading pickle files from untrusted sources. Default: True (advanced). |
+| embedding | Embeddings | The embedding function to use for the vector store. |
+| number_of_results | Integer | Number of results to return from the search. Default: 4 (advanced). |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------------------------|--------------------------------|
+| vector_store | FAISS | A FAISS vector store instance configured with the specified parameters. |
+
+## Hyper-Converged Database (HCD) Vector Store
+
+This component implements a Vector Store using HCD.
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| collection_name | Collection Name | The name of the collection within HCD where the vectors will be stored (required) |
+| username | HCD Username | Authentication username for accessing HCD (default: "hcd-superuser", required) |
+| password | HCD Password | Authentication password for accessing HCD (required) |
+| api_endpoint | HCD API Endpoint | API endpoint URL for the HCD service (required) |
+| search_input | Search Input | Query string for similarity search |
+| ingest_data | Ingest Data | Data to be ingested into the vector store |
+| namespace | Namespace | Optional namespace within HCD to use for the collection (default: "default_namespace") |
+| ca_certificate | CA Certificate | Optional CA certificate for TLS connections to HCD |
+| metric | Metric | Optional distance metric for vector comparisons (options: "cosine", "dot_product", "euclidean") |
+| batch_size | Batch Size | Optional number of data to process in a single batch |
+| bulk_insert_batch_concurrency | Bulk Insert Batch Concurrency | Optional concurrency level for bulk insert operations |
+| bulk_insert_overwrite_concurrency | Bulk Insert Overwrite Concurrency | Optional concurrency level for bulk insert operations that overwrite existing data |
+| bulk_delete_concurrency | Bulk Delete Concurrency | Optional concurrency level for bulk delete operations |
+| setup_mode | Setup Mode | Configuration mode for setting up the vector store (options: "Sync", "Async", "Off", default: "Sync") |
+| pre_delete_collection | Pre Delete Collection | Boolean flag to determine whether to delete the collection before creating a new one |
+| metadata_indexing_include | Metadata Indexing Include | Optional list of metadata fields to include in the indexing |
+| embedding | Embedding or Astra Vectorize | Allows either an embedding model or an Astra Vectorize configuration |
+| metadata_indexing_exclude | Metadata Indexing Exclude | Optional list of metadata fields to exclude from the indexing |
+| collection_indexing_policy | Collection Indexing Policy | Optional dictionary defining the indexing policy for the collection |
+| number_of_results | Number of Results | Number of results to return in similarity search (default: 4) |
+| search_type | Search Type | Search type to use (options: "Similarity", "Similarity with score threshold", "MMR (Max Marginal Relevance)", default: "Similarity") |
+| search_score_threshold | Search Score Threshold | Minimum similarity score threshold for search results (default: 0) |
+| search_filter | Search Metadata Filter | Optional dictionary of filters to apply to the search query |
+
+### Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| vector_store | Vector Store | An HCD vector store instance The results of the similarity search as a list of `Data` objects.|
+| search_results | Search Results | The results of the similarity search as a list of `Data` objects. |
+
+## Milvus
+
+This component creates a Milvus Vector Store with search capabilities.
+For more information, see the [Milvus documentation](https://milvus.io/docs).
+
+### Inputs
+
+| Name | Type | Description |
+|-------------------------|---------------|--------------------------------------------------|
+| collection_name | String | Name of the Milvus collection |
+| collection_description | String | Description of the Milvus collection |
+| uri | String | Connection URI for Milvus |
+| password | SecretString | Password for Milvus |
+| username | SecretString | Username for Milvus |
+| batch_size | Integer | Number of data to process in a single batch |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+| search_type | String | Type of search to perform |
+| search_score_threshold | Float | Minimum similarity score for search results |
+| search_filter | Dict | Metadata filters for search query |
+| setup_mode | String | Configuration mode for setting up the vector store |
+| vector_dimensions | Integer | Number of dimensions of the vectors |
+| pre_delete_collection | Boolean | Whether to delete the collection before creating a new one |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------------------------|--------------------------------|
+| vector_store | Milvus | A Milvus vector store instance configured with the specified parameters. |
+
+## MongoDB Atlas
+
+This component creates a MongoDB Atlas Vector Store with search capabilities.
+For more information, see the [MongoDB Atlas documentation](https://www.mongodb.com/docs/atlas/atlas-vector-search/tutorials/vector-search-quick-start/).
+
+### Inputs
+
+| Name | Type | Description |
+| ------------------------ | ------------ | ----------------------------------------- |
+| mongodb_atlas_cluster_uri | SecretString | MongoDB Atlas Cluster URI |
+| db_name | String | Database name |
+| collection_name | String | Collection name |
+| index_name | String | Index name |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ---------------------- | ----------------------------------------- |
+| vector_store | MongoDBAtlasVectorSearch| MongoDB Atlas vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+## Opensearch
+
+This component creates an Opensearch vector store with search capabilities
+For more information, see [Opensearch documentation](https://opensearch.org/platform/search/vector-database.html).
+
+### Inputs
+
+| Name | Type | Description |
+|------------------------|--------------|------------------------------------------------------------------------------------------------------------------------|
+| opensearch_url | String | URL for OpenSearch cluster (e.g. https://192.168.1.1:9200) |
+| index_name | String | The index name where the vectors will be stored in OpenSearch cluster |
+| search_input | String | Enter a search query. Leave empty to retrieve all documents or if hybrid search is being used |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| search_type | String | Valid values are "similarity", "similarity_score_threshold", "mmr" |
+| number_of_results | Integer | Number of results to return in search |
+| search_score_threshold | Float | Minimum similarity score threshold for search results |
+| username | String | username for the opensource cluster |
+| password | SecretString | password for the opensource cluster |
+| use_ssl | Boolean | Use SSL |
+| verify_certs | Boolean | Verify certificates |
+| hybrid_search_query | String | Provide a custom hybrid search query in JSON format. This allows you to combine vector similarity and keyword matching |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- |------------------------|---------------------------------------------|
+| vector_store | OpenSearchVectorSearch | OpenSearch vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+## PGVector
+
+This component creates a PGVector Vector Store with search capabilities.
+For more information, see the [PGVector documentation](https://github.com/pgvector/pgvector).
+
+### Inputs
+
+| Name | Type | Description |
+| --------------- | ------------ | ----------------------------------------- |
+| pg_server_url | SecretString | PostgreSQL server connection string |
+| collection_name | String | Table name for the vector store |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ----------- | ----------------------------------------- |
+| vector_store | PGVector | PGVector vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+
+## Pinecone
+
+This component creates a Pinecone Vector Store with search capabilities.
+For more information, see the [Pinecone documentation](https://docs.pinecone.io/home).
+
+### Inputs
+
+| Name | Type | Description |
+| ----------------- | ------------ | ----------------------------------------- |
+| index_name | String | Name of the Pinecone index |
+| namespace | String | Namespace for the index |
+| distance_strategy | String | Strategy for calculating distance between vectors |
+| pinecone_api_key | SecretString | API key for Pinecone |
+| text_key | String | Key in the record to use as text |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ---------- | ----------------------------------------- |
+| vector_store | Pinecone | Pinecone vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+
+## Qdrant
+
+This component creates a Qdrant Vector Store with search capabilities.
+For more information, see the [Qdrant documentation](https://qdrant.tech/documentation/).
+
+### Inputs
+
+| Name | Type | Description |
+| -------------------- | ------------ | ----------------------------------------- |
+| collection_name | String | Name of the Qdrant collection |
+| host | String | Qdrant server host |
+| port | Integer | Qdrant server port |
+| grpc_port | Integer | Qdrant gRPC port |
+| api_key | SecretString | API key for Qdrant |
+| prefix | String | Prefix for Qdrant |
+| timeout | Integer | Timeout for Qdrant operations |
+| path | String | Path for Qdrant |
+| url | String | URL for Qdrant |
+| distance_func | String | Distance function for vector similarity |
+| content_payload_key | String | Key for content payload |
+| metadata_payload_key | String | Key for metadata payload |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | -------- | ----------------------------------------- |
+| vector_store | Qdrant | Qdrant vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+
+## Redis
+
+This component creates a Redis Vector Store with search capabilities.
+For more information, see the [Redis documentation](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/vectors/).
+
+### Inputs
+
+| Name | Type | Description |
+| ----------------- | ------------ | ----------------------------------------- |
+| redis_server_url | SecretString | Redis server connection string |
+| redis_index_name | String | Name of the Redis index |
+| code | String | Custom code for Redis (advanced) |
+| schema | String | Schema for Redis index |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| number_of_results | Integer | Number of results to return in search |
+| embedding | Embeddings | Embedding function to use |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | -------- | ----------------------------------------- |
+| vector_store | Redis | Redis vector store instance |
+| search_results| List[Data]| Results of similarity search |
+
+
+## Supabase
+
+This component creates a connection to a Supabase Vector Store with search capabilities.
+For more information, see the [Supabase documentation](https://supabase.com/docs/guides/ai).
+
+### Inputs
+
+| Name | Type | Description |
+| ------------------- | ------------ | ----------------------------------------- |
+| supabase_url | String | URL of the Supabase instance |
+| supabase_service_key| SecretString | Service key for Supabase authentication |
+| table_name | String | Name of the table in Supabase |
+| query_name | String | Name of the query to use |
+| search_query | String | Query for similarity search |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ------------------ | ----------------------------------------- |
+| vector_store | SupabaseVectorStore | Supabase vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+
+## Upstash
+
+This component creates an Upstash Vector Store with search capabilities.
+For more information, see the [Upstash documentation](https://upstash.com/docs/introduction).
+
+### Inputs
+
+| Name | Type | Description |
+| --------------- | ------------ | ----------------------------------------- |
+| index_url | String | The URL of the Upstash index |
+| index_token | SecretString | The token for the Upstash index |
+| text_key | String | The key in the record to use as text |
+| namespace | String | Namespace for the index |
+| search_query | String | Query for similarity search |
+| metadata_filter | String | Filters documents by metadata |
+| ingest_data | Data | Data to be ingested into the vector store |
+| embedding | Embeddings | Embedding function to use (optional) |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ---------------- | ----------------------------------------- |
+| vector_store | UpstashVectorStore| Upstash vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+
+## Vectara
+
+This component creates a Vectara Vector Store with search capabilities.
+For more information, see the [Vectara documentation](https://docs.vectara.com/docs/).
+
+### Inputs
+
+| Name | Type | Description |
+| ---------------- | ------------ | ----------------------------------------- |
+| vectara_customer_id | String | Vectara customer ID |
+| vectara_corpus_id | String | Vectara corpus ID |
+| vectara_api_key | SecretString | Vectara API key |
+| embedding | Embeddings | Embedding function to use (optional) |
+| ingest_data | List[Document/Data] | Data to be ingested into the vector store |
+| search_query | String | Query for similarity search |
+| number_of_results | Integer | Number of results to return in search |
+
+### Outputs
+
+| Name | Type | Description |
+| ------------- | ----------------- | ----------------------------------------- |
+| vector_store | VectaraVectorStore | Vectara vector store instance |
+| search_results| List[Data] | Results of similarity search |
+
+## Vectara Search
+
+This component searches a Vectara Vector Store for documents based on the provided input.
+For more information, see the [Vectara documentation](https://docs.vectara.com/docs/).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------------|--------------|-------------------------------------------|
+| search_type | String | Type of search, such as "Similarity" or "MMR" |
+| input_value | String | Search query |
+| vectara_customer_id | String | Vectara customer ID |
+| vectara_corpus_id | String | Vectara corpus ID |
+| vectara_api_key | SecretString | Vectara API key |
+| files_url | List[String] | Optional URLs for file initialization |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------------|----------------------------|
+| search_results | List[Data] | Results of similarity search |
+
+## Vectara RAG
+
+This component leverages Vectara's Retrieval Augmented Generation (RAG) capabilities to search and summarize documents based on the provided input. For more information, see the [Vectara documentation](https://docs.vectara.com/docs/).
+
+### Inputs
+
+| Name | Type | Description |
+|-----------------------|--------------|------------------------------------------------------------|
+| vectara_customer_id | String | Vectara customer ID |
+| vectara_corpus_id | String | Vectara corpus ID |
+| vectara_api_key | SecretString | Vectara API key |
+| search_query | String | The query to receive an answer on |
+| lexical_interpolation | Float | Hybrid search factor (0.005 to 0.1) |
+| filter | String | Metadata filters to narrow the search |
+| reranker | String | Reranker type (mmr, rerank_multilingual_v1, none) |
+| reranker_k | Integer | Number of results to rerank (1 to 100) |
+| diversity_bias | Float | Diversity bias for MMR reranker (0 to 1) |
+| max_results | Integer | Maximum number of search results to summarize (1 to 100) |
+| response_lang | String | Language code for the response (for example, "eng", "auto") |
+| prompt | String | Prompt name for summarization |
+
+### Outputs
+
+| Name | Type | Description |
+|--------|---------|-----------------------|
+| answer | Message | Generated RAG response|
+
+## Weaviate
+
+This component facilitates a Weaviate Vector Store setup, optimizing text and document indexing and retrieval.
+For more information, see the [Weaviate Documentation](https://weaviate.io/developers/weaviate).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------|--------------|-------------------------------------------|
+| weaviate_url | String | Default instance URL |
+| search_by_text| Boolean | Indicates whether to search by text |
+| api_key | SecretString | Optional API key for authentication |
+| index_name | String | Optional index name |
+| text_key | String | Default text extraction key |
+| input | Document | Document or record |
+| embedding | Embeddings | Model used |
+| attributes | List[String] | Optional additional attributes |
+
+### Outputs
+
+| Name | Type | Description |
+|--------------|------------------|-------------------------------|
+| vector_store | WeaviateVectorStore | Weaviate vector store instance |
+
+## Weaviate Search
+
+This component searches a Weaviate Vector Store for documents similar to the input.
+For more information, see the [Weaviate Documentation](https://weaviate.io/developers/weaviate).
+
+### Inputs
+
+| Name | Type | Description |
+|---------------|--------------|-------------------------------------------|
+| search_type | String | Type of search, such as "Similarity" or "MMR" |
+| input_value | String | Search query |
+| weaviate_url | String | Default instance URL |
+| search_by_text| Boolean | Indicates whether to search by text |
+| api_key | SecretString | Optional API key for authentication |
+| index_name | String | Optional index name |
+| text_key | String | Default text extraction key |
+| embedding | Embeddings | Model used |
+| attributes | List[String] | Optional additional attributes |
+
+### Outputs
+
+| Name | Type | Description |
+|----------------|------------|----------------------------|
+| search_results | List[Data] | Results of similarity search |
+
+
+
diff --git a/langflow/docs/docs/Concepts/concepts-api.md b/langflow/docs/docs/Concepts/concepts-api.md
new file mode 100644
index 0000000..ff66841
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-api.md
@@ -0,0 +1,234 @@
+---
+title: API pane
+slug: /concepts-api
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The **API** pane presents code templates for integrating your flow into external applications.
+
+
+
+
+
+
+
+The **cURL** tab displays sample code for posting a query to your flow. Modify the `input_value` to change your input message. Copy the code and run it to post a query to your flow and get the result.
+
+
+
+
+
+The **Python API** tab displays code to interact with your flow using the Python HTTP `requests` library.
+
+To use the `requests` library:
+
+1. Copy and paste the code into a Python script.
+2. Run the script and pass your message with it.
+
+```python
+python3 python-test-script.py --message="tell me about something interesting"
+```
+
+
+
+
+
+The **JavaScript API** tab displays code to interact with your flow in JavaScript.
+
+1. Copy and paste the code into a JavaScript file.
+2. Run the script with any necessary arguments for your flow:
+
+```plain
+node test-script.js "tell me about something interesting"
+```
+
+The response content depends on your flow. Make sure the endpoint returns a successful response.
+
+
+
+
+
+
+The **Python Code** tab displays code to interact with your flow's `.json` file using the Langflow runtime.
+
+To use your code in a Python application using the Langflow runtime, you have to first download your flow’s JSON file.
+
+1. In your **Workspace**, click **Settings**, and then select **Export**.
+
+2. Download the flow to your local machine. Make sure the flow path in the script matches the flow’s location on your machine.
+
+3. Copy and paste the code from the **Python Code** tab into a Python script file.
+
+4. Run the script:
+
+```python
+python python-test-script.py
+```
+
+
+
+
+
+## Chat Widget
+
+The **Chat Widget HTML** tab displays code that can be inserted in the `` of your HTML to interact with your flow.
+
+The **Langflow Chat Widget** is a powerful web component that enables communication with a Langflow project. This widget allows for a chat interface embedding, allowing the integration of Langflow into web applications effortlessly.
+
+You can get the HTML code embedded with the chat by clicking the Code button at the Sidebar after building a flow.
+
+Clicking the Chat Widget HTML tab, you'll get the code to be inserted. Read below to learn how to use it with HTML, React and Angular.
+
+### Embed the chat widget into HTML
+
+To embed the chat widget into any HTML page, insert the code snippet. inside a `` tag.
+
+```html
+
+
+
+```
+
+### Embed the chat widget with React
+
+To embed the Chat Widget using React, insert this `
+```
+
+Declare your Web Component and encapsulate it in a React component.
+
+```javascript
+declare global {
+ namespace JSX {
+ interface IntrinsicElements {
+ "langflow-chat": any;
+ }
+ }
+}
+
+export default function ChatWidget({ className }) {
+ return (
+
+
+
+ );
+}
+```
+
+Place the component anywhere in your code to display the Chat Widget.
+
+### Embed the chat widget with Angular
+
+To use the chat widget in Angular, first add this `
+```
+
+When you use a custom web component in an Angular template, the Angular compiler might show a warning when it doesn't recognize the custom elements by default. To suppress this warning, add `CUSTOM_ELEMENTS_SCHEMA` to the module's `@NgModule.schemas`.
+
+- Open the module file (it typically ends with _.module.ts_) where you'd add the `langflow-chat` web component.
+- Import `CUSTOM_ELEMENTS_SCHEMA` at the top of the file:
+
+`import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';`
+
+- Add `CUSTOM_ELEMENTS_SCHEMA` to the 'schemas' array inside the '@NgModule' decorator:
+
+```javascript
+@NgModule({
+ declarations: [
+ // ... Other components and directives ...
+ ],
+ imports: [
+ // ... Other imported modules ...
+ ],
+ schemas: [
+ CUSTOM_ELEMENTS_SCHEMA // Add the CUSTOM_ELEMENTS_SCHEMA here
+ ]
+})
+export class YourModule { }
+```
+
+In your Angular project, find the component belonging to the module where `CUSTOM_ELEMENTS_SCHEMA` was added. Inside the template, add the `langflow-chat` tag to include the Chat Widget in your component's view:
+
+```javascript
+
+```
+
+:::tip
+
+`CUSTOM_ELEMENTS_SCHEMA` is a built-in schema that allows Angular to recognize custom elements. Adding `CUSTOM_ELEMENTS_SCHEMA` tells Angular to allow custom elements in your templates, and it will suppress the warning related to unknown elements like `langflow-chat`. Notice that you can only use the Chat Widget in components that are part of the module where you added `CUSTOM_ELEMENTS_SCHEMA`.
+
+:::
+
+## Chat widget configuration
+
+Use the widget API to customize your Chat Widget:
+
+:::caution
+Props with the type JSON need to be passed as stringified JSONs, with the format \{"key":"value"\}.
+:::
+
+
+| Prop | Type | Required | Description |
+| --------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| bot_message_style | JSON | No | Applies custom formatting to bot messages. |
+| chat_input_field | String | Yes | Defines the type of the input field for chat messages. |
+| chat_inputs | JSON | Yes | Determines the chat input elements and their respective values. |
+| chat_output_key | String | No | Specifies which output to display if multiple outputs are available. |
+| chat_position | String | No | Positions the chat window on the screen (options include: top-left, top-center, top-right, center-left, center-right, bottom-right, bottom-center, bottom-left). |
+| chat_trigger_style | JSON | No | Styles the chat trigger button. |
+| chat_window_style | JSON | No | Customizes the overall appearance of the chat window. |
+| error_message_style | JSON | No | Sets the format for error messages within the chat window. |
+| flow_id | String | Yes | Identifies the flow that the component is associated with. |
+| height | Number | No | Sets the height of the chat window in pixels. |
+| host_url | String | Yes | Specifies the URL of the host for chat component communication. |
+| input_container_style | JSON | No | Applies styling to the container where chat messages are entered. |
+| input_style | JSON | No | Sets the style for the chat input field. |
+| online | Boolean | No | Toggles the online status of the chat component. |
+| online_message | String | No | Sets a custom message to display when the chat component is online. |
+| placeholder | String | No | Sets the placeholder text for the chat input field. |
+| placeholder_sending | String | No | Sets the placeholder text to display while a message is being sent. |
+| send_button_style | JSON | No | Sets the style for the send button in the chat window. |
+| send_icon_style | JSON | No | Sets the style for the send icon in the chat window. |
+| tweaks | JSON | No | Applies additional custom adjustments for the associated flow. |
+| user_message_style | JSON | No | Determines the formatting for user messages in the chat window. |
+| width | Number | No | Sets the width of the chat window in pixels. |
+| window_title | String | No | Sets the title displayed in the chat window's header or title bar. |
+
+
+## Tweaks
+
+The **Tweaks** tab displays the available parameters for your flow. Modifying the parameters changes the code parameters across all windows. For example, changing the **Chat Input** component's `input_value` will change that value across all API calls.
+
+## Send image files to your flow with the API
+
+For information on sending files to the Langflow API, see [API examples](/api-reference-api-examples#upload-image-files).
+
+## Webhook cURL
+
+When a **Webhook** component is added to the workspace, a new **Webhook cURL** tab becomes available in the **API** pane that contains an HTTP POST request for triggering the webhook component. For example:
+
+```bash
+curl -X POST \
+ "http://127.0.0.1:7860/api/v1/webhook/**YOUR_FLOW_ID**" \
+ -H 'Content-Type: application/json'\
+ -d '{"any": "data"}'
+ ```
+
+To test the **Webhook** component in your flow, see the [Webhook component](/components-data#webhook).
+
diff --git a/langflow/docs/docs/Concepts/concepts-components.md b/langflow/docs/docs/Concepts/concepts-components.md
new file mode 100644
index 0000000..8483127
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-components.md
@@ -0,0 +1,225 @@
+---
+title: Components
+slug: /concepts-components
+---
+
+import Icon from "@site/src/components/icon";
+
+# Langflow components overview
+
+A component is a single building block within a flow with inputs, outputs, functions, and parameters that define its functionality. A single component is like a class within a larger application.
+
+To add a component to a flow, drag it from the **Component** menu to the **Workspace**.
+
+Learn more about components and how they work on this page.
+
+## Component menu
+
+Each component is unique, but all have a menu bar at the top that looks something like the following:
+
+
+
+Use the component controls to do the following:
+
+- **Code** — Modify the component's Python code and save your changes.
+- **Controls** — Adjust all component parameters.
+- **Freeze Path** — After a component runs, lock its previous output state to prevent it from re-running.
+
+Click **All** to see additional options for a component.
+
+To view a component’s output and logs, click the icon.
+
+To run a single component, click **Play**.
+
+A **Checkmark** indicates that the component ran successfully.
+
+Running a single component with the **Play** button is different from running the entire flow. In a single component run, the `build_vertex` function is called, which builds and runs only the single component with direct inputs provided through the UI (the `inputs_dict` parameter). The `VertexBuildResult` data is passed to the `build_and_run` method, which calls the component's `build` method and runs it. Unlike running the full flow, running a single component does not automatically execute its upstream dependencies.
+
+## Component ports
+
+Handles ( ) on the side of a component indicate the types of inputs and outputs that can be connected at that port. Hover over a handle to see connection details.
+
+
+
+### Component port data type colors
+
+The following table lists the handle colors and their corresponding data types:
+
+| Data type | Handle color | Handle |
+|-----------|--------------|----------|
+| BaseLanguageModel | Fuchsia | |
+| Data | Red | |
+| Document | Lime | |
+| Embeddings | Emerald | |
+| LanguageModel | Fuchsia | |
+| Message | Indigo | |
+| Prompt | Violet | |
+| str | Indigo | |
+| Text | Indigo | |
+| unknown | Gray | |
+
+## Component code
+
+A component inherits from a base `Component` class that defines its interface and behavior.
+
+For example, the [Recursive character text splitter](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/langchain_utilities/recursive_character.py) is a child of the [LCTextSplitterComponent](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/base/textsplitters/model.py) class.
+
+```python
+from typing import Any
+
+from langchain_text_splitters import RecursiveCharacterTextSplitter, TextSplitter
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.inputs.inputs import DataInput, IntInput, MessageTextInput
+from langflow.utils.util import unescape_string
+
+class RecursiveCharacterTextSplitterComponent(LCTextSplitterComponent):
+ display_name: str = "Recursive Character Text Splitter"
+ description: str = "Split text trying to keep all related text together."
+ documentation: str = "https://docs.langflow.org/components-processing"
+ name = "RecursiveCharacterTextSplitter"
+ icon = "LangChain"
+
+ inputs = [
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum length of each chunk.",
+ value=1000,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="The amount of overlap between chunks.",
+ value=200,
+ ),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The texts to split.",
+ input_types=["Document", "Data"],
+ ),
+ MessageTextInput(
+ name="separators",
+ display_name="Separators",
+ info='The characters to split on.\nIf left empty defaults to ["\\n\\n", "\\n", " ", ""].',
+ is_list=True,
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_text_splitter(self) -> TextSplitter:
+ if not self.separators:
+ separators: list[str] | None = None
+ else:
+ # check if the separators list has escaped characters
+ # if there are escaped characters, unescape them
+ separators = [unescape_string(x) for x in self.separators]
+
+ return RecursiveCharacterTextSplitter(
+ separators=separators,
+ chunk_size=self.chunk_size,
+ chunk_overlap=self.chunk_overlap,
+ )
+
+```
+
+Components include definitions for inputs and outputs, which are represented in the UI with color-coded ports.
+
+**Input Definition:** Each input (like `IntInput` or `DataInput`) specifies an input's type, name, and display properties, which appear as configurable fields in the component's UI panel.
+
+**Methods:** Components have methods or functions that handle their functionality. This component has two methods.
+`get_data_input` retrieves the text data to be split from the component's input. This makes the data available to the class.
+`build_text_splitter` creates a `RecursiveCharacterTextSplitter` object by calling its parent class's `build` method. The text is split with the created splitter and passed to the next component.
+When used in a flow, this component:
+
+1. Displays its configuration options in the UI.
+2. Validates user inputs based on the input types.
+3. Processes data using the configured parameters.
+4. Passes results to the next component.
+
+## Freeze path
+
+After a component runs, **Freeze Path** locks the component's previous output state to prevent it from re-running.
+
+If you’re expecting consistent output from a component and don’t need to re-run it, click **Freeze Path**.
+
+Enabling **Freeze Path** freezes all components upstream of the selected component.
+
+If you only want to freeze a single component, select **Freeze** instead.
+
+A icon appears on all frozen components.
+
+## Additional component options
+
+Click **All** to see additional options for a component.
+
+To modify a component's name or description, double-click in the **Name** or **Description** fields. Component descriptions accept Markdown syntax.
+
+### Component shortcuts
+
+The following keyboard shortcuts are available when a component is selected.
+
+| Menu item | Windows shortcut | Mac shortcut | Description |
+|-----------|-----------------|--------------|-------------|
+| Code | Space | Space | Opens the code editor for the component. |
+| Advanced Settings | Ctrl + Shift + A | ⌘ + Shift + A | Opens advanced settings for the component. |
+| Save Changes | Ctrl + S | ⌘ + S | Saves changes to the current flow. |
+| Save Component | Ctrl + Alt + S | ⌘ + Alt + S | Saves the current component to Saved components. |
+| Duplicate | Ctrl + D | ⌘ + D | Creates a duplicate of the component. |
+| Copy | Ctrl + C | ⌘ + C | Copies the selected component. |
+| Cut | Ctrl + X | ⌘ + X | Cuts the selected component. |
+| Paste | Ctrl + V | ⌘ + V | Pastes the copied/cut component. |
+| Docs | Ctrl + Shift + D | ⌘ + Shift + D | Opens related documentation. |
+| Minimize | Ctrl + . | ⌘ + . | Minimizes the current component. |
+| Freeze | Ctrl + F | ⌘ + F | Freezes the current component state. |
+| Freeze Path | Ctrl + Shift + F | ⌘ + Shift + F | Freezes component state and upstream components. |
+| Download | Ctrl + J | ⌘ + J | Downloads the component as JSON. |
+| Delete | Backspace | Backspace | Deletes the component. |
+| Group | Ctrl + G | ⌘ + G | Groups selected components. |
+| Undo | Ctrl + Z | ⌘ + Z | Undoes the last action. |
+| Redo | Ctrl + Y | ⌘ + Y | Redoes the last undone action. |
+| Redo (alternative) | Ctrl + Shift + Z | ⌘ + Shift + Z | Alternative shortcut for redo. |
+| Share Component | Ctrl + Shift + S | ⌘ + Shift + S | Shares the component. |
+| Share Flow | Ctrl + Shift + B | ⌘ + Shift + B | Shares the entire flow. |
+| Toggle Sidebar | Ctrl + B | ⌘ + B | Shows/hides the sidebar. |
+| Search Components | / | / | Focuses the component search bar. |
+| Tool Mode | Ctrl + Shift + M | ⌘ + Shift + M | Toggles tool mode. |
+| Update | Ctrl + U | ⌘ + U | Updates the component. |
+| Open Playground | Ctrl + K | ⌘ + K | Opens the playground. |
+| Output Inspection | O | O | Opens output inspection. |
+| Play | P | P | Plays/executes the flow. |
+| API | R | R | Opens the API view. |
+
+## Group components in the workspace
+
+Multiple components can be grouped into a single component for reuse. This is useful when combining large flows into single components, for example RAG with a vector database, and saving space.
+
+1. Hold **Shift** and drag to select components.
+2. Select **Group**.
+The components merge into a single component.
+3. Double-click the name and description to change them.
+4. Save your grouped component to the sidebar for later use.
+
+## Component version
+
+A component's initial state is stored in a database. As soon as you drag a component from the sidebar to the workspace, the two components are no longer in parity.
+
+A component keeps the version number it is initialized to the workspace with. If a component is at version `1.0` when it is dragged to the workspace, it will stay at version `1.0` until you update it.
+
+Langflow notifies you when a component's workspace version is behind the database version and an update is available.
+Click the **Update Component** icon to update the component to the `latest` version. This will change the code of the component in place so you can validate that the component was updated by checking its Python code before and after updating it.
+
+## Components sidebar
+
+Components are listed in the sidebar by component type.
+
+Component **bundles** are components grouped by provider. For example, Langchain modules like **RunnableExecutor** and **CharacterTextSplitter** are grouped under the **Langchain** bundle.
+
+The sidebar includes a component **Search** bar, and includes flags for showing or hiding **Beta** and **Legacy** components.
+
+**Beta** components are still being tested and are not suitable for production workloads.
+
+**Legacy** components are available to use but no longer supported.
diff --git a/langflow/docs/docs/Concepts/concepts-flows.md b/langflow/docs/docs/Concepts/concepts-flows.md
new file mode 100644
index 0000000..182a997
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-flows.md
@@ -0,0 +1,15 @@
+# Flows
+
+Flows in Langflow are fully serializable and can be saved and loaded from the file system. In this guide, we'll explore how to import and export flows.
+
+## Import Flow
+
+If you've already got a Langflow JSON file, import it into Langflow by clicking on the project name and choosing **Import Flow**.
+
+
+
+Once imported, your flow is ready to use.
+
+## Export Flow
+
+The option to export a flow is available in the same menu as shown above. Once exported as JSON, you can import your flow into another Langflow instance.
diff --git a/langflow/docs/docs/Concepts/concepts-objects.md b/langflow/docs/docs/Concepts/concepts-objects.md
new file mode 100644
index 0000000..ce602be
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-objects.md
@@ -0,0 +1,206 @@
+---
+title: Langflow objects
+slug: /concepts-objects
+---
+
+In Langflow, objects are [Pydantic](https://docs.pydantic.dev/latest/api/base_model/) models that serve as structured, functional representations of data.
+
+## Data object
+
+The `Data` object is a [Pydantic](https://docs.pydantic.dev/latest/api/base_model/) model that serves as a container for storing and manipulating data. It carries `data`—a dictionary that can be accessed as attributes—and uses `text_key` to specify which key in the dictionary should be considered the primary text content.
+
+- **Main Attributes:**
+ - `text_key`: Specifies the key to retrieve the primary text data.
+ - `data`: A dictionary to store additional data.
+ - `default_value`: default value when the `text_key` is not present in the `data` dictionary.
+
+### Create a Data Object
+
+Create a `Data` object by directly assigning key-value pairs to it. For example:
+
+```python
+from langflow.schema import Data
+
+# Creating a Data object with specified key-value pairs
+data = Data(text="my_string", bar=3, foo="another_string")
+
+# Outputs:
+print(data.text) # Outputs: "my_string"
+print(data.bar) # Outputs: 3
+print(data.foo) # Outputs: "another_string"
+```
+
+The `text_key` specifies which key in the `data` dictionary should be considered the primary text content. The `default_value` provides a fallback if the `text_key` is not present.
+
+```python
+# Creating a Data object with a specific text_key and default_value
+data = Data(data={"title": "Hello, World!"}, text_key="content", default_value="No content available")
+
+# Accessing the primary text using text_key and default_value
+print(data.get_text()) # Outputs: "No content available" because "content" key is not in the data dictionary
+
+# Accessing data keys by calling the attribute directly
+print(data.title) # Outputs: "Hello, World!" because "title" key is in the data dictionary
+```
+
+The `Data` object is also convenient for visualization of outputs, since the output preview has visual elements to inspect data as a table and its cells as pop ups for basic types. The idea is to create a unified way to work and visualize complex information in Langflow.
+
+To receive `Data` objects in a component input, use the `DataInput` input type.
+
+```python
+inputs = [
+ DataInput(name="data", display_name="Data", info="Helpful info about the incoming data object.", is_list=True),
+]
+```
+
+## Message object
+
+The `Message` object extends the functionality of `Data` and includes additional attributes and methods for chat interactions.
+
+- **Core message data:**
+
+ - `text`: The main text content of the message
+ - `sender`: Identifier for the sender ("User" or "AI")
+ - `sender_name`: Name of the sender
+ - `session_id`: Identifier for the chat session (`string` or `UUID`)
+ - `timestamp`: Timestamp when the message was created (UTC)
+ - `flow_id`: Identifier for the flow (`string` or `UUID`)
+ - `id`: Unique identifier for the message
+
+- **Content and files:**
+
+ - `files`: List of files or images associated with the message
+ - `content_blocks`: List of structured content block objects
+ - `properties`: Additional properties including visual styling and source information
+
+- **Message state:**
+ - `error`: Boolean indicating if there was an error
+ - `edit`: Boolean indicating if the message was edited
+ - `category`: Message category ("message", "error", "warning", "info")
+
+The `Message` object can be used to send, store, and manipulate chat messages within Langflow.
+
+### Create a Message object
+
+You can create a `Message` object by directly assigning key-value pairs to it. For example:
+
+```python
+from langflow.schema.message import Message
+
+message = Message(text="Hello, AI!", sender="User", sender_name="John Doe")
+```
+
+To receive `Message` objects in a component input, you can use the `MessageInput` input type or `MessageTextInput` when the goal is to extract just the `text` field of the `Message` object.
+
+## ContentBlock object
+
+The `ContentBlock` object is a list of multiple `ContentTypes`. It allows you to include multiple types of content within a single `Message`, including images, videos, and text.
+
+Content types are Pydantic base classes constructed from the types in [content_types.py](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/schema/content_types.py).
+
+Each content type has specific fields related to its data type. For example:
+
+* `TextContent` has a `text` field for storing strings of text
+* `MediaContent` has a `urls` field for storing media file URLs
+* `CodeContent` has `code` and `language` fields for code snippets
+* `JSONContent` has a `data` field for storing arbitrary JSON data
+* `ToolContent` has a `tool_input` field for storing input parameters for the tool
+
+### Create a ContentBlock object
+
+Create a `ContentBlock` object with a list of different content types.
+
+```python
+content_block = ContentBlock(
+ title="Mixed Content Example",
+ contents=[
+ TextContent(text="This is a text content"),
+ MediaContent(urls=["http://example.com/image.jpg"]),
+ JSONContent(data={"key": "value"}),
+ CodeContent(code="print('Hello')", language="python")
+ ],
+ media_url=["http://example.com/additional_image.jpg"]
+)
+```
+
+### Add ContentBlocks objects to a message
+
+In this example, a text and a media `ContentBlock` are added to a message.
+
+```python
+from langflow.schema.message import Message
+from langflow.schema.content_block import ContentBlock
+from langflow.schema.content_types import TextContent, MediaContent
+
+message = Message(
+ text="Main message text",
+ sender="User",
+ sender_name="John Doe",
+ content_blocks=[
+ ContentBlock(
+ title="Text Block",
+ contents=[
+ TextContent(type="text", text="This is some text content")
+ ]
+ ),
+ ContentBlock(
+ title="Media Block",
+ contents=[
+ MediaContent(type="media", urls=["http://example.com/image.jpg"])
+ ]
+ )
+ ]
+)
+```
+
+## DataFrame object
+
+The `DataFrame` class is a custom extension of the Pandas [DataFrame](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) class, specifically designed to work seamlessly with Langflow's `Data` objects. The class includes methods for converting between `DataFrame` and lists of `Data` objects.
+
+A `DataFrame` object accepts various input formats, including lists of `Data` objects, dictionaries, and existing `DataFrames`.
+
+### Create a DataFrame object
+
+You can create a DataFrame object using different data formats:
+
+```python
+from langflow.schema import Data
+from langflow.schema.data import DataFrame
+
+# From a list of Data objects
+data_list = [Data(data={"name": "John"}), Data(data={"name": "Jane"})]
+df = DataFrame(data_list)
+
+# From a list of dictionaries
+dict_list = [{"name": "John"}, {"name": "Jane"}]
+df = DataFrame(dict_list)
+
+# From a dictionary of lists
+data_dict = {"name": ["John", "Jane"], "age": [30, 25]}
+df = DataFrame(data_dict)
+Key Methods
+to_data_list(): Converts the DataFrame back to a list of Data objects.
+add_row(data): Adds a single row (either a Data object or a dictionary) to the DataFrame.
+add_rows(data): Adds multiple rows (list of Data objects or dictionaries) to the DataFrame.
+Usage Example
+python
+# Create a DataFrame
+df = DataFrame([Data(data={"name": "John"}), Data(data={"name": "Jane"})])
+
+# Add a new row
+df = df.add_row({"name": "Alice"})
+
+# Convert back to a list of Data objects
+data_list = df.to_data_list()
+
+# Use pandas functionality
+filtered_df = df[df["name"].str.startswith("J")]
+```
+
+To use DataFrame objects in a component input,use the DataFrameInput input type.
+
+```python
+DataFrameInput(
+ name="dataframe_input", display_name="DataFrame Input", info="Input for DataFrame objects.", tool_mode=True
+),
+```
diff --git a/langflow/docs/docs/Concepts/concepts-overview.md b/langflow/docs/docs/Concepts/concepts-overview.md
new file mode 100644
index 0000000..0533b23
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-overview.md
@@ -0,0 +1,147 @@
+---
+title: Langflow overview
+slug: /concepts-overview
+---
+
+import Icon from "@site/src/components/icon";
+
+This page explores the fundamental building blocks of Langflow, beginning with the question, **"What is a flow?"**
+
+## What is a flow?
+
+A **flow** is an application. It receives input, processes it, and produces output.
+
+Flows are created in the **workspace** with components dragged from the components sidebar.
+
+
+
+A flow can be as simple as the [basic prompting flow](/get-started-quickstart), which creates an OpenAI chatbot with four components.
+
+* Each component in a flow is a **node** that performs a specific task, like an AI model or a data source.
+* Each component has a **Configuration** menu. Click the **Code** pane to see a component's underlying Python code.
+* Components are connected with **edges** to form flows.
+
+If you're familiar with [React Flow](https://reactflow.dev/learn), a **flow** is a node-based application, a **component** is a node, and the connections between components are **edges**.
+
+When a flow is run, Langflow builds a Directed Acyclic Graph (DAG) graph object from the nodes (components) and edges (connections between components), with the nodes sorted to determine the order of execution. The graph build calls the individual components' `def_build` functions to validate and prepare the nodes. This graph is then processed in dependency order. Each node is built and executed sequentially, with results from each built node being passed to nodes that are dependent on the previous node's results.
+
+Flows are stored on local disk at these default locations:
+
+* **Linux or WSL on Windows**: `home//.cache/langflow/`
+* **MacOS**: `/Users//Library/Caches/langflow/`
+
+The flow storage location can be customized with the [LANGFLOW_CONFIG_DIR](/environment-variables#LANGFLOW_CONFIG_DIR) environment variable.
+
+## Find your way around
+
+If you're new to Langflow, it's OK to feel a bit lost at first. We’ll take you on a tour, so you can orient yourself and start creating applications quickly.
+
+Langflow has four distinct regions: the [workspace](#workspace) is the main area where you build your flows. The components sidebar is on the left, and lists the available [components](#components). The [playground](#playground) and [API pane](#api-pane) are available in the upper right corner.
+
+
+
+## Workspace
+
+The **workspace** is where you create AI applications by connecting and running components in flows.
+
+The workspace controls allow you to adjust your view and lock your flows in place.
+
+* Add **Notes** to flows with the **Add Note** button, similar to commenting in code.
+* To access the [Settings](#settings) menu, click **Settings**.
+
+This menu contains configuration for **Global Variables**, **Langflow API**, **Shortcuts**, and **Messages**.
+
+## Components
+
+A **component** is a single building block within a flow and consists of inputs, outputs, and parameters that define its functionality.
+
+To add a component to your flow, drag it from the sidebar onto the workspace.
+
+To connect components, drag a line from the output handle (⚪) of one component to the input handle of another.
+
+For more information, see [Components overview](/concepts-components).
+
+
+
+## Playground
+
+The **Playground** executes the current flow in the workspace.
+
+Chat with your flow, view inputs and outputs, and modify your AI's memories to tune your responses in real time.
+
+Either the **Chat Input** or **Chat Output** component can be opened in the **Playground** and tested in real time.
+
+For more information, see the [Playground](/concepts-playground).
+
+
+
+## API pane {#api-pane}
+
+The **API** pane provides code templates to integrate your flows into external applications.
+
+For more information, see the [API pane](/concepts-api).
+
+
+
+## View logs
+
+The **Logs** pane provides a detailed record of all component executions within a workspace.
+
+To access the **Logs** pane, click your **Flow Name**, and then select **Logs**.
+
+
+
+Langflow stores logs at the location specified in the `LANGFLOW_CONFIG_DIR` environment variable.
+
+This directory's default location depends on your operating system.
+
+* **Linux/WSL**: `~/.cache/langflow/`
+* **macOS**: `/Users//Library/Caches/langflow/`
+* **Windows**: `%LOCALAPPDATA%\langflow\langflow\Cache`
+
+To modify the location of your log file:
+
+1. Add `LANGFLOW_LOG_FILE=path/to/logfile.log` in your `.env.` file.
+2. To start Langflow with the values from your `.env` file, start Langflow with `uv run langflow run --env-file .env`.
+
+An example `.env` file is available in the [project repository](https://github.com/langflow-ai/langflow/blob/main/.env.example).
+
+## Projects and folders
+
+The **My Projects** page displays all the flows and components you've created in the Langflow workspace.
+
+
+
+**My Projects** is the default folder where all new projects and components are initially stored.
+
+Projects, folders, and flows are exchanged as JSON objects.
+
+* To create a new folder, click 📁 **New Folder**.
+
+* To rename a folder, double-click the folder name.
+
+* To download a folder, click 📥 **Download**.
+
+* To upload a folder, click 📤 **Upload**. The default maximum file upload size is 100 MB.
+
+* To move a flow or component, drag and drop it into the desired folder.
+
+## Options menu
+
+The dropdown menu labeled with the project name offers several management and customization options for the current flow in the Langflow workspace.
+
+* **New**: Create a new flow from scratch.
+* **Settings**: Adjust settings specific to the current flow, such as its name, description, and endpoint name.
+* **Logs**: View logs for the current project, including execution history, errors, and other runtime events.
+* **Import**: Import a flow or component from a JSON file into the workspace.
+* **Export**: Export the current flow as a JSON file.
+* **Undo (⌘Z)**: Revert the last action taken in the project.
+* **Redo (⌘Y)**: Reapply a previously undone action.
+* **Refresh All**: Refresh all components and delete cache.
+
+## Settings
+
+Click **Settings** to access **Global variables**, **Langflow API**, **Shortcuts**, and **Messages**.
+
+
+
diff --git a/langflow/docs/docs/Concepts/concepts-playground.md b/langflow/docs/docs/Concepts/concepts-playground.md
new file mode 100644
index 0000000..bc6092e
--- /dev/null
+++ b/langflow/docs/docs/Concepts/concepts-playground.md
@@ -0,0 +1,57 @@
+---
+title: Playground
+slug: /concepts-playground
+---
+
+import Icon from "@site/src/components/icon";
+
+The **Playground** is a dynamic interface designed for real-time interaction with LLMs, allowing users to chat, access memories, and monitor inputs and outputs. Here, users can directly prototype their models, making adjustments and observing different outcomes.
+
+As long as you have an [Input or Output](/components-io) component working, you can open it by clicking the **Playground** button.
+The Playground's window arrangement changes depending on what components are being used.
+
+
+
+## Run a flow in the playgound
+
+When you run a flow in the **Playground**, Langflow calls the `/build/{flow_id}/flow` endpoint in [chat.py](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/api/v1/chat.py#L162). This call retrieves the flow data, builds a graph, and executes the graph. As each component (or node) is executed, the `build_vertex` function calls `build_and_run`, which may call the individual components' `def_build` method, if it exists. If a component doesn't have a `def_build` function, the build still returns a component.
+
+The `build` function allows components to execute logic at runtime. For example, the [Recursive character text splitter](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/langchain_utilities/recursive_character.py) is a child of the `LCTextSplitterComponent` class. When text needs to be processed, the parent class's `build` method is called, which creates a `RecursiveCharacterTextSplitter` object and uses it to split the text according to the defined parameters. The split text is then passed on to the next component. This all occurs when the component is built.
+
+## View playground messages by session ID
+
+When you send a message from the **Playground** interface, the interactions are stored in the **Message Logs** by `session_id`.
+A single flow can have multiple chats, and different flows can share the same chat. Each chat will have a different `session_id`.
+
+To view messages by `session_id` within the Playground, click the menu of any chat session, and then select **Message Logs**.
+
+
+
+Individual messages in chat memory can be edited or deleted. Modifying these memories influences the behavior of the chatbot responses.
+
+To learn more about chat memories in Langflow, see [Memory components](/components-memories).
+
+## Use custom Session IDs for multiple user interactions
+
+`session_id` values are used to track user interactions in a flow.
+By default, if the `session_id` value is empty, it is set to the same value as the `flow_id`. In this case, every chat call uses the same `session_id`, and you effectively have one chat session.
+
+The `session_id` value can be configured in the **Advanced Settings** of the **Chat Input** and **Chat Output** components.
+
+To have more than one session in a single flow, pass a specific Session ID to a flow with the `session_id` parameter in the URL. All the components in the flow will automatically use this `session_id` value.
+
+To post a message to a flow with a specific Session ID with curl, enter the following command:
+
+```bash
+ curl -X POST "http://127.0.0.1:7860/api/v1/run/$FLOW_ID" \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "session_id": "custom_session_123",
+ "input_value": "message",
+ "input_type": "chat",
+ "output_type": "chat"
+ }'
+```
+
+Check your flow's **Playground**. In addition to the messages stored for the Default Session, a new session is started with your custom Session ID.
+
diff --git a/langflow/docs/docs/Configuration/configuration-api-keys.md b/langflow/docs/docs/Configuration/configuration-api-keys.md
new file mode 100644
index 0000000..7569c78
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-api-keys.md
@@ -0,0 +1,192 @@
+---
+title: API keys
+slug: /configuration-api-keys
+---
+
+Langflow provides an API key functionality that allows users to access their individual components and flows without traditional login authentication. The API key is a user-specific token that can be included in the request header, query parameter, or as a command line argument to authenticate API calls. This documentation outlines how to generate, use, and manage API keys in Langflow.
+
+:::info
+
+The default user and password are set using the LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD environment variables. The default values are `langflow` and `langflow`, respectively.
+
+:::
+
+## Generate an API key
+
+Generate a user-specific token to use with Langflow.
+
+### Generate an API key with the Langflow UI
+
+1. Click your user icon and select **Settings**.
+2. Click **Langflow API**, and then click **Add New**.
+3. Name your key, and then click **Create Secret Key**.
+4. Copy the API key and store it in a secure location.
+
+### Generate an API key with the Langflow CLI
+
+```shell
+langflow api-key
+# or
+python -m langflow api-key
+╭─────────────────────────────────────────────────────────────────────╮
+│ API Key Created Successfully: │
+│ │
+│ sk-O0elzoWID1izAH8RUKrnnvyyMwIzHi2Wk-uXWoNJ2Ro │
+│ │
+│ This is the only time the API key will be displayed. │
+│ Make sure to store it in a secure location. │
+│ │
+│ The API key has been copied to your clipboard. Cmd + V to paste it. │
+╰──────────────────────────────
+
+```
+
+## Authenticate requests with the Langflow API key
+
+Include your API key in API requests to authenticate requests to Langflow.
+
+### Include the API key in the HTTP header
+
+To use the API key when making API requests with cURL, include the API key in the HTTP header.
+
+```shell
+curl -X POST \
+ "http://127.0.0.1:7860/api/v1/run/*`YOUR_FLOW_ID`*?stream=false" \
+ -H 'Content-Type: application/json' \
+ -H 'x-api-key: *`YOUR_API_KEY`*' \
+ -d '{"inputs": {"text":""}, "tweaks": {}}'
+```
+
+To instead pass the API key as a query parameter, do the following:
+
+```shell
+curl -X POST \
+ "http://127.0.0.1:7860/api/v1/run/*`YOUR_FLOW_ID`*?x-api-key=*`YOUR_API_KEY`*?stream=false" \
+ -H 'Content-Type: application/json' \
+ -d '{"inputs": {"text":""}, "tweaks": {}}'
+```
+
+To use the API key when making API requests with the Python `requests` library, include the API key as a variable string.
+
+```python
+import argparse
+import json
+from argparse import RawTextHelpFormatter
+import requests
+from typing import Optional
+import warnings
+try:
+ from langflow.load import upload_file
+except ImportError:
+ warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.")
+ upload_file = None
+
+BASE_API_URL = "http://127.0.0.1:7860"
+FLOW_ID = "*`YOUR_FLOW_ID`*"
+ENDPOINT = "" # You can set a specific endpoint name in the flow settings
+
+# You can tweak the flow by adding a tweaks dictionary
+# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
+TWEAKS = {
+ "ChatInput-8a86T": {},
+ "Prompt-pKfl9": {},
+ "ChatOutput-WcGpD": {},
+ "OpenAIModel-5UyvQ": {}
+}
+
+def run_flow(message: str,
+ endpoint: str,
+ output_type: str = "chat",
+ input_type: str = "chat",
+ tweaks: Optional[dict] = None,
+ api_key: Optional[str] = None) -> dict:
+ """
+ Run a flow with a given message and optional tweaks.
+
+ :param message: The message to send to the flow
+ :param endpoint: The ID or the endpoint name of the flow
+ :param tweaks: Optional tweaks to customize the flow
+ :return: The JSON response from the flow
+ """
+ api_url = f"{BASE_API_URL}/api/v1/run/{endpoint}"
+
+ payload = {
+ "input_value": message,
+ "output_type": output_type,
+ "input_type": input_type,
+ }
+ headers = None
+ if tweaks:
+ payload["tweaks"] = tweaks
+ if api_key:
+ headers = {"x-api-key": api_key}
+ response = requests.post(api_url, json=payload, headers=headers)
+ return response.json()
+
+def main():
+ parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.
+Run it like: python .py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""",
+ formatter_class=RawTextHelpFormatter)
+ parser.add_argument("message", type=str, help="The message to send to the flow")
+ parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow")
+ parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS))
+ parser.add_argument("--api_key", type=str, help="API key for authentication", default=None)
+ parser.add_argument("--output_type", type=str, default="chat", help="The output type")
+ parser.add_argument("--input_type", type=str, default="chat", help="The input type")
+ parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None)
+ parser.add_argument("--components", type=str, help="Components to upload the file to", default=None)
+
+ args = parser.parse_args()
+ try:
+ tweaks = json.loads(args.tweaks)
+ except json.JSONDecodeError:
+ raise ValueError("Invalid tweaks JSON string")
+
+ if args.upload_file:
+ if not upload_file:
+ raise ImportError("Langflow is not installed. Please install it to use the upload_file function.")
+ elif not args.components:
+ raise ValueError("You need to provide the components to upload the file to.")
+ tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=args.endpoint, components=[args.components], tweaks=tweaks)
+
+ response = run_flow(
+ message=args.message,
+ endpoint=args.endpoint,
+ output_type=args.output_type,
+ input_type=args.input_type,
+ tweaks=tweaks,
+ api_key=args.api_key
+ )
+
+ print(json.dumps(response, indent=2))
+
+if __name__ == "__main__":
+ main()
+
+```
+
+To pass the API key to your script with a command line argument, do the following:
+
+```shell
+python your_script.py "*`YOUR_INPUT_MESSAGE`*" --api_key "*`YOUR_API_KEY`*"
+```
+
+## Security considerations
+
+- **Visibility**: For security reasons, the API key cannot be retrieved again through the UI.
+- **Scope**: The key allows access only to the flows and components of the specific user to whom it was issued.
+
+## Custom API endpoint
+
+To choose a custom name for your API endpoint, select **Project Settings** > **Endpoint Name** and name your endpoint.
+
+## Revoke an API key
+
+To revoke an API key, delete it from the list of keys in the **Settings** menu.
+
+1. Click your user icon and select **Settings**.
+2. Click **Langflow API**.
+3. Select the keys you want to delete and click the trash can icon.
+
+This action immediately invalidates the key and prevents it from being used again.
+
diff --git a/langflow/docs/docs/Configuration/configuration-authentication.md b/langflow/docs/docs/Configuration/configuration-authentication.md
new file mode 100644
index 0000000..6c494b1
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-authentication.md
@@ -0,0 +1,190 @@
+---
+title: Authentication
+slug: /configuration-authentication
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The login functionality in Langflow serves to authenticate users and protect sensitive routes in the application.
+
+## Create a superuser and new users in Langflow
+
+Learn how to create a new superuser, log in to Langflow, and add new users.
+
+1. Create a `.env` file and open it in your preferred editor.
+
+2. Add the following environment variables to your file.
+
+```bash
+LANGFLOW_AUTO_LOGIN=False
+LANGFLOW_SUPERUSER=admin
+LANGFLOW_SUPERUSER_PASSWORD=securepassword
+LANGFLOW_SECRET_KEY=randomly_generated_secure_key
+LANGFLOW_NEW_USER_IS_ACTIVE=False
+```
+
+For more information, see [Authentication configuration values](#values).
+
+:::tip
+The Langflow project includes a [`.env.example`](https://github.com/langflow-ai/langflow/blob/main/.env.example) file to help you get started.
+You can copy the contents of this file into your own `.env` file and replace the example values with your own preferred settings.
+:::
+
+3. Save your `.env` file.
+4. Run Langflow with the configured environment variables.
+
+```bash
+python -m langflow run --env-file .env
+```
+
+5. Sign in with your username `admin` and password `securepassword`.
+6. To open the **Admin Page**, click your user profile image, and then select **Admin Page**.
+ You can also go to `http://127.0.0.1:7861/admin`.
+7. To add a new user, click **New User**, and then add the **Username** and **Password**.
+8. To activate the new user, select **Active**.
+ The user can only sign in if you select them as **Active**.
+9. To give the user `superuser` privileges, click **Superuser**.
+10. Click **Save**.
+11. To confirm your new user has been created, sign out of Langflow, and then sign back in using your new **Username** and **Password**.
+
+## Manage Superuser with the Langflow CLI
+
+Langflow provides a command-line utility for interactively creating superusers:
+
+1. Enter the CLI command:
+
+```bash
+langflow superuser
+```
+
+2. Langflow prompts you for a **Username** and **Password**:
+
+```
+langflow superuser
+Username: new_superuser_1
+Password:
+Default folder created successfully.
+Superuser created successfully.
+```
+
+3. To confirm your new superuser was created successfully, go to the **Admin Page** at `http://127.0.0.1:7861/admin`.
+
+## Authentication configuration values {#values}
+
+The following table lists the available authentication configuration variables, their descriptions, and default values:
+
+| Variable | Description | Default |
+| ----------------------------- | ------------------------------------- | ------- |
+| `LANGFLOW_AUTO_LOGIN` | Enables automatic login | `True` |
+| `LANGFLOW_SUPERUSER` | Superuser username | - |
+| `LANGFLOW_SUPERUSER_PASSWORD` | Superuser password | - |
+| `LANGFLOW_SECRET_KEY` | Key for encrypting superuser password | - |
+| `LANGFLOW_NEW_USER_IS_ACTIVE` | Automatically activates new users | `False` |
+
+### LANGFLOW_AUTO_LOGIN
+
+By default, this variable is set to `True`. When enabled, Langflow operates as it did in versions prior to 0.5, including automatic login without requiring explicit user authentication.
+
+To disable automatic login and enforce user authentication:
+
+```shell
+LANGFLOW_AUTO_LOGIN=False
+```
+
+### LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
+
+These environment variables are only relevant when LANGFLOW_AUTO_LOGIN is set to False. They specify the username and password for the superuser, which is essential for administrative tasks.
+To create a superuser manually:
+
+```bash
+LANGFLOW_SUPERUSER=admin
+LANGFLOW_SUPERUSER_PASSWORD=securepassword
+```
+
+### LANGFLOW_SECRET_KEY
+
+This environment variable holds a secret key used for encrypting sensitive data like API keys.
+
+```bash
+LANGFLOW_SECRET_KEY=dBuuuB_FHLvU8T9eUNlxQF9ppqRxwWpXXQ42kM2_fb
+```
+
+Langflow uses the [Fernet](https://pypi.org/project/cryptography/) library for secret key encryption.
+
+### Create a LANGFLOW_SECRET_KEY
+
+The `LANGFLOW_SECRET_KEY` is used for encrypting sensitive data. It must be:
+- At least 32 bytes long
+- URL-safe base64 encoded
+
+1. To create a `LANGFLOW_SECRET_KEY`, run the following command:
+
+
+
+
+```bash
+# Copy to clipboard (macOS)
+python3 -c "from secrets import token_urlsafe; print(f'LANGFLOW_SECRET_KEY={token_urlsafe(32)}')" | pbcopy
+
+# Copy to clipboard (Linux)
+python3 -c "from secrets import token_urlsafe; print(f'LANGFLOW_SECRET_KEY={token_urlsafe(32)}')" | xclip -selection clipboard
+
+# Or just print
+python3 -c "from secrets import token_urlsafe; print(f'LANGFLOW_SECRET_KEY={token_urlsafe(32)}')"
+```
+
+
+
+
+```bash
+# Copy to clipboard
+python -c "from secrets import token_urlsafe; print(f'LANGFLOW_SECRET_KEY={token_urlsafe(32)}')" | clip
+
+# Or just print
+python -c "from secrets import token_urlsafe; print(f'LANGFLOW_SECRET_KEY={token_urlsafe(32)}')"
+```
+
+
+
+
+The command generates a secure key like `dBuuuB_FHLvU8T9eUNlxQF9ppqRxwWpXXQ42kM2_fbg`.
+Treat the generated secure key as you would an application access token. Do not commit the key to code and keep it in a safe place.
+
+2. Create a `.env` file with the following configuration, and include your generated secret key value.
+```bash
+LANGFLOW_AUTO_LOGIN=False
+LANGFLOW_SUPERUSER=admin
+LANGFLOW_SUPERUSER_PASSWORD=securepassword
+LANGFLOW_SECRET_KEY=dBuuuB_FHLvU8T9eUNlxQF9ppqRxwWpXXQ42kM2_fbg # Your generated key
+LANGFLOW_NEW_USER_IS_ACTIVE=False
+```
+
+3. Start Langflow with the values from your `.env` file.
+```bash
+uv run langflow run --env-file .env
+```
+
+The generated secret key value is now used to encrypt your global variables.
+
+If no key is provided, Langflow will automatically generate a secure key. This is not recommended for production environments, because in a multi-instance deployment like Kubernetes, auto-generated keys won't be able to decrypt data encrypted by other instances. Instead, you should explicitly set the `LANGFLOW_SECRET_KEY` environment variable in the deployment configuration to be the same across all instances.
+
+### Rotate the LANGFLOW_SECRET_KEY
+
+To rotate the key, follow these steps.
+
+1. Create a new `LANGFLOW_SECRET_KEY` with the command in [Create a LANGFLOW_SECRET_KEY](#create-a-langflow_secret_key).
+2. Stop your Langflow instance.
+3. Update the `LANGFLOW_SECRET_KEY` in your `.env` file with the new key.
+4. Restart Langflow with the updated environment file:
+```bash
+langflow run --env-file .env
+```
+
+### LANGFLOW_NEW_USER_IS_ACTIVE
+
+By default, this variable is set to `False`. When enabled, new users are automatically activated and can log in without requiring explicit activation by the superuser.
+
+```bash
+LANGFLOW_NEW_USER_IS_ACTIVE=False
+```
\ No newline at end of file
diff --git a/langflow/docs/docs/Configuration/configuration-auto-saving.md b/langflow/docs/docs/Configuration/configuration-auto-saving.md
new file mode 100644
index 0000000..ded36e8
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-auto-saving.md
@@ -0,0 +1,47 @@
+---
+title: Auto-saving
+slug: /configuration-auto-save
+---
+
+Langflow supports both manual and auto-saving functionality.
+
+## Auto-saving {#auto-saving}
+
+When Langflow is in auto-saving mode, all changes are saved automatically. Auto-save progress is indicated in the left side of the top bar.
+
+* When a flow is being saved, a loading icon indicates that the flow is being saved in the database.
+
+* If you try to exit the flow page before auto-save completes, you are prompted to confirm you want to exit before the flow has saved.
+
+* When the flow has successfully saved, click **Exit**.
+
+## Disable auto-saving {#environment}
+
+To disable auto-saving,
+
+1. Set an environment variable in your `.env` file.
+
+```env
+LANGFLOW_AUTO_SAVING=false
+```
+
+2. Start Langflow with the values from your `.env` file.
+
+```shell
+python -m langflow run --env-file .env
+```
+
+Alternatively, disable auto-saving by passing the `--no-auto-saving` flag at startup.
+
+```shell
+python -m langflow --no-auto-saving
+```
+
+## Save a flow manually {#manual-saving}
+
+When auto-saving is disabled, you will need to manually save your flow when making changes.
+
+To manually save your flow, click the **Save** button or enter Ctrl+S or Command+S.
+
+If you try to exit after making changes and not saving, a confirmation dialog appears.
+
diff --git a/langflow/docs/docs/Configuration/configuration-backend-only.md b/langflow/docs/docs/Configuration/configuration-backend-only.md
new file mode 100644
index 0000000..0ffc993
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-backend-only.md
@@ -0,0 +1,122 @@
+---
+title: Run Langflow in backend-only mode
+slug: /configuration-backend-only
+---
+
+Langflow can run in `--backend-only` mode to expose a Langflow app as an API endpoint, without running the frontend UI.
+This is also known as "headless" mode. Running Langflow without the frontend is useful for automation, testing, and situations where you just need to serve a flow as a workload without creating a new flow in the UI.
+
+To run Langflow in backend-only mode, pass the `--backend-only` flag at startup.
+
+```python
+python3 -m langflow run --backend-only
+```
+
+The terminal prints `Welcome to ⛓ Langflow`, and Langflow will now serve requests to its API without the frontend running.
+
+## Set up a basic prompting flow in backend-only mode
+
+This example shows you how to set up a [Basic Prompting flow](/starter-projects-basic-prompting) as an endpoint in backend-only mode.
+However, you can use these same instructions as guidelines for using any type of flow in backend-only mode.
+
+### Prerequisites
+
+- [Langflow is installed](/get-started-installation)
+- [You have an OpenAI API key](https://platform.openai.com/)
+- [You have a Langflow Basic Prompting flow](/starter-projects-basic-prompting)
+
+### Get your flow's ID
+
+This guide assumes you have created a [Basic Prompting flow](/starter-projects-basic-prompting) or have another working flow available.
+
+1. In the Langflow UI, click **API**.
+2. Click **curl** > **Copy code** to copy the curl command.
+This command will POST input to your flow's endpoint.
+It will look something like this:
+
+```text
+curl -X POST \
+ "http://127.0.0.1:7861/api/v1/run/fff8dcaa-f0f6-4136-9df0-b7cb38de42e0?stream=false" \
+ -H 'Content-Type: application/json'\
+ -d '{"input_value": "message",
+ "output_type": "chat",
+ "input_type": "chat",
+ "tweaks": {
+ "ChatInput-8a86T": {},
+ "Prompt-pKfl9": {},
+ "ChatOutput-WcGpD": {},
+ "OpenAIModel-5UyvQ": {}
+}}'
+```
+
+The flow ID in this example is `fff8dcaa-f0f6-4136-9df0-b7cb38de42e0`, a UUID generated by Langflow and used in the endpoint URL.
+See [API](/configuration-api-keys) to change the endpoint.
+
+3. To stop Langflow, press **Ctrl+C**.
+
+### Start Langflow in backend-only mode
+
+1. Start Langflow in backend-only mode.
+
+```python
+python3 -m langflow run --backend-only
+```
+
+The terminal prints `Welcome to ⛓ Langflow`.
+Langflow is now serving requests to its API.
+
+2. Run the curl code you copied from the UI.
+You should get a result like this:
+
+```shell
+{"session_id":"ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef:bf81d898868ac87e1b4edbd96c131c5dee801ea2971122cc91352d144a45b880","outputs":[{"inputs":{"input_value":"hi, are you there?"},"outputs":[{"results":{"result":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?"},"artifacts":{"message":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?","sender":"Machine","sender_name":"AI"},"messages":[{"message":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?","sender":"Machine","sender_name":"AI","component_id":"ChatOutput-ktwdw"}],"component_display_name":"Chat Output","component_id":"ChatOutput-ktwdw","used_frozen_result":false}]}]}%
+```
+
+This confirms Langflow is receiving your POST request, running the flow, and returning the result without running the frontend.
+
+You can interact with this endpoint using the other options in the **API** menu, including the Python and Javascript APIs.
+
+### Query the Langflow endpoint with a Python script
+
+Using the same flow ID, run a Python sample script to send a query and get a prettified JSON response back.
+
+1. Create a Python file and name it `langflow_api_demo.py`.
+
+```python
+import requests
+import json
+
+def query_langflow(message):
+ url = "http://127.0.0.1:7861/api/v1/run/fff8dcaa-f0f6-4136-9df0-b7cb38de42e0"
+ headers = {"Content-Type": "application/json"}
+ data = {"input_value": message}
+
+ response = requests.post(url, headers=headers, json=data)
+ return response.json()
+
+user_input = input("Enter your message: ")
+result = query_langflow(user_input)
+
+print(json.dumps(result, indent=2))
+```
+2. Run the script.
+
+```python
+python langflow_api_demo.py
+```
+
+3. Enter your message when prompted.
+You will get a prettified JSON response back containing a response to your message.
+
+### Configure host and ports in backend-only mode
+
+To change the host and port, pass the values as additional flags.
+
+```python
+python -m langflow run --host 127.0.0.1 --port 7860 --backend-only
+```
+
+
+
+
+
diff --git a/langflow/docs/docs/Configuration/configuration-cli.md b/langflow/docs/docs/Configuration/configuration-cli.md
new file mode 100644
index 0000000..355f2e3
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-cli.md
@@ -0,0 +1,171 @@
+---
+title: Langflow CLI
+slug: /configuration-cli
+---
+
+import Link from '@docusaurus/Link';
+
+# Langflow CLI
+
+The Langflow command line interface (Langflow CLI) is the main interface for managing and running the Langflow server.
+
+## CLI commands
+
+The following sections describe the available CLI commands and their options, as well as their corresponding [environment variables](./environment-variables.md).
+
+### langflow
+
+Running the CLI without any arguments displays a list of available options and commands.
+
+```bash
+langflow [OPTIONS]
+# or
+python -m langflow [OPTIONS]
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--install-completion` | *Not applicable* | *Not applicable* | Install auto-completion for the current shell. |
+| `--show-completion` | *Not applicable* | *Not applicable* | Show the location of the auto-completion config file (if installed). |
+| `--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
+
+### langflow api-key
+
+Create an API key for the default superuser if the [`LANGFLOW_AUTO_LOGIN` environment variable] is set to `true`.
+
+```bash
+langflow api-key [OPTIONS]
+# or
+python -m langflow api-key [OPTIONS]
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--install-completion` | *Not applicable* | *Not applicable* | Install auto-completion for the current shell. |
+| `--show-completion` | *Not applicable* | *Not applicable* | Show the location of the auto-completion config file (if installed). |
+| `--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
+
+### langflow copy-db
+
+Copy the database files to the current directory.
+Copy the Langflow database files, `langflow.db` and `langflow-pre.db` (if they exist), from the cache directory to the current directory.
+
+:::note
+The current directory is the directory containing `__main__.py`.
+You can find this directory by running `which langflow`.
+:::
+
+```bash
+langflow copy-db
+# or
+python -m langflow copy-db
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
+
+### langflow migration
+
+Run or test database migrations.
+
+```bash
+langflow migration [OPTIONS]
+# or
+python -m langflow migration [OPTIONS]
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--test` | `true` | [Boolean](#boolean) | Run migrations in test mode. Use `--no-test` to disable test mode. |
+| `--fix` | `false` (`--no-fix`) | [Boolean](#boolean) | Fix migrations. This is a destructive operation, and all affected data will be deleted. Only use this option if you know what you are doing. |
+| `--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
+
+### langflow run
+
+Start the Langflow server.
+
+```bash
+langflow run [OPTIONS]
+# or
+python -m langflow run [OPTIONS]
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--host` | `127.0.0.1` | String | The host on which the Langflow server will run. See [`LANGFLOW_HOST` variable](./environment-variables.md#LANGFLOW_HOST). |
+| `--workers` | `1` | Integer | Number of worker processes. See [`LANGFLOW_WORKERS` variable](./environment-variables.md#LANGFLOW_WORKERS). |
+| `--worker-timeout` | `300` | Integer | Worker timeout in seconds. See [`LANGFLOW_WORKER_TIMEOUT` variable](./environment-variables.md#LANGFLOW_WORKER_TIMEOUT). |
+| `--port` | `7860` | Integer | The port on which the Langflow server will run. The server automatically selects a free port if the specified port is in use. See [`LANGFLOW_PORT` variable](./environment-variables.md#LANGFLOW_PORT). |
+| `--components-path` | `langflow/components` | String | Path to the directory containing custom components. See [`LANGFLOW_COMPONENTS_PATH` variable](./environment-variables.md#LANGFLOW_COMPONENTS_PATH). |
+| `--env-file` | Not set | String | Path to the `.env` file containing environment variables. See [Import environment variables from a .env file](./environment-variables.md#configure-variables-env-file). |
+| `--log-level` | `critical` | `debug` `info` `warning` `error` `critical` | Set the logging level. See [`LANGFLOW_LOG_LEVEL` variable](./environment-variables.md#LANGFLOW_LOG_LEVEL). |
+| `--log-file` | `logs/langflow.log` | String | Set the path to the log file for Langflow. See [`LANGFLOW_LOG_FILE` variable](./environment-variables.md#LANGFLOW_LOG_FILE). |
+| `--cache` | `InMemoryCache` | `InMemoryCache` `SQLiteCache` | Type of cache to use. See [`LANGFLOW_LANGCHAIN_CACHE` variable](./environment-variables.md#LANGFLOW_LANGCHAIN_CACHE). |
+| `--dev` | `false` (`--no-dev`) | [Boolean](#boolean) | Run Langflow in development mode (may contain bugs). See [`LANGFLOW_DEV` variable](./environment-variables.md#LANGFLOW_DEV). |
+| `--frontend-path` | `./frontend` | String | Path to the frontend directory containing build files. This is for development purposes only. See [`LANGFLOW_FRONTEND_PATH` variable](./environment-variables.md#LANGFLOW_FRONTEND_PATH). |
+| `--open-browser` | `true` | [Boolean](#boolean) | Open the system web browser on startup. Use `--no-open-browser` to disable opening the system web browser on startup. See [`LANGFLOW_OPEN_BROWSER` variable](./environment-variables.md#LANGFLOW_OPEN_BROWSER). |
+| `--remove-api-keys` | `false` (`--no-remove-api-keys`) | [Boolean](#boolean) | Remove API keys from the projects saved in the database. See [`LANGFLOW_REMOVE_API_KEYS` variable](./environment-variables.md#LANGFLOW_REMOVE_API_KEYS). |
+| `--backend-only` | `false` (`--no-backend-only`) | [Boolean](#boolean) | Only run Langflow's backend server (no frontend). See [`LANGFLOW_BACKEND_ONLY` variable](./environment-variables.md#LANGFLOW_BACKEND_ONLY). |
+| `--store` | `true` | [Boolean](#boolean) | Enable the Langflow Store features. Use `--no-store` to disable the Langflow Store features. See [`LANGFLOW_STORE` variable](./environment-variables.md#LANGFLOW_STORE). |
+| `--auto-saving` | `true` | [Boolean](#boolean) | Enable flow auto-saving. Use `--no-auto-saving` to disable flow auto-saving. See [`LANGFLOW_AUTO_SAVING` variable](./environment-variables.md#LANGFLOW_AUTO_SAVING). |
+| `--auto-saving-interval` | `1000` | Integer | Set the interval for flow auto-saving in milliseconds. See [`LANGFLOW_AUTO_SAVING_INTERVAL` variable](./environment-variables.md#LANGFLOW_AUTO_SAVING_INTERVAL). |
+| `--health-check-max-retries` | `5` | Integer | Set the maximum number of retries for the health check. Use `--no-health-check-max-retries` to disable the maximum number of retries for the health check. See [`LANGFLOW_HEALTH_CHECK_MAX_RETRIES` variable](./environment-variables.md#LANGFLOW_HEALTH_CHECK_MAX_RETRIES). |
+| `--max-file-size-upload` | `100` | Integer | Set the maximum file size for the upload in megabytes. See [`LANGFLOW_MAX_FILE_SIZE_UPLOAD` variable](./environment-variables.md#LANGFLOW_MAX_FILE_SIZE_UPLOAD). |
+| `--help` | *Not applicable* | *Not applicable* | Display information about the command usage and its options and arguments. |
+
+### langflow superuser
+
+Create a superuser account.
+
+```bash
+langflow superuser [OPTIONS]
+# or
+python -m langflow superuser [OPTIONS]
+```
+
+#### Options
+
+| Option | Default | Values | Description |
+|--------|---------|--------|-------------|
+| `--username` | Required | String | Specify the name for the superuser. See [`LANGFLOW_SUPERUSER` variable](./environment-variables.md#LANGFLOW_SUPERUSER). |
+| `--password` | Required | String | Specify the password for the superuser. See [`LANGFLOW_SUPERUSER_PASSWORD` variable](./environment-variables.md#LANGFLOW_SUPERUSER_PASSWORD). |
+| `--log-level` | `critical` | `debug` `info` `warning` `error` `critical` | Set the logging level. |
+
+## Precedence
+
+Langflow CLI options override the values of corresponding [environment variables](./environment-variables.md).
+
+For example, if you have `LANGFLOW_PORT=7860` defined as an environment variable, but you run the CLI with `--port 7880`, then Langflow will set the port to **`7880`** (the value passed with the CLI).
+
+## Assign values
+
+There are two ways you can assign a value to a CLI option.
+You can write the option flag and its value with a single space between them: `--option value`.
+Or, you can write them using an equals sign (`=`) between the option flag and the value: `--option=value`.
+
+Values that contain spaces must be surrounded by quotation marks: `--option 'Value with Spaces'` or `--option='Value with Spaces'`.
+
+### Boolean values {#boolean}
+
+Boolean options turn a behavior on or off, and therefore accept no arguments.
+To activate a boolean option, type it on the command line.
+For example:
+
+```bash
+langflow run --remove-api-keys
+```
+
+All boolean options have a corresponding option that negates it.
+For example, the negating option for `--remove-api-keys` is `--no-remove-api-keys`.
+These options let you negate boolean options that you may have set using [environment variables](./environment-variables.md).
diff --git a/langflow/docs/docs/Configuration/configuration-custom-database.md b/langflow/docs/docs/Configuration/configuration-custom-database.md
new file mode 100644
index 0000000..f471532
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-custom-database.md
@@ -0,0 +1,76 @@
+---
+title: Configure an external PostgreSQL database
+slug: /configuration-custom-database
+---
+Langflow's default database is [SQLite](https://www.sqlite.org/docs.html), but you can configure Langflow to use PostgreSQL instead.
+
+This guide will walk you through the process of setting up an external database for Langflow by replacing the default SQLite connection string `sqlite:///./langflow.db` with PostgreSQL.
+
+## Prerequisite
+
+* A [PostgreSQL](https://www.pgadmin.org/download/) database
+
+## Connect Langflow to PostgreSQL
+
+To connect Langflow to PostgreSQL, follow these steps.
+
+1. Find your PostgreSQL database's connection string.
+It looks like `postgresql://user:password@host:port/dbname`.
+For example, if you started PostgreSQL with this Docker command:
+
+```
+docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d -p 5432:5432 postgres
+```
+
+Your connection string would be `postgresql://some-postgres:mysecretpassword@localhost:5432/postgres`.
+
+2. Create a `.env` file for configuring Langflow.
+```
+touch .env
+```
+
+3. To set the database URL environment variable, add it to your `.env` file:
+```plaintext
+LANGFLOW_DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
+```
+
+:::tip
+The Langflow project includes a [`.env.example`](https://github.com/langflow-ai/langflow/blob/main/.env.example) file to help you get started.
+You can copy the contents of this file into your own `.env` file and replace the example values with your own preferred settings.
+Replace the value for `LANGFLOW_DATABASE_URL` with your PostgreSQL connection string.
+:::
+
+4. Run Langflow with the `.env` file:
+```bash
+langflow run --env-file .env
+```
+
+5. In Langflow, create traffic by running a flow.
+6. Inspect your PostgreSQL deployment's tables and activity.
+You will see new tables and traffic created.
+
+## Example Langflow and PostgreSQL docker-compose.yml
+
+The Langflow project includes a [`docker-compose.yml`](https://github.com/langflow-ai/langflow/blob/main/docker_example/docker-compose.yml) file for quick deployment with PostgreSQL.
+
+This configuration launches Langflow and PostgreSQL containers, with Langflow pre-configured to use the PostgreSQL database. Customize the database credentials as needed.
+
+To start the services, navigate to the `/docker_example` directory, and then run `docker-compose up`.
+
+```yaml
+services:
+ langflow:
+ image: langflow-ai/langflow:latest
+ environment:
+ - LANGFLOW_DATABASE_URL=postgresql://user:password@postgres:5432/langflow
+ depends_on:
+ - postgres
+
+ postgres:
+ image: postgres:15
+ environment:
+ - POSTGRES_USER=user
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=langflow
+```
+
diff --git a/langflow/docs/docs/Configuration/configuration-global-variables.md b/langflow/docs/docs/Configuration/configuration-global-variables.md
new file mode 100644
index 0000000..2f327d3
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-global-variables.md
@@ -0,0 +1,198 @@
+---
+title: Global variables
+slug: /configuration-global-variables
+---
+
+import Icon from "@site/src/components/icon";
+import ReactPlayer from "react-player";
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+Global variables let you store and reuse generic input values and credentials across your projects.
+You can use a global variable in any text input field that displays the **Globe** icon.
+
+Langflow stores global variables in its internal database, and encrypts the values using a secret key.
+
+## Create a global variable {#3543d5ef00eb453aa459b97ba85501e5}
+
+1. In the Langflow UI, click your profile icon, and then select **Settings**.
+
+2. Click **Global Variables**.
+
+3. Click **Add New**.
+
+4. In the **Create Variable** dialog, enter a name for your variable in the **Variable Name** field.
+
+5. Optional: Select a **Type** for your global variable. The available types are **Generic** (default) and **Credential**.
+
+ No matter which **Type** you select, Langflow still encrypts the **Value** of the global variable.
+
+6. Enter the **Value** for your global variable.
+
+7. Optional: Use the **Apply To Fields** menu to select one or more fields that you want Langflow to automatically apply your global variable to. For example, if you select **OpenAI API Key**, Langflow will automatically apply the variable to any **OpenAI API Key** field.
+
+8. Click **Save Variable**.
+
+You can now select your global variable from any text input field that displays the 🌐 icon.
+
+:::info
+Because values are encrypted, you can't view the actual values of your global variables.
+In **Settings > Global Variables**, the **Value** column shows the encrypted hash for **Generic** type variables, and shows nothing for **Credential** type variables.
+:::
+
+## Edit a global variable
+
+1. In the Langflow UI, click your profile icon, and then select **Settings**.
+
+2. Click **Global Variables**.
+
+3. Click on the global variable you want to edit.
+
+4. In the **Update Variable** dialog, you can edit the following fields: **Variable Name**, **Value**, and **Apply To Fields**.
+
+5. Click **Update Variable**.
+
+## Delete a global variable
+
+:::warning
+Deleting a global variable permanently deletes any references to it from your existing projects.
+:::
+
+1. In the Langflow UI, click your profile icon, and then select **Settings**.
+
+2. Click **Global Variables**.
+
+3. Click the checkbox next to the global variable that you want to delete.
+
+4. Click the Trash icon.
+
+The global variable, and any existing references to it, are deleted.
+
+## Add global variables from the environment {#76844a93dbbc4d1ba551ea1a4a89ccdd}
+
+### Custom environment variables
+
+You can use the `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` environment variable to source global variables from your runtime environment.
+
+
+
+
+
+If you installed Langflow locally, you must define the `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` environment variable in a `.env` file.
+
+1. Create a `.env` file and open it in your preferred editor.
+
+2. Add the `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` environment variable as follows:
+
+ ```plaintext title=".env"
+ LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT=VARIABLE1,VARIABLE2
+ ```
+
+ Replace `VARIABLE1,VARIABLE2` with a comma-separated list (no spaces) of variables that you want Langflow to source from the environment.
+ For example, `my_key,some_string`.
+
+3. Save and close the file.
+
+4. Start Langflow with the `.env` file:
+
+ ```bash
+ VARIABLE1="VALUE1" VARIABLE2="VALUE2" python -m langflow run --env-file .env
+ ```
+
+ :::note
+ In this example, the environment variables (`VARIABLE1="VALUE1"` and `VARIABLE2="VALUE2"`) are prefixed to the startup command.
+ This is a rudimentary method for exposing environment variables to Python on the command line, and is meant for illustrative purposes.
+ Make sure to expose your environment variables to Langflow in a manner that best suits your own environment.
+ :::
+
+5. Confirm that Langflow successfully sourced the global variables from the environment.
+
+ 1. In the Langflow UI, click your profile icon, and then select **Settings**.
+
+ 2. Click **Global Variables**.
+
+ The environment variables appear in the list of **Global Variables**.
+
+
+
+
+
+If you're using Docker, you can pass `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` directly from the command line or from a `.env` file.
+
+To pass `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` directly from the command line:
+
+```bash
+docker run -it --rm \
+ -p 7860:7860 \
+ -e LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT="VARIABLE1,VARIABLE2" \
+ -e VARIABLE1="VALUE1" \
+ -e VARIABLE2="VALUE2" \
+ langflowai/langflow:latest
+```
+
+To pass `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` from a `.env` file:
+
+```bash
+docker run -it --rm \
+ -p 7860:7860 \
+ --env-file .env \
+ -e VARIABLE1="VALUE1" \
+ -e VARIABLE2="VALUE2" \
+ langflowai/langflow:latest
+```
+
+
+
+
+
+:::info
+When adding global variables from the environment, the following limitations apply:
+
+- You can only source the **Name** and **Value** from the environment.
+ To add additional parameters, such as the **Apply To Fields** parameter, you must edit the global variables in the Langflow UI.
+
+- Global variables that you add from the environment always have the **Credential** type.
+ :::
+
+:::tip
+If you want to explicitly prevent Langflow from sourcing global variables from the environment, set `LANGFLOW_STORE_ENVIRONMENT_VARIABLES` to `false` in your `.env` file:
+
+```plaintext title=".env"
+LANGFLOW_STORE_ENVIRONMENT_VARIABLES=false
+```
+
+:::
+
+### Default environment variables
+
+Langflow automatically detects and converts some environment variables into global variables of the type **Credential**, which are applied to the specific fields in components that require them. Currently, the following variables are supported:
+
+- `OPENAI_API_KEY`
+- `ANTHROPIC_API_KEY`
+- `GOOGLE_API_KEY`
+- `COHERE_API_KEY`
+- `GROQ_API_KEY`
+- `HUGGINGFACEHUB_API_TOKEN`
+- `SEARCHAPI_API_KEY`
+- `SERPAPI_API_KEY`
+- `AZURE_OPENAI_API_KEY`
+- `AZURE_OPENAI_API_VERSION`
+- `AZURE_OPENAI_API_INSTANCE_NAME`
+- `AZURE_OPENAI_API_DEPLOYMENT_NAME`
+- `AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME`
+- `PINECONE_API_KEY`
+- `ASTRA_DB_APPLICATION_TOKEN`
+- `ASTRA_DB_API_ENDPOINT`
+- `UPSTASH_VECTOR_REST_URL`
+- `UPSTASH_VECTOR_REST_TOKEN`
+- `VECTARA_CUSTOMER_ID`
+- `VECTARA_CORPUS_ID`
+- `VECTARA_API_KEY`
+- `AWS_ACCESS_KEY_ID`
+- `AWS_SECRET_ACCESS_KEY`
+
+For information about other environment variables and their usage, see [Environment Variables](/environment-variables).
+
+## Security best practices
+
+For information about securing your global variables and other sensitive data, see [Security best practices](/configuration-security-best-practices).
\ No newline at end of file
diff --git a/langflow/docs/docs/Configuration/configuration-security-best-practices.md b/langflow/docs/docs/Configuration/configuration-security-best-practices.md
new file mode 100644
index 0000000..15600b7
--- /dev/null
+++ b/langflow/docs/docs/Configuration/configuration-security-best-practices.md
@@ -0,0 +1,47 @@
+---
+title: Security best practices
+slug: /configuration-security-best-practices
+---
+
+This guide outlines security best practices for deploying and managing Langflow.
+
+## Secret key protection
+
+The secret key is critical for encrypting sensitive data in Langflow. Follow these guidelines:
+
+- Always use a custom secret key in production:
+
+ ```bash
+ LANGFLOW_SECRET_KEY=your-secure-secret-key
+ ```
+
+- Store the secret key securely:
+
+ - Use environment variables or secure secret management systems.
+ - Never commit the secret key to version control.
+ - Regularly rotate the secret key.
+
+- Use the default secret key locations:
+ - macOS: `~/Library/Caches/langflow/secret_key`
+ - Linux: `~/.cache/langflow/secret_key`
+ - Windows: `%USERPROFILE%\AppData\Local\langflow\secret_key`
+
+## API keys and credentials
+
+- Store API keys and credentials as encrypted global variables.
+- Use the Credential type for sensitive information.
+- Implement proper access controls for users who can view/edit credentials.
+- Regularly audit and rotate API keys.
+
+## Database file protection
+
+- Store the database in a secure location:
+
+ ```bash
+ LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
+ LANGFLOW_CONFIG_DIR=/secure/path/to/config
+ ```
+
+- Use the default database locations:
+ - macOS/Linux: `PYTHON_LOCATION/site-packages/langflow/langflow.db`
+ - Windows: `PYTHON_LOCATION\Lib\site-packages\langflow\langflow.db`
diff --git a/langflow/docs/docs/Configuration/environment-variables.md b/langflow/docs/docs/Configuration/environment-variables.md
new file mode 100644
index 0000000..4be2c99
--- /dev/null
+++ b/langflow/docs/docs/Configuration/environment-variables.md
@@ -0,0 +1,298 @@
+---
+title: Environment variables
+slug: /environment-variables
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import Link from '@docusaurus/Link';
+
+
+Langflow lets you configure a number of settings using environment variables.
+
+## Configure environment variables
+
+Langflow recognizes [supported environment variables](#supported-variables) from the following sources:
+
+- Environment variables that you've set in your terminal.
+- Environment variables that you've imported from a `.env` file using the `--env-file` option in the Langflow CLI.
+
+You can choose to use one source exclusively, or use both sources together.
+If you choose to use both sources together, be aware that environment variables imported from a `.env` file take [precedence](#precedence) over those set in your terminal.
+
+### Set environment variables in your terminal {#configure-variables-terminal}
+
+Run the following commands to set environment variables for your current terminal session:
+
+
+
+
+```bash
+export VARIABLE_NAME='VALUE'
+```
+
+
+
+```
+set VARIABLE_NAME='VALUE'
+```
+
+
+
+```bash
+docker run -it --rm \
+ -p 7860:7860 \
+ -e VARIABLE_NAME='VALUE' \
+ langflowai/langflow:latest
+```
+
+
+
+
+When you start Langflow, it looks for environment variables that you've set in your terminal.
+If it detects a supported environment variable, then it automatically adopts the specified value, subject to [precedence rules](#precedence).
+
+### Import environment variables from a .env file {#configure-variables-env-file}
+
+1. Create a `.env` file and open it in your preferred editor.
+
+2. Add your environment variables to the file:
+
+ ```plaintext title=".env"
+ VARIABLE_NAME='VALUE'
+ VARIABLE_NAME='VALUE'
+ ```
+
+ :::tip
+ The Langflow project includes a [`.env.example`](https://github.com/langflow-ai/langflow/blob/main/.env.example) file to help you get started.
+ You can copy the contents of this file into your own `.env` file and replace the example values with your own preferred settings.
+ :::
+
+3. Save and close the file.
+
+4. Start Langflow using the `--env-file` option to define the path to your `.env` file:
+
+
+
+
+ ```bash
+ python -m langflow run --env-file .env
+ ```
+
+
+
+ ```bash
+ docker run -it --rm \
+ -p 7860:7860 \
+ --env-file .env \
+ langflowai/langflow:latest
+ ```
+
+
+
+
+On startup, Langflow imports the environment variables from your `.env` file, as well as any that you [set in your terminal](#configure-variables-terminal), and adopts their specified values.
+
+## Precedence {#precedence}
+
+Environment variables [defined in the .env file](#configure-variables-env-file) take precedence over those [set in your terminal](#configure-variables-terminal).
+That means, if you happen to set the same environment variable in both your terminal and your `.env` file, Langflow adopts the value from the the `.env` file.
+
+:::info[CLI precedence]
+[Langflow CLI options](./configuration-cli.md) override the value of corresponding environment variables defined in the `.env` file as well as any environment variables set in your terminal.
+:::
+
+## Supported environment variables {#supported-variables}
+
+The following table lists the environment variables supported by Langflow.
+
+| Variable | Format / Values | Default | Description |
+|----------|---------------|---------|-------------|
+| `DO_NOT_TRACK` | Boolean | `false` | If enabled, Langflow will not track telemetry. |
+| `LANGFLOW_AUTO_LOGIN` | Boolean | `true` | Enable automatic login for Langflow. Set to `false` to disable automatic login and require the login form to log into the Langflow UI. Setting to `false` requires [`LANGFLOW_SUPERUSER`](#LANGFLOW_SUPERUSER) and [`LANGFLOW_SUPERUSER_PASSWORD`](environment-variables.md#LANGFLOW_SUPERUSER_PASSWORD) to be set. |
+| `LANGFLOW_AUTO_SAVING` | Boolean | `true` | Enable flow auto-saving. See [`--auto-saving` option](./configuration-cli.md#run-auto-saving). |
+| `LANGFLOW_AUTO_SAVING_INTERVAL` | Integer | `1000` | Set the interval for flow auto-saving in milliseconds. See [`--auto-saving-interval` option](./configuration-cli.md#run-auto-saving-interval). |
+| `LANGFLOW_BACKEND_ONLY` | Boolean | `false` | Only run Langflow's backend server (no frontend). See [`--backend-only` option](./configuration-cli.md#run-backend-only). |
+| `LANGFLOW_CACHE_TYPE` | `async` `redis` `memory` `disk` `critical` | `async` | Set the cache type for Langflow. If you set the type to `redis`, then you must also set the following environment variables: [`LANGFLOW_REDIS_HOST`](#LANGFLOW_REDIS_HOST), [`LANGFLOW_REDIS_PORT`](#LANGFLOW_REDIS_PORT), [`LANGFLOW_REDIS_DB`](#LANGFLOW_REDIS_DB), and [`LANGFLOW_REDIS_CACHE_EXPIRE`](#LANGFLOW_REDIS_CACHE_EXPIRE). |
+| `LANGFLOW_COMPONENTS_PATH` | String | `langflow/components` | Path to the directory containing custom components. See [`--components-path` option](./configuration-cli.md#run-components-path). |
+| `LANGFLOW_CONFIG_DIR` | String | **Linux/WSL**: `~/.cache/langflow/` **macOS**: `/Users//Library/Caches/langflow/` **Windows**: `%LOCALAPPDATA%\langflow\langflow\Cache` | Set the Langflow configuration directory where files, logs, and the Langflow database are stored. |
+| `LANGFLOW_DATABASE_URL` | String | Not set | Set the database URL for Langflow. If not provided, Langflow will use a SQLite database. |
+| `LANGFLOW_DATABASE_CONNECTION_RETRY` | Boolean | `false` | If True, Langflow will retry to connect to the database if it fails. |
+| `LANGFLOW_DB_POOL_SIZE` | Integer | `10` | **DEPRECATED:** Use `LANGFLOW_DB_CONNECTION_SETTINGS` instead. The number of connections to keep open in the connection pool. |
+| `LANGFLOW_DB_MAX_OVERFLOW` | Integer | `20` | **DEPRECATED:** Use `LANGFLOW_DB_CONNECTION_SETTINGS` instead. The number of connections to allow that can be opened beyond the pool size. |
+| `LANGFLOW_DB_CONNECT_TIMEOUT` | Integer | `20` | The number of seconds to wait before giving up on a lock to be released or establishing a connection to the database. |
+| `LANGFLOW_DB_CONNECTION_SETTINGS` | JSON | Not set | A JSON dictionary to centralize database connection parameters. Example: `{"pool_size": 10, "max_overflow": 20}` |
+| `LANGFLOW_DEV` | Boolean | `false` | Run Langflow in development mode (may contain bugs). See [`--dev` option](./configuration-cli.md#run-dev). |
+| `LANGFLOW_FALLBACK_TO_ENV_VAR` | Boolean | `true` | If enabled, [global variables](../Configuration/configuration-global-variables.md) set in the Langflow UI fall back to an environment variable with the same name when Langflow fails to retrieve the variable value. |
+| `LANGFLOW_FRONTEND_PATH` | String | `./frontend` | Path to the frontend directory containing build files. This is for development purposes only. See [`--frontend-path` option](./configuration-cli.md#run-frontend-path). |
+| `LANGFLOW_HEALTH_CHECK_MAX_RETRIES` | Integer | `5` | Set the maximum number of retries for the health check. See [`--health-check-max-retries` option](./configuration-cli.md#run-health-check-max-retries). |
+| `LANGFLOW_HOST` | String | `127.0.0.1` | The host on which the Langflow server will run. See [`--host` option](./configuration-cli.md#run-host). |
+| `LANGFLOW_LANGCHAIN_CACHE` | `InMemoryCache` `SQLiteCache` | `InMemoryCache` | Type of cache to use. See [`--cache` option](./configuration-cli.md#run-cache). |
+| `LANGFLOW_LOG_LEVEL` | `DEBUG` `INFO` `WARNING` `ERROR` `CRITICAL` | `INFO` | Set the logging level for Langflow. |
+| `LANGFLOW_LOG_FILE` | String | Not set | Path to the log file. If not set, logs will be written to stdout. |
+| `LANGFLOW_MAX_FILE_SIZE_UPLOAD` | Integer | `100` | Set the maximum file size for the upload in megabytes. See [`--max-file-size-upload` option](./configuration-cli.md#run-max-file-size-upload). |
+| `LANGFLOW_MCP_SERVER_ENABLED` | Boolean | `true` | If set to False, Langflow will not enable the MCP server. |
+| `LANGFLOW_MCP_SERVER_ENABLE_PROGRESS_NOTIFICATIONS` | Boolean | `false` | If set to True, Langflow will send progress notifications in the MCP server. |
+| `LANGFLOW_NEW_USER_IS_ACTIVE` | Boolean | `false` | When enabled, new users are automatically activated and can log in without requiring explicit activation by the superuser. |
+| `LANGFLOW_OPEN_BROWSER` | Boolean | `false` | Open the system web browser on startup. See [`--open-browser` option](./configuration-cli.md#run-open-browser). |
+| `LANGFLOW_PORT` | Integer | `7860` | The port on which the Langflow server will run. The server automatically selects a free port if the specified port is in use. See [`--port` option](./configuration-cli.md#run-port). |
+| `LANGFLOW_PROMETHEUS_ENABLED` | Boolean | `false` | Expose Prometheus metrics. |
+| `LANGFLOW_PROMETHEUS_PORT` | Integer | `9090` | Set the port on which Langflow exposes Prometheus metrics. |
+| `LANGFLOW_REDIS_CACHE_EXPIRE` | Integer | `3600` | See [`LANGFLOW_CACHE_TYPE`](#LANGFLOW_CACHE_TYPE). |
+| `LANGFLOW_REDIS_DB` | Integer | `0` | See [`LANGFLOW_CACHE_TYPE`](#LANGFLOW_CACHE_TYPE). |
+| `LANGFLOW_REDIS_HOST` | String | `localhost` | See [`LANGFLOW_CACHE_TYPE`](#LANGFLOW_CACHE_TYPE). |
+| `LANGFLOW_REDIS_PORT` | String | `6379` | See [`LANGFLOW_CACHE_TYPE`](#LANGFLOW_CACHE_TYPE). |
+| `LANGFLOW_REMOVE_API_KEYS` | Boolean | `false` | Remove API keys from the projects saved in the database. See [`--remove-api-keys` option](./configuration-cli.md#run-remove-api-keys). |
+| `LANGFLOW_SAVE_DB_IN_CONFIG_DIR` | Boolean | `false` | Save the Langflow database in [`LANGFLOW_CONFIG_DIR`](#LANGFLOW_CONFIG_DIR) instead of in the Langflow package directory. Note, when this variable is set to default (`false`), the database isn't shared between different virtual environments and the database is deleted when you uninstall Langflow. |
+| `LANGFLOW_SECRET_KEY` | String | Auto-generated | Key used for encrypting sensitive data like API keys. If not provided, a secure key will be auto-generated. For production environments with multiple instances, you should explicitly set this to ensure consistent encryption across instances. |
+| `LANGFLOW_STORE` | Boolean | `true` | Enable the Langflow Store. See [`--store` option](./configuration-cli.md#run-store). |
+| `LANGFLOW_STORE_ENVIRONMENT_VARIABLES` | Boolean | `true` | Store environment variables as [global variables](../Configuration/configuration-global-variables.md) in the database. |
+| `LANGFLOW_SUPERUSER` | String | `langflow` | Set the name for the superuser. Required if [`LANGFLOW_AUTO_LOGIN`](#LANGFLOW_AUTO_LOGIN) is set to `false`. See [`superuser --username` option](./configuration-cli.md#superuser-username). |
+| `LANGFLOW_SUPERUSER_PASSWORD` | String | `langflow` | Set the password for the superuser. Required if [`LANGFLOW_AUTO_LOGIN`](#LANGFLOW_AUTO_LOGIN) is set to `false`. See [`superuser --password` option](./configuration-cli.md#superuser-password). |
+| `LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT` | String | Not set | Comma-separated list of environment variables to get from the environment and store as [global variables](../Configuration/configuration-global-variables.md). |
+| `LANGFLOW_LOAD_FLOWS_PATH` | String | Not set | Path to a directory containing flow JSON files to be loaded on startup. Note that this feature only works if `LANGFLOW_AUTO_LOGIN` is enabled. |
+| `LANGFLOW_WORKER_TIMEOUT` | Integer | `300` | Worker timeout in seconds. See [`--worker-timeout` option](./configuration-cli.md#run-worker-timeout). |
+| `LANGFLOW_WORKERS` | Integer | `1` | Number of worker processes. See [`--workers` option](./configuration-cli.md#run-workers). |
+
+## Configure .env, override.conf, and tasks.json files
+
+The following examples show how to configure Langflow using environment variables in different scenarios.
+
+
+
+
+The `.env` file is a text file that contains key-value pairs of environment variables.
+
+Create or edit a file named `.env` in your project root directory and add your configuration:
+
+```plaintext title=".env"
+DO_NOT_TRACK=true
+LANGFLOW_AUTO_LOGIN=false
+LANGFLOW_AUTO_SAVING=true
+LANGFLOW_AUTO_SAVING_INTERVAL=1000
+LANGFLOW_BACKEND_ONLY=false
+LANGFLOW_CACHE_TYPE=async
+LANGFLOW_COMPONENTS_PATH=/path/to/components/
+LANGFLOW_CONFIG_DIR=/path/to/config/
+LANGFLOW_DATABASE_URL=postgresql://user:password@localhost:5432/langflow
+LANGFLOW_DEV=false
+LANGFLOW_FALLBACK_TO_ENV_VAR=false
+LANGFLOW_HEALTH_CHECK_MAX_RETRIES=5
+LANGFLOW_HOST=127.0.0.1
+LANGFLOW_LANGCHAIN_CACHE=InMemoryCache
+LANGFLOW_MAX_FILE_SIZE_UPLOAD=10000
+LANGFLOW_LOG_LEVEL=error
+LANGFLOW_OPEN_BROWSER=false
+LANGFLOW_PORT=7860
+LANGFLOW_REMOVE_API_KEYS=false
+LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
+LANGFLOW_SECRET_KEY=somesecretkey
+LANGFLOW_STORE=true
+LANGFLOW_STORE_ENVIRONMENT_VARIABLES=true
+LANGFLOW_SUPERUSER=adminuser
+LANGFLOW_SUPERUSER_PASSWORD=adminpass
+LANGFLOW_WORKER_TIMEOUT=60000
+LANGFLOW_WORKERS=3
+```
+
+
+
+
+A systemd service configuration file configures Linux system services.
+
+To add environment variables, create or edit a service configuration file and add an `override.conf` file. This file allows you to override the default environment variables for the service.
+
+```ini title="override.conf"
+[Service]
+Environment="DO_NOT_TRACK=true"
+Environment="LANGFLOW_AUTO_LOGIN=false"
+Environment="LANGFLOW_AUTO_SAVING=true"
+Environment="LANGFLOW_AUTO_SAVING_INTERVAL=1000"
+Environment="LANGFLOW_BACKEND_ONLY=false"
+Environment="LANGFLOW_CACHE_TYPE=async"
+Environment="LANGFLOW_COMPONENTS_PATH=/path/to/components/"
+Environment="LANGFLOW_CONFIG_DIR=/path/to/config"
+Environment="LANGFLOW_DATABASE_URL=postgresql://user:password@localhost:5432/langflow"
+Environment="LANGFLOW_DEV=false"
+Environment="LANGFLOW_FALLBACK_TO_ENV_VAR=false"
+Environment="LANGFLOW_HEALTH_CHECK_MAX_RETRIES=5"
+Environment="LANGFLOW_HOST=127.0.0.1"
+Environment="LANGFLOW_LANGCHAIN_CACHE=InMemoryCache"
+Environment="LANGFLOW_MAX_FILE_SIZE_UPLOAD=10000"
+Environment="LANGFLOW_LOG_ENV=container_json"
+Environment="LANGFLOW_LOG_FILE=logs/langflow.log"
+Environment="LANGFLOW_LOG_LEVEL=error"
+Environment="LANGFLOW_OPEN_BROWSER=false"
+Environment="LANGFLOW_PORT=7860"
+Environment="LANGFLOW_REMOVE_API_KEYS=false"
+Environment="LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true"
+Environment="LANGFLOW_SECRET_KEY=somesecretkey"
+Environment="LANGFLOW_STORE=true"
+Environment="LANGFLOW_STORE_ENVIRONMENT_VARIABLES=true"
+Environment="LANGFLOW_SUPERUSER=adminuser"
+Environment="LANGFLOW_SUPERUSER_PASSWORD=adminpass"
+Environment="LANGFLOW_WORKER_TIMEOUT=60000"
+Environment="LANGFLOW_WORKERS=3"
+```
+
+For more information on systemd, see the [Red Hat documentation](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_systemd_unit_files_to_customize_and_optimize_your_system/assembly_working-with-systemd-unit-files_working-with-systemd#assembly_working-with-systemd-unit-files_working-with-systemd).
+
+
+
+
+The `tasks.json` file located in `.vscode/tasks.json` is a configuration file for development environments using Visual Studio Code.
+
+Create or edit the `.vscode/tasks.json` file in your project root:
+
+```json title=".vscode/tasks.json"
+{
+ "version": "2.0.0",
+ "options": {
+ "env": {
+ "DO_NOT_TRACK": "true",
+ "LANGFLOW_AUTO_LOGIN": "false",
+ "LANGFLOW_AUTO_SAVING": "true",
+ "LANGFLOW_AUTO_SAVING_INTERVAL": "1000",
+ "LANGFLOW_BACKEND_ONLY": "false",
+ "LANGFLOW_CACHE_TYPE": "async",
+ "LANGFLOW_COMPONENTS_PATH": "D:/path/to/components/",
+ "LANGFLOW_CONFIG_DIR": "D:/path/to/config/",
+ "LANGFLOW_DATABASE_URL": "postgresql://postgres:password@localhost:5432/langflow",
+ "LANGFLOW_DEV": "false",
+ "LANGFLOW_FALLBACK_TO_ENV_VAR": "false",
+ "LANGFLOW_HEALTH_CHECK_MAX_RETRIES": "5",
+ "LANGFLOW_HOST": "localhost",
+ "LANGFLOW_LANGCHAIN_CACHE": "InMemoryCache",
+ "LANGFLOW_MAX_FILE_SIZE_UPLOAD": "10000",
+ "LANGFLOW_LOG_ENV": "container_csv",
+ "LANGFLOW_LOG_FILE": "langflow.log",
+ "LANGFLOW_LOG_LEVEL": "error",
+ "LANGFLOW_OPEN_BROWSER": "false",
+ "LANGFLOW_PORT": "7860",
+ "LANGFLOW_REMOVE_API_KEYS": "true",
+ "LANGFLOW_SAVE_DB_IN_CONFIG_DIR": "false",
+ "LANGFLOW_SECRET_KEY": "somesecretkey",
+ "LANGFLOW_STORE": "true",
+ "LANGFLOW_STORE_ENVIRONMENT_VARIABLES": "true",
+ "LANGFLOW_SUPERUSER": "adminuser",
+ "LANGFLOW_SUPERUSER_PASSWORD": "adminpass",
+ "LANGFLOW_WORKER_TIMEOUT": "60000",
+ "LANGFLOW_WORKERS": "3"
+ }
+ },
+ "tasks": [
+ {
+ "label": "langflow backend",
+ "type": "shell",
+ "command": ". ./langflownightly/Scripts/activate && langflow run",
+ "isBackground": true,
+ "problemMatcher": []
+ }
+ ]
+}
+```
+
+To run Langflow using the above VSCode `tasks.json` file, in the VSCode command palette, select **Tasks: Run Task** > **langflow backend**.
+
+
+
diff --git a/langflow/docs/docs/Contributing/contributing-community.md b/langflow/docs/docs/Contributing/contributing-community.md
new file mode 100644
index 0000000..4cdf32e
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-community.md
@@ -0,0 +1,25 @@
+---
+title: Join the Langflow community
+slug: /contributing-community
+---
+
+
+
+## Join the Langflow Discord server
+
+Join the [Langflow Discord Server](https://discord.gg/EqksyE2EX9) to ask questions and showcase your projects.
+
+## Follow Langflow on X
+
+Follow [@langflow_ai](https://twitter.com/langflow_ai) on X to get the latest news about Langflow.
+
+## Star Langflow on GitHub
+
+You can [star Langflow in GitHub](https://github.com/langflow-ai/langflow).
+
+By adding a star, other users will be able to find Langflow more easily, and see that it has been already useful for others.
+
+## Watch the GitHub repository for releases
+
+You can [watch Langflow in GitHub](https://github.com/langflow-ai/langflow). If you select **Watching** instead of **Releases only** you will receive notifications when someone creates a new issue or question. You can also specify that you want to be notified only about new issues, discussions, and PRs so you can try to help solve those issues.
+
diff --git a/langflow/docs/docs/Contributing/contributing-components.md b/langflow/docs/docs/Contributing/contributing-components.md
new file mode 100644
index 0000000..138d52b
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-components.md
@@ -0,0 +1,23 @@
+---
+title: Contribute components
+slug: /contributing-components
+---
+
+
+New components are added as objects of the [CustomComponent](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/custom/custom_component/custom_component.py) class.
+
+Any dependencies are added to the [pyproject.toml](https://github.com/langflow-ai/langflow/blob/main/pyproject.toml#L148) file.
+
+### Contribute an example component to Langflow
+
+Anyone can contribute an example component. For example, if you created a new document loader called **MyCustomDocumentLoader**, you can follow these steps to contribute it to Langflow.
+
+1. Write your loader as an object of the [CustomComponent](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/custom/custom_component/custom_component.py) class. You'll create a new class, `MyCustomDocumentLoader`, that will inherit from `CustomComponent` and override the base class's methods.
+2. Define optional attributes like `display_name`, `description`, and `documentation` to provide information about your custom component.
+3. Implement the `build_config` method to define the configuration options for your custom component.
+4. Implement the `build` method to define the logic for taking input parameters specified in the `build_config` method and returning the desired output.
+5. Add the code to the [/components/documentloaders](https://github.com/langflow-ai/langflow/tree/dev/src/backend/base/langflow/components) folder.
+6. Add the dependency to [/documentloaders/__init__.py](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/components/documentloaders/__init__.py) as `from .MyCustomDocumentLoader import MyCustomDocumentLoader`.
+7. Add any new dependencies to the [pyproject.toml](https://github.com/langflow-ai/langflow/blob/main/pyproject.toml#L148) file.
+8. Submit documentation for your component. For this example, you'd submit documentation to the [loaders page](https://github.com/langflow-ai/langflow/blob/main/docs/docs/Components/components-loaders.md).
+9. Submit your changes as a pull request. The Langflow team will have a look, suggest changes, and add your component to Langflow.
\ No newline at end of file
diff --git a/langflow/docs/docs/Contributing/contributing-github-discussion-board.md b/langflow/docs/docs/Contributing/contributing-github-discussion-board.md
new file mode 100644
index 0000000..0a64f87
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-github-discussion-board.md
@@ -0,0 +1,12 @@
+---
+title: Ask for help on the Discussions board
+slug: /contributing-github-discussions
+---
+
+If you're looking for help with your code, consider posting a question on the Langflow [GitHub Discussions board](https://github.com/langflow-ai/langflow/discussions). The Langflow team cannot provide individual support via email. The team also believes that help is much more valuable if it's shared publicly, so that more people can benefit from it.
+
+Since the Discussions board is public, please follow this guidance when posting your code questions.
+
+* When describing your issue, try to provide as many details as possible. What exactly goes wrong? _How_ is it failing? Is there an error? "XY doesn't work" usually isn't that helpful for tracking down problems. Always remember to include the code you ran and if possible, extract only the relevant parts and don't just dump your entire script. This will make it easier for us to reproduce the error.
+
+* When you include long code, logs, or tracebacks, wrap them in `` and ` ` tags. This [collapses the content](https://developer.mozilla.org/en/docs/Web/HTML/Element/details) so the contents only becomes visible on click, making the issue easier to read and follow.
\ No newline at end of file
diff --git a/langflow/docs/docs/Contributing/contributing-github-issues.md b/langflow/docs/docs/Contributing/contributing-github-issues.md
new file mode 100644
index 0000000..e49addd
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-github-issues.md
@@ -0,0 +1,6 @@
+---
+title: Request an enhancement or report a bug
+slug: /contributing-github-issues
+---
+
+The [Issues page in the Langflow repo](https://github.com/langflow-ai/langflow/issues) is kept up to date with bugs, improvements, and feature requests. Labels are used to help with sorting and discovery of issues of interest. For an overview of the system Langflow uses to tag issues and pull requests, see the Langflow repo's [labels page](https://github.com/langflow-ai/langflow/labels).
\ No newline at end of file
diff --git a/langflow/docs/docs/Contributing/contributing-how-to-contribute.md b/langflow/docs/docs/Contributing/contributing-how-to-contribute.md
new file mode 100644
index 0000000..8d0bdc9
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-how-to-contribute.md
@@ -0,0 +1,115 @@
+---
+title: Contribute to Langflow
+slug: /contributing-how-to-contribute
+---
+
+This guide is intended to help you start contributing to Langflow.
+As an open-source project in a rapidly developing field, Langflow welcomes contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
+
+To contribute code or documentation to this project, follow the [fork and pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
+
+## Contribute code
+
+Develop Langflow locally with [uv](https://docs.astral.sh/uv/getting-started/installation/) and [Node.js](https://nodejs.org/en/download/package-manager).
+
+### Prerequisites
+
+* [uv(>=0.4)](https://docs.astral.sh/uv/getting-started/installation/)
+* [Node.js](https://nodejs.org/en/download/package-manager)
+
+### Clone the Langflow Repository
+
+1. Navigate to the [Langflow GitHub repository](https://github.com/langflow-ai/langflow), and then click **Fork**.
+
+2. Add the new remote to your local repository on your local machine:
+
+```bash
+git remote add fork https://github.com//langflow.git
+```
+
+### Prepare the development environment
+
+1. Create development hooks.
+
+```bash
+make init
+```
+
+This command sets up the development environment by installing backend and frontend dependencies, building the frontend static files, and initializing the project. It runs `make install_backend`, `make install_frontend`, `make build_frontend`, and finally `uv run langflow run` to start the application.
+
+2. Run `make lint`, `make format`, and `make unit_tests` before pushing to the repository.
+
+### Debug
+
+The repo includes a `.vscode/launch.json` file for debugging the backend in VSCode, which is faster than debugging with Docker Compose. To debug Langflow with the `launch.json` file in VSCode:
+
+1. Open Langflow in VSCode.
+2. Press **Ctrl+Shift+D** for Windows **or Cmd+Shift+D** for Mac to open the Run and Debug view.
+3. From the **Run and Debug** dropdown, choose a debugging configuration.
+4. Click the green **Play** button or press F5 to start debugging.
+
+Use `launch.json` to quickly debug different parts of your application, like the backend, frontend, or CLI, directly from VSCode.
+
+### Run Langflow locally
+
+After setting up the environment with `make init`, you can run Langflow's backend and frontend separately for development.
+Langflow recommends using a virtual environment like [venv](https://docs.python.org/3/library/venv.html) or [conda](https://anaconda.org/anaconda/conda) to isolate dependencies.
+
+Before you begin, ensure you have [uv](https://docs.astral.sh/uv/getting-started/installation/) and [Node.js](https://nodejs.org/en/download/package-manager) installed.
+
+1. In the repository root, install the dependencies and start the development server for the backend:
+
+```bash
+make backend
+```
+
+2. Install dependencies and start the frontend:
+
+```bash
+make frontend
+```
+
+This approach allows you to work on the backend and frontend independently, with hot-reloading for faster development.
+
+## Contribute documentation
+
+The documentation is built using [Docusaurus](https://docusaurus.io/) and written in [Markdown](https://docusaurus.io/docs/markdown-features).
+
+### Prerequisites
+
+* [Node.js](https://nodejs.org/en/download/package-manager)
+
+### Clone the Langflow repository
+
+1. Navigate to the [Langflow GitHub repository](https://github.com/langflow-ai/langflow), and then click **Fork**.
+
+2. Add the new remote to your local repository on your local machine:
+
+```bash
+git remote add fork https://github.com//langflow.git
+```
+
+3. To run the documentation locally, run the following commands:
+
+```bash
+cd docs
+yarn install
+yarn start
+```
+
+The documentation will be available at `localhost:3000` and all the files are located in the `docs/docs` folder.
+
+## Open a pull request
+
+Once you have written and manually tested your changes with `make lint` and `make unit_tests`, open a pull request to send your changes upstream to the main Langflow repository.
+
+1. Open a new GitHub pull request with your patch against the `main` branch.
+2. Ensure the PR title follows semantic commit conventions. For example, features are `feat: add new feature` and fixes are `fix: correct issue with X`.
+3. A Langflow maintainer will review your pull request. Thanks for your contribution!
+
+Some additional guidance on pull request titles:
+* Ensure the pull request description clearly describes the problem and solution. If the PR fixes an issue, include a link to the fixed issue in the PR description with `Fixes #1234`.
+* Pull request titles appear in Langflow's release notes, so they should explain what the PR does as explicitly as possible.
+* Pull requests should strive to fix one thing **only**, and should contain a good description of what is being fixed.
+
+For more information, see the [Python Developer's Guide](https://devguide.python.org/getting-started/pull-request-lifecycle/index.html#making-good-commits).
\ No newline at end of file
diff --git a/langflow/docs/docs/Contributing/contributing-telemetry.md b/langflow/docs/docs/Contributing/contributing-telemetry.md
new file mode 100644
index 0000000..0d7d24b
--- /dev/null
+++ b/langflow/docs/docs/Contributing/contributing-telemetry.md
@@ -0,0 +1,49 @@
+---
+title: Telemetry
+slug: /contributing-telemetry
+---
+
+Langflow uses anonymous telemetry to collect essential usage statistics to enhance functionality and the user experience. This data helps us identify popular features and areas that need improvement, and ensures development efforts align with what you need.
+
+We respect your privacy and are committed to protecting your data. We do not collect any personal information or sensitive data. All telemetry data is anonymized and used solely for improving Langflow.
+
+## Opt out of telemetry
+
+To opt out of telemetry, set the `LANGFLOW_DO_NOT_TRACK` or `DO_NOT_TRACK` environment variable to `true` before running Langflow. This disables telemetry data collection.
+
+## Data that Langflow collects
+
+### Run {#2d427dca4f0148ae867997f6789e8bfb}
+
+- **IsWebhook**: Indicates whether the operation was triggered via a webhook.
+- **Seconds**: Duration in seconds for how long the operation lasted, providing insights into performance.
+- **Success**: Boolean value indicating whether the operation was successful, helping identify potential errors or issues.
+- **ErrorMessage**: Provides error message details if the operation was unsuccessful, aiding in troubleshooting and enhancements.
+
+### Shutdown {#081e4bd4faec430fb05b657026d1a69c}
+
+- **Time Running**: Total runtime before shutdown, useful for understanding application lifecycle and optimizing uptime.
+
+### Version {#dc09f6aba6c64c7b8dad3d86a7cba6d6}
+
+- **Version**: The specific version of Langflow used, which helps in tracking feature adoption and compatibility.
+- **Platform**: Operating system of the host machine, which aids in focusing our support for popular platforms like Windows, macOS, and Linux.
+- **Python**: The version of Python used, assisting in maintaining compatibility and support for various Python versions.
+- **Arch**: Architecture of the system (e.g., x86, ARM), which helps optimize our software for different hardware.
+- **AutoLogin**: Indicates whether the auto-login feature is enabled, reflecting user preference settings.
+- **CacheType**: Type of caching mechanism used, which impacts performance and efficiency.
+- **BackendOnly**: Boolean indicating whether you are running Langflow in a backend-only mode, useful for understanding deployment configurations.
+
+### Playground {#ae6c3859f612441db3c15a7155e9f920}
+
+- **Seconds**: Duration in seconds for playground execution, offering insights into performance during testing or experimental stages.
+- **ComponentCount**: Number of components used in the playground, which helps understand complexity and usage patterns.
+- **Success**: Success status of the playground operation, aiding in identifying the stability of experimental features.
+
+### Component {#630728d6654c40a6b8901459a4bc3a4e}
+
+- **Name**: Identifies the component, providing data on which components are most utilized or prone to issues.
+- **Seconds**: Time taken by the component to execute, offering performance metrics.
+- **Success**: Whether the component operated successfully, which helps in quality control.
+- **ErrorMessage**: Details of any errors encountered, crucial for debugging and improvement.
+
diff --git a/langflow/docs/docs/Deployment/deployment-docker.md b/langflow/docs/docs/Deployment/deployment-docker.md
new file mode 100644
index 0000000..54203a2
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-docker.md
@@ -0,0 +1,125 @@
+---
+title: Deploy Langflow on Docker
+slug: /deployment-docker
+---
+
+This guide demonstrates deploying Langflow with Docker and Docker Compose.
+
+## Prerequisites
+
+* [Docker](https://docs.docker.com/)
+* [Docker Compose](https://docs.docker.com/compose/)
+
+## Clone the repo and build the Langflow Docker container
+
+1. Clone the Langflow repository:
+
+ `git clone https://github.com/langflow-ai/langflow.git`
+
+2. Navigate to the `docker_example` directory:
+
+ `cd langflow/docker_example`
+
+3. Run the Docker Compose file:
+
+ `docker compose up`
+
+
+Langflow is now accessible at `http://localhost:7860/`.
+
+## Configure Docker services
+
+The Docker Compose configuration spins up two services: `langflow` and `postgres`.
+
+To configure values for these services at container startup, include them in your `.env` file.
+
+An example `.env` file is available in the [project repository](https://github.com/langflow-ai/langflow/blob/main/.env.example).
+
+To pass the `.env` values at container startup, include the flag in your `docker run` command:
+
+```
+docker run -it --rm \
+ -p 7860:7860 \
+ --env-file .env \
+ langflowai/langflow:latest
+```
+
+### Langflow service
+
+The `langflow`service serves both the backend API and frontend UI of the Langflow web application.
+
+The `langflow` service uses the `langflowai/langflow:latest` Docker image and exposes port `7860`. It depends on the `postgres` service.
+
+Environment variables:
+
+* `LANGFLOW_DATABASE_URL`: The connection string for the PostgreSQL database.
+* `LANGFLOW_CONFIG_DIR`: The directory where Langflow stores logs, file storage, monitor data, and secret keys.
+
+Volumes:
+
+* `langflow-data`: This volume is mapped to `/app/langflow` in the container.
+
+### PostgreSQL service
+
+The `postgres` service is a database that stores Langflow's persistent data including flows, users, and settings.
+
+The service runs on port 5432 and includes a dedicated volume for data storage.
+
+The `postgres` service uses the `postgres:16` Docker image.
+
+Environment variables:
+
+* `POSTGRES_USER`: The username for the PostgreSQL database.
+* `POSTGRES_PASSWORD`: The password for the PostgreSQL database.
+* `POSTGRES_DB`: The name of the PostgreSQL database.
+
+Volumes:
+
+* `langflow-postgres`: This volume is mapped to `/var/lib/postgresql/data` in the container.
+
+### Deploy a specific Langflow version with Docker Compose
+
+If you want to deploy a specific version of Langflow, you can modify the `image` field under the `langflow` service in the Docker Compose file. For example, to use version `1.0-alpha`, change `langflowai/langflow:latest` to `langflowai/langflow:1.0-alpha`.
+
+## Package your flow as a Docker image
+
+You can include your Langflow flow with the application image.
+When you build the image, your saved flow `.JSON` flow is included.
+This enables you to serve a flow from a container, push the image to Docker Hub, and deploy on Kubernetes.
+
+An example flow is available in the [Langflow Helm Charts](https://github.com/langflow-ai/langflow-helm-charts/tree/main/examples/flows) repository, or you can provide your own `JSON` file.
+
+1. Create a project directory:
+```shell
+mkdir langflow-custom && cd langflow-custom
+```
+
+2. Download the example flow or include your flow's `.JSON` file in the `langflow-custom` directory.
+
+```shell
+wget https://raw.githubusercontent.com/langflow-ai/langflow-helm-charts/refs/heads/main/examples/flows/basic-prompting-hello-world.json
+```
+
+3. Create a Dockerfile:
+```dockerfile
+FROM langflowai/langflow:latest
+RUN mkdir /app/flows
+COPY ./*json /app/flows/.
+```
+The `COPY ./*json` command copies all JSON files in your current directory to the `/flows` folder.
+
+4. Build and run the image locally.
+```shell
+docker build -t myuser/langflow-hello-world:1.0.0 .
+docker run -p 7860:7860 myuser/langflow-hello-world:1.0.0
+```
+
+5. Build and push the image to Docker Hub.
+Replace `myuser` with your Docker Hub username.
+```shell
+docker build -t myuser/langflow-hello-world:1.0.0 .
+docker push myuser/langflow-hello-world:1.0.0
+```
+
+To deploy the image with Helm, see [Langflow runtime deployment](/deployment-kubernetes#langflow-runtime-deployment).
+
diff --git a/langflow/docs/docs/Deployment/deployment-gcp.md b/langflow/docs/docs/Deployment/deployment-gcp.md
new file mode 100644
index 0000000..e472769
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-gcp.md
@@ -0,0 +1,29 @@
+---
+title: Deploy Langflow on Google Cloud Platform
+slug: /deployment-gcp
+---
+
+This guide demonstrates deploying Langflow on Google Cloud Platform.
+
+To deploy Langflow on Google Cloud Platform using Cloud Shell, use the below script.
+
+The script guides you through setting up a Debian-based VM with the Langflow package, Nginx, and the necessary configurations to run the Langflow dev environment in GCP.
+
+## Prerequisites
+
+* A [Google Cloud](https://console.cloud.google.com/) project with the necessary permissions to create resources
+
+## Deploy Langflow in GCP
+
+1. Click the following button to launch Cloud Shell:
+
+ [](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial.md)
+
+2. Click **Trust repo**. Some gcloud commands may not run in an ephemeral Cloud Shell environment.
+3. Click **Start** and follow the tutorial to deploy Langflow.
+
+## Pricing
+
+This deployment uses a [spot (preemptible) instance](https://cloud.google.com/compute/docs/instances/preemptible), which is a cost-effective option for running Langflow. However, **due to the nature of spot instances, the VM may be terminated at any time if Google Cloud needs to reclaim the resources**.
+
+For more information, see the [GCP pricing calculator](https://cloud.google.com/products/calculator?hl=en).
diff --git a/langflow/docs/docs/Deployment/deployment-hugging-face-spaces.md b/langflow/docs/docs/Deployment/deployment-hugging-face-spaces.md
new file mode 100644
index 0000000..414adab
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-hugging-face-spaces.md
@@ -0,0 +1,21 @@
+---
+title: Deploy Langflow on HuggingFace Spaces
+slug: /deployment-hugging-face-spaces
+---
+
+This guide explains how to deploy Langflow on [HuggingFace Spaces](https://huggingface.co/spaces/).
+
+1. Go to the [Langflow Space](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true).
+
+2. Click **Duplicate Space**.
+3. In the configuration dialog, do the following:
+ - Enter a name for your Space.
+ - Select either public or private visibility.
+ - Click **Duplicate Space**
+
+ 
+
+Wait for the setup to complete. You'll be redirected to your new Space automatically.
+
+Your Langflow instance is now ready to use.
+
diff --git a/langflow/docs/docs/Deployment/deployment-kubernetes.md b/langflow/docs/docs/Deployment/deployment-kubernetes.md
new file mode 100644
index 0000000..895182f
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-kubernetes.md
@@ -0,0 +1,360 @@
+---
+title: Deploy Langflow on Kubernetes
+slug: /deployment-kubernetes
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+This guide demonstrates deploying Langflow on a Kubernetes cluster.
+
+Two charts are available at the [Langflow Helm Charts repository](https://github.com/langflow-ai/langflow-helm-charts):
+
+- Deploy the [Langflow IDE](deployment-kubernetes#langflow-ide-deployment) for the complete Langflow development environment.
+- Deploy the [Langflow runtime](/deployment-kubernetes#langflow-runtime-deployment) to deploy a standalone Langflow application in a more secure and stable environment.
+
+## Deploy the Langflow IDE
+
+The Langflow IDE deployment is a complete environment for developers to create, test, and debug their flows. It includes both the API and the UI.
+
+The `langflow-ide` Helm chart is available in the [Langflow Helm Charts repository](https://github.com/langflow-ai/langflow-helm-charts/tree/main/charts/langflow-ide).
+
+### Prerequisites
+
+- A [Kubernetes](https://kubernetes.io/docs/setup/) cluster
+- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
+- [Helm](https://helm.sh/docs/intro/install/)
+
+### Prepare a Kubernetes cluster
+
+This example uses [Minikube](https://minikube.sigs.k8s.io/docs/start/), but you can use any Kubernetes cluster.
+
+1. Create a Kubernetes cluster on Minikube.
+
+ ```text
+ minikube start
+ ```
+
+2. Set `kubectl` to use Minikube.
+
+ ```text
+ kubectl config use-context minikube
+ ```
+
+### Install the Langflow IDE Helm chart
+
+1. Add the repository to Helm and update it.
+
+ ```text
+ helm repo add langflow https://langflow-ai.github.io/langflow-helm-charts
+ helm repo update
+ ```
+
+2. Install Langflow with the default options in the `langflow` namespace.
+
+ ```text
+ helm install langflow-ide langflow/langflow-ide -n langflow --create-namespace
+ ```
+
+3. Check the status of the pods
+
+ ```text
+ kubectl get pods -n langflow
+ ```
+
+
+ ```text
+ NAME READY STATUS RESTARTS AGE
+ langflow-0 1/1 Running 0 33s
+ langflow-frontend-5d9c558dbb-g7tc9 1/1 Running 0 38s
+ ```
+
+
+### Configure port forwarding to access Langflow
+
+Enable local port forwarding to access Langflow from your local machine.
+
+1. To make the Langflow API accessible from your local machine at port 7860:
+```text
+kubectl port-forward -n langflow svc/langflow-service-backend 7860:7860
+```
+
+2. To make the Langflow UI accessible from your local machine at port 8080:
+```text
+kubectl port-forward -n langflow svc/langflow-service 8080:8080
+```
+
+Now you can access:
+- The Langflow API at `http://localhost:7860`
+- The Langflow UI at `http://localhost:8080`
+
+
+### Configure the Langflow version
+
+Langflow is deployed with the `latest` version by default.
+
+To specify a different Langflow version, set the `langflow.backend.image.tag` and `langflow.frontend.image.tag` values in the [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-ide/values.yaml) file.
+
+
+```yaml
+langflow:
+ backend:
+ image:
+ tag: "1.0.0a59"
+ frontend:
+ image:
+ tag: "1.0.0a59"
+
+```
+
+
+### Configure external storage
+
+By default, the chart deploys a SQLite database stored in a local persistent disk.
+If you want to use an external PostgreSQL database, you can configure it in two ways:
+
+* Use the built-in PostgreSQL chart:
+```yaml
+postgresql:
+ enabled: true
+ auth:
+ username: "langflow"
+ password: "langflow-postgres"
+ database: "langflow-db"
+```
+
+* Use an external database:
+```yaml
+postgresql:
+ enabled: false
+
+langflow:
+ backend:
+ externalDatabase:
+ enabled: true
+ driver:
+ value: "postgresql"
+ port:
+ value: "5432"
+ user:
+ value: "langflow"
+ password:
+ valueFrom:
+ secretKeyRef:
+ key: "password"
+ name: "your-secret-name"
+ database:
+ value: "langflow-db"
+ sqlite:
+ enabled: false
+```
+
+
+### Configure scaling
+
+Scale the number of replicas and resources for both frontend and backend services:
+
+```yaml
+langflow:
+ backend:
+ replicaCount: 1
+ resources:
+ requests:
+ cpu: 0.5
+ memory: 1Gi
+ # limits:
+ # cpu: 0.5
+ # memory: 1Gi
+
+ frontend:
+ enabled: true
+ replicaCount: 1
+ resources:
+ requests:
+ cpu: 0.3
+ memory: 512Mi
+ # limits:
+ # cpu: 0.3
+ # memory: 512Mi
+```
+
+## Deploy the Langflow runtime
+
+The runtime chart is tailored for deploying applications in a production environment. It is focused on stability, performance, isolation, and security to ensure that applications run reliably and efficiently.
+
+The `langflow-runtime` Helm chart is available in the [Langflow Helm Charts repository](https://github.com/langflow-ai/langflow-helm-charts/tree/main/charts/langflow-runtime).
+
+### Prerequisites
+
+- A [Kubernetes](https://kubernetes.io/docs/setup/) server
+- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
+- [Helm](https://helm.sh/docs/intro/install/)
+
+### Install the Langflow runtime Helm chart
+
+1. Add the repository to Helm.
+
+```shell
+helm repo add langflow https://langflow-ai.github.io/langflow-helm-charts
+helm repo update
+```
+
+2. Install the Langflow app with the default options in the `langflow` namespace.
+
+If you have a created a [custom image with packaged flows](/deployment-docker#package-your-flow-as-a-docker-image), you can deploy Langflow by overriding the default [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-runtime/values.yaml) file with the `--set` flag.
+
+* Use a custom image with bundled flows:
+```shell
+helm install my-langflow-app langflow/langflow-runtime -n langflow --create-namespace --set image.repository=myuser/langflow-hello-world --set image.tag=1.0.0
+```
+
+* Alternatively, install the chart and download the flows from a URL with the `--set` flag:
+```shell
+helm install my-langflow-app-with-flow langflow/langflow-runtime \
+ -n langflow \
+ --create-namespace \
+ --set 'downloadFlows.flows[0].url=https://raw.githubusercontent.com/langflow-ai/langflow/dev/tests/data/basic_example.json'
+```
+
+:::important
+You may need to escape the square brackets in this command if you are using a shell that requires it:
+```shell
+helm install my-langflow-app-with-flow langflow/langflow-runtime \
+ -n langflow \
+ --create-namespace \
+ --set 'downloadFlows.flows\[0\].url=https://raw.githubusercontent.com/langflow-ai/langflow/dev/tests/data/basic_example.json'
+```
+:::
+
+3. Check the status of the pods.
+```shell
+kubectl get pods -n langflow
+```
+
+### Access the Langflow app API
+
+1. Get your service name.
+```shell
+kubectl get svc -n langflow
+```
+
+The service name is your release name followed by `-langflow-runtime`. For example, if you used `helm install my-langflow-app-with-flow` the service name is `my-langflow-app-with-flow-langflow-runtime`.
+
+2. Enable port forwarding to access Langflow from your local machine:
+
+```shell
+kubectl port-forward -n langflow svc/my-langflow-app-with-flow-langflow-runtime 7860:7860
+```
+
+3. Confirm you can access the API at `http://localhost:7860/api/v1/flows/` and view a list of flows.
+```shell
+curl -v http://localhost:7860/api/v1/flows/
+```
+
+4. Execute the packaged flow.
+
+The following command gets the first flow ID from the flows list and runs the flow.
+
+```shell
+# Get flow ID
+id=$(curl -s "http://localhost:7860/api/v1/flows/" | jq -r '.[0].id')
+
+# Run flow
+curl -X POST \
+ "http://localhost:7860/api/v1/run/$id?stream=false" \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "input_value": "Hello!",
+ "output_type": "chat",
+ "input_type": "chat"
+ }'
+```
+
+### Configure secrets
+
+To inject secrets and Langflow global variables, use the `secrets` and `env` sections in the [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-runtime/values.yaml) file.
+
+For example, the [example flow JSON](https://raw.githubusercontent.com/langflow-ai/langflow-helm-charts/refs/heads/main/examples/flows/basic-prompting-hello-world.json) uses a global variable that is a secret. When you export the flow as JSON, it's recommended to not include the secret.
+
+Instead, when importing the flow in the Langflow runtime, you can set the global variable in one of the following ways:
+
+
+
+
+```yaml
+env:
+ - name: openai_key_var
+ valueFrom:
+ secretKeyRef:
+ name: openai-key
+ key: openai-key
+```
+
+Or directly in the values file (not recommended for secret values):
+
+```yaml
+env:
+ - name: openai_key_var
+ value: "sk-...."
+```
+
+
+
+
+1. Create the secret:
+```shell
+kubectl create secret generic openai-credentials \
+ --namespace langflow \
+ --from-literal=OPENAI_API_KEY=sk...
+```
+
+2. Verify the secret exists. The result is encrypted.
+```shell
+kubectl get secrets -n langflow openai-credentials
+```
+
+3. Upgrade the Helm release to use the secret.
+```shell
+helm upgrade my-langflow-app-image langflow/langflow-runtime -n langflow \
+ --reuse-values \
+ --set "extraEnv[0].name=OPENAI_API_KEY" \
+ --set "extraEnv[0].valueFrom.secretKeyRef.name=openai-credentials" \
+ --set "extraEnv[0].valueFrom.secretKeyRef.key=OPENAI_API_KEY"
+```
+
+
+
+
+### Configure the log level
+
+Set the log level and other Langflow configurations in the [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-runtime/values.yaml) file.
+
+```yaml
+env:
+ - name: LANGFLOW_LOG_LEVEL
+ value: "INFO"
+```
+
+### Configure scaling
+
+To scale the number of replicas for the Langflow appplication, change the `replicaCount` value in the [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-runtime/values.yaml) file.
+
+```yaml
+replicaCount: 3
+```
+
+To scale the application vertically by increasing the resources for the pods, change the `resources` values in the [values.yaml](https://github.com/langflow-ai/langflow-helm-charts/blob/main/charts/langflow-runtime/values.yaml) file.
+
+
+```yaml
+resources:
+ requests:
+ memory: "2Gi"
+ cpu: "1000m"
+```
+
+## Deploy Langflow on AWS EKS, Google GKE, or Azure AKS and other examples
+
+For more information, see the [Langflow Helm Charts repository](https://github.com/langflow-ai/langflow-helm-charts).
+
+
diff --git a/langflow/docs/docs/Deployment/deployment-railway.md b/langflow/docs/docs/Deployment/deployment-railway.md
new file mode 100644
index 0000000..91c0113
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-railway.md
@@ -0,0 +1,20 @@
+---
+title: Deploy Langflow on Railway
+slug: /deployment-railway
+---
+
+This guide explains how to deploy Langflow on [Railway](https://railway.app/), a cloud infrastructure platform that provides auto-deploy, managed databases, and automatic scaling.
+
+1. Click the following button to go to Railway:
+
+ [](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
+
+2. Click **Deploy Now**.
+Railway automatically does the following:
+ - Sets up the infrastructure.
+ - Deploys Langflow.
+ - Starts the application.
+
+Wait for the deployment to complete.
+
+Your Langflow instance is now ready to use.
diff --git a/langflow/docs/docs/Deployment/deployment-render.md b/langflow/docs/docs/Deployment/deployment-render.md
new file mode 100644
index 0000000..f723b33
--- /dev/null
+++ b/langflow/docs/docs/Deployment/deployment-render.md
@@ -0,0 +1,23 @@
+---
+title: Deploy Langflow on Render
+slug: /deployment-render
+---
+
+This guide explains how to deploy Langflow on [Render](https://render.com/), a cloud platform for deploying web applications and APIs.
+
+:::note
+Langflow requires at least 2 GB of RAM to run, so it uses a **standard** Render instance. This may require a credit card. Review [Render's pricing](https://render.com/pricing) before proceeding.
+:::
+
+1. Click the following button to go to Render:
+
+ [](https://render.com/deploy?repo=https%3A%2F%2Fgithub.com%2Flangflow-ai%2Flangflow%2Ftree%2Fdev)
+
+2. Enter a blueprint name, and then select the branch for your `render.yaml` file.
+
+3. Click **Deploy Blueprint**.
+
+Wait for the deployment to complete.
+
+Your Langflow instance is now ready to use.
+
diff --git a/langflow/docs/docs/Get-Started/get-started-installation.md b/langflow/docs/docs/Get-Started/get-started-installation.md
new file mode 100644
index 0000000..5d35c29
--- /dev/null
+++ b/langflow/docs/docs/Get-Started/get-started-installation.md
@@ -0,0 +1,150 @@
+---
+title: Install Langflow
+slug: /get-started-installation
+---
+
+You can deploy Langflow either locally or as a hosted service with [**Datastax Langflow**](#datastax-langflow).
+
+## Install Langflow locally
+
+Install Langflow locally with [uv (recommended)](https://docs.astral.sh/uv/getting-started/installation/), [pip](https://pypi.org/project/pip/), or [pipx](https://pipx.pypa.io/stable/installation/).
+
+### Prerequisites
+
+- [Python 3.10 to 3.13](https://www.python.org/downloads/release/python-3100/) installed
+- [uv](https://docs.astral.sh/uv/getting-started/installation/), [pip](https://pypi.org/project/pip/), or [pipx](https://pipx.pypa.io/stable/installation/) installed
+- Before installing Langflow, we recommend creating a virtual environment to isolate your Python dependencies with [uv](https://docs.astral.sh/uv/pip/environments), [venv](https://docs.python.org/3/library/venv.html), or [conda](https://anaconda.org/anaconda/conda)
+
+### Install Langflow with pip or pipx
+
+Install Langflow with uv:
+
+```bash
+uv pip install langflow
+```
+
+Install Langflow with pip:
+
+```bash
+python -m pip install langflow
+```
+
+Install Langflow with pipx using the Python 3.10 executable:
+
+```bash
+pipx install langflow --python python3.10
+```
+
+## Run Langflow
+
+1. To run Langflow with uv, enter the following command.
+
+```bash
+uv run langflow run
+```
+
+2. To run Langflow with pip, enter the following command.
+
+```bash
+python -m langflow run
+```
+
+3. Confirm that a local Langflow instance starts by visiting `http://127.0.0.1:7860` in a Chromium-based browser.
+
+Now that Langflow is running, follow the [Quickstart](/get-started-quickstart) to create your first flow.
+
+## Manage Langflow versions
+
+To upgrade Langflow to the latest version with uv, use the uv pip upgrade command.
+
+```bash
+uv pip install langflow -U
+```
+
+To upgrade Langflow to the latest version, use the pip upgrade command.
+
+```bash
+python -m pip install langflow -U
+```
+
+To install a specific version of the Langflow package, add the required version to the command.
+
+```bash
+python -m pip install langflow==1.1
+```
+
+To reinstall Langflow and all of its dependencies, add the `--force-reinstall` flag to the command.
+
+```bash
+python -m pip install langflow --force-reinstall
+```
+
+## DataStax Langflow {#datastax-langflow}
+
+**DataStax Langflow** is a hosted version of Langflow integrated with [Astra DB](https://www.datastax.com/products/datastax-astra). Be up and running in minutes with no installation or setup required. [Sign up for free](https://astra.datastax.com/signup?type=langflow).
+
+## Common installation issues
+
+This is a list of possible issues that you may encounter when installing and running Langflow.
+
+### No `langflow.__main__` module
+
+When you try to run Langflow with the command `langflow run`, you encounter the following error:
+
+```bash
+> No module named 'langflow.__main__'
+```
+
+1. Run `python -m langflow run` instead of `langflow run`.
+2. If that doesn't work, reinstall the latest Langflow version with `python -m pip install langflow -U`.
+3. If that doesn't work, reinstall Langflow and its dependencies with `python -m pip install langflow --pre -U --force-reinstall`.
+
+### Langflow runTraceback
+
+When you try to run Langflow using the command `langflow run`, you encounter the following error:
+
+```bash
+> langflow runTraceback (most recent call last): File ".../langflow", line 5, in from langflow.__main__ import mainModuleNotFoundError: No module named 'langflow.__main__'
+```
+
+There are two possible reasons for this error:
+
+1. You've installed Langflow using `pip install langflow` but you already had a previous version of Langflow installed in your system. In this case, you might be running the wrong executable. To solve this issue, run the correct executable by running `python -m langflow run` instead of `langflow run`. If that doesn't work, try uninstalling and reinstalling Langflow with `python -m pip install langflow --pre -U`.
+2. Some version conflicts might have occurred during the installation process. Run `python -m pip install langflow --pre -U --force-reinstall` to reinstall Langflow and its dependencies.
+
+### Something went wrong running migrations
+
+```bash
+> Something went wrong running migrations. Please, run 'langflow migration --fix'
+```
+
+Clear the cache by deleting the contents of the cache folder.
+
+This folder can be found at:
+
+- **Linux or WSL2 on Windows**: `home//.cache/langflow/`
+- **MacOS**: `/Users//Library/Caches/langflow/`
+
+This error can occur during Langflow upgrades when the new version can't override `langflow-pre.db` in `.cache/langflow/`. Clearing the cache removes this file but also erases your settings.
+
+If you wish to retain your files, back them up before clearing the folder.
+
+### Langflow installation freezes at pip dependency resolution
+
+Installing Langflow with `pip install langflow` slowly fails with this error message:
+
+```plain
+pip is looking at multiple versions of <> to determine which version is compatible with other requirements. This could take a while.
+```
+
+To work around this issue, install Langflow with [`uv`](https://docs.astral.sh/uv/getting-started/installation/) instead of `pip`.
+
+```plain
+uv pip install langflow
+```
+
+To run Langflow with uv:
+
+```plain
+uv run langflow run
+```
diff --git a/langflow/docs/docs/Get-Started/get-started-quickstart.md b/langflow/docs/docs/Get-Started/get-started-quickstart.md
new file mode 100644
index 0000000..6a041ec
--- /dev/null
+++ b/langflow/docs/docs/Get-Started/get-started-quickstart.md
@@ -0,0 +1,193 @@
+---
+title: Quickstart
+slug: /get-started-quickstart
+---
+
+import Icon from "@site/src/components/icon";
+
+Get to know Langflow by building an OpenAI-powered chatbot application. After you've constructed a chatbot, add Retrieval Augmented Generation (RAG) to chat with your own data.
+
+## Prerequisites
+
+* [An OpenAI API key](https://platform.openai.com/)
+* [An Astra DB vector database](https://docs.datastax.com/en/astra-db-serverless/get-started/quickstart.html) with:
+ * An Astra DB application token scoped to read and write to the database
+ * A collection created in [Astra](https://docs.datastax.com/en/astra-db-serverless/databases/manage-collections.html#create-collection) or a new collection created in the **Astra DB** component
+
+## Open Langflow and start a new project
+
+1. From the Langflow dashboard, click **New Flow**, and then select **Blank Flow**. A blank workspace opens where you can build your flow.
+
+:::tip
+If you don't want to create a blank flow, click **New Flow**, and then select **Basic Prompting** for a pre-built flow.
+Continue to [Run the basic prompting flow](#run-basic-prompting-flow).
+:::
+
+2. Select **Basic Prompting**.
+
+3. The **Basic Prompting** flow is created.
+
+## Build the basic prompting flow
+
+The Basic Prompting flow will look like this when it's completed:
+
+
+
+To build the **Basic Prompting** flow, follow these steps:
+
+1. Click **Inputs**, select the **Chat Input** component, and then drag it to the canvas.
+The [Chat Input](/components-io#chat-input) component accepts user input to the chat.
+2. Click **Prompt**, select the **Prompt** component, and then drag it to the canvas.
+The [Prompt](/components-prompts) component combines the user input with a user-defined prompt.
+3. Click **Outputs**, select the **Chat Output** component, and then drag it to the canvas.
+The [Chat Output](/components-io#chat-output) component prints the flow's output to the chat.
+4. Click **Models**, select the **OpenAI** component, and then drag it to the canvas.
+The [OpenAI](components-models#openai) model component sends the user input and prompt to the OpenAI API and receives a response.
+
+You should now have a flow that looks like this:
+
+
+
+With no connections between them, the components won't interact with each other.
+You want data to flow from **Chat Input** to **Chat Output** through the connections between the components.
+Each component accepts inputs on its left side, and sends outputs on its right side.
+Hover over the connection ports to see the data types that the component accepts.
+For more on component inputs and outputs, see [Components overview](/concepts-components).
+
+5. To connect the **Chat Input** component to the OpenAI model component, click and drag a line from the blue **Message** port to the OpenAI model component's **Input** port.
+6. To connect the **Prompt** component to the OpenAI model component, click and drag a line from the blue **Prompt Message** port to the OpenAI model component's **System Message** port.
+7. To connect the **OpenAI** model component to the **Chat Output**, click and drag a line from the blue **Text** port to the **Chat Output** component's **Text** port.
+
+Your finished basic prompting flow should look like this:
+
+
+
+### Run the Basic Prompting flow {#run-basic-prompting-flow}
+
+Add your OpenAI API key to the OpenAI model component, and add a prompt to the Prompt component to instruct the model how to respond.
+
+1. Add your credentials to the OpenAI component. The fastest way to complete these fields is with Langflow’s [Global Variables](/configuration-global-variables).
+
+ 1. In the OpenAI component’s OpenAI API Key field, click the **Globe** button, and then click **Add New Variable**.
+ Alternatively, click your username in the top right corner, and then click **Settings**, **Global Variables**, and then **Add New**.
+ 2. Name your variable. Paste your OpenAI API key (sk-…) in the Value field.
+ 3. In the **Apply To Fields** field, select the OpenAI API Key field to apply this variable to all OpenAI Embeddings components.
+
+2. To add a prompt to the **Prompt** component, click the **Template** field, and then enter your prompt.
+The prompt guides the bot's responses to input.
+If you're unsure, use `Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.`
+3. Click **Playground** to start a chat session.
+4. Enter a query, and then make sure the bot responds according to the prompt you set in the **Prompt** component.
+
+You have successfully created a chatbot application using OpenAI in the Langflow Workspace.
+
+## Add vector RAG to your application
+
+You created a chatbot application with Langflow, but let's try an experiment.
+
+1. Ask the bot: `Who won the Oscar in 2024 for best movie?`
+2. The bot's response is similar to this:
+
+```plain
+I'm sorry, but I don't have information on events or awards that occurred after
+October 2023, including the Oscars in 2024.
+You may want to check the latest news or the official Oscars website
+for the most current information.
+```
+
+Well, that's unfortunate, but you can load more up-to-date data with **Retrieval Augmented Generation**, or **RAG**.
+
+Vector RAG allows you to load your own data and chat with it, unlocking a wider range of possibilities for your chatbot application.
+
+## Add vector RAG with the Astra DB component
+
+Build on the basic prompting flow and add vector RAG to your chatbot application with the **Astra DB Vector Store** component.
+
+Add document ingestion to your basic prompting flow, with the **Astra DB** component as the vector store.
+
+:::tip
+If you don't want to create a blank flow, click **New Flow**, and then select **Vector RAG** for a pre-built flow.
+:::
+
+Adding vector RAG to the basic prompting flow will look like this when completed:
+
+
+
+To build the flow, follow these steps:
+
+1. Disconnect the **Chat Input** component from the **OpenAI** component by double-clicking on the connecting line.
+2. Click **Vector Stores**, select the **Astra DB** component, and then drag it to the canvas.
+The [Astra DB vector store](/components-vector-stores#astra-db-vector-store) component connects to your **Astra DB** database.
+3. Click **Data**, select the **File** component, and then drag it to the canvas.
+The [File](/components-data#file) component loads files from your local machine.
+4. Click **Processing**, select the **Split Text** component, and then drag it to the canvas.
+The [Split Text](/components-processing#split-text) component splits the loaded text into smaller chunks.
+5. Click **Processing**, select the **Parse Data** component, and then drag it to the canvas.
+The [Data to Message](/components-processing#data-to-message) component converts the data from the **Astra DB** component into plain text.
+6. Click **Embeddings**, select the **OpenAI Embeddings** component, and then drag it to the canvas.
+The [OpenAI Embeddings](/components-embedding-models#openai-embeddings) component generates embeddings for the user's input, which are compared to the vector data in the database.
+7. Connect the new components into the existing flow, so your flow looks like this:
+
+
+
+8. Configure the **Astra DB** component.
+ 1. In the **Astra DB Application Token** field, add your **Astra DB** application token.
+ The component connects to your database and populates the menus with existing databases and collections.
+ 2. Select your **Database**.
+ If you don't have a collection, select **New database**.
+ Complete the **Name**, **Cloud provider**, and **Region** fields, and then click **Create**. **Database creation takes a few minutes**.
+ 3. Select your **Collection**. Collections are created in your [Astra DB deployment](https://astra.datastax.com) for storing vector data.
+ :::info
+ If you select a collection embedded with NVIDIA through Astra's vectorize service, the **Embedding Model** port is removed, because you have already generated embeddings for this collection with the NVIDIA `NV-Embed-QA` model. The component fetches the data from the collection, and uses the same embeddings for queries.
+ :::
+
+9. If you don't have a collection, create a new one within the component.
+ 1. Select **New collection**.
+ 2. Complete the **Name**, **Embedding generation method**, **Embedding model**, and **Dimensions** fields, and then click **Create**.
+
+ Your choice for the **Embedding generation method** and **Embedding model** depends on whether you want to use embeddings generated by a provider through Astra's vectorize service, or generated by a component in Langflow.
+
+ * To use embeddings generated by a provider through Astra's vectorize service, select the model from the **Embedding generation method** dropdown menu, and then select the model from the **Embedding model** dropdown menu.
+ * To use embeddings generated by a component in Langflow, select **Bring your own** for both the **Embedding generation method** and **Embedding model** fields. In this starter project, the option for the embeddings method and model is the **OpenAI Embeddings** component connected to the **Astra DB** component.
+ * The **Dimensions** value must match the dimensions of your collection. This field is **not required** if you use embeddings generated through Astra's vectorize service. You can find this value in the **Collection** in your [Astra DB deployment](https://astra.datastax.com).
+
+ For more information, see the [DataStax Astra DB Serverless documentation](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html).
+
+
+If you used Langflow's **Global Variables** feature, the RAG application flow components are already configured with the necessary credentials.
+
+### Run the chatbot with retrieved context
+
+1. Modify the **Prompt** component to contain variables for both `{user_question}` and `{context}`.
+The `{context}` variable gives the bot additional context for answering `{user_question}` beyond what the LLM was trained on.
+
+```plain
+Given the context
+{context}
+Answer the question
+{user_question}
+```
+
+2. In the **File** component, upload a text file from your local machine with data you want to ingest into the **Astra DB** component database.
+This example uploads an up-to-date CSV about Oscar winners.
+3. Click **Playground** to start a chat session.
+4. Ask the bot: `Who won the Oscar in 2024 for best movie?`
+5. The bot's response should be similar to this:
+
+```plain
+The Oscar for Best Picture in 2024 was awarded to "Oppenheimer,"
+produced by Emma Thomas, Charles Roven, and Christopher Nolan.
+```
+
+Adding an **Astra DB** vector store brought your chatbot all the way into 2024.
+You have successfully added RAG to your chatbot application using the **Astra DB** component.
+
+## Next steps
+
+This example used movie data, but the RAG pattern can be used with any data you want to load and chat with.
+
+Make the **Astra DB** database the brain that [Agents](/agents-overview) use to make decisions.
+
+Expose this flow as an [API](/concepts-api) and call it from your external applications.
+
+For more on the **Astra DB** component, see [Astra DB vector store](/components-vector-stores#astra-db-vector-store).
diff --git a/langflow/docs/docs/Get-Started/welcome-to-langflow.md b/langflow/docs/docs/Get-Started/welcome-to-langflow.md
new file mode 100644
index 0000000..f540289
--- /dev/null
+++ b/langflow/docs/docs/Get-Started/welcome-to-langflow.md
@@ -0,0 +1,39 @@
+---
+title: Welcome to Langflow
+slug: /
+---
+
+Langflow is a new, visual framework for building multi-agent and RAG applications. It is open-source, Python-powered, fully customizable, and LLM and vector store agnostic.
+
+Its intuitive interface allows for easy manipulation of AI building blocks, enabling developers to quickly prototype and turn their ideas into powerful, real-world solutions.
+
+Langflow empowers developers to rapidly prototype and build AI applications with its user-friendly interface and powerful features. Whether you're a seasoned AI developer or just starting out, Langflow provides the tools you need to bring your AI ideas to life.
+
+## Visual flow builder
+
+Langflow is an intuitive visual flow builder. This drag-and-drop interface allows developers to create complex AI workflows without writing extensive code. You can easily connect different components, such as prompts, language models, and data sources, to build sophisticated AI applications.
+
+
+
+## Use cases
+
+Langflow can be used for a wide range of AI applications, including:
+
+* [Craft intelligent chatbots](/tutorials-memory-chatbot)
+* [Build document analysis systems](/tutorials-document-qa)
+* [Generate compelling content](/tutorials-blog-writer)
+* [Orchestrate multi-agent applications](/starter-projects-simple-agent)
+
+## Community and support
+
+Join Langflow's vibrant community of developers and AI enthusiasts. See the following resources to join discussions, share your projects, and get support:
+
+* [Contribute to Langflow](contributing-how-to-contribute)
+* [Langflow Discord Server](https://discord.gg/EqksyE2EX9)
+* [@langflow_ai](https://twitter.com/langflow_ai)
+
+## Get started with Langflow
+
+- [Install Langflow](/get-started-installation)
+- [Quickstart](/get-started-quickstart)
+
diff --git a/langflow/docs/docs/Integrations/505849097.png b/langflow/docs/docs/Integrations/505849097.png
new file mode 100644
index 0000000..50f17c9
Binary files /dev/null and b/langflow/docs/docs/Integrations/505849097.png differ
diff --git a/langflow/docs/docs/Integrations/965098683.png b/langflow/docs/docs/Integrations/965098683.png
new file mode 100644
index 0000000..c7e44ed
Binary files /dev/null and b/langflow/docs/docs/Integrations/965098683.png differ
diff --git a/langflow/docs/docs/Integrations/Apify/apify_agent_flow.png b/langflow/docs/docs/Integrations/Apify/apify_agent_flow.png
new file mode 100644
index 0000000..fb93d1b
Binary files /dev/null and b/langflow/docs/docs/Integrations/Apify/apify_agent_flow.png differ
diff --git a/langflow/docs/docs/Integrations/Apify/apify_agent_flow_simple.png b/langflow/docs/docs/Integrations/Apify/apify_agent_flow_simple.png
new file mode 100644
index 0000000..ee6ca2b
Binary files /dev/null and b/langflow/docs/docs/Integrations/Apify/apify_agent_flow_simple.png differ
diff --git a/langflow/docs/docs/Integrations/Apify/apify_flow_wcc.png b/langflow/docs/docs/Integrations/Apify/apify_flow_wcc.png
new file mode 100644
index 0000000..776560b
Binary files /dev/null and b/langflow/docs/docs/Integrations/Apify/apify_flow_wcc.png differ
diff --git a/langflow/docs/docs/Integrations/Apify/integrations-apify.md b/langflow/docs/docs/Integrations/Apify/integrations-apify.md
new file mode 100644
index 0000000..68114cd
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Apify/integrations-apify.md
@@ -0,0 +1,67 @@
+---
+title: Apify
+slug: /integrations-apify
+---
+
+# Integrate Apify with Langflow
+
+[Apify](https://apify.com/) is a web scraping and data extraction platform. It provides an [Actor Store](https://apify.com/store) with more than 3,000 ready-made cloud tools called **Actors**.
+
+Apify components in Langflow run **Actors** to accomplish tasks like data extraction, content analysis, and SQL operations.
+
+## Prerequisites
+
+* An [Apify API token](https://docs.apify.com/platform/integrations/api)
+
+## Use the Apify Actors component in a flow
+
+To use an **Apify Actor** in your flow:
+
+1. Click and drag the **Apify Actors** component to your **workspace**.
+2. In the **Apify Actor** component's **Apify Token** field, add your **Apify API token**.
+3. In the **Apify Actor** component's **Actor** field, add your **Actor ID**.
+You can find the Actor ID in the [Apify Actor Store](https://apify.com/store).
+For example, the [Website Content Crawler](https://apify.com/apify/website-content-crawler) has Actor ID `apify/website-content-crawler`.
+4. The component can now be used as a **Tool** to be connected to an **Agent** component, or configured to run manually.
+For more information on running the component manually, see the **JSON Example** in the [Apify documentation](https://apify.com/apify/website-content-crawler/input-schema).
+
+## Example flows
+
+Here are some example flows that use the **Apify Actors** component.
+
+### Extract website text content in Markdown
+
+Use the [Website Content Crawler Actor](https://apify.com/apify/website-content-crawler) to extract text content in Markdown format from a website and process it in your flow.
+
+
+
+### Process web content with an agent
+
+Extract website content using the [Website Content Crawler Actor](https://apify.com/apify/website-content-crawler), and then process it with an agent.
+
+The agent takes the extracted data and transforms it into summaries, insights, or structured responses to make the information more actionable.
+
+
+
+### Analyze social media profiles with multiple actors
+
+Perform comprehensive social media research with multiple Apify Actors.
+
+Add the [Google Search Results Scraper Actor](https://apify.com/apify/google-search-scraper) to find relevant social media profiles, and then add the [TikTok Data Extractor Actor](https://apify.com/clockworks/free-tiktok-scraper) to gather data and videos.
+
+The agent collects the links from Google and content from TikTok and analyzes the data to provide insights about a person, brand, or topic.
+
+
+## Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| apify_token | Apify Token | Your Apify API key. |
+| actor | Actor | The Apify Actor to run, for example `apify/website-content-crawler`. |
+| run_input | Run Input | The JSON input for configuring the Actor run. For more information, see the [Apify documentation](https://apify.com/apify/website-content-crawler/input-schema). |
+
+## Outputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| output | Actor Run Result | The JSON response containing the output of the Actor run. |
diff --git a/langflow/docs/docs/Integrations/Arize/integrations-arize.md b/langflow/docs/docs/Integrations/Arize/integrations-arize.md
new file mode 100644
index 0000000..49dd7e2
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Arize/integrations-arize.md
@@ -0,0 +1,54 @@
+---
+title: Integrate Arize with Langflow
+slug: /integrations-arize
+---
+
+Arize is a tool built on [OpenTelemetry](https://opentelemetry.io/) and [OpenInference](https://docs.arize.com/phoenix/reference/open-inference) for monitoring and optimizing LLM applications.
+
+To add tracing to your Langflow application, add the `ARIZE_SPACE_ID` and `ARIZE_API_KEY` environment variables to your Langflow application.
+
+## Prerequisites
+
+* If you are using the [standard Arize platform](https://docs.arize.com/arize), you need an **Arize Space ID** and **API API Key**.
+* If you are using the open-source [Arize Phoenix platform](https://docs.arize.com/phoenix), you need an Arize Phoenix API key and a project name.
+
+## Connect Arize to Langflow
+
+1. To retrieve your **Arize Space ID** and **API API Key**, navigate to the [Arize dashboard](https://app.arize.com/).
+2. Click **Settings**, and then click **Space Settings and Keys**.
+3. Copy the **SpaceID** and **API Key (Ingestion Service Account Key)** values.
+4. Create a `.env` file in the root of your Langflow application.
+5. Add the `ARIZE_SPACE_ID` and `ARIZE_API_KEY` environment variables to your Langflow application.
+You do not need to specify the **Arize Project** name if you're using the standard Arize platform. The **Project** name in Arize is the same as the Langflow **Flow** name.
+
+```bash
+export ARIZE_SPACE_ID=
+export ARIZE_API_KEY=
+```
+
+6. Start your Langflow application with the values from the `.env` file.
+
+```bash
+uv run langflow run --env-file .env
+```
+
+## Run a flow and view metrics in Arize
+
+1. In Langflow, select the [Simple agent](/starter-projects-simple-agent) starter project.
+2. In the **Agent** component's **OpenAI API Key** field, paste your **OpenAI API key**.
+3. Click **Playground**.
+Ask your Agent some questions to generate traffic.
+4. Navigate to the [Arize dashboard](https://app.arize.com/), and then open your project.
+You may have to wait a few minutes for Arize to process the data.
+5. The **LLM Tracing** tab shows metrics for your flow.
+Each Langflow execution generates two traces in Arize.
+The `AgentExecutor` trace is the Arize trace of Langchain's `AgentExecutor`. The UUID trace is the trace of the Langflow components.
+6. To view traces, click the **Traces** tab.
+A **trace** is the complete journey of a request, made of multiple **spans**.
+7. To view **Spans**, select the **Spans** tab.
+A **span** is a single operation within a trace. For example, a **span** could be a single API call to OpenAI or a single function call to a custom tool.
+For more on traces, spans, and other metrics in Arize, see the [Arize documentation](https://docs.arize.com/arize/llm-tracing/tracing).
+8. All metrics in the **LLM Tracing** tab can be added to **Datasets**.
+To add a span to a **Dataset**, click the **Add to Dataset** button.
+9. To view a **Dataset**, click the **Datasets** tab, and then select your **Dataset**.
+For more on **Datasets**, see the [Arize documentation](https://docs.arize.com/arize/llm-datasets-and-experiments/datasets-and-experiments).
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/AssemblyAI_Flow.json b/langflow/docs/docs/Integrations/AssemblyAI_Flow.json
new file mode 100644
index 0000000..195bb19
--- /dev/null
+++ b/langflow/docs/docs/Integrations/AssemblyAI_Flow.json
@@ -0,0 +1,1431 @@
+{
+ "name": "AssemblyAI Transcription and Speech AI Flow",
+ "icon": null,
+ "is_component": false,
+ "endpoint_name": null,
+ "data": {
+ "nodes": [
+ {
+ "id": "Prompt-IO8Cq",
+ "type": "genericNode",
+ "position": {
+ "x": -1376.3296370680628,
+ "y": 928.8860970980681
+ },
+ "data": {
+ "type": "Prompt",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "template": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "Provide a brief summary of the transcript.",
+ "display_name": "Template",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "prompt",
+ "_input_type": "PromptInput"
+ }
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "icon": "prompts",
+ "is_input": null,
+ "is_output": null,
+ "is_composition": null,
+ "base_classes": [
+ "Message"
+ ],
+ "name": "",
+ "display_name": "Prompt",
+ "documentation": "",
+ "custom_fields": {
+ "template": []
+ },
+ "output_types": [],
+ "full_path": null,
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "prompt",
+ "hidden": null,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "template"
+ ],
+ "beta": false,
+ "error": null,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "Prompt-IO8Cq"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 324,
+ "positionAbsolute": {
+ "x": -1376.3296370680628,
+ "y": 928.8860970980681
+ },
+ "dragging": false
+ },
+ {
+ "id": "AssemblyAITranscriptionJobCreator-Idt7P",
+ "type": "genericNode",
+ "position": {
+ "x": -1957.7132501771657,
+ "y": 470.79685053457587
+ },
+ "data": {
+ "type": "AssemblyAITranscriptionJobCreator",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "audio_file": {
+ "trace_as_metadata": true,
+ "file_path": "fa69381c-d1c4-4535-bc23-bc2fb4956e1e/2024-09-26_16-47-01_sports_injuries.mp3",
+ "fileTypes": [
+ "3ga",
+ "8svx",
+ "aac",
+ "ac3",
+ "aif",
+ "aiff",
+ "alac",
+ "amr",
+ "ape",
+ "au",
+ "dss",
+ "flac",
+ "flv",
+ "m4a",
+ "m4b",
+ "m4p",
+ "m4r",
+ "mp3",
+ "mpga",
+ "ogg",
+ "oga",
+ "mogg",
+ "opus",
+ "qcp",
+ "tta",
+ "voc",
+ "wav",
+ "wma",
+ "wv",
+ "webm",
+ "mts",
+ "m2ts",
+ "ts",
+ "mov",
+ "mp2",
+ "mp4",
+ "m4p",
+ "m4v",
+ "mxf"
+ ],
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "audio_file",
+ "value": "sports_injuries.mp3",
+ "display_name": "Audio File",
+ "advanced": false,
+ "dynamic": false,
+ "info": "The audio file to transcribe",
+ "title_case": false,
+ "type": "file",
+ "_input_type": "FileInput",
+ "load_from_db": false
+ },
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": null,
+ "display_name": "Assembly API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "audio_file_url": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "audio_file_url",
+ "value": "",
+ "display_name": "Audio File URL",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The URL of the audio file to transcribe (Can be used instead of a File)",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import os\n\nimport assemblyai as aai\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAITranscriptionJobCreator(Component):\n display_name = \"AssemblyAI Start Transcript\"\n description = \"Create a transcription job for an audio file using AssemblyAI with advanced options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n ),\n FileInput(\n name=\"audio_file\",\n display_name=\"Audio File\",\n file_types=[\n \"3ga\",\n \"8svx\",\n \"aac\",\n \"ac3\",\n \"aif\",\n \"aiff\",\n \"alac\",\n \"amr\",\n \"ape\",\n \"au\",\n \"dss\",\n \"flac\",\n \"flv\",\n \"m4a\",\n \"m4b\",\n \"m4p\",\n \"m4r\",\n \"mp3\",\n \"mpga\",\n \"ogg\",\n \"oga\",\n \"mogg\",\n \"opus\",\n \"qcp\",\n \"tta\",\n \"voc\",\n \"wav\",\n \"wma\",\n \"wv\",\n \"webm\",\n \"mts\",\n \"m2ts\",\n \"ts\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"m4p\",\n \"m4v\",\n \"mxf\",\n ],\n info=\"The audio file to transcribe\",\n ),\n MessageTextInput(\n name=\"audio_file_url\",\n display_name=\"Audio File URL\",\n info=\"The URL of the audio file to transcribe (Can be used instead of a File)\",\n advanced=True,\n ),\n DropdownInput(\n name=\"speech_model\",\n display_name=\"Speech Model\",\n options=[\n \"best\",\n \"nano\",\n ],\n value=\"best\",\n info=\"The speech model to use for the transcription\",\n advanced=True,\n ),\n BoolInput(\n name=\"language_detection\",\n display_name=\"Automatic Language Detection\",\n info=\"Enable automatic language detection\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"language_code\",\n display_name=\"Language\",\n info=\"\"\"\n The language of the audio file. Can be set manually if automatic language detection is disabled. \n See https://www.assemblyai.com/docs/getting-started/supported-languages for a list of supported language codes.\n \"\"\",\n advanced=True,\n ),\n BoolInput(\n name=\"speaker_labels\",\n display_name=\"Enable Speaker Labels\",\n info=\"Enable speaker diarization\",\n ),\n MessageTextInput(\n name=\"speakers_expected\",\n display_name=\"Expected Number of Speakers\",\n info=\"Set the expected number of speakers (optional, enter a number)\",\n advanced=True,\n ),\n BoolInput(\n name=\"punctuate\",\n display_name=\"Punctuate\",\n info=\"Enable automatic punctuation\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"format_text\",\n display_name=\"Format Text\",\n info=\"Enable text formatting\",\n advanced=True,\n value=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript ID\", name=\"transcript_id\", method=\"create_transcription_job\"),\n ]\n\n def create_transcription_job(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # Convert speakers_expected to int if it's not empty\n speakers_expected = None\n if self.speakers_expected and self.speakers_expected.strip():\n try:\n speakers_expected = int(self.speakers_expected)\n except ValueError:\n self.status = \"Error: Expected Number of Speakers must be a valid integer\"\n return Data(data={\"error\": \"Error: Expected Number of Speakers must be a valid integer\"})\n\n language_code = self.language_code if self.language_code else None\n\n config = aai.TranscriptionConfig(\n speech_model=self.speech_model,\n language_detection=self.language_detection,\n language_code=language_code,\n speaker_labels=self.speaker_labels,\n speakers_expected=speakers_expected,\n punctuate=self.punctuate,\n format_text=self.format_text,\n )\n\n audio = None\n if self.audio_file:\n if self.audio_file_url:\n logger.warning(\"Both an audio file an audio URL were specified. The audio URL was ignored.\")\n\n # Check if the file exists\n if not os.path.exists(self.audio_file):\n self.status = \"Error: Audio file not found\"\n return Data(data={\"error\": \"Error: Audio file not found\"})\n audio = self.audio_file\n elif self.audio_file_url:\n audio = self.audio_file_url\n else:\n self.status = \"Error: Either an audio file or an audio URL must be specified\"\n return Data(data={\"error\": \"Error: Either an audio file or an audio URL must be specified\"})\n\n try:\n transcript = aai.Transcriber().submit(audio, config=config)\n\n if transcript.error:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n else:\n result = Data(data={\"transcript_id\": transcript.id})\n self.status = result\n return result\n except Exception as e:\n self.status = f\"An error occurred: {str(e)}\"\n return Data(data={\"error\": f\"An error occurred: {str(e)}\"})\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "format_text": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "format_text",
+ "value": true,
+ "display_name": "Format Text",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Enable text formatting",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "language_code": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "language_code",
+ "value": "",
+ "display_name": "Language",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "\n The language of the audio file. Can be set manually if automatic language detection is disabled. \n See https://www.assemblyai.com/docs/getting-started/supported-languages for a list of supported language codes.\n ",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "language_detection": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "language_detection",
+ "value": false,
+ "display_name": "Automatic Language Detection",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Enable automatic language detection",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "punctuate": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "punctuate",
+ "value": true,
+ "display_name": "Punctuate",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Enable automatic punctuation",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "speaker_labels": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "speaker_labels",
+ "value": true,
+ "display_name": "Enable Speaker Labels",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Enable speaker diarization",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput",
+ "load_from_db": false
+ },
+ "speakers_expected": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "speakers_expected",
+ "value": "",
+ "display_name": "Expected Number of Speakers",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Set the expected number of speakers (optional, enter a number)",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "speech_model": {
+ "trace_as_metadata": true,
+ "options": [
+ "best",
+ "nano"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "speech_model",
+ "value": "best",
+ "display_name": "Speech Model",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The speech model to use for the transcription",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Create a transcription job for an audio file using AssemblyAI with advanced options",
+ "icon": "AssemblyAI",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "AssemblyAI Start Transcript",
+ "documentation": "https://www.assemblyai.com/docs",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": true,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "transcript_id",
+ "display_name": "Transcript ID",
+ "method": "create_transcription_job",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "api_key",
+ "audio_file",
+ "audio_file_url",
+ "speech_model",
+ "language_detection",
+ "language_code",
+ "speaker_labels",
+ "speakers_expected",
+ "punctuate",
+ "format_text"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "AssemblyAITranscriptionJobCreator-Idt7P",
+ "description": "Create a transcription job for an audio file using AssemblyAI with advanced options",
+ "display_name": "AssemblyAI Start Transcript"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 482,
+ "positionAbsolute": {
+ "x": -1957.7132501771657,
+ "y": 470.79685053457587
+ },
+ "dragging": false
+ },
+ {
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "type": "genericNode",
+ "position": {
+ "x": -1408.0967182254753,
+ "y": 461.5039554434261
+ },
+ "data": {
+ "type": "AssemblyAITranscriptionJobPoller",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "transcript_id": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "transcript_id",
+ "value": "",
+ "display_name": "Transcript ID",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The ID of the transcription job to poll",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": null,
+ "display_name": "Assembly API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import assemblyai as aai\n\nfrom langflow.custom import Component\nfrom langflow.io import DataInput, FloatInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAITranscriptionJobPoller(Component):\n display_name = \"AssemblyAI Poll Transcript\"\n description = \"Poll for the status of a transcription job using AssemblyAI\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n ),\n DataInput(\n name=\"transcript_id\",\n display_name=\"Transcript ID\",\n info=\"The ID of the transcription job to poll\",\n ),\n FloatInput(\n name=\"polling_interval\",\n display_name=\"Polling Interval\",\n value=3.0,\n info=\"The polling interval in seconds\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcription Result\", name=\"transcription_result\", method=\"poll_transcription_job\"),\n ]\n\n def poll_transcription_job(self) -> Data:\n \"\"\"Polls the transcription status until completion and returns the Data.\"\"\"\n aai.settings.api_key = self.api_key\n aai.settings.polling_interval = self.polling_interval\n\n # check if it's an error message from the previous step\n if self.transcript_id.data.get(\"error\"):\n self.status = self.transcript_id.data[\"error\"]\n return self.transcript_id\n\n try:\n transcript = aai.Transcript.get_by_id(self.transcript_id.data[\"transcript_id\"])\n except Exception as e:\n error = f\"Getting transcription failed: {str(e)}\"\n self.status = error\n return Data(data={\"error\": error})\n\n if transcript.status == aai.TranscriptStatus.completed:\n json_response = transcript.json_response\n text = json_response.pop(\"text\", None)\n utterances = json_response.pop(\"utterances\", None)\n transcript_id = json_response.pop(\"id\", None)\n sorted_data = { \"text\": text, \"utterances\": utterances, \"id\": transcript_id}\n sorted_data.update(json_response)\n data = Data(data=sorted_data)\n self.status = data\n return data\n else:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "polling_interval": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "polling_interval",
+ "value": 3,
+ "display_name": "Polling Interval",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The polling interval in seconds",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ }
+ },
+ "description": "Poll for the status of a transcription job using AssemblyAI",
+ "icon": "AssemblyAI",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "AssemblyAI Poll Transcript",
+ "documentation": "https://www.assemblyai.com/docs",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "transcription_result",
+ "display_name": "Transcription Result",
+ "method": "poll_transcription_job",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "api_key",
+ "transcript_id",
+ "polling_interval"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "description": "Poll for the status of a transcription job using AssemblyAI",
+ "display_name": "AssemblyAI Poll Transcript"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 368,
+ "positionAbsolute": {
+ "x": -1408.0967182254753,
+ "y": 461.5039554434261
+ },
+ "dragging": false
+ },
+ {
+ "id": "AssemblyAIGetSubtitles-3sjU6",
+ "type": "genericNode",
+ "position": {
+ "x": -867.5862690424032,
+ "y": 368.91683022842676
+ },
+ "data": {
+ "type": "AssemblyAIGetSubtitles",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "transcription_result": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "transcription_result",
+ "value": "",
+ "display_name": "Transcription Result",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The transcription result from AssemblyAI",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": null,
+ "display_name": "Assembly API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "chars_per_caption": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "chars_per_caption",
+ "value": 0,
+ "display_name": "Characters per Caption",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The maximum number of characters per caption (0 for no limit)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import assemblyai as aai\n\nfrom langflow.custom import Component\nfrom langflow.io import DataInput, DropdownInput, IntInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAIGetSubtitles(Component):\n display_name = \"AssemblyAI Get Subtitles\"\n description = \"Export your transcript in SRT or VTT format for subtitles and closed captions\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n ),\n DataInput(\n name=\"transcription_result\",\n display_name=\"Transcription Result\",\n info=\"The transcription result from AssemblyAI\",\n ),\n DropdownInput(\n name=\"subtitle_format\",\n display_name=\"Subtitle Format\",\n options=[\"srt\", \"vtt\"],\n value=\"srt\",\n info=\"The format of the captions (SRT or VTT)\",\n ),\n IntInput(\n name=\"chars_per_caption\",\n display_name=\"Characters per Caption\",\n info=\"The maximum number of characters per caption (0 for no limit)\",\n value=0,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Subtitles\", name=\"subtitles\", method=\"get_subtitles\"),\n ]\n\n def get_subtitles(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # check if it's an error message from the previous step\n if self.transcription_result.data.get(\"error\"):\n self.status = self.transcription_result.data[\"error\"]\n return self.transcription_result\n\n try:\n transcript_id = self.transcription_result.data[\"id\"]\n transcript = aai.Transcript.get_by_id(transcript_id)\n except Exception as e:\n error = f\"Getting transcription failed: {str(e)}\"\n self.status = error\n return Data(data={\"error\": error})\n\n if transcript.status == aai.TranscriptStatus.completed:\n subtitles = None\n chars_per_caption = self.chars_per_caption if self.chars_per_caption > 0 else None\n if self.subtitle_format == \"srt\":\n subtitles = transcript.export_subtitles_srt(chars_per_caption)\n else:\n subtitles = transcript.export_subtitles_vtt(chars_per_caption)\n\n result = Data(\n subtitles=subtitles,\n format=self.subtitle_format,\n transcript_id=transcript_id,\n chars_per_caption=chars_per_caption,\n )\n\n self.status = result\n return result\n else:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "subtitle_format": {
+ "trace_as_metadata": true,
+ "options": [
+ "srt",
+ "vtt"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "subtitle_format",
+ "value": "srt",
+ "display_name": "Subtitle Format",
+ "advanced": false,
+ "dynamic": false,
+ "info": "The format of the captions (SRT or VTT)",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Export your transcript in SRT or VTT format for subtitles and closed captions",
+ "icon": "AssemblyAI",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "AssemblyAI Get Subtitles",
+ "documentation": "https://www.assemblyai.com/docs",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "subtitles",
+ "display_name": "Subtitles",
+ "method": "get_subtitles",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "api_key",
+ "transcription_result",
+ "subtitle_format",
+ "chars_per_caption"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "AssemblyAIGetSubtitles-3sjU6",
+ "description": "Export your transcript in SRT or VTT format for subtitles and closed captions",
+ "display_name": "AssemblyAI Get Subtitles"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 454,
+ "positionAbsolute": {
+ "x": -867.5862690424032,
+ "y": 368.91683022842676
+ },
+ "dragging": false
+ },
+ {
+ "id": "AssemblyAIListTranscripts-3prc4",
+ "type": "genericNode",
+ "position": {
+ "x": -380.99808133361984,
+ "y": 401.2674645310267
+ },
+ "data": {
+ "type": "AssemblyAIListTranscripts",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": null,
+ "display_name": "Assembly API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import assemblyai as aai\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAIListTranscripts(Component):\n display_name = \"AssemblyAI List Transcripts\"\n description = \"Retrieve a list of transcripts from AssemblyAI with filtering options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n ),\n IntInput(\n name=\"limit\",\n display_name=\"Limit\",\n info=\"Maximum number of transcripts to retrieve (default: 20, use 0 for all)\",\n value=20,\n ),\n DropdownInput(\n name=\"status_filter\",\n display_name=\"Status Filter\",\n options=[\"all\", \"queued\", \"processing\", \"completed\", \"error\"],\n value=\"all\",\n info=\"Filter by transcript status\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"created_on\",\n display_name=\"Created On\",\n info=\"Only get transcripts created on this date (YYYY-MM-DD)\",\n advanced=True,\n ),\n BoolInput(\n name=\"throttled_only\",\n display_name=\"Throttled Only\",\n info=\"Only get throttled transcripts, overrides the status filter\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript List\", name=\"transcript_list\", method=\"list_transcripts\"),\n ]\n\n def list_transcripts(self) -> list[Data]:\n aai.settings.api_key = self.api_key\n\n params = aai.ListTranscriptParameters()\n if self.limit:\n params.limit = self.limit\n if self.status_filter != \"all\":\n params.status = self.status_filter\n if self.created_on and self.created_on.text:\n params.created_on = self.created_on.text\n if self.throttled_only:\n params.throttled_only = True\n\n try:\n transcriber = aai.Transcriber()\n\n def convert_page_to_data_list(page):\n return [Data(**t.dict()) for t in page.transcripts]\n\n if self.limit == 0:\n # paginate over all pages\n params.limit = 100\n page = transcriber.list_transcripts(params)\n transcripts = convert_page_to_data_list(page)\n\n while page.page_details.before_id_of_prev_url is not None:\n params.before_id = page.page_details.before_id_of_prev_url\n page = transcriber.list_transcripts(params)\n transcripts.extend(convert_page_to_data_list(page))\n else:\n # just one page\n page = transcriber.list_transcripts(params)\n transcripts = convert_page_to_data_list(page)\n\n self.status = transcripts\n return transcripts\n except Exception as e:\n error_data = Data(data={\"error\": f\"An error occurred: {str(e)}\"})\n self.status = [error_data]\n return [error_data]\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "created_on": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "created_on",
+ "value": "",
+ "display_name": "Created On",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Only get transcripts created on this date (YYYY-MM-DD)",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "limit": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "limit",
+ "value": 20,
+ "display_name": "Limit",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Maximum number of transcripts to retrieve (default: 20, use 0 for all)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "status_filter": {
+ "trace_as_metadata": true,
+ "options": [
+ "all",
+ "queued",
+ "processing",
+ "completed",
+ "error"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "status_filter",
+ "value": "all",
+ "display_name": "Status Filter",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Filter by transcript status",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "throttled_only": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "throttled_only",
+ "value": false,
+ "display_name": "Throttled Only",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Only get throttled transcripts, overrides the status filter",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Retrieve a list of transcripts from AssemblyAI with filtering options",
+ "icon": "AssemblyAI",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "AssemblyAI List Transcripts",
+ "documentation": "https://www.assemblyai.com/docs",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "transcript_list",
+ "display_name": "Transcript List",
+ "method": "list_transcripts",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "api_key",
+ "limit",
+ "status_filter",
+ "created_on",
+ "throttled_only"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "AssemblyAIListTranscripts-3prc4",
+ "description": "Retrieve a list of transcripts from AssemblyAI with filtering options",
+ "display_name": "AssemblyAI List Transcripts"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 410,
+ "positionAbsolute": {
+ "x": -380.99808133361984,
+ "y": 401.2674645310267
+ },
+ "dragging": false
+ },
+ {
+ "id": "AssemblyAILeMUR-jzwHZ",
+ "type": "genericNode",
+ "position": {
+ "x": -875.6482330011189,
+ "y": 887.1705799007382
+ },
+ "data": {
+ "type": "AssemblyAILeMUR",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "transcription_result": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "transcription_result",
+ "value": "",
+ "display_name": "Transcription Result",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The transcription result from AssemblyAI",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": null,
+ "display_name": "Assembly API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import assemblyai as aai\n\nfrom langflow.custom import Component\nfrom langflow.io import DataInput, DropdownInput, FloatInput, IntInput, MultilineInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAILeMUR(Component):\n display_name = \"AssemblyAI LeMUR\"\n description = \"Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework\"\n documentation = \"https://www.assemblyai.com/docs/lemur\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n advanced=False,\n ),\n DataInput(\n name=\"transcription_result\",\n display_name=\"Transcription Result\",\n info=\"The transcription result from AssemblyAI\",\n ),\n MultilineInput(\n name=\"prompt\",\n display_name=\"Input Prompt\",\n info=\"The text to prompt the model\",\n ),\n DropdownInput(\n name=\"final_model\",\n display_name=\"Final Model\",\n options=[\"claude3_5_sonnet\", \"claude3_opus\", \"claude3_haiku\", \"claude3_sonnet\"],\n value=\"claude3_5_sonnet\",\n info=\"The model that is used for the final prompt after compression is performed\",\n advanced=True,\n ),\n FloatInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n advanced=True,\n value=0.0,\n info=\"The temperature to use for the model\",\n ),\n IntInput(\n name=\"max_output_size\",\n display_name=\" Max Output Size\",\n advanced=True,\n value=2000,\n info=\"Max output size in tokens, up to 4000\",\n ),\n DropdownInput(\n name=\"endpoint\",\n display_name=\"Endpoint\",\n options=[\"task\", \"summary\", \"question-answer\"],\n value=\"task\",\n info=\"The LeMUR endpoint to use. For 'summary' and 'question-answer', no prompt input is needed. See https://www.assemblyai.com/docs/api-reference/lemur/ for more info.\",\n advanced=True,\n ),\n MultilineInput(\n name=\"questions\",\n display_name=\"Questions\",\n info=\"Comma-separated list of your questions. Only used if Endpoint is 'question-answer'\",\n advanced=True,\n ),\n MultilineInput(\n name=\"transcript_ids\",\n display_name=\"Transcript IDs\",\n info=\"Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts. If provided, the Transcription Result is ignored.\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"LeMUR Response\", name=\"lemur_response\", method=\"run_lemur\"),\n ]\n\n def run_lemur(self) -> Data:\n \"\"\"Use the LeMUR task endpoint to input the LLM prompt.\"\"\"\n aai.settings.api_key = self.api_key\n\n if not self.transcription_result and not self.transcript_ids:\n error = \"Either a Transcription Result or Transcript IDs must be provided\"\n self.status = error\n return Data(data={\"error\": error})\n elif self.transcription_result and self.transcription_result.data.get(\"error\"):\n # error message from the previous step\n self.status = self.transcription_result.data[\"error\"]\n return self.transcription_result\n elif self.endpoint == \"task\" and not self.prompt:\n self.status = \"No prompt specified for the task endpoint\"\n return Data(data={\"error\": \"No prompt specified\"})\n elif self.endpoint == \"question-answer\" and not self.questions:\n error = \"No Questions were provided for the question-answer endpoint\"\n self.status = error\n return Data(data={\"error\": error})\n\n # Check for valid transcripts\n transcript_ids = None\n if self.transcription_result and \"id\" in self.transcription_result.data:\n transcript_ids = [self.transcription_result.data[\"id\"]]\n elif self.transcript_ids:\n transcript_ids = self.transcript_ids.split(\",\")\n transcript_ids = [t.strip() for t in transcript_ids]\n \n if not transcript_ids:\n error = \"Either a valid Transcription Result or valid Transcript IDs must be provided\"\n self.status = error\n return Data(data={\"error\": error})\n\n # Get TranscriptGroup and check if there is any error\n transcript_group = aai.TranscriptGroup(transcript_ids=transcript_ids)\n transcript_group, failures = transcript_group.wait_for_completion(return_failures=True)\n if failures:\n error = f\"Getting transcriptions failed: {failures[0]}\"\n self.status = error\n return Data(data={\"error\": error})\n \n for t in transcript_group.transcripts:\n if t.status == aai.TranscriptStatus.error:\n self.status = t.error\n return Data(data={\"error\": t.error})\n\n # Perform LeMUR action\n try:\n response = self.perform_lemur_action(transcript_group, self.endpoint)\n result = Data(data=response)\n self.status = result\n return result\n except Exception as e:\n error = f\"An Error happened: {str(e)}\"\n self.status = error\n return Data(data={\"error\": error})\n\n def perform_lemur_action(self, transcript_group: aai.TranscriptGroup, endpoint: str) -> dict:\n print(\"Endpoint:\", endpoint, type(endpoint))\n if endpoint == \"task\":\n result = transcript_group.lemur.task(\n prompt=self.prompt,\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n elif endpoint == \"summary\":\n result = transcript_group.lemur.summarize(\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n elif endpoint == \"question-answer\":\n questions = self.questions.split(\",\")\n questions = [aai.LemurQuestion(question=q) for q in questions]\n result = transcript_group.lemur.question(\n questions=questions,\n final_model=self.get_final_model(self.final_model),\n temperature=self.temperature,\n max_output_size=self.max_output_size,\n )\n else:\n raise ValueError(f\"Endpoint not supported: {endpoint}\")\n\n return result.dict()\n \n def get_final_model(self, model_name: str) -> aai.LemurModel:\n if model_name == \"claude3_5_sonnet\":\n return aai.LemurModel.claude3_5_sonnet\n elif model_name == \"claude3_opus\":\n return aai.LemurModel.claude3_opus\n elif model_name == \"claude3_haiku\":\n return aai.LemurModel.claude3_haiku\n elif model_name == \"claude3_sonnet\":\n return aai.LemurModel.claude3_sonnet\n else:\n raise ValueError(f\"Model name not supported: {model_name}\")\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "endpoint": {
+ "trace_as_metadata": true,
+ "options": [
+ "task",
+ "summary",
+ "question-answer"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "endpoint",
+ "value": "task",
+ "display_name": "Endpoint",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The LeMUR endpoint to use. For 'summary' and 'question-answer', no prompt input is needed. See https://www.assemblyai.com/docs/api-reference/lemur/ for more info.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "final_model": {
+ "trace_as_metadata": true,
+ "options": [
+ "claude3_5_sonnet",
+ "claude3_opus",
+ "claude3_haiku",
+ "claude3_sonnet"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "final_model",
+ "value": "claude3_5_sonnet",
+ "display_name": "Final Model",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The model that is used for the final prompt after compression is performed",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "max_output_size": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_output_size",
+ "value": 2000,
+ "display_name": " Max Output Size",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Max output size in tokens, up to 4000",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "prompt",
+ "value": "",
+ "display_name": "Input Prompt",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The text to prompt the model",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "questions": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "questions",
+ "value": "",
+ "display_name": "Questions",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Comma-separated list of your questions. Only used if Endpoint is 'question-answer'",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "temperature": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": 0,
+ "display_name": "Temperature",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The temperature to use for the model",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "transcript_ids": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "transcript_ids",
+ "value": "",
+ "display_name": "Transcript IDs",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts. If provided, the Transcription Result is ignored.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework",
+ "icon": "AssemblyAI",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "AssemblyAI LeMUR",
+ "documentation": "https://www.assemblyai.com/docs/lemur",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "lemur_response",
+ "display_name": "LeMUR Response",
+ "method": "run_lemur",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "api_key",
+ "transcription_result",
+ "prompt",
+ "final_model",
+ "temperature",
+ "max_output_size",
+ "endpoint",
+ "questions",
+ "transcript_ids"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "AssemblyAILeMUR-jzwHZ",
+ "description": "Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework",
+ "display_name": "AssemblyAI LeMUR"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 454,
+ "positionAbsolute": {
+ "x": -875.6482330011189,
+ "y": 887.1705799007382
+ },
+ "dragging": false
+ },
+ {
+ "id": "ParseData-th7JM",
+ "type": "genericNode",
+ "position": {
+ "x": -862.5843195492909,
+ "y": -56.71774780191424
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{text}",
+ "display_name": "Template",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.18"
+ },
+ "id": "ParseData-th7JM"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 368,
+ "positionAbsolute": {
+ "x": -862.5843195492909,
+ "y": -56.71774780191424
+ },
+ "dragging": false
+ }
+ ],
+ "edges": [
+ {
+ "source": "AssemblyAITranscriptionJobCreator-Idt7P",
+ "sourceHandle": "{œdataTypeœ:œAssemblyAITranscriptionJobCreatorœ,œidœ:œAssemblyAITranscriptionJobCreator-Idt7Pœ,œnameœ:œtranscript_idœ,œoutput_typesœ:[œDataœ]}",
+ "target": "AssemblyAITranscriptionJobPoller-F46nf",
+ "targetHandle": "{œfieldNameœ:œtranscript_idœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "transcript_id",
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobCreator",
+ "id": "AssemblyAITranscriptionJobCreator-Idt7P",
+ "name": "transcript_id",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AssemblyAITranscriptionJobCreator-Idt7P{œdataTypeœ:œAssemblyAITranscriptionJobCreatorœ,œidœ:œAssemblyAITranscriptionJobCreator-Idt7Pœ,œnameœ:œtranscript_idœ,œoutput_typesœ:[œDataœ]}-AssemblyAITranscriptionJobPoller-F46nf{œfieldNameœ:œtranscript_idœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "animated": false,
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "AssemblyAITranscriptionJobPoller-F46nf",
+ "sourceHandle": "{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}",
+ "target": "AssemblyAIGetSubtitles-3sjU6",
+ "targetHandle": "{œfieldNameœ:œtranscription_resultœ,œidœ:œAssemblyAIGetSubtitles-3sjU6œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "transcription_result",
+ "id": "AssemblyAIGetSubtitles-3sjU6",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobPoller",
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "name": "transcription_result",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AssemblyAITranscriptionJobPoller-F46nf{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}-AssemblyAIGetSubtitles-3sjU6{œfieldNameœ:œtranscription_resultœ,œidœ:œAssemblyAIGetSubtitles-3sjU6œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "AssemblyAITranscriptionJobPoller-F46nf",
+ "sourceHandle": "{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ParseData-th7JM",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-th7JMœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-th7JM",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobPoller",
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "name": "transcription_result",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AssemblyAITranscriptionJobPoller-F46nf{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}-ParseData-th7JM{œfieldNameœ:œdataœ,œidœ:œParseData-th7JMœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "Prompt-IO8Cq",
+ "sourceHandle": "{œdataTypeœ:œPromptœ,œidœ:œPrompt-IO8Cqœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "AssemblyAILeMUR-jzwHZ",
+ "targetHandle": "{œfieldNameœ:œpromptœ,œidœ:œAssemblyAILeMUR-jzwHZœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "prompt",
+ "id": "AssemblyAILeMUR-jzwHZ",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-IO8Cq",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Prompt-IO8Cq{œdataTypeœ:œPromptœ,œidœ:œPrompt-IO8Cqœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-AssemblyAILeMUR-jzwHZ{œfieldNameœ:œpromptœ,œidœ:œAssemblyAILeMUR-jzwHZœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "AssemblyAITranscriptionJobPoller-F46nf",
+ "sourceHandle": "{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}",
+ "target": "AssemblyAILeMUR-jzwHZ",
+ "targetHandle": "{œfieldNameœ:œtranscription_resultœ,œidœ:œAssemblyAILeMUR-jzwHZœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "transcription_result",
+ "id": "AssemblyAILeMUR-jzwHZ",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobPoller",
+ "id": "AssemblyAITranscriptionJobPoller-F46nf",
+ "name": "transcription_result",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AssemblyAITranscriptionJobPoller-F46nf{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-F46nfœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}-AssemblyAILeMUR-jzwHZ{œfieldNameœ:œtranscription_resultœ,œidœ:œAssemblyAILeMUR-jzwHZœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "animated": false,
+ "className": ""
+ }
+ ],
+ "viewport": {
+ "x": 733.3920447354355,
+ "y": -42.8262727047815,
+ "zoom": 0.2612816498236053
+ }
+ },
+ "user_id": "9c01eee4-17dd-460e-8c52-bba36d635a9d",
+ "folder_id": "54fc9211-d42d-4c3f-a932-ee4987f61988",
+ "description": "Transcribe and analyze audio with AssemblyAI",
+ "icon_bg_color": null,
+ "updated_at": "2024-09-26T14:55:47+00:00",
+ "webhook": false,
+ "id": "fa69381c-d1c4-4535-bc23-bc2fb4956e1e"
+}
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Composio/integrations-composio.md b/langflow/docs/docs/Integrations/Composio/integrations-composio.md
new file mode 100644
index 0000000..d6c211a
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Composio/integrations-composio.md
@@ -0,0 +1,114 @@
+---
+title: Integrate Composio with Langflow
+slug: /integrations-composio
+---
+
+Langflow integrates with [Composio](https://docs.composio.dev/introduction/intro/overview) as a toolset for your **Agent** component.
+
+Instead of juggling multiple integrations and components in your flow, connect the Composio component to an **Agent** component to use all of Composio's supported APIs and actions as **Tools** for your agent.
+
+## Prerequisites
+
+- [A Composio API key](https://app.composio.dev/)
+- [An OpenAI API key](https://platform.openai.com/)
+- [A Gmail account](https://mail.google.com)
+
+## Connect Langflow to a Composio tool
+
+1. In the Langflow **Workspace**, add an **Agent** component.
+2. In the **Workspace**, add the **Composio Tools** component.
+3. Connect the **Agent** component's **Tools** port to the **Composio Tools** component's **Tools** port.
+4. In the **Composio API Key** field, paste your Composio API key.
+Alternatively, add the key as a [global variable](/configuration-global-variables).
+5. In the **App Name** field, select the tool you want your agent to have access to.
+For this example, select the **GMAIL** tool, which allows your agent to control an email account with the Composio tool.
+6. Click **Refresh**.
+The component's fields change based on the tool you selected.
+The **Gmail** tool requires authentication with Google, so it presents an **Authentication Link** button.
+7. Click the link to authenticate.
+8. In the Google authentication dialog, enter the credentials for the Gmail account you want to control with Composio, and then click **Authenticate**.
+9. Return to Langflow.
+10. To update the Composio component, click **Refresh**.
+The **Auth Status** field changes to a ✅, which indicates the Langflow component is connected to your Composio account.
+11. In the **Actions to use** field, select the action you want the **Agent** to take with the **Gmail** tool.
+The **Gmail** tool supports multiple actions, and also supports multiple actions within the same tool.
+The default value of **GMAIL_CREATE_EMAIL_DRAFT** is OK for this example.
+For more information, see the [Composio documentation](https://docs.composio.dev/patterns/tools/use-tools/use-specific-actions).
+
+## Create a Composio flow
+
+1. In the **Workspace**, add **Chat Input** and **Chat Output** components to your flow.
+2. Connect the components so they look like this.
+
+
+
+3. In the **OpenAI API Key** field of the **Agent** component, paste your OpenAI API key.
+Alternatively, add the key as a [global variable](/configuration-global-variables).
+4. To open the **Playground** pane, click **Playground**.
+5. Ask your AI:
+
+```plain
+What tools are available to you?
+```
+
+The response should be similar to:
+
+```plain
+I have access to the following tools:
+
+1. **GMAIL_CREATE_EMAIL_DRAFT**: This tool allows me to create a draft email using Gmail's API. I can specify the recipient's email address, subject, body content, and whether the body content is HTML.
+
+2. **CurrentDate-get_current_date**: This tool retrieves the current date and time in a specified timezone.
+```
+
+This confirms your **Agent** and **Composio** are communicating.
+
+6. Tell your AI to write a draft email.
+
+```plain
+Create a draft email with the subject line "Greetings from Composio"
+recipient: "your.email@address.com"
+Body content: "Hello from composio!"
+```
+
+Inspect the response to see how the agent used the attached tool to write an email.
+This example response is abbreviated.
+
+```plain
+The draft email has been successfully created with the following details:
+Recipient: your.email@address.com
+Subject: Greetings from Composio
+Body: Hello from composio!
+```
+
+```json
+{
+ "recipient_email": "your.email@address.com",
+ "subject": "Greetings from Composio",
+ "body": "Hello from composio!",
+ "is_html": false
+}
+
+{
+ "successfull": true,
+ "data": {
+ "response_data": {
+ "id": "r-7515791375860110875",
+ "message": {
+ "id": "1939d1830046d2cb",
+ "threadId": "1939d1830046d2cb",
+ "labelIds": [
+ "DRAFT"
+ ]
+ }
+ }
+ },
+ "error": null
+}
+```
+
+7. To confirm further, navigate to the Gmail account you authenticated with Composio.
+Your email is visible in **Drafts**.
+
+You have successfully integrated your Langflow component with Composio.
+To add more tools, add another Composio component.
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Google/integrations-setup-google-cloud-vertex-ai-langflow.md b/langflow/docs/docs/Integrations/Google/integrations-setup-google-cloud-vertex-ai-langflow.md
new file mode 100644
index 0000000..e71c552
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Google/integrations-setup-google-cloud-vertex-ai-langflow.md
@@ -0,0 +1,38 @@
+---
+title: Integrate Google Cloud Vertex AI with Langflow
+slug: /integrations-setup-google-cloud-vertex-ai-langflow
+description: "A comprehensive guide on creating a Google OAuth app, obtaining tokens, and integrating them with Langflow's Google components."
+---
+
+Langflow integrates with the [Google Vertex AI API](https://console.cloud.google.com/marketplace/product/google/aiplatform.googleapis.com) for authenticating the [Vertex AI embeddings model](/components-embedding-models#vertexai-embeddings) and [Vertex AI](/components-models#vertexai) components.
+
+Learn how to create a service account JSON in Google Cloud to authenticate Langflow’s Vertex AI components.
+
+## Create a service account with Vertex AI access
+
+1. Select and enable your Google Cloud project.
+For more information, see [Create a Google Cloud project](https://developers.google.com/workspace/guides/create-project).
+2. Create a service account in your Google Cloud project.
+For more information, see [Create a service account](https://developers.google.com/workspace/guides/create-credentials#service-account).
+3. Assign the **Vertex AI Service Agent** role to your new account.
+This role allows Langflow to access Vertex AI resources.
+For more information, see [Vertex AI access control with IAM](https://cloud.google.com/vertex-ai/docs/general/access-control).
+4. To generate a new JSON key for the service account, navigate to your service account.
+5. Click **Add Key**, and then click **Create new key**.
+6. Under **Key type**, select **JSON**, and then click **Create**.
+A JSON private key file is downloaded.
+Now that you have a service account and a JSON private key, you need to configure the credentials in Langflow components.
+
+## Configure credentials in Langflow components
+
+With your service account configured and your credentials JSON file created, follow these steps to authenticate the Langflow application.
+
+1. Create a new project in Langflow.
+2. From the components sidebar, drag and drop either the **Vertex AI** or **Vertex AI Embeddings** component to your workspace.
+3. In the Vertex AI component's **Credentials** field, add the service account JSON file.
+4. Confirm the component can access the Vertex AI resources.
+Connect a **Chat input** and **Chat output** component to the Vertex AI component.
+A successful chat confirms the component has access to the Vertex AI resources.
+
+
+
diff --git a/langflow/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md b/langflow/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md
new file mode 100644
index 0000000..172de6f
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md
@@ -0,0 +1,69 @@
+---
+title: Integrate Google OAuth with Langflow
+slug: /integrations-setup-google-oauth-langflow
+
+description: "A comprehensive guide on creating a Google OAuth app, obtaining tokens, and integrating them with Langflow's Google components."
+---
+
+import TOCInline from '@theme/TOCInline';
+
+Langflow integrates with [Google OAuth](https://developers.google.com/identity/protocols/oauth2) for authenticating the [Gmail loader](/components-data#gmail-loader), [Google Drive loader](components-data#google-drive-loader), and [Google Drive Search](/components-data#google-drive-search) components.
+
+Learn how to create an OAuth app in Google Cloud, obtain the necessary credentials and access tokens, and add them to Langflow’s Google components.
+
+## Create an OAuth Application in Google Cloud {#5b8981b15d86192d17b0e5725c1f95e7}
+
+1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/).
+
+2. Click **Select a project**, and then click **New Project** to create a new project.
+
+
+
+3. To enable APIs for the project, select **APIs & Services**, and then click **Library**. Enable the APIs you need for your project. For example, if your flow uses the Google Drive component, enable the Google Drive API.
+4. To navigate to the OAuth consent screen, click **APIs & Services**, and then click **OAuth consent screen**.
+5. Populate your OAuth consent screen with the application name, user support email, required [scopes](https://developers.google.com/identity/protocols/oauth2/scopes), and authorized domains.
+6. To create an **OAuth Client ID**, navigate to **Clients**, and then click **Create Client**.
+7. Choose **Desktop app** as the application type, and then name your client ID.
+8. Click **Create**. A Client ID and Client Secret are created. Download the credentials as a JSON file to your local machine and save it securely.
+
+
+
+---
+
+## Retrieve Access and Refresh Tokens
+
+With your OAuth application configured and your credentials JSON file created, follow these steps to authenticate the Langflow application.
+
+1. Create a new project in Langflow.
+2. Add a **Google OAuth Token** component to your flow.
+3. In the **Credentials File** field of the Google OAuth Token component, enter the path to your **Credentials File**, the JSON file containing the Client ID credentials you downloaded from Google in the previous steps.
+4. To authenticate your application, in the **Google OAuth Token** component, click **Play**.
+A new tab opens in the browser to authenticate your application using your Google Cloud account. You must authenticate the application with the same Google account that created the OAuth credentials.
+
+:::info
+If a new tab does not open automatically, check the Langflow **Logs** for the Google authentication URL. Open this URL in your browser to complete the authentication.
+:::
+
+5. After successful authentication, your Langflow application can now request and refresh tokens. These tokens enable Langflow to interact with Google services on your behalf and execute the requests you’ve specified.
+
+## Create a flow with Google Drive loader
+
+For a pre-built JSON file of a flow that uses the Google Drive loader component, download the Google Drive Document Translation Example Flow JSON to your local machine.
+
+In this example, the **Google Drive loader** component loads a text file hosted on Google Drive, translates the text to Spanish, and returns it to a chat output.
+
+1. Download the Google Drive Document Translation Example Flow JSON to your local machine.
+2. To import the downloaded JSON to Langflow, click **Options**, and then select **Import**.
+3. In the **Credentials File** field of the Google OAuth Token component, enter the path to your **Credentials File**, the JSON file containing the Client ID credentials you downloaded from Google in the previous steps.
+4. In the Google Drive loader component, in the `JSON String of the Service Account Token` field, enter the JSON string containing the token returned in the output of the Google OAuth Token component.
+
+The example flow includes a **Parse data** component to convert the `data` output of the Google OAuth Token component to the `text` input of the JSON Cleaner component.
+
+5. To allow the Langflow component to access the file in Google Drive, copy the Google Drive File ID from the document's URL.
+:::info
+The file ID is located between `/d/` and `/edit` in a Google Drive document's URL.
+For example, in the URL `https://drive.google.com/file/d/1a2b3c4D5E6F7gHI8J9klmnopQ/edit`, the File ID is `1a2b3c4D5E6F7gHI8J9klmnopQ`.
+:::
+6. In the Google Drive loader component, in the **Document ID** field, paste the document URL.
+7. Click the **Chat output** component, and then click **Play**.
+The chat output should display a translated document.
diff --git a/langflow/docs/docs/Integrations/Notion/Conversational_Notion_Agent.json b/langflow/docs/docs/Integrations/Notion/Conversational_Notion_Agent.json
new file mode 100644
index 0000000..e977769
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Notion/Conversational_Notion_Agent.json
@@ -0,0 +1,3137 @@
+{
+ "id": "e070f0be-edc4-4512-bb0f-e53307062a26",
+ "data": {
+ "nodes": [
+ {
+ "id": "AddContentToPage-ZezUn",
+ "type": "genericNode",
+ "position": {
+ "x": 1416.217259177943,
+ "y": 1709.6205867919527
+ },
+ "data": {
+ "type": "AddContentToPage",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "block_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "block_id",
+ "value": "",
+ "display_name": "Page/Block ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the page/block to add the content.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nfrom typing import Dict, Any, Union\nfrom markdown import markdown\nfrom bs4 import BeautifulSoup\nimport requests\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\n\nclass AddContentToPage(LCToolComponent):\n display_name: str = \"Add Content to Page \"\n description: str = \"Convert markdown text to Notion blocks and append them to a Notion page.\"\n documentation: str = \"https://developers.notion.com/reference/patch-block-children\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n MultilineInput(\n name=\"markdown_text\",\n display_name=\"Markdown Text\",\n info=\"The markdown text to convert to Notion blocks.\",\n ),\n StrInput(\n name=\"block_id\",\n display_name=\"Page/Block ID\",\n info=\"The ID of the page/block to add the content.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class AddContentToPageSchema(BaseModel):\n markdown_text: str = Field(..., description=\"The markdown text to convert to Notion blocks.\")\n block_id: str = Field(..., description=\"The ID of the page/block to add the content.\")\n\n def run_model(self) -> Data:\n result = self._add_content_to_page(self.markdown_text, self.block_id)\n return Data(data=result, text=json.dumps(result))\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"add_content_to_notion_page\",\n description=\"Convert markdown text to Notion blocks and append them to a Notion page.\",\n func=self._add_content_to_page,\n args_schema=self.AddContentToPageSchema,\n )\n\n def _add_content_to_page(self, markdown_text: str, block_id: str) -> Union[Dict[str, Any], str]:\n try:\n html_text = markdown(markdown_text)\n soup = BeautifulSoup(html_text, \"html.parser\")\n blocks = self.process_node(soup)\n\n url = f\"https://api.notion.com/v1/blocks/{block_id}/children\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"children\": blocks,\n }\n\n response = requests.patch(url, headers=headers, json=data)\n response.raise_for_status()\n\n return response.json()\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to add content to Notion page. {str(e)}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e:\n return f\"Error: An unexpected error occurred while adding content to Notion page. {str(e)}\"\n\n def process_node(self, node):\n blocks = []\n if isinstance(node, str):\n text = node.strip()\n if text:\n if text.startswith(\"#\"):\n heading_level = text.count(\"#\", 0, 6)\n heading_text = text[heading_level:].strip()\n if heading_level == 1:\n blocks.append(self.create_block(\"heading_1\", heading_text))\n elif heading_level == 2:\n blocks.append(self.create_block(\"heading_2\", heading_text))\n elif heading_level == 3:\n blocks.append(self.create_block(\"heading_3\", heading_text))\n else:\n blocks.append(self.create_block(\"paragraph\", text))\n elif node.name == \"h1\":\n blocks.append(self.create_block(\"heading_1\", node.get_text(strip=True)))\n elif node.name == \"h2\":\n blocks.append(self.create_block(\"heading_2\", node.get_text(strip=True)))\n elif node.name == \"h3\":\n blocks.append(self.create_block(\"heading_3\", node.get_text(strip=True)))\n elif node.name == \"p\":\n code_node = node.find(\"code\")\n if code_node:\n code_text = code_node.get_text()\n language, code = self.extract_language_and_code(code_text)\n blocks.append(self.create_block(\"code\", code, language=language))\n elif self.is_table(str(node)):\n blocks.extend(self.process_table(node))\n else:\n blocks.append(self.create_block(\"paragraph\", node.get_text(strip=True)))\n elif node.name == \"ul\":\n blocks.extend(self.process_list(node, \"bulleted_list_item\"))\n elif node.name == \"ol\":\n blocks.extend(self.process_list(node, \"numbered_list_item\"))\n elif node.name == \"blockquote\":\n blocks.append(self.create_block(\"quote\", node.get_text(strip=True)))\n elif node.name == \"hr\":\n blocks.append(self.create_block(\"divider\", \"\"))\n elif node.name == \"img\":\n blocks.append(self.create_block(\"image\", \"\", image_url=node.get(\"src\")))\n elif node.name == \"a\":\n blocks.append(self.create_block(\"bookmark\", node.get_text(strip=True), link_url=node.get(\"href\")))\n elif node.name == \"table\":\n blocks.extend(self.process_table(node))\n\n for child in node.children:\n if isinstance(child, str):\n continue\n blocks.extend(self.process_node(child))\n\n return blocks\n\n def extract_language_and_code(self, code_text):\n lines = code_text.split(\"\\n\")\n language = lines[0].strip()\n code = \"\\n\".join(lines[1:]).strip()\n return language, code\n\n def is_code_block(self, text):\n return text.startswith(\"```\")\n\n def extract_code_block(self, text):\n lines = text.split(\"\\n\")\n language = lines[0].strip(\"`\").strip()\n code = \"\\n\".join(lines[1:]).strip(\"`\").strip()\n return language, code\n\n def is_table(self, text):\n rows = text.split(\"\\n\")\n if len(rows) < 2:\n return False\n\n has_separator = False\n for i, row in enumerate(rows):\n if \"|\" in row:\n cells = [cell.strip() for cell in row.split(\"|\")]\n cells = [cell for cell in cells if cell] # Remove empty cells\n if i == 1 and all(set(cell) <= set(\"-|\") for cell in cells):\n has_separator = True\n elif not cells:\n return False\n\n return has_separator and len(rows) >= 3\n\n def process_list(self, node, list_type):\n blocks = []\n for item in node.find_all(\"li\"):\n item_text = item.get_text(strip=True)\n checked = item_text.startswith(\"[x]\")\n is_checklist = item_text.startswith(\"[ ]\") or checked\n\n if is_checklist:\n item_text = item_text.replace(\"[x]\", \"\").replace(\"[ ]\", \"\").strip()\n blocks.append(self.create_block(\"to_do\", item_text, checked=checked))\n else:\n blocks.append(self.create_block(list_type, item_text))\n return blocks\n\n def process_table(self, node):\n blocks = []\n header_row = node.find(\"thead\").find(\"tr\") if node.find(\"thead\") else None\n body_rows = node.find(\"tbody\").find_all(\"tr\") if node.find(\"tbody\") else []\n\n if header_row or body_rows:\n table_width = max(\n len(header_row.find_all([\"th\", \"td\"])) if header_row else 0,\n max(len(row.find_all([\"th\", \"td\"])) for row in body_rows),\n )\n\n table_block = self.create_block(\"table\", \"\", table_width=table_width, has_column_header=bool(header_row))\n blocks.append(table_block)\n\n if header_row:\n header_cells = [cell.get_text(strip=True) for cell in header_row.find_all([\"th\", \"td\"])]\n header_row_block = self.create_block(\"table_row\", header_cells)\n blocks.append(header_row_block)\n\n for row in body_rows:\n cells = [cell.get_text(strip=True) for cell in row.find_all([\"th\", \"td\"])]\n row_block = self.create_block(\"table_row\", cells)\n blocks.append(row_block)\n\n return blocks\n\n def create_block(self, block_type: str, content: str, **kwargs) -> Dict[str, Any]:\n block: dict[str, Any] = {\n \"object\": \"block\",\n \"type\": block_type,\n block_type: {},\n }\n\n if block_type in [\n \"paragraph\",\n \"heading_1\",\n \"heading_2\",\n \"heading_3\",\n \"bulleted_list_item\",\n \"numbered_list_item\",\n \"quote\",\n ]:\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n elif block_type == \"to_do\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"checked\"] = kwargs.get(\"checked\", False)\n elif block_type == \"code\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"language\"] = kwargs.get(\"language\", \"plain text\")\n elif block_type == \"image\":\n block[block_type] = {\"type\": \"external\", \"external\": {\"url\": kwargs.get(\"image_url\", \"\")}}\n elif block_type == \"divider\":\n pass\n elif block_type == \"bookmark\":\n block[block_type][\"url\"] = kwargs.get(\"link_url\", \"\")\n elif block_type == \"table\":\n block[block_type][\"table_width\"] = kwargs.get(\"table_width\", 0)\n block[block_type][\"has_column_header\"] = kwargs.get(\"has_column_header\", False)\n block[block_type][\"has_row_header\"] = kwargs.get(\"has_row_header\", False)\n elif block_type == \"table_row\":\n block[block_type][\"cells\"] = [[{\"type\": \"text\", \"text\": {\"content\": cell}} for cell in content]]\n\n return block\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "markdown_text": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "markdown_text",
+ "value": "",
+ "display_name": "Markdown Text",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The markdown text to convert to Notion blocks.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Convert markdown text to Notion blocks and append them to a Notion page.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Add Content to Page ",
+ "documentation": "https://developers.notion.com/reference/patch-block-children",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "markdown_text",
+ "block_id",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "AddContentToPage-ZezUn",
+ "description": "Convert markdown text to Notion blocks and append them to a Notion page.",
+ "display_name": "Add Content to Page "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 330,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1416.217259177943,
+ "y": 1709.6205867919527
+ }
+ },
+ {
+ "id": "NotionPageCreator-6SCB5",
+ "type": "genericNode",
+ "position": {
+ "x": 1413.9782390799146,
+ "y": 2051.645785494985
+ },
+ "data": {
+ "type": "NotionPageCreator",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nfrom typing import Dict, Any, Union\nimport requests\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom langflow.io import Output\n\nclass NotionPageCreator(LCToolComponent):\n display_name: str = \"Create Page \"\n description: str = \"A component for creating Notion pages.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/page-create\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n MultilineInput(\n name=\"properties_json\",\n display_name=\"Properties (JSON)\",\n info=\"The properties of the new page as a JSON string.\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionPageCreatorSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n properties_json: str = Field(..., description=\"The properties of the new page as a JSON string.\")\n\n def run_model(self) -> Data:\n result = self._create_notion_page(self.database_id, self.properties_json)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the created page data\n output = \"Created page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"create_notion_page\",\n description=\"Create a new page in a Notion database. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._create_notion_page,\n args_schema=self.NotionPageCreatorSchema,\n )\n\n def _create_notion_page(self, database_id: str, properties_json: str) -> Union[Dict[str, Any], str]:\n if not database_id or not properties_json:\n return \"Invalid input. Please provide 'database_id' and 'properties_json'.\"\n\n try:\n properties = json.loads(properties_json)\n except json.JSONDecodeError as e:\n return f\"Invalid properties format. Please provide a valid JSON string. Error: {str(e)}\"\n\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"parent\": {\"database_id\": database_id},\n \"properties\": properties,\n }\n\n try:\n response = requests.post(\"https://api.notion.com/v1/pages\", headers=headers, json=data)\n response.raise_for_status()\n result = response.json()\n return result\n except requests.exceptions.RequestException as e:\n error_message = f\"Failed to create Notion page. Error: {str(e)}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n\n def __call__(self, *args, **kwargs):\n return self._create_notion_page(*args, **kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "database_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "database_id",
+ "value": "",
+ "display_name": "Database ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion database.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "properties_json": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "properties_json",
+ "value": "",
+ "display_name": "Properties (JSON)",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The properties of the new page as a JSON string.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "A component for creating Notion pages.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Create Page ",
+ "documentation": "https://docs.langflow.org/integrations/notion/page-create",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "database_id",
+ "notion_secret",
+ "properties_json"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionPageCreator-6SCB5",
+ "description": "A component for creating Notion pages.",
+ "display_name": "Create Page "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1413.9782390799146,
+ "y": 2051.645785494985
+ }
+ },
+ {
+ "id": "NotionDatabaseProperties-aeWil",
+ "type": "genericNode",
+ "position": {
+ "x": 1004.5753613670959,
+ "y": 1713.914531491452
+ },
+ "data": {
+ "type": "NotionDatabaseProperties",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import Dict, Union\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom langflow.io import Output\n\nclass NotionDatabaseProperties(LCToolComponent):\n display_name: str = \"List Database Properties \"\n description: str = \"Retrieve properties of a Notion database.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/list-database-properties\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionDatabasePropertiesSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n\n def run_model(self) -> Data:\n result = self._fetch_database_properties(self.database_id)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the properties\n return Data(text=str(result), data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_database_properties\",\n description=\"Retrieve properties of a Notion database. Input should include the database ID.\",\n func=self._fetch_database_properties,\n args_schema=self.NotionDatabasePropertiesSchema,\n )\n\n def _fetch_database_properties(self, database_id: str) -> Union[Dict, str]:\n url = f\"https://api.notion.com/v1/databases/{database_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n try:\n response = requests.get(url, headers=headers)\n response.raise_for_status()\n data = response.json()\n properties = data.get(\"properties\", {})\n return properties\n except requests.exceptions.RequestException as e:\n return f\"Error fetching Notion database properties: {str(e)}\"\n except ValueError as e:\n return f\"Error parsing Notion API response: {str(e)}\"\n except Exception as e:\n return f\"An unexpected error occurred: {str(e)}\"\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "database_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "database_id",
+ "value": "",
+ "display_name": "Database ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion database.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Retrieve properties of a Notion database.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Database Properties ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-database-properties",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "database_id",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionDatabaseProperties-aeWil",
+ "description": "Retrieve properties of a Notion database.",
+ "display_name": "List Database Properties "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1004.5753613670959,
+ "y": 1713.914531491452
+ }
+ },
+ {
+ "id": "NotionListPages-znA3w",
+ "type": "genericNode",
+ "position": {
+ "x": 1006.1848442547046,
+ "y": 2022.7880909242833
+ },
+ "data": {
+ "type": "NotionListPages",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nimport json\nfrom typing import Dict, Any, List, Optional\nfrom pydantic import BaseModel, Field\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionListPages(LCToolComponent):\n display_name: str = \"List Pages \"\n description: str = (\n \"Query a Notion database with filtering and sorting. \"\n \"The input should be a JSON string containing the 'filter' and 'sorts' objects. \"\n \"Example input:\\n\"\n '{\"filter\": {\"property\": \"Status\", \"select\": {\"equals\": \"Done\"}}, \"sorts\": [{\"timestamp\": \"created_time\", \"direction\": \"descending\"}]}'\n )\n documentation: str = \"https://docs.langflow.org/integrations/notion/list-pages\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database to query.\",\n ),\n MultilineInput(\n name=\"query_json\",\n display_name=\"Database query (JSON)\",\n info=\"A JSON string containing the filters and sorts that will be used for querying the database. Leave empty for no filters or sorts.\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n class NotionListPagesSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database to query.\")\n query_json: Optional[str] = Field(\n default=\"\",\n description=\"A JSON string containing the filters and sorts for querying the database. Leave empty for no filters or sorts.\",\n )\n\n def run_model(self) -> List[Data]:\n result = self._query_notion_database(self.database_id, self.query_json)\n\n if isinstance(result, str):\n # An error occurred, return it as a single record\n return [Data(text=result)]\n\n records = []\n combined_text = f\"Pages found: {len(result)}\\n\\n\"\n\n for page in result:\n page_data = {\n \"id\": page[\"id\"],\n \"url\": page[\"url\"],\n \"created_time\": page[\"created_time\"],\n \"last_edited_time\": page[\"last_edited_time\"],\n \"properties\": page[\"properties\"],\n }\n\n text = (\n f\"id: {page['id']}\\n\"\n f\"url: {page['url']}\\n\"\n f\"created_time: {page['created_time']}\\n\"\n f\"last_edited_time: {page['last_edited_time']}\\n\"\n f\"properties: {json.dumps(page['properties'], indent=2)}\\n\\n\"\n )\n\n combined_text += text\n records.append(Data(text=text, **page_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_pages\",\n description=self.description,\n func=self._query_notion_database,\n args_schema=self.NotionListPagesSchema,\n )\n\n def _query_notion_database(self, database_id: str, query_json: Optional[str] = None) -> List[Dict[str, Any]] | str:\n url = f\"https://api.notion.com/v1/databases/{database_id}/query\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n query_payload = {}\n if query_json and query_json.strip():\n try:\n query_payload = json.loads(query_json)\n except json.JSONDecodeError as e:\n return f\"Invalid JSON format for query: {str(e)}\"\n\n try:\n response = requests.post(url, headers=headers, json=query_payload)\n response.raise_for_status()\n results = response.json()\n return results[\"results\"]\n except requests.exceptions.RequestException as e:\n return f\"Error querying Notion database: {str(e)}\"\n except KeyError:\n return \"Unexpected response format from Notion API\"\n except Exception as e:\n return f\"An unexpected error occurred: {str(e)}\"\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "database_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "database_id",
+ "value": "",
+ "display_name": "Database ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion database to query.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "query_json": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "query_json",
+ "value": "",
+ "display_name": "Database query (JSON)",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "A JSON string containing the filters and sorts that will be used for querying the database. Leave empty for no filters or sorts.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Query a Notion database with filtering and sorting. The input should be a JSON string containing the 'filter' and 'sorts' objects. Example input:\n{\"filter\": {\"property\": \"Status\", \"select\": {\"equals\": \"Done\"}}, \"sorts\": [{\"timestamp\": \"created_time\", \"direction\": \"descending\"}]}",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Pages ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-pages",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "notion_secret",
+ "database_id",
+ "query_json"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionListPages-znA3w",
+ "description": "Query a Notion database with filtering and sorting. The input should be a JSON string containing the 'filter' and 'sorts' objects. Example input:\n{\"filter\": {\"property\": \"Status\", \"select\": {\"equals\": \"Done\"}}, \"sorts\": [{\"timestamp\": \"created_time\", \"direction\": \"descending\"}]}",
+ "display_name": "List Pages "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 470,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1006.1848442547046,
+ "y": 2022.7880909242833
+ }
+ },
+ {
+ "id": "NotionUserList-C3eGn",
+ "type": "genericNode",
+ "position": {
+ "x": 2260.15497405973,
+ "y": 1717.4551881467207
+ },
+ "data": {
+ "type": "NotionUserList",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import List, Dict\nfrom pydantic import BaseModel\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionUserList(LCToolComponent):\n display_name = \"List Users \"\n description = \"Retrieve users from Notion.\"\n documentation = \"https://docs.langflow.org/integrations/notion/list-users\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionUserListSchema(BaseModel):\n pass\n\n def run_model(self) -> List[Data]:\n users = self._list_users()\n records = []\n combined_text = \"\"\n\n for user in users:\n output = \"User:\\n\"\n for key, value in user.items():\n output += f\"{key.replace('_', ' ').title()}: {value}\\n\"\n output += \"________________________\\n\"\n\n combined_text += output\n records.append(Data(text=output, data=user))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_users\",\n description=\"Retrieve users from Notion.\",\n func=self._list_users,\n args_schema=self.NotionUserListSchema,\n )\n\n def _list_users(self) -> List[Dict]:\n url = \"https://api.notion.com/v1/users\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n response = requests.get(url, headers=headers)\n response.raise_for_status()\n\n data = response.json()\n results = data[\"results\"]\n\n users = []\n for user in results:\n user_data = {\n \"id\": user[\"id\"],\n \"type\": user[\"type\"],\n \"name\": user.get(\"name\", \"\"),\n \"avatar_url\": user.get(\"avatar_url\", \"\"),\n }\n users.append(user_data)\n\n return users\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Retrieve users from Notion.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Users ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-users",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionUserList-C3eGn",
+ "description": "Retrieve users from Notion.",
+ "display_name": "List Users "
+ },
+ "selected": true,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 2260.15497405973,
+ "y": 1717.4551881467207
+ }
+ },
+ {
+ "id": "NotionPageContent-SlL21",
+ "type": "genericNode",
+ "position": {
+ "x": 1826.4242329724448,
+ "y": 1715.6365113286927
+ },
+ "data": {
+ "type": "NotionPageContent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom langflow.io import Output\n\nclass NotionPageContent(LCToolComponent):\n display_name = \"Page Content Viewer \"\n description = \"Retrieve the content of a Notion page as plain text.\"\n documentation = \"https://docs.langflow.org/integrations/notion/page-content-viewer\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"page_id\",\n display_name=\"Page ID\",\n info=\"The ID of the Notion page to retrieve.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionPageContentSchema(BaseModel):\n page_id: str = Field(..., description=\"The ID of the Notion page to retrieve.\")\n\n def run_model(self) -> Data:\n result = self._retrieve_page_content(self.page_id)\n if isinstance(result, str) and result.startswith(\"Error:\"):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the content\n return Data(text=result, data={\"content\": result})\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_page_content\",\n description=\"Retrieve the content of a Notion page as plain text.\",\n func=self._retrieve_page_content,\n args_schema=self.NotionPageContentSchema,\n )\n\n def _retrieve_page_content(self, page_id: str) -> str:\n blocks_url = f\"https://api.notion.com/v1/blocks/{page_id}/children?page_size=100\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n try:\n blocks_response = requests.get(blocks_url, headers=headers)\n blocks_response.raise_for_status()\n blocks_data = blocks_response.json()\n return self.parse_blocks(blocks_data.get(\"results\", []))\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to retrieve Notion page content. {str(e)}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e:\n return f\"Error: An unexpected error occurred while retrieving Notion page content. {str(e)}\"\n\n def parse_blocks(self, blocks: list) -> str:\n content = \"\"\n for block in blocks:\n block_type = block.get(\"type\")\n if block_type in [\"paragraph\", \"heading_1\", \"heading_2\", \"heading_3\", \"quote\"]:\n content += self.parse_rich_text(block[block_type].get(\"rich_text\", [])) + \"\\n\\n\"\n elif block_type in [\"bulleted_list_item\", \"numbered_list_item\"]:\n content += self.parse_rich_text(block[block_type].get(\"rich_text\", [])) + \"\\n\"\n elif block_type == \"to_do\":\n content += self.parse_rich_text(block[\"to_do\"].get(\"rich_text\", [])) + \"\\n\"\n elif block_type == \"code\":\n content += self.parse_rich_text(block[\"code\"].get(\"rich_text\", [])) + \"\\n\\n\"\n elif block_type == \"image\":\n content += f\"[Image: {block['image'].get('external', {}).get('url', 'No URL')}]\\n\\n\"\n elif block_type == \"divider\":\n content += \"---\\n\\n\"\n return content.strip()\n\n def parse_rich_text(self, rich_text: list) -> str:\n return \"\".join(segment.get(\"plain_text\", \"\") for segment in rich_text)\n\n def __call__(self, *args, **kwargs):\n return self._retrieve_page_content(*args, **kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "page_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "page_id",
+ "value": "",
+ "display_name": "Page ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion page to retrieve.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ }
+ },
+ "description": "Retrieve the content of a Notion page as plain text.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Page Content Viewer ",
+ "documentation": "https://docs.langflow.org/integrations/notion/page-content-viewer",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "page_id",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionPageContent-SlL21",
+ "description": "Retrieve the content of a Notion page as plain text.",
+ "display_name": "Page Content Viewer "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 330,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1826.4242329724448,
+ "y": 1715.6365113286927
+ }
+ },
+ {
+ "id": "NotionSearch-VS2mI",
+ "type": "genericNode",
+ "position": {
+ "x": 2258.1166047519732,
+ "y": 2034.3959294952945
+ },
+ "data": {
+ "type": "NotionSearch",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import Dict, Any, List\nfrom pydantic import BaseModel, Field\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, DropdownInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionSearch(LCToolComponent):\n display_name: str = \"Search \"\n description: str = \"Searches all pages and databases that have been shared with an integration. The search field can be an empty value to show all values from that search\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/search\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The text that the API compares page and database titles against.\",\n ),\n DropdownInput(\n name=\"filter_value\",\n display_name=\"Filter Type\",\n info=\"Limits the results to either only pages or only databases.\",\n options=[\"page\", \"database\"],\n value=\"page\",\n ),\n DropdownInput(\n name=\"sort_direction\",\n display_name=\"Sort Direction\",\n info=\"The direction to sort the results.\",\n options=[\"ascending\", \"descending\"],\n value=\"descending\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionSearchSchema(BaseModel):\n query: str = Field(..., description=\"The search query text.\")\n filter_value: str = Field(default=\"page\", description=\"Filter type: 'page' or 'database'.\")\n sort_direction: str = Field(default=\"descending\", description=\"Sort direction: 'ascending' or 'descending'.\")\n\n def run_model(self) -> List[Data]:\n results = self._search_notion(self.query, self.filter_value, self.sort_direction)\n records = []\n combined_text = f\"Results found: {len(results)}\\n\\n\"\n\n for result in results:\n result_data = {\n \"id\": result[\"id\"],\n \"type\": result[\"object\"],\n \"last_edited_time\": result[\"last_edited_time\"],\n }\n\n if result[\"object\"] == \"page\":\n result_data[\"title_or_url\"] = result[\"url\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['url']}\\n\"\n elif result[\"object\"] == \"database\":\n if \"title\" in result and isinstance(result[\"title\"], list) and len(result[\"title\"]) > 0:\n result_data[\"title_or_url\"] = result[\"title\"][0][\"plain_text\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['title'][0]['plain_text']}\\n\"\n else:\n result_data[\"title_or_url\"] = \"N/A\"\n text = f\"id: {result['id']}\\ntitle_or_url: N/A\\n\"\n\n text += f\"type: {result['object']}\\nlast_edited_time: {result['last_edited_time']}\\n\\n\"\n combined_text += text\n records.append(Data(text=text, data=result_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_search\",\n description=\"Search Notion pages and databases. Input should include the search query and optionally filter type and sort direction.\",\n func=self._search_notion,\n args_schema=self.NotionSearchSchema,\n )\n\n def _search_notion(\n self, query: str, filter_value: str = \"page\", sort_direction: str = \"descending\"\n ) -> List[Dict[str, Any]]:\n url = \"https://api.notion.com/v1/search\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"query\": query,\n \"filter\": {\"value\": filter_value, \"property\": \"object\"},\n \"sort\": {\"direction\": sort_direction, \"timestamp\": \"last_edited_time\"},\n }\n\n response = requests.post(url, headers=headers, json=data)\n response.raise_for_status()\n\n results = response.json()\n return results[\"results\"]\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "filter_value": {
+ "trace_as_metadata": true,
+ "options": [
+ "page",
+ "database"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "filter_value",
+ "value": "page",
+ "display_name": "Filter Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Limits the results to either only pages or only databases.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "query": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "query",
+ "value": "",
+ "display_name": "Search Query",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The text that the API compares page and database titles against.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "sort_direction": {
+ "trace_as_metadata": true,
+ "options": [
+ "ascending",
+ "descending"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sort_direction",
+ "value": "descending",
+ "display_name": "Sort Direction",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The direction to sort the results.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Searches all pages and databases that have been shared with an integration. The search field can be an empty value to show all values from that search",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Search ",
+ "documentation": "https://docs.langflow.org/integrations/notion/search",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "notion_secret",
+ "query",
+ "filter_value",
+ "sort_direction"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionSearch-VS2mI",
+ "description": "Searches all pages and databases that have been shared with an integration.",
+ "display_name": "Search "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 386,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 2258.1166047519732,
+ "y": 2034.3959294952945
+ }
+ },
+ {
+ "id": "NotionPageUpdate-6FyYd",
+ "type": "genericNode",
+ "position": {
+ "x": 1827.0574354713603,
+ "y": 2055.9948126656136
+ },
+ "data": {
+ "type": "NotionPageUpdate",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nimport requests\nfrom typing import Dict, Any, Union\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom loguru import logger\nfrom langflow.io import Output\n\nclass NotionPageUpdate(LCToolComponent):\n display_name: str = \"Update Page Property \"\n description: str = \"Update the properties of a Notion page.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/page-update\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"page_id\",\n display_name=\"Page ID\",\n info=\"The ID of the Notion page to update.\",\n ),\n MultilineInput(\n name=\"properties\",\n display_name=\"Properties\",\n info=\"The properties to update on the page (as a JSON string or a dictionary).\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionPageUpdateSchema(BaseModel):\n page_id: str = Field(..., description=\"The ID of the Notion page to update.\")\n properties: Union[str, Dict[str, Any]] = Field(\n ..., description=\"The properties to update on the page (as a JSON string or a dictionary).\"\n )\n\n def run_model(self) -> Data:\n result = self._update_notion_page(self.page_id, self.properties)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the updated page data\n output = \"Updated page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"update_notion_page\",\n description=\"Update the properties of a Notion page. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._update_notion_page,\n args_schema=self.NotionPageUpdateSchema,\n )\n\n def _update_notion_page(self, page_id: str, properties: Union[str, Dict[str, Any]]) -> Union[Dict[str, Any], str]:\n url = f\"https://api.notion.com/v1/pages/{page_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n\n # Parse properties if it's a string\n if isinstance(properties, str):\n try:\n parsed_properties = json.loads(properties)\n except json.JSONDecodeError as e:\n error_message = f\"Invalid JSON format for properties: {str(e)}\"\n logger.error(error_message)\n return error_message\n\n else:\n parsed_properties = properties\n\n data = {\"properties\": parsed_properties}\n\n try:\n logger.info(f\"Sending request to Notion API: URL: {url}, Data: {json.dumps(data)}\")\n response = requests.patch(url, headers=headers, json=data)\n response.raise_for_status()\n updated_page = response.json()\n\n logger.info(f\"Successfully updated Notion page. Response: {json.dumps(updated_page)}\")\n return updated_page\n except requests.exceptions.HTTPError as e:\n error_message = f\"HTTP Error occurred: {str(e)}\"\n if e.response is not None:\n error_message += f\"\\nStatus code: {e.response.status_code}\"\n error_message += f\"\\nResponse body: {e.response.text}\"\n logger.error(error_message)\n return error_message\n except requests.exceptions.RequestException as e:\n error_message = f\"An error occurred while making the request: {str(e)}\"\n logger.error(error_message)\n return error_message\n except Exception as e:\n error_message = f\"An unexpected error occurred: {str(e)}\"\n logger.error(error_message)\n return error_message\n\n def __call__(self, *args, **kwargs):\n return self._update_notion_page(*args, **kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "page_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "page_id",
+ "value": "",
+ "display_name": "Page ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion page to update.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "properties": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "properties",
+ "value": "",
+ "display_name": "Properties",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The properties to update on the page (as a JSON string or a dictionary).",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Update the properties of a Notion page.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Update Page Property ",
+ "documentation": "https://docs.langflow.org/integrations/notion/page-update",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "page_id",
+ "properties",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionPageUpdate-6FyYd",
+ "description": "Update the properties of a Notion page.",
+ "display_name": "Update Page Property "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1827.0574354713603,
+ "y": 2055.9948126656136
+ }
+ },
+ {
+ "id": "ToolCallingAgent-50Gcd",
+ "type": "genericNode",
+ "position": {
+ "x": 2186.0530739759893,
+ "y": 612.1744804997304
+ },
+ "data": {
+ "type": "ToolCallingAgent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "chat_history": {
+ "trace_as_metadata": true,
+ "list": true,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "chat_history",
+ "value": "",
+ "display_name": "Chat History",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "llm": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "llm",
+ "value": "",
+ "display_name": "Language Model",
+ "advanced": false,
+ "input_types": [
+ "LanguageModel"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "tools": {
+ "trace_as_metadata": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tools",
+ "value": "",
+ "display_name": "Tools",
+ "advanced": false,
+ "input_types": [
+ "Tool",
+ "BaseTool"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from typing import Optional, List\n\nfrom langchain.agents import create_tool_calling_agent\nfrom langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.inputs import MultilineInput\nfrom langflow.inputs.inputs import HandleInput, DataInput\nfrom langflow.schema import Data\n\n\nclass ToolCallingAgentComponent(LCToolsAgentComponent):\n display_name: str = \"Tool Calling Agent\"\n description: str = \"Agent that uses tools\"\n icon = \"LangChain\"\n beta = True\n name = \"ToolCallingAgent\"\n\n inputs = LCToolsAgentComponent._base_inputs + [\n HandleInput(name=\"llm\", display_name=\"Language Model\", input_types=[\"LanguageModel\"], required=True),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"System Prompt\",\n info=\"System prompt for the agent.\",\n value=\"You are a helpful assistant\",\n ),\n MultilineInput(\n name=\"user_prompt\", display_name=\"Prompt\", info=\"This prompt must contain 'input' key.\", value=\"{input}\"\n ),\n DataInput(name=\"chat_history\", display_name=\"Chat History\", is_list=True, advanced=True),\n ]\n\n def get_chat_history_data(self) -> Optional[List[Data]]:\n return self.chat_history\n\n def create_agent_runnable(self):\n if \"input\" not in self.user_prompt:\n raise ValueError(\"Prompt must contain 'input' key.\")\n messages = [\n (\"system\", self.system_prompt),\n (\"placeholder\", \"{chat_history}\"),\n HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=[\"input\"], template=self.user_prompt)),\n (\"placeholder\", \"{agent_scratchpad}\"),\n ]\n prompt = ChatPromptTemplate.from_messages(messages)\n return create_tool_calling_agent(self.llm, self.tools, prompt)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "handle_parsing_errors": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "handle_parsing_errors",
+ "value": true,
+ "display_name": "Handle Parse Errors",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "max_iterations": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_iterations",
+ "value": 15,
+ "display_name": "Max Iterations",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "system_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_prompt",
+ "value": "",
+ "display_name": "System Prompt",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System prompt for the agent.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "user_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "user_prompt",
+ "value": "{input}",
+ "display_name": "Prompt",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "This prompt must contain 'input' key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "verbose": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "verbose",
+ "value": true,
+ "display_name": "Verbose",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Agent that uses tools",
+ "icon": "LangChain",
+ "base_classes": [
+ "AgentExecutor",
+ "Message"
+ ],
+ "display_name": "Tool Calling Agent",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "AgentExecutor"
+ ],
+ "selected": "AgentExecutor",
+ "name": "agent",
+ "display_name": "Agent",
+ "method": "build_agent",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "response",
+ "display_name": "Response",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "tools",
+ "llm",
+ "system_prompt",
+ "user_prompt",
+ "chat_history"
+ ],
+ "beta": true,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ToolCallingAgent-50Gcd"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 532,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 2186.0530739759893,
+ "y": 612.1744804997304
+ }
+ },
+ {
+ "id": "ChatOutput-TSCup",
+ "type": "genericNode",
+ "position": {
+ "x": 2649.190603849412,
+ "y": 841.0466487848925
+ },
+ "data": {
+ "type": "ChatOutput",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if (\n self.session_id\n and isinstance(message, Message)\n and isinstance(message.text, str)\n and self.should_store_message\n ):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "data_template": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data_template",
+ "value": "{text}",
+ "display_name": "Data Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Text",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "sender": {
+ "trace_as_metadata": true,
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender",
+ "value": "Machine",
+ "display_name": "Sender Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Type of sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender_name": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender_name",
+ "value": "AI",
+ "display_name": "Sender Name",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "session_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "session_id",
+ "value": "",
+ "display_name": "Session ID",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "should_store_message": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "should_store_message",
+ "value": true,
+ "display_name": "Store Messages",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Display a chat message in the Playground.",
+ "icon": "ChatOutput",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Chat Output",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "message",
+ "display_name": "Message",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ChatOutput-TSCup"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 2649.190603849412,
+ "y": 841.0466487848925
+ },
+ "dragging": false
+ },
+ {
+ "id": "ChatInput-bcq6D",
+ "type": "genericNode",
+ "position": {
+ "x": 557.6262725075026,
+ "y": 724.8518930903978
+ },
+ "data": {
+ "type": "ChatInput",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "files": {
+ "trace_as_metadata": true,
+ "file_path": "",
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "files",
+ "value": "",
+ "display_name": "Files",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Files to be sent with the message.",
+ "title_case": false,
+ "type": "file",
+ "_input_type": "FileInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, FileInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, MESSAGE_SENDER_NAME_USER\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n name = \"ChatInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n )\n\n if (\n self.session_id\n and isinstance(message, Message)\n and isinstance(message.text, str)\n and self.should_store_message\n ):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "list users",
+ "display_name": "Text",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "sender": {
+ "trace_as_metadata": true,
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender",
+ "value": "User",
+ "display_name": "Sender Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Type of sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender_name": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender_name",
+ "value": "User",
+ "display_name": "Sender Name",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "session_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "session_id",
+ "value": "",
+ "display_name": "Session ID",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "should_store_message": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "should_store_message",
+ "value": true,
+ "display_name": "Store Messages",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Get chat inputs from the Playground.",
+ "icon": "ChatInput",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Chat Input",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "message",
+ "display_name": "Message",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ChatInput-bcq6D"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 557.6262725075026,
+ "y": 724.8518930903978
+ },
+ "dragging": false
+ },
+ {
+ "id": "ToolkitComponent-2lNG0",
+ "type": "genericNode",
+ "position": {
+ "x": 1731.8884789245508,
+ "y": 1378.7846304343796
+ },
+ "data": {
+ "type": "ToolkitComponent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "tools": {
+ "trace_as_metadata": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tools",
+ "value": "",
+ "display_name": "Tools",
+ "advanced": false,
+ "input_types": [
+ "Tool"
+ ],
+ "dynamic": false,
+ "info": "List of tools to combine.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from typing import List\r\nfrom langflow.custom import Component\r\nfrom langflow.inputs import HandleInput, MessageTextInput\r\nfrom langflow.template import Output\r\nfrom langflow.field_typing import Tool, Embeddings\r\nfrom langchain.tools.base import BaseTool, StructuredTool\r\nfrom langflow.schema import Data\r\n\r\nclass ToolkitComponent(Component):\r\n display_name = \"Toolkit\"\r\n description = \"Combines multiple tools into a single list of tools.\"\r\n icon = \"pocket-knife\"\r\n\r\n inputs = [\r\n HandleInput(\r\n name=\"tools\",\r\n display_name=\"Tools\",\r\n input_types=[\"Tool\"],\r\n info=\"List of tools to combine.\",\r\n is_list=True,\r\n ),\r\n ]\r\n\r\n outputs = [\r\n Output(display_name=\"Tools\", name=\"generated_tools\", method=\"generate_toolkit\"),\r\n Output(display_name=\"Tool Data\", name=\"tool_data\", method=\"generate_tool_data\"),\r\n ]\r\n\r\n def generate_toolkit(self) -> List[BaseTool]:\r\n combined_tools = []\r\n name_count = {}\r\n for index, tool in enumerate(self.tools):\r\n self.log(f\"Processing tool {index}: {type(tool)}\")\r\n if isinstance(tool, (BaseTool, StructuredTool)):\r\n processed_tool = tool\r\n elif hasattr(tool, 'build_tool'):\r\n processed_tool = tool.build_tool()\r\n else:\r\n self.log(f\"Unsupported tool type: {type(tool)}. Attempting to process anyway.\")\r\n processed_tool = tool\r\n\r\n original_name = getattr(processed_tool, 'name', f\"UnnamedTool_{index}\")\r\n self.log(f\"Original tool name: {original_name}\")\r\n\r\n if original_name not in name_count:\r\n name_count[original_name] = 0\r\n final_name = original_name\r\n else:\r\n name_count[original_name] += 1\r\n final_name = f\"{original_name}_{name_count[original_name]}\"\r\n\r\n if hasattr(processed_tool, 'name'):\r\n processed_tool.name = final_name\r\n\r\n self.log(f\"Final tool name: {final_name}\")\r\n\r\n if isinstance(processed_tool, StructuredTool) and hasattr(processed_tool, 'args_schema'):\r\n processed_tool.args_schema.name = f\"{final_name}_Schema\"\r\n\r\n combined_tools.append(processed_tool)\r\n\r\n debug_info = \"\\n\".join([f\"Tool {i}: {getattr(tool, 'name', f'UnnamedTool_{i}')} (Original: {getattr(tool, '_original_name', 'N/A')}) - Type: {type(tool)}\" for i, tool in enumerate(combined_tools)])\r\n self.log(\"Final toolkit composition:\")\r\n self.log(debug_info)\r\n\r\n\r\n self.status = combined_tools\r\n return combined_tools\r\n\r\n def generate_tool_data(self) -> List[Data]:\r\n tool_data = []\r\n for tool in self.generate_toolkit():\r\n tool_data.append(Data(\r\n data={\r\n \"name\": getattr(tool, 'name', 'Unnamed Tool'),\r\n \"description\": getattr(tool, 'description', 'No description available')\r\n }\r\n ))\r\n return tool_data",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ }
+ },
+ "description": "Combines multiple tools into a single list of tools.",
+ "icon": "pocket-knife",
+ "base_classes": [
+ "BaseTool",
+ "Data"
+ ],
+ "display_name": "Toolkit",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "BaseTool"
+ ],
+ "selected": "BaseTool",
+ "name": "generated_tools",
+ "display_name": "Tools",
+ "method": "generate_toolkit",
+ "value": "__UNDEFINED__",
+ "cache": true
+ },
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "tool_data",
+ "display_name": "Tool Data",
+ "method": "generate_tool_data",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ }
+ ],
+ "field_order": [
+ "tools"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "ToolkitComponent-2lNG0"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 292,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1731.8884789245508,
+ "y": 1378.7846304343796
+ }
+ },
+ {
+ "id": "OpenAIModel-BJWIg",
+ "type": "genericNode",
+ "position": {
+ "x": 1718.9773974162958,
+ "y": 603.4642741725065
+ },
+ "data": {
+ "type": "OpenAIModel",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "load_from_db": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": "",
+ "display_name": "OpenAI API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import operator\nfrom functools import reduce\n\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = LCModelComponent._base_inputs + [\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # self.output_schema is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output # type: ignore\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageInput"
+ },
+ "json_mode": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "json_mode",
+ "value": false,
+ "display_name": "JSON Mode",
+ "advanced": true,
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "max_tokens": {
+ "trace_as_metadata": true,
+ "range_spec": {
+ "step_type": "float",
+ "min": 0,
+ "max": 128000,
+ "step": 0.1
+ },
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_tokens",
+ "value": "",
+ "display_name": "Max Tokens",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "model_kwargs": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_kwargs",
+ "value": {},
+ "display_name": "Model Kwargs",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "model_name": {
+ "trace_as_metadata": true,
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-0125"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_name",
+ "value": "gpt-4o",
+ "display_name": "Model Name",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "openai_api_base": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "openai_api_base",
+ "value": "",
+ "display_name": "OpenAI API Base",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "output_schema": {
+ "trace_as_input": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "output_schema",
+ "value": {},
+ "display_name": "Schema",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "seed": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "seed",
+ "value": 1,
+ "display_name": "Seed",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "stream": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stream",
+ "value": false,
+ "display_name": "Stream",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "system_message": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_message",
+ "value": "",
+ "display_name": "System Message",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "temperature": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": "0.2",
+ "display_name": "Temperature",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ }
+ },
+ "description": "Generates text using OpenAI LLMs.",
+ "icon": "OpenAI",
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "display_name": "OpenAI",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text_output",
+ "display_name": "Text",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ },
+ {
+ "types": [
+ "LanguageModel"
+ ],
+ "selected": "LanguageModel",
+ "name": "model_output",
+ "display_name": "Language Model",
+ "method": "build_model",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "OpenAIModel-BJWIg"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 433,
+ "positionAbsolute": {
+ "x": 1718.9773974162958,
+ "y": 603.4642741725065
+ },
+ "dragging": false
+ },
+ {
+ "id": "Memory-CTQWu",
+ "type": "genericNode",
+ "position": {
+ "x": 1240.7186213296432,
+ "y": 1059.5754404393747
+ },
+ "data": {
+ "type": "Memory",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "memory": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "memory",
+ "value": "",
+ "display_name": "External Memory",
+ "advanced": true,
+ "input_types": [
+ "BaseChatMessageHistory"
+ ],
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langchain.memory import ConversationBufferMemory\n\nfrom langflow.custom import Component\nfrom langflow.field_typing import BaseChatMemory\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import LCBuiltinChatMemory, get_messages\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Chat Memory\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"BaseChatMessageHistory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Messages (Data)\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Messages (Text)\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n Output(display_name=\"Memory\", name=\"lc_memory\", method=\"build_lc_memory\"),\n ]\n\n def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = self.memory.messages\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = get_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n\n def build_lc_memory(self) -> BaseChatMemory:\n if self.memory:\n chat_memory = self.memory\n else:\n chat_memory = LCBuiltinChatMemory(flow_id=self.flow_id, session_id=self.session_id)\n return ConversationBufferMemory(chat_memory=chat_memory)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "n_messages": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "n_messages",
+ "value": 100,
+ "display_name": "Number of Messages",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "order": {
+ "trace_as_metadata": true,
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "order",
+ "value": "Ascending",
+ "display_name": "Order",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender": {
+ "trace_as_metadata": true,
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender",
+ "value": "Machine and User",
+ "display_name": "Sender Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender_name": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender_name",
+ "value": "",
+ "display_name": "Sender Name",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "session_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "session_id",
+ "value": "",
+ "display_name": "Session ID",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{sender_name}: {text}",
+ "display_name": "Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Retrieves stored chat messages from Langflow tables or an external memory.",
+ "icon": "message-square-more",
+ "base_classes": [
+ "BaseChatMemory",
+ "Data",
+ "Message"
+ ],
+ "display_name": "Chat Memory",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "messages",
+ "display_name": "Messages (Data)",
+ "method": "retrieve_messages",
+ "value": "__UNDEFINED__",
+ "cache": true
+ },
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "messages_text",
+ "display_name": "Messages (Text)",
+ "method": "retrieve_messages_as_text",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "BaseChatMemory"
+ ],
+ "selected": "BaseChatMemory",
+ "name": "lc_memory",
+ "display_name": "Memory",
+ "method": "build_lc_memory",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ }
+ ],
+ "field_order": [
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "Memory-CTQWu"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 244,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1240.7186213296432,
+ "y": 1059.5754404393747
+ }
+ },
+ {
+ "id": "Prompt-0dWZu",
+ "type": "genericNode",
+ "position": {
+ "x": 1227.4862876736101,
+ "y": 616.3826667128244
+ },
+ "data": {
+ "type": "Prompt",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "template": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "\nYou are a Notion Agent, an AI assistant designed to help users interact with their Notion workspace. Your role is to understand user requests, utilize the appropriate Notion tools to fulfill these requests, and communicate clearly with the user throughout the process.\n\nGeneral Guidelines:\n\n1. Carefully analyze each user request to determine which tool(s) you need to use.\n\n2. Before using any tool, ensure you have all the necessary information. If you need more details, ask the user clear and concise questions.\n\n3. When using a tool, provide a brief explanation to the user about what you're doing and why.\n\n4. After using a tool, interpret the results for the user in a clear, concise manner.\n\n5. If a task requires multiple steps, outline your plan to the user before proceeding.\n\n6. If you encounter an error or limitation, explain it to the user and suggest possible solutions or alternative approaches.\n\n7. Always maintain a helpful and professional tone in your interactions.\n\n8. Be proactive in offering suggestions or alternatives if the user's initial request can't be fulfilled exactly as stated.\n\n9. When providing information or results, focus on relevance and clarity. Summarize when necessary, but provide details when they're important.\n\n10. If a user's request is unclear or could be interpreted in multiple ways, ask for clarification before proceeding.\n\n11. After completing a task, summarize what was accomplished and suggest any relevant next steps or additional actions the user might want to take.\n\n12. If a user asks about capabilities you don't have or tools you can't access, clearly explain your limitations and suggest alternative ways to assist if possible.\n\nRemember, your primary goal is to assist the user effectively with their Notion-related tasks using the provided tools. Always strive for clarity, accuracy, and helpfulness in your interactions. Adapt your communication style to the user's level of technical understanding and familiarity with Notion.\n\nNow, you're ready to assist the user\n\nToday is: {CURRENT_DATE}\n ",
+ "display_name": "Template",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "prompt",
+ "_input_type": "PromptInput"
+ },
+ "CURRENT_DATE": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "CURRENT_DATE",
+ "display_name": "CURRENT_DATE",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ }
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "icon": "prompts",
+ "is_input": null,
+ "is_output": null,
+ "is_composition": null,
+ "base_classes": [
+ "Message"
+ ],
+ "name": "",
+ "display_name": "Prompt",
+ "documentation": "",
+ "custom_fields": {
+ "template": [
+ "CURRENT_DATE"
+ ]
+ },
+ "output_types": [],
+ "full_path": null,
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "prompt",
+ "hidden": null,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "template"
+ ],
+ "beta": false,
+ "error": null,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "Prompt-0dWZu"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 416,
+ "positionAbsolute": {
+ "x": 1227.4862876736101,
+ "y": 616.3826667128244
+ },
+ "dragging": false
+ },
+ {
+ "id": "CurrentDateComponent-NSNQ8",
+ "type": "genericNode",
+ "position": {
+ "x": 1092.5108512311297,
+ "y": 868.3249850335523
+ },
+ "data": {
+ "type": "CurrentDateComponent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from datetime import datetime\r\nfrom zoneinfo import ZoneInfo\r\nfrom typing import List\r\n\r\nfrom langflow.custom import Component\r\nfrom langflow.io import DropdownInput, Output\r\nfrom langflow.schema.message import Message\r\n\r\nclass CurrentDateComponent(Component):\r\n display_name = \"Current Date 🕰️\"\r\n description = \"Returns the current date and time in the selected timezone.\"\r\n icon = \"clock\"\r\n\r\n inputs = [\r\n DropdownInput(\r\n name=\"timezone\",\r\n display_name=\"Timezone\",\r\n options=[\r\n \"UTC\",\r\n \"US/Eastern\",\r\n \"US/Central\",\r\n \"US/Mountain\",\r\n \"US/Pacific\",\r\n \"Europe/London\",\r\n \"Europe/Paris\",\r\n \"Asia/Tokyo\",\r\n \"Australia/Sydney\",\r\n \"America/Sao_Paulo\",\r\n \"America/Cuiaba\",\r\n ],\r\n value=\"UTC\",\r\n info=\"Select the timezone for the current date and time.\",\r\n ),\r\n ]\r\n\r\n outputs = [\r\n Output(display_name=\"Current Date\", name=\"current_date\", method=\"get_current_date\"),\r\n ]\r\n\r\n def get_current_date(self) -> Message:\r\n try:\r\n tz = ZoneInfo(self.timezone)\r\n current_date = datetime.now(tz).strftime(\"%Y-%m-%d %H:%M:%S %Z\")\r\n result = f\"Current date and time in {self.timezone}: {current_date}\"\r\n self.status = result\r\n return Message(text=result)\r\n except Exception as e:\r\n error_message = f\"Error: {str(e)}\"\r\n self.status = error_message\r\n return Message(text=error_message)",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "timezone": {
+ "trace_as_metadata": true,
+ "options": [
+ "UTC",
+ "US/Eastern",
+ "US/Central",
+ "US/Mountain",
+ "US/Pacific",
+ "Europe/London",
+ "Europe/Paris",
+ "Asia/Tokyo",
+ "Australia/Sydney",
+ "America/Sao_Paulo",
+ "America/Cuiaba"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "timezone",
+ "value": "UTC",
+ "display_name": "Timezone",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Select the timezone for the current date and time.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Returns the current date and time in the selected timezone.",
+ "icon": "clock",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Current Date",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "current_date",
+ "display_name": "Current Date",
+ "method": "get_current_date",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "timezone"
+ ],
+ "beta": false,
+ "edited": true,
+ "official": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "CurrentDateComponent-NSNQ8",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1092.5108512311297,
+ "y": 868.3249850335523
+ }
+ }
+ ],
+ "edges": [
+ {
+ "source": "ChatInput-bcq6D",
+ "target": "ToolCallingAgent-50Gcd",
+ "sourceHandle": "{œdataTypeœ:œChatInputœ,œidœ:œChatInput-bcq6Dœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}",
+ "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "id": "reactflow__edge-ChatInput-bcq6D{œdataTypeœ:œChatInputœ,œidœ:œChatInput-bcq6Dœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ToolCallingAgent-50Gcd{œfieldNameœ:œinput_valueœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ToolCallingAgent-50Gcd",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-bcq6D",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ToolCallingAgent-50Gcd",
+ "target": "ChatOutput-TSCup",
+ "sourceHandle": "{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-50Gcdœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}",
+ "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-TSCupœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "id": "reactflow__edge-ToolCallingAgent-50Gcd{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-50Gcdœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-TSCup{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-TSCupœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-TSCup",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ToolCallingAgent",
+ "id": "ToolCallingAgent-50Gcd",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ToolkitComponent-2lNG0",
+ "target": "ToolCallingAgent-50Gcd",
+ "sourceHandle": "{œdataTypeœ:œToolkitComponentœ,œidœ:œToolkitComponent-2lNG0œ,œnameœ:œgenerated_toolsœ,œoutput_typesœ:[œBaseToolœ]}",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "id": "reactflow__edge-ToolkitComponent-2lNG0{œdataTypeœ:œToolkitComponentœ,œidœ:œToolkitComponent-2lNG0œ,œnameœ:œgenerated_toolsœ,œoutput_typesœ:[œBaseToolœ]}-ToolCallingAgent-50Gcd{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-50Gcd",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "ToolkitComponent",
+ "id": "ToolkitComponent-2lNG0",
+ "name": "generated_tools",
+ "output_types": [
+ "BaseTool"
+ ]
+ }
+ },
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionPageUpdate-6FyYd",
+ "sourceHandle": "{œdataTypeœ:œNotionPageUpdateœ,œidœ:œNotionPageUpdate-6FyYdœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionPageUpdate",
+ "id": "NotionPageUpdate-6FyYd",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionPageUpdate-6FyYd{œdataTypeœ:œNotionPageUpdateœ,œidœ:œNotionPageUpdate-6FyYdœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionPageCreator-6SCB5",
+ "sourceHandle": "{œdataTypeœ:œNotionPageCreatorœ,œidœ:œNotionPageCreator-6SCB5œ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionPageCreator",
+ "id": "NotionPageCreator-6SCB5",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionPageCreator-6SCB5{œdataTypeœ:œNotionPageCreatorœ,œidœ:œNotionPageCreator-6SCB5œ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "AddContentToPage-ZezUn",
+ "sourceHandle": "{œdataTypeœ:œAddContentToPageœ,œidœ:œAddContentToPage-ZezUnœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AddContentToPage",
+ "id": "AddContentToPage-ZezUn",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AddContentToPage-ZezUn{œdataTypeœ:œAddContentToPageœ,œidœ:œAddContentToPage-ZezUnœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionDatabaseProperties-aeWil",
+ "sourceHandle": "{œdataTypeœ:œNotionDatabasePropertiesœ,œidœ:œNotionDatabaseProperties-aeWilœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionDatabaseProperties",
+ "id": "NotionDatabaseProperties-aeWil",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionDatabaseProperties-aeWil{œdataTypeœ:œNotionDatabasePropertiesœ,œidœ:œNotionDatabaseProperties-aeWilœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionListPages-znA3w",
+ "sourceHandle": "{œdataTypeœ:œNotionListPagesœ,œidœ:œNotionListPages-znA3wœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionListPages",
+ "id": "NotionListPages-znA3w",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionListPages-znA3w{œdataTypeœ:œNotionListPagesœ,œidœ:œNotionListPages-znA3wœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionPageContent-SlL21",
+ "sourceHandle": "{œdataTypeœ:œNotionPageContentœ,œidœ:œNotionPageContent-SlL21œ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionPageContent",
+ "id": "NotionPageContent-SlL21",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionPageContent-SlL21{œdataTypeœ:œNotionPageContentœ,œidœ:œNotionPageContent-SlL21œ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionUserList-C3eGn",
+ "sourceHandle": "{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-C3eGnœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionUserList",
+ "id": "NotionUserList-C3eGn",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionUserList-C3eGn{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-C3eGnœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "NotionSearch-VS2mI",
+ "sourceHandle": "{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-VS2mIœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolkitComponent-2lNG0",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolkitComponent-2lNG0",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionSearch",
+ "id": "NotionSearch-VS2mI",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionSearch-VS2mI{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-VS2mIœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolkitComponent-2lNG0{œfieldNameœ:œtoolsœ,œidœ:œToolkitComponent-2lNG0œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "OpenAIModel-BJWIg",
+ "sourceHandle": "{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BJWIgœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}",
+ "target": "ToolCallingAgent-50Gcd",
+ "targetHandle": "{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "ToolCallingAgent-50Gcd",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-BJWIg",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-BJWIg{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BJWIgœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-ToolCallingAgent-50Gcd{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "Memory-CTQWu",
+ "sourceHandle": "{œdataTypeœ:œMemoryœ,œidœ:œMemory-CTQWuœ,œnameœ:œmessagesœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ToolCallingAgent-50Gcd",
+ "targetHandle": "{œfieldNameœ:œchat_historyœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "chat_history",
+ "id": "ToolCallingAgent-50Gcd",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "Memory",
+ "id": "Memory-CTQWu",
+ "name": "messages",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Memory-CTQWu{œdataTypeœ:œMemoryœ,œidœ:œMemory-CTQWuœ,œnameœ:œmessagesœ,œoutput_typesœ:[œDataœ]}-ToolCallingAgent-50Gcd{œfieldNameœ:œchat_historyœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "Prompt-0dWZu",
+ "sourceHandle": "{œdataTypeœ:œPromptœ,œidœ:œPrompt-0dWZuœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "ToolCallingAgent-50Gcd",
+ "targetHandle": "{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "system_prompt",
+ "id": "ToolCallingAgent-50Gcd",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-0dWZu",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Prompt-0dWZu{œdataTypeœ:œPromptœ,œidœ:œPrompt-0dWZuœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ToolCallingAgent-50Gcd{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-50Gcdœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "className": "",
+ "selected": false
+ },
+ {
+ "source": "CurrentDateComponent-NSNQ8",
+ "sourceHandle": "{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-NSNQ8œ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-0dWZu",
+ "targetHandle": "{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-0dWZuœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "CURRENT_DATE",
+ "id": "Prompt-0dWZu",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "CurrentDateComponent",
+ "id": "CurrentDateComponent-NSNQ8",
+ "name": "current_date",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-CurrentDateComponent-NSNQ8{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-NSNQ8œ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}-Prompt-0dWZu{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-0dWZuœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "className": "",
+ "selected": false
+ }
+ ],
+ "viewport": {
+ "x": 97.72528949998423,
+ "y": -211.85229348429561,
+ "zoom": 0.41621432461249197
+ }
+ },
+ "description": "This flow creates an AI assistant that interacts with your Notion workspace. It understands natural language requests, performs actions in Notion (like creating pages or searching for information), and provides helpful responses. To use it, simply start a conversation by asking the agent to perform a Notion-related task, and it will guide you through the process, making it easy to manage your Notion workspace through chat.",
+ "name": "Conversational Notion Agent",
+ "last_tested_version": "1.0.17",
+ "endpoint_name": null,
+ "is_component": false
+}
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Notion/Meeting_Notes_Agent.json b/langflow/docs/docs/Integrations/Notion/Meeting_Notes_Agent.json
new file mode 100644
index 0000000..e567567
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Notion/Meeting_Notes_Agent.json
@@ -0,0 +1,4150 @@
+{
+ "id": "b6de0fdb-31a2-40bf-b921-719bc0890a0e",
+ "data": {
+ "nodes": [
+ {
+ "id": "TextInput-iJPEJ",
+ "type": "genericNode",
+ "position": {
+ "x": 94.43614181571661,
+ "y": 387.24602783243165
+ },
+ "data": {
+ "type": "TextInput",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Playground.\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n message = Message(\n text=self.input_value,\n )\n return message\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "Good morning. Thanks for joining this project review meeting. We've got quite a few tasks to discuss, especially some Notion-related ones. Shall we get started?\n\nMorning, Felipe. Absolutely, let's dive in. I see we have several projects and tasks on our plate.\n\nGreat. Let's begin with the AI Content Gen project. I'm currently working on \"Montar base agente seletor de cortes.\" It's in progress, and I'm aiming to complete it by June 14th. Have you had a chance to look at this task, Cezar?\n\nI haven't been directly involved with that one. Can you give me an overview of what it entails?\n\nOf course. Essentially, we're building a base agent that can intelligently select and edit content. It's part of our larger AI-driven content generation initiative. The challenge is creating an algorithm that can understand context and make smart editing decisions.\n\nInteresting. How's the progress so far?\n\nIt's coming along. I've set up the basic framework, but fine-tuning the selection criteria is proving to be more complex than initially anticipated. I might need an extra day or two beyond the June 14th deadline.\n\nUnderstood, Felipe. Keep me posted if you need any resources or if the deadline needs to be adjusted. By the way, I've been meaning to ask - have you had a chance to look into that new NLP library I mentioned last week? I think it could be useful for this project.\n\nActually, Cezar, I haven't gotten to that yet. Should we add it as a new task? Maybe \"Evaluate NLP library for content selection\"?\n\nGood idea. Let's add that to our task list with a due date of next Friday. Now, moving on to the next task in this project - \"Create Notion Task Automation.\" It's assigned to you and set for June 19th, but you haven't started it yet, right? This is where I'd like to focus our discussion today.\n\nThat's correct. So, the goal is to streamline our workflow by automating certain tasks within Notion. I'm thinking we could create scripts or use Notion's API to automatically create, assign, and update tasks based on certain triggers or schedules.\n\nThat sounds like it could save us a lot of time. What specific automations are you considering?\n\nI'm glad you asked, Cezar. I'm thinking of a few key areas:\n1. Automatic task creation based on project milestones\n2. Assigning tasks to team members based on their expertise and current workload\n3. Updating task statuses based on linked database entries\n4. Generating weekly progress reports\n5. Setting up reminders for overdue tasks\n\nThose all sound valuable. Have you looked into the technical requirements for implementing these?\n\nI've done some initial research. Notion's API seems robust enough to handle these automations. We'll likely need to use a combination of Notion's API and a server to run our scripts. I'm thinking of using Node.js for this.\n\nGood thinking. Do you foresee any challenges?\n\nThe main challenge will be ensuring our automations are flexible enough to handle different project structures and team dynamics. We'll need to build in some configurability.\n\nAgreed. Let's make sure we involve the team in defining these automations. Their input will be crucial for making this truly useful. Oh, and speaking of team input, I think we should add a task for \"Conduct team survey on Notion pain points.\" This could help us prioritize which automations to tackle first.\n\nThat's an excellent idea, Cezar. I'll create that task and aim to complete the survey by next Wednesday. Now, I see we have another Notion-related task: \"Subir Notion Agent no Langflow Prod.\" Can you remind me what this entails?\n\nYes, this task is about deploying our Notion integration agent to the Langflow production environment. It's not started yet, but it's a crucial step in making our Notion automations available to the whole team.\n\nI see. What's the timeline for this?\n\nWe haven't set a specific deadline yet, but I think we should aim to complete this shortly after the automation task. Let's tentatively say by the end of June?\n\nSounds reasonable. Make sure to coordinate with the DevOps team for a smooth deployment. And while we're on the topic of deployment, we should probably add a task for \"Create documentation for Notion Agent usage.\" We want to make sure the team knows how to use these new tools once they're available.\n\nYou're right, Felipe. I'll add that to our task list. Now, switching gears a bit, let's talk about the Internal Projects. I see you're working on \"Crypto Links\" - it's in progress.\n\nAh yes, our blockchain initiative. It's moving forward. I'm researching various blockchain platforms and their potential applications for our projects. I'm particularly interested in smart contract capabilities.\n\nInteresting. Keep me updated on any promising findings. By the way, have you considered reaching out to any blockchain experts for consultation? It might be worth adding a task for \"Schedule blockchain expert consultation.\"\n\nThat's a great suggestion, Cezar. I'll add it to my to-do list. Now, for the Internal Tasks, I see you're assigned to \"Revisar modos do Charlinho, preparar para open source.\" What's the status on that?\n\nI haven't started yet, but it's on my radar. The deadline is June 7th, so I'll be diving into it this week. Essentially, we need to review and refine Charlinho's modes before we open-source the project.\n\nSounds good. Let me know if you need any assistance with that. Oh, and don't forget we need to add a task for \"Prepare Charlinho documentation for open source.\" We want to make sure our project is well-documented when we release it.\n\nYou're right, Felipe. I'll make sure to include that in our task list. Now, I see you have several tasks assigned to you in the Internal Tasks section. Can you give me a quick rundown?\n\nOf course. I'm working on finding a freelancer to create flows in ComfyUI - that's in progress and due May 28th. I'm also handling the conception of the Agent UI, due May 30th. Both are moving along well.\n\nThere's also a task to \"Check, install and test Gladia to use a bot in Google Meet.\" That's in progress, and I'm collaborating with C on it.\n\nThat's quite a workload. How are you managing all these tasks?\n\nIt's challenging, but I'm prioritizing based on deadlines and dependencies. The Notion automation project is a high priority because it'll help us manage tasks more efficiently in the long run.\n\nGood strategy, Felipe. Is there anything you need from me or the team to help move these tasks forward?\n\nActually, yes. For the \"pegar os arquivos necessários para tentarmos montar um stinger com ffmpeg\" task, I could use some input on which files are critical for this. It's a low-priority task due June 2nd, but any insights would be helpful.\n\nI'll review our asset library and send you a list of potential files by tomorrow. Oh, and let's add a task for \"Create ffmpeg stinger tutorial\" once we figure out the process. It could be useful for the team in the future.\n\nGreat idea, Cezar. I'll add that to our backlog. Anything else we should discuss?\n\nI think we've covered the major points. Oh, one last thing - for the \"Create Notion Task Automation\" project, I was thinking of setting up a series of short daily meetings next week to keep everyone aligned. What do you think?\n\nThat's a good idea. Maybe 15-minute stand-ups? We can use those to address any roadblocks quickly. And let's add a task for \"Set up Notion Automation progress tracking board\" to help visualize our progress during these stand-ups.\n\nPerfect. I'll send out calendar invites this afternoon and create that tracking board task. Any final thoughts or concerns, Cezar?\n\nNot from my side. I think we have a clear path forward, especially with the Notion-related tasks and the new items we've added to our list.\n\nAgreed. Let's plan to reconvene next week to check on progress, particularly for the Notion automation project and these new tasks we've discussed. Thanks for the comprehensive update, Felipe.\n\nThank you, Cezar. I'll send out a summary of our discussion and action items shortly, including all the new tasks we've identified during this meeting.\n",
+ "display_name": "Text",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Text to be passed as input.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Get text inputs from the Playground.",
+ "icon": "type",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Meeting Transcript",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "TextInput-iJPEJ"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 94.43614181571661,
+ "y": 387.24602783243165
+ }
+ },
+ {
+ "id": "NotionUserList-TvIKS",
+ "type": "genericNode",
+ "position": {
+ "x": 80.49204196902156,
+ "y": 741.0568511678105
+ },
+ "data": {
+ "type": "NotionUserList",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import List, Dict\nfrom pydantic import BaseModel\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionUserList(LCToolComponent):\n display_name = \"List Users \"\n description = \"Retrieve users from Notion.\"\n documentation = \"https://docs.langflow.org/integrations/notion/list-users\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionUserListSchema(BaseModel):\n pass\n\n def run_model(self) -> List[Data]:\n users = self._list_users()\n records = []\n combined_text = \"\"\n\n for user in users:\n output = \"User:\\n\"\n for key, value in user.items():\n output += f\"{key.replace('_', ' ').title()}: {value}\\n\"\n output += \"________________________\\n\"\n\n combined_text += output\n records.append(Data(text=output, data=user))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_users\",\n description=\"Retrieve users from Notion.\",\n func=self._list_users,\n args_schema=self.NotionUserListSchema,\n )\n\n def _list_users(self) -> List[Dict]:\n url = \"https://api.notion.com/v1/users\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n response = requests.get(url, headers=headers)\n response.raise_for_status()\n\n data = response.json()\n results = data[\"results\"]\n\n users = []\n for user in results:\n user_data = {\n \"id\": user[\"id\"],\n \"type\": user[\"type\"],\n \"name\": user.get(\"name\", \"\"),\n \"avatar_url\": user.get(\"avatar_url\", \"\"),\n }\n users.append(user_data)\n\n return users\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Retrieve users from Notion.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Users ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-users",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ }
+ ],
+ "field_order": [
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionUserList-TvIKS",
+ "description": "Retrieve users from Notion.",
+ "display_name": "List Users "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 80.49204196902156,
+ "y": 741.0568511678105
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionSearch-M66HF",
+ "type": "genericNode",
+ "position": {
+ "x": 1095.6934863134345,
+ "y": 407.8718765800806
+ },
+ "data": {
+ "type": "NotionSearch",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import Dict, Any, List\nfrom pydantic import BaseModel, Field\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, DropdownInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionSearch(LCToolComponent):\n display_name: str = \"Search \"\n description: str = \"Searches all pages and databases that have been shared with an integration. The search field can be an empty value to show all values from that search\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/search\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The text that the API compares page and database titles against.\",\n ),\n DropdownInput(\n name=\"filter_value\",\n display_name=\"Filter Type\",\n info=\"Limits the results to either only pages or only databases.\",\n options=[\"page\", \"database\"],\n value=\"page\",\n ),\n DropdownInput(\n name=\"sort_direction\",\n display_name=\"Sort Direction\",\n info=\"The direction to sort the results.\",\n options=[\"ascending\", \"descending\"],\n value=\"descending\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionSearchSchema(BaseModel):\n query: str = Field(..., description=\"The search query text.\")\n filter_value: str = Field(default=\"page\", description=\"Filter type: 'page' or 'database'.\")\n sort_direction: str = Field(default=\"descending\", description=\"Sort direction: 'ascending' or 'descending'.\")\n\n def run_model(self) -> List[Data]:\n results = self._search_notion(self.query, self.filter_value, self.sort_direction)\n records = []\n combined_text = f\"Results found: {len(results)}\\n\\n\"\n\n for result in results:\n result_data = {\n \"id\": result[\"id\"],\n \"type\": result[\"object\"],\n \"last_edited_time\": result[\"last_edited_time\"],\n }\n\n if result[\"object\"] == \"page\":\n result_data[\"title_or_url\"] = result[\"url\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['url']}\\n\"\n elif result[\"object\"] == \"database\":\n if \"title\" in result and isinstance(result[\"title\"], list) and len(result[\"title\"]) > 0:\n result_data[\"title_or_url\"] = result[\"title\"][0][\"plain_text\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['title'][0]['plain_text']}\\n\"\n else:\n result_data[\"title_or_url\"] = \"N/A\"\n text = f\"id: {result['id']}\\ntitle_or_url: N/A\\n\"\n\n text += f\"type: {result['object']}\\nlast_edited_time: {result['last_edited_time']}\\n\\n\"\n combined_text += text\n records.append(Data(text=text, data=result_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_search\",\n description=\"Search Notion pages and databases. Input should include the search query and optionally filter type and sort direction.\",\n func=self._search_notion,\n args_schema=self.NotionSearchSchema,\n )\n\n def _search_notion(\n self, query: str, filter_value: str = \"page\", sort_direction: str = \"descending\"\n ) -> List[Dict[str, Any]]:\n url = \"https://api.notion.com/v1/search\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"query\": query,\n \"filter\": {\"value\": filter_value, \"property\": \"object\"},\n \"sort\": {\"direction\": sort_direction, \"timestamp\": \"last_edited_time\"},\n }\n\n response = requests.post(url, headers=headers, json=data)\n response.raise_for_status()\n\n results = response.json()\n return results[\"results\"]\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "filter_value": {
+ "trace_as_metadata": true,
+ "options": [
+ "page",
+ "database"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "filter_value",
+ "value": "database",
+ "display_name": "Filter Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Limits the results to either only pages or only databases.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "query": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "query",
+ "value": "",
+ "display_name": "Search Query",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The text that the API compares page and database titles against.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "sort_direction": {
+ "trace_as_metadata": true,
+ "options": [
+ "ascending",
+ "descending"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sort_direction",
+ "value": "descending",
+ "display_name": "Sort Direction",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The direction to sort the results.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Searches all pages and databases that have been shared with an integration. The search field can be an empty value to show all values from that search",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Search ",
+ "documentation": "https://docs.langflow.org/integrations/notion/search",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": false
+ }
+ ],
+ "field_order": [
+ "notion_secret",
+ "query",
+ "filter_value",
+ "sort_direction"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionSearch-M66HF",
+ "description": "Searches all pages and databases that have been shared with an integration.",
+ "display_name": "Search "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 386,
+ "positionAbsolute": {
+ "x": 1095.6934863134345,
+ "y": 407.8718765800806
+ },
+ "dragging": false
+ },
+ {
+ "id": "Prompt-19rub",
+ "type": "genericNode",
+ "position": {
+ "x": 688.7954025956392,
+ "y": 456.4686463487848
+ },
+ "data": {
+ "type": "Prompt",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "template": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "\nYou are an AI assistant specialized in analyzing meeting transcripts and identifying tasks. Your goal is to extract relevant tasks from the given transcript, search for related existing tasks in Notion, and provide a comprehensive list of tasks with their current status and any needed updates.\n\nYou have access to the following input:\n\n\n{TRANSCRIPT}\n \n\n\n{USERS}\n \n\nFollow these steps to complete your task:\n\n1. Carefully read through the transcript and identify any mentioned tasks, action items, or follow-ups.\n\n2. For each identified task:\n a. Use the notion_search tool to find if there's an existing related task in Notion.\n b. If a related task is found, note its ID and current status.\n c. If no related task is found, mark it as a new task.\n\n3. For each task (existing or new), determine:\n a. The task name or description\n b. The assigned person (if mentioned)\n c. The current status (for existing tasks) or suggested status (for new tasks)\n d. Any updates or changes mentioned in the transcript\n\n4. Compile your findings into a list of tasks using the following format:\n\n\n\n[Notion page ID if existing, or \"NEW\" if new task] \n[Task name or description] \n[Assigned person, if mentioned] \n[Current status for existing tasks, or suggested status for new tasks] \n[Any updates or changes mentioned in the transcript] \n \n \n\nRemember to focus on tasks that are directly related to the meeting discussion. Do not include general conversation topics or unrelated mentions as tasks.\n\nProvide your final output in the format specified above, with each task enclosed in its own tags within the overall structure.\n\nToday is: {CURRENT_DATE}\n \n\n\n",
+ "display_name": "Template",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "prompt",
+ "_input_type": "PromptInput"
+ },
+ "TRANSCRIPT": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "TRANSCRIPT",
+ "display_name": "TRANSCRIPT",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ },
+ "USERS": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "USERS",
+ "display_name": "USERS",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ },
+ "CURRENT_DATE": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "CURRENT_DATE",
+ "display_name": "CURRENT_DATE",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ }
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "icon": "prompts",
+ "is_input": null,
+ "is_output": null,
+ "is_composition": null,
+ "base_classes": [
+ "Message"
+ ],
+ "name": "",
+ "display_name": "Prompt",
+ "documentation": "",
+ "custom_fields": {
+ "template": [
+ "TRANSCRIPT",
+ "USERS",
+ "CURRENT_DATE"
+ ]
+ },
+ "output_types": [],
+ "full_path": null,
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "prompt",
+ "hidden": null,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "template"
+ ],
+ "beta": false,
+ "error": null,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "Prompt-19rub"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 588,
+ "positionAbsolute": {
+ "x": 688.7954025956392,
+ "y": 456.4686463487848
+ },
+ "dragging": false
+ },
+ {
+ "id": "ParseData-aNk1v",
+ "type": "genericNode",
+ "position": {
+ "x": 540.4151030255898,
+ "y": 834.2819856588019
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{text}",
+ "display_name": "Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ParseData-aNk1v",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 540.4151030255898,
+ "y": 834.2819856588019
+ }
+ },
+ {
+ "id": "ToolCallingAgent-rVWeq",
+ "type": "genericNode",
+ "position": {
+ "x": 1566.291217492157,
+ "y": 583.6687094567968
+ },
+ "data": {
+ "type": "ToolCallingAgent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "chat_history": {
+ "trace_as_metadata": true,
+ "list": true,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "chat_history",
+ "value": "",
+ "display_name": "Chat History",
+ "advanced": true,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "llm": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "llm",
+ "value": "",
+ "display_name": "Language Model",
+ "advanced": false,
+ "input_types": [
+ "LanguageModel"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "tools": {
+ "trace_as_metadata": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tools",
+ "value": "",
+ "display_name": "Tools",
+ "advanced": false,
+ "input_types": [
+ "Tool",
+ "BaseTool"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from typing import Optional, List\n\nfrom langchain.agents import create_tool_calling_agent\nfrom langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.inputs import MultilineInput\nfrom langflow.inputs.inputs import HandleInput, DataInput\nfrom langflow.schema import Data\n\n\nclass ToolCallingAgentComponent(LCToolsAgentComponent):\n display_name: str = \"Tool Calling Agent\"\n description: str = \"Agent that uses tools\"\n icon = \"LangChain\"\n beta = True\n name = \"ToolCallingAgent\"\n\n inputs = LCToolsAgentComponent._base_inputs + [\n HandleInput(name=\"llm\", display_name=\"Language Model\", input_types=[\"LanguageModel\"], required=True),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"System Prompt\",\n info=\"System prompt for the agent.\",\n value=\"You are a helpful assistant\",\n ),\n MultilineInput(\n name=\"user_prompt\", display_name=\"Prompt\", info=\"This prompt must contain 'input' key.\", value=\"{input}\"\n ),\n DataInput(name=\"chat_history\", display_name=\"Chat History\", is_list=True, advanced=True),\n ]\n\n def get_chat_history_data(self) -> Optional[List[Data]]:\n return self.chat_history\n\n def create_agent_runnable(self):\n if \"input\" not in self.user_prompt:\n raise ValueError(\"Prompt must contain 'input' key.\")\n messages = [\n (\"system\", self.system_prompt),\n (\"placeholder\", \"{chat_history}\"),\n HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=[\"input\"], template=self.user_prompt)),\n (\"placeholder\", \"{agent_scratchpad}\"),\n ]\n prompt = ChatPromptTemplate.from_messages(messages)\n return create_tool_calling_agent(self.llm, self.tools, prompt)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "handle_parsing_errors": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "handle_parsing_errors",
+ "value": true,
+ "display_name": "Handle Parse Errors",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "Analyze this meeting",
+ "display_name": "Input",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "max_iterations": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_iterations",
+ "value": 15,
+ "display_name": "Max Iterations",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "system_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_prompt",
+ "value": "",
+ "display_name": "System Prompt",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System prompt for the agent.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "user_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "user_prompt",
+ "value": "{input}",
+ "display_name": "Prompt",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "This prompt must contain 'input' key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "verbose": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "verbose",
+ "value": true,
+ "display_name": "Verbose",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Agent that uses tools",
+ "icon": "LangChain",
+ "base_classes": [
+ "AgentExecutor",
+ "Message"
+ ],
+ "display_name": "Tool Calling Agent",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "AgentExecutor"
+ ],
+ "selected": "AgentExecutor",
+ "name": "agent",
+ "display_name": "Agent",
+ "method": "build_agent",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "response",
+ "display_name": "Response",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "tools",
+ "llm",
+ "system_prompt",
+ "user_prompt",
+ "chat_history"
+ ],
+ "beta": true,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ToolCallingAgent-rVWeq"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 398,
+ "positionAbsolute": {
+ "x": 1566.291217492157,
+ "y": 583.6687094567968
+ },
+ "dragging": false
+ },
+ {
+ "id": "OpenAIModel-Ht8xI",
+ "type": "genericNode",
+ "position": {
+ "x": 1097.0545781920632,
+ "y": 805.60631548423
+ },
+ "data": {
+ "type": "OpenAIModel",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": "",
+ "display_name": "OpenAI API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import operator\nfrom functools import reduce\n\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = LCModelComponent._base_inputs + [\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # self.output_schema is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output # type: ignore\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageInput"
+ },
+ "json_mode": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "json_mode",
+ "value": false,
+ "display_name": "JSON Mode",
+ "advanced": true,
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "max_tokens": {
+ "trace_as_metadata": true,
+ "range_spec": {
+ "step_type": "float",
+ "min": 0,
+ "max": 128000,
+ "step": 0.1
+ },
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_tokens",
+ "value": "",
+ "display_name": "Max Tokens",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "model_kwargs": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_kwargs",
+ "value": {},
+ "display_name": "Model Kwargs",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "model_name": {
+ "trace_as_metadata": true,
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-0125"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_name",
+ "value": "gpt-4o",
+ "display_name": "Model Name",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "openai_api_base": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "openai_api_base",
+ "value": "",
+ "display_name": "OpenAI API Base",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "output_schema": {
+ "trace_as_input": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "output_schema",
+ "value": {},
+ "display_name": "Schema",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "seed": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "seed",
+ "value": 1,
+ "display_name": "Seed",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "stream": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stream",
+ "value": false,
+ "display_name": "Stream",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "system_message": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_message",
+ "value": "",
+ "display_name": "System Message",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "temperature": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": 0.1,
+ "display_name": "Temperature",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ }
+ },
+ "description": "Generates text using OpenAI LLMs.",
+ "icon": "OpenAI",
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "display_name": "OpenAI",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text_output",
+ "display_name": "Text",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "LanguageModel"
+ ],
+ "selected": "LanguageModel",
+ "name": "model_output",
+ "display_name": "Language Model",
+ "method": "build_model",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "OpenAIModel-Ht8xI"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 1097.0545781920632,
+ "y": 805.60631548423
+ }
+ },
+ {
+ "id": "Prompt-Lbxk6",
+ "type": "genericNode",
+ "position": {
+ "x": 3042.6844997246735,
+ "y": 416.83992118486856
+ },
+ "data": {
+ "type": "Prompt",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "template": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "\nYou are an AI assistant responsible for updating tasks in Notion based on the information provided from a meeting analysis. Your goal is to create new tasks and update existing ones using the Notion API tools available to you, and then provide a summary in a simple markdown format suitable for a chat interface.\n\nYou have access to the following inputs:\n\n\n{TASK_LIST}\n \n\n\n{DATABASES}\n \n\n\n{USERS}\n \n\nFollow these steps to update the tasks in Notion and generate a markdown summary:\n\n1. Identify the Task database ID from the provided list.\n\n2. Before processing any tasks, retrieve the database properties for the Task database:\n a. Use the notion_database_properties and carefully review the properties, their types, and any options for select or multi-select properties.\n b. Pay attention to the properties format for further usage.\n\n3. For each task in the task list:\n a. If the task ID is \"NEW\", create a new task using the create_notion_page tool.\n b. If the task has an existing ID, update the task using the update_notion_page tool.\n c. Remember to use the properties from the DB retrieved from the notion_database_properties tool\n\n4. When creating a new task:\n a. Use the create_notion_page tool.\n b. Include the task name, assignee (if available), status, and any other relevant properties based on the database structure.\n c. Ensure that the property names and types match exactly with what you retrieved from the notion_database_properties call.\n\n5. When updating an existing task:\n a. Use the update_notion_page tool.\n b. Update the status, assignee, or any other relevant properties mentioned in the field.\n c. Ensure that the property names and types match exactly with what you retrieved from the notion_database_properties call.\n\n6. After each function call, wait for the before proceeding to the next task.\n\n7. If you encounter any errors during the process, note them and continue with the next task.\n\n8. Provide a summary of your actions for each task in a simple markdown format. Use the following structure:\n # Task Update Summary\n\n ## Created Tasks\n - **[Task Name]**: Assigned to [Assignee], Status: [Status]\n - Details: [Brief description of the new task]\n\n ## Updated Tasks\n - **[Task Name]** (ID: [Notion Page ID])\n - Changes: [Brief description of changes]\n - Status: [Success/Error]\n\n ## Errors\n - **[Task Name or ID]**: [Description of the error encountered]\n\n\nRemember to use the exact property names, types, and options as specified in the Notion database properties you retrieved at the beginning. This is crucial for ensuring that all updates and creations are done correctly.\n\nIf you encounter any errors or uncertainties, include them in the Errors section of the markdown summary. With enough detail to the user understand the issues.\n\nProvide your final output as a complete markdown document containing all the tasks you've processed, whether they were created, updated, or encountered errors. Use only basic markdown formatting (headers, bold, lists) to ensure compatibility with chat interfaces. Do not include any XML tags or complex formatting in your final output.\n\nToday is: {CURRENT_DATE}\n\n ",
+ "display_name": "Template",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "prompt",
+ "_input_type": "PromptInput"
+ },
+ "TASK_LIST": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "TASK_LIST",
+ "display_name": "TASK_LIST",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ },
+ "DATABASES": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "DATABASES",
+ "display_name": "DATABASES",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ },
+ "USERS": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "USERS",
+ "display_name": "USERS",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ },
+ "CURRENT_DATE": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "CURRENT_DATE",
+ "display_name": "CURRENT_DATE",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ }
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "icon": "prompts",
+ "is_input": null,
+ "is_output": null,
+ "is_composition": null,
+ "base_classes": [
+ "Message"
+ ],
+ "name": "",
+ "display_name": "Prompt",
+ "documentation": "",
+ "custom_fields": {
+ "template": [
+ "TASK_LIST",
+ "DATABASES",
+ "USERS",
+ "CURRENT_DATE"
+ ]
+ },
+ "output_types": [],
+ "full_path": null,
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "prompt",
+ "hidden": null,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "template"
+ ],
+ "beta": false,
+ "error": null,
+ "edited": false
+ },
+ "id": "Prompt-Lbxk6"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 674,
+ "positionAbsolute": {
+ "x": 3042.6844997246735,
+ "y": 416.83992118486856
+ },
+ "dragging": false
+ },
+ {
+ "id": "ToolCallingAgent-GurdE",
+ "type": "genericNode",
+ "position": {
+ "x": 3974.1377259893243,
+ "y": 867.4647271037014
+ },
+ "data": {
+ "type": "ToolCallingAgent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "chat_history": {
+ "trace_as_metadata": true,
+ "list": true,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "chat_history",
+ "value": "",
+ "display_name": "Chat History",
+ "advanced": true,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "llm": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "llm",
+ "value": "",
+ "display_name": "Language Model",
+ "advanced": false,
+ "input_types": [
+ "LanguageModel"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "tools": {
+ "trace_as_metadata": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tools",
+ "value": "",
+ "display_name": "Tools",
+ "advanced": false,
+ "input_types": [
+ "Tool",
+ "BaseTool"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from typing import Optional, List\n\nfrom langchain.agents import create_tool_calling_agent\nfrom langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.inputs import MultilineInput\nfrom langflow.inputs.inputs import HandleInput, DataInput\nfrom langflow.schema import Data\n\n\nclass ToolCallingAgentComponent(LCToolsAgentComponent):\n display_name: str = \"Tool Calling Agent\"\n description: str = \"Agent that uses tools\"\n icon = \"LangChain\"\n beta = True\n name = \"ToolCallingAgent\"\n\n inputs = LCToolsAgentComponent._base_inputs + [\n HandleInput(name=\"llm\", display_name=\"Language Model\", input_types=[\"LanguageModel\"], required=True),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"System Prompt\",\n info=\"System prompt for the agent.\",\n value=\"You are a helpful assistant\",\n ),\n MultilineInput(\n name=\"user_prompt\", display_name=\"Prompt\", info=\"This prompt must contain 'input' key.\", value=\"{input}\"\n ),\n DataInput(name=\"chat_history\", display_name=\"Chat History\", is_list=True, advanced=True),\n ]\n\n def get_chat_history_data(self) -> Optional[List[Data]]:\n return self.chat_history\n\n def create_agent_runnable(self):\n if \"input\" not in self.user_prompt:\n raise ValueError(\"Prompt must contain 'input' key.\")\n messages = [\n (\"system\", self.system_prompt),\n (\"placeholder\", \"{chat_history}\"),\n HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=[\"input\"], template=self.user_prompt)),\n (\"placeholder\", \"{agent_scratchpad}\"),\n ]\n prompt = ChatPromptTemplate.from_messages(messages)\n return create_tool_calling_agent(self.llm, self.tools, prompt)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "handle_parsing_errors": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "handle_parsing_errors",
+ "value": true,
+ "display_name": "Handle Parse Errors",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "Do your task.",
+ "display_name": "Input",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "max_iterations": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_iterations",
+ "value": 15,
+ "display_name": "Max Iterations",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "system_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_prompt",
+ "value": "",
+ "display_name": "System Prompt",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System prompt for the agent.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "user_prompt": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "user_prompt",
+ "value": "{input}",
+ "display_name": "Prompt",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "This prompt must contain 'input' key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "verbose": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "verbose",
+ "value": true,
+ "display_name": "Verbose",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Agent that uses tools",
+ "icon": "LangChain",
+ "base_classes": [
+ "AgentExecutor",
+ "Message"
+ ],
+ "display_name": "Tool Calling Agent",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "AgentExecutor"
+ ],
+ "selected": "AgentExecutor",
+ "name": "agent",
+ "display_name": "Agent",
+ "method": "build_agent",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "response",
+ "display_name": "Response",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "tools",
+ "llm",
+ "system_prompt",
+ "user_prompt",
+ "chat_history"
+ ],
+ "beta": true,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ToolCallingAgent-GurdE"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 398,
+ "positionAbsolute": {
+ "x": 3974.1377259893243,
+ "y": 867.4647271037014
+ },
+ "dragging": false
+ },
+ {
+ "id": "OpenAIModel-OTfnt",
+ "type": "genericNode",
+ "position": {
+ "x": 3513.5648778762093,
+ "y": 710.2099422974287
+ },
+ "data": {
+ "type": "OpenAIModel",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "load_from_db": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": "",
+ "display_name": "OpenAI API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import operator\nfrom functools import reduce\n\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = LCModelComponent._base_inputs + [\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # self.output_schema is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output # type: ignore\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageInput"
+ },
+ "json_mode": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "json_mode",
+ "value": false,
+ "display_name": "JSON Mode",
+ "advanced": true,
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "max_tokens": {
+ "trace_as_metadata": true,
+ "range_spec": {
+ "step_type": "float",
+ "min": 0,
+ "max": 128000,
+ "step": 0.1
+ },
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_tokens",
+ "value": "",
+ "display_name": "Max Tokens",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "model_kwargs": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_kwargs",
+ "value": {},
+ "display_name": "Model Kwargs",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "model_name": {
+ "trace_as_metadata": true,
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-0125"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_name",
+ "value": "gpt-4o",
+ "display_name": "Model Name",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "openai_api_base": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "openai_api_base",
+ "value": "",
+ "display_name": "OpenAI API Base",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "output_schema": {
+ "trace_as_input": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "output_schema",
+ "value": {},
+ "display_name": "Schema",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "seed": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "seed",
+ "value": 1,
+ "display_name": "Seed",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "stream": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stream",
+ "value": false,
+ "display_name": "Stream",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "system_message": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_message",
+ "value": "",
+ "display_name": "System Message",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "temperature": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": 0.1,
+ "display_name": "Temperature",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ }
+ },
+ "description": "Generates text using OpenAI LLMs.",
+ "icon": "OpenAI",
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "display_name": "OpenAI",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text_output",
+ "display_name": "Text",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "LanguageModel"
+ ],
+ "selected": "LanguageModel",
+ "name": "model_output",
+ "display_name": "Language Model",
+ "method": "build_model",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "OpenAIModel-OTfnt"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 3513.5648778762093,
+ "y": 710.2099422974287
+ },
+ "dragging": false
+ },
+ {
+ "id": "AddContentToPage-vrAvx",
+ "type": "genericNode",
+ "position": {
+ "x": 2649.2991466550634,
+ "y": 1050.6250104897197
+ },
+ "data": {
+ "type": "AddContentToPage",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "block_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "block_id",
+ "value": "",
+ "display_name": "Page/Block ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the page/block to add the content.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nfrom typing import Dict, Any, Union\nfrom markdown import markdown\nfrom bs4 import BeautifulSoup\nimport requests\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom pydantic import BaseModel, Field\n\n\nclass AddContentToPage(LCToolComponent):\n display_name: str = \"Add Content to Page \"\n description: str = \"Convert markdown text to Notion blocks and append them to a Notion page.\"\n documentation: str = \"https://developers.notion.com/reference/patch-block-children\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n MultilineInput(\n name=\"markdown_text\",\n display_name=\"Markdown Text\",\n info=\"The markdown text to convert to Notion blocks.\",\n ),\n StrInput(\n name=\"block_id\",\n display_name=\"Page/Block ID\",\n info=\"The ID of the page/block to add the content.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class AddContentToPageSchema(BaseModel):\n markdown_text: str = Field(..., description=\"The markdown text to convert to Notion blocks.\")\n block_id: str = Field(..., description=\"The ID of the page/block to add the content.\")\n\n def run_model(self) -> Data:\n result = self._add_content_to_page(self.markdown_text, self.block_id)\n return Data(data=result, text=json.dumps(result))\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"add_content_to_notion_page\",\n description=\"Convert markdown text to Notion blocks and append them to a Notion page.\",\n func=self._add_content_to_page,\n args_schema=self.AddContentToPageSchema,\n )\n\n def _add_content_to_page(self, markdown_text: str, block_id: str) -> Union[Dict[str, Any], str]:\n try:\n html_text = markdown(markdown_text)\n soup = BeautifulSoup(html_text, \"html.parser\")\n blocks = self.process_node(soup)\n\n url = f\"https://api.notion.com/v1/blocks/{block_id}/children\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"children\": blocks,\n }\n\n response = requests.patch(url, headers=headers, json=data)\n response.raise_for_status()\n\n return response.json()\n except requests.exceptions.RequestException as e:\n error_message = f\"Error: Failed to add content to Notion page. {str(e)}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n except Exception as e:\n return f\"Error: An unexpected error occurred while adding content to Notion page. {str(e)}\"\n\n def process_node(self, node):\n blocks = []\n if isinstance(node, str):\n text = node.strip()\n if text:\n if text.startswith(\"#\"):\n heading_level = text.count(\"#\", 0, 6)\n heading_text = text[heading_level:].strip()\n if heading_level == 1:\n blocks.append(self.create_block(\"heading_1\", heading_text))\n elif heading_level == 2:\n blocks.append(self.create_block(\"heading_2\", heading_text))\n elif heading_level == 3:\n blocks.append(self.create_block(\"heading_3\", heading_text))\n else:\n blocks.append(self.create_block(\"paragraph\", text))\n elif node.name == \"h1\":\n blocks.append(self.create_block(\"heading_1\", node.get_text(strip=True)))\n elif node.name == \"h2\":\n blocks.append(self.create_block(\"heading_2\", node.get_text(strip=True)))\n elif node.name == \"h3\":\n blocks.append(self.create_block(\"heading_3\", node.get_text(strip=True)))\n elif node.name == \"p\":\n code_node = node.find(\"code\")\n if code_node:\n code_text = code_node.get_text()\n language, code = self.extract_language_and_code(code_text)\n blocks.append(self.create_block(\"code\", code, language=language))\n elif self.is_table(str(node)):\n blocks.extend(self.process_table(node))\n else:\n blocks.append(self.create_block(\"paragraph\", node.get_text(strip=True)))\n elif node.name == \"ul\":\n blocks.extend(self.process_list(node, \"bulleted_list_item\"))\n elif node.name == \"ol\":\n blocks.extend(self.process_list(node, \"numbered_list_item\"))\n elif node.name == \"blockquote\":\n blocks.append(self.create_block(\"quote\", node.get_text(strip=True)))\n elif node.name == \"hr\":\n blocks.append(self.create_block(\"divider\", \"\"))\n elif node.name == \"img\":\n blocks.append(self.create_block(\"image\", \"\", image_url=node.get(\"src\")))\n elif node.name == \"a\":\n blocks.append(self.create_block(\"bookmark\", node.get_text(strip=True), link_url=node.get(\"href\")))\n elif node.name == \"table\":\n blocks.extend(self.process_table(node))\n\n for child in node.children:\n if isinstance(child, str):\n continue\n blocks.extend(self.process_node(child))\n\n return blocks\n\n def extract_language_and_code(self, code_text):\n lines = code_text.split(\"\\n\")\n language = lines[0].strip()\n code = \"\\n\".join(lines[1:]).strip()\n return language, code\n\n def is_code_block(self, text):\n return text.startswith(\"```\")\n\n def extract_code_block(self, text):\n lines = text.split(\"\\n\")\n language = lines[0].strip(\"`\").strip()\n code = \"\\n\".join(lines[1:]).strip(\"`\").strip()\n return language, code\n\n def is_table(self, text):\n rows = text.split(\"\\n\")\n if len(rows) < 2:\n return False\n\n has_separator = False\n for i, row in enumerate(rows):\n if \"|\" in row:\n cells = [cell.strip() for cell in row.split(\"|\")]\n cells = [cell for cell in cells if cell] # Remove empty cells\n if i == 1 and all(set(cell) <= set(\"-|\") for cell in cells):\n has_separator = True\n elif not cells:\n return False\n\n return has_separator and len(rows) >= 3\n\n def process_list(self, node, list_type):\n blocks = []\n for item in node.find_all(\"li\"):\n item_text = item.get_text(strip=True)\n checked = item_text.startswith(\"[x]\")\n is_checklist = item_text.startswith(\"[ ]\") or checked\n\n if is_checklist:\n item_text = item_text.replace(\"[x]\", \"\").replace(\"[ ]\", \"\").strip()\n blocks.append(self.create_block(\"to_do\", item_text, checked=checked))\n else:\n blocks.append(self.create_block(list_type, item_text))\n return blocks\n\n def process_table(self, node):\n blocks = []\n header_row = node.find(\"thead\").find(\"tr\") if node.find(\"thead\") else None\n body_rows = node.find(\"tbody\").find_all(\"tr\") if node.find(\"tbody\") else []\n\n if header_row or body_rows:\n table_width = max(\n len(header_row.find_all([\"th\", \"td\"])) if header_row else 0,\n max(len(row.find_all([\"th\", \"td\"])) for row in body_rows),\n )\n\n table_block = self.create_block(\"table\", \"\", table_width=table_width, has_column_header=bool(header_row))\n blocks.append(table_block)\n\n if header_row:\n header_cells = [cell.get_text(strip=True) for cell in header_row.find_all([\"th\", \"td\"])]\n header_row_block = self.create_block(\"table_row\", header_cells)\n blocks.append(header_row_block)\n\n for row in body_rows:\n cells = [cell.get_text(strip=True) for cell in row.find_all([\"th\", \"td\"])]\n row_block = self.create_block(\"table_row\", cells)\n blocks.append(row_block)\n\n return blocks\n\n def create_block(self, block_type: str, content: str, **kwargs) -> Dict[str, Any]:\n block: dict[str, Any] = {\n \"object\": \"block\",\n \"type\": block_type,\n block_type: {},\n }\n\n if block_type in [\n \"paragraph\",\n \"heading_1\",\n \"heading_2\",\n \"heading_3\",\n \"bulleted_list_item\",\n \"numbered_list_item\",\n \"quote\",\n ]:\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n elif block_type == \"to_do\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"checked\"] = kwargs.get(\"checked\", False)\n elif block_type == \"code\":\n block[block_type][\"rich_text\"] = [\n {\n \"type\": \"text\",\n \"text\": {\n \"content\": content,\n },\n }\n ]\n block[block_type][\"language\"] = kwargs.get(\"language\", \"plain text\")\n elif block_type == \"image\":\n block[block_type] = {\"type\": \"external\", \"external\": {\"url\": kwargs.get(\"image_url\", \"\")}}\n elif block_type == \"divider\":\n pass\n elif block_type == \"bookmark\":\n block[block_type][\"url\"] = kwargs.get(\"link_url\", \"\")\n elif block_type == \"table\":\n block[block_type][\"table_width\"] = kwargs.get(\"table_width\", 0)\n block[block_type][\"has_column_header\"] = kwargs.get(\"has_column_header\", False)\n block[block_type][\"has_row_header\"] = kwargs.get(\"has_row_header\", False)\n elif block_type == \"table_row\":\n block[block_type][\"cells\"] = [[{\"type\": \"text\", \"text\": {\"content\": cell}} for cell in content]]\n\n return block\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "markdown_text": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "markdown_text",
+ "value": "",
+ "display_name": "Markdown Text",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The markdown text to convert to Notion blocks.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Convert markdown text to Notion blocks and append them to a Notion page.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Add Content to Page ",
+ "documentation": "https://developers.notion.com/reference/patch-block-children",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "markdown_text",
+ "block_id",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "AddContentToPage-vrAvx",
+ "description": "Convert markdown text to Notion blocks and append them to a Notion page.",
+ "display_name": "Add Content to Page "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 330,
+ "positionAbsolute": {
+ "x": 2649.2991466550634,
+ "y": 1050.6250104897197
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionPageCreator-Exc7f",
+ "type": "genericNode",
+ "position": {
+ "x": 3050.8201437255634,
+ "y": 1391.0449862668834
+ },
+ "data": {
+ "type": "NotionPageCreator",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nfrom typing import Dict, Any, Union\nimport requests\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom langflow.io import Output\n\nclass NotionPageCreator(LCToolComponent):\n display_name: str = \"Create Page \"\n description: str = \"A component for creating Notion pages.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/page-create\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n MultilineInput(\n name=\"properties_json\",\n display_name=\"Properties (JSON)\",\n info=\"The properties of the new page as a JSON string.\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionPageCreatorSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n properties_json: str = Field(..., description=\"The properties of the new page as a JSON string.\")\n\n def run_model(self) -> Data:\n result = self._create_notion_page(self.database_id, self.properties_json)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the created page data\n output = \"Created page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"create_notion_page\",\n description=\"Create a new page in a Notion database. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._create_notion_page,\n args_schema=self.NotionPageCreatorSchema,\n )\n\n def _create_notion_page(self, database_id: str, properties_json: str) -> Union[Dict[str, Any], str]:\n if not database_id or not properties_json:\n return \"Invalid input. Please provide 'database_id' and 'properties_json'.\"\n\n try:\n properties = json.loads(properties_json)\n except json.JSONDecodeError as e:\n return f\"Invalid properties format. Please provide a valid JSON string. Error: {str(e)}\"\n\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"parent\": {\"database_id\": database_id},\n \"properties\": properties,\n }\n\n try:\n response = requests.post(\"https://api.notion.com/v1/pages\", headers=headers, json=data)\n response.raise_for_status()\n result = response.json()\n return result\n except requests.exceptions.RequestException as e:\n error_message = f\"Failed to create Notion page. Error: {str(e)}\"\n if hasattr(e, \"response\") and e.response is not None:\n error_message += f\" Status code: {e.response.status_code}, Response: {e.response.text}\"\n return error_message\n\n def __call__(self, *args, **kwargs):\n return self._create_notion_page(*args, **kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "database_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "database_id",
+ "value": "",
+ "display_name": "Database ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion database.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "properties_json": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "properties_json",
+ "value": "",
+ "display_name": "Properties (JSON)",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The properties of the new page as a JSON string.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "A component for creating Notion pages.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Create Page ",
+ "documentation": "https://docs.langflow.org/integrations/notion/page-create",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "database_id",
+ "notion_secret",
+ "properties_json"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionPageCreator-Exc7f",
+ "description": "A component for creating Notion pages.",
+ "display_name": "Create Page "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 3050.8201437255634,
+ "y": 1391.0449862668834
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionDatabaseProperties-IjzLV",
+ "type": "genericNode",
+ "position": {
+ "x": 3053.0023230574693,
+ "y": 1061.535907149244
+ },
+ "data": {
+ "type": "NotionDatabaseProperties",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import Dict, Union\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom langflow.io import Output\n\nclass NotionDatabaseProperties(LCToolComponent):\n display_name: str = \"List Database Properties \"\n description: str = \"Retrieve properties of a Notion database.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/list-database-properties\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"database_id\",\n display_name=\"Database ID\",\n info=\"The ID of the Notion database.\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionDatabasePropertiesSchema(BaseModel):\n database_id: str = Field(..., description=\"The ID of the Notion database.\")\n\n def run_model(self) -> Data:\n result = self._fetch_database_properties(self.database_id)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the properties\n return Data(text=str(result), data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_database_properties\",\n description=\"Retrieve properties of a Notion database. Input should include the database ID.\",\n func=self._fetch_database_properties,\n args_schema=self.NotionDatabasePropertiesSchema,\n )\n\n def _fetch_database_properties(self, database_id: str) -> Union[Dict, str]:\n url = f\"https://api.notion.com/v1/databases/{database_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n try:\n response = requests.get(url, headers=headers)\n response.raise_for_status()\n data = response.json()\n properties = data.get(\"properties\", {})\n return properties\n except requests.exceptions.RequestException as e:\n return f\"Error fetching Notion database properties: {str(e)}\"\n except ValueError as e:\n return f\"Error parsing Notion API response: {str(e)}\"\n except Exception as e:\n return f\"An unexpected error occurred: {str(e)}\"\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "database_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "database_id",
+ "value": "",
+ "display_name": "Database ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion database.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Retrieve properties of a Notion database.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Database Properties ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-database-properties",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "database_id",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionDatabaseProperties-IjzLV",
+ "description": "Retrieve properties of a Notion database.",
+ "display_name": "List Database Properties "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 3053.0023230574693,
+ "y": 1061.535907149244
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionPageUpdate-bexvy",
+ "type": "genericNode",
+ "position": {
+ "x": 2649.2991466550625,
+ "y": 1385.262204377853
+ },
+ "data": {
+ "type": "NotionPageUpdate",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nimport requests\nfrom typing import Dict, Any, Union\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, MultilineInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\nfrom loguru import logger\nfrom langflow.io import Output\n\nclass NotionPageUpdate(LCToolComponent):\n display_name: str = \"Update Page Property \"\n description: str = \"Update the properties of a Notion page.\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/page-update\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n StrInput(\n name=\"page_id\",\n display_name=\"Page ID\",\n info=\"The ID of the Notion page to update.\",\n ),\n MultilineInput(\n name=\"properties\",\n display_name=\"Properties\",\n info=\"The properties to update on the page (as a JSON string or a dictionary).\",\n ),\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionPageUpdateSchema(BaseModel):\n page_id: str = Field(..., description=\"The ID of the Notion page to update.\")\n properties: Union[str, Dict[str, Any]] = Field(\n ..., description=\"The properties to update on the page (as a JSON string or a dictionary).\"\n )\n\n def run_model(self) -> Data:\n result = self._update_notion_page(self.page_id, self.properties)\n if isinstance(result, str):\n # An error occurred, return it as text\n return Data(text=result)\n else:\n # Success, return the updated page data\n output = \"Updated page properties:\\n\"\n for prop_name, prop_value in result.get(\"properties\", {}).items():\n output += f\"{prop_name}: {prop_value}\\n\"\n return Data(text=output, data=result)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"update_notion_page\",\n description=\"Update the properties of a Notion page. IMPORTANT: Use the tool to check the Database properties for more details before using this tool.\",\n func=self._update_notion_page,\n args_schema=self.NotionPageUpdateSchema,\n )\n\n def _update_notion_page(self, page_id: str, properties: Union[str, Dict[str, Any]]) -> Union[Dict[str, Any], str]:\n url = f\"https://api.notion.com/v1/pages/{page_id}\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\", # Use the latest supported version\n }\n\n # Parse properties if it's a string\n if isinstance(properties, str):\n try:\n parsed_properties = json.loads(properties)\n except json.JSONDecodeError as e:\n error_message = f\"Invalid JSON format for properties: {str(e)}\"\n logger.error(error_message)\n return error_message\n\n else:\n parsed_properties = properties\n\n data = {\"properties\": parsed_properties}\n\n try:\n logger.info(f\"Sending request to Notion API: URL: {url}, Data: {json.dumps(data)}\")\n response = requests.patch(url, headers=headers, json=data)\n response.raise_for_status()\n updated_page = response.json()\n\n logger.info(f\"Successfully updated Notion page. Response: {json.dumps(updated_page)}\")\n return updated_page\n except requests.exceptions.HTTPError as e:\n error_message = f\"HTTP Error occurred: {str(e)}\"\n if e.response is not None:\n error_message += f\"\\nStatus code: {e.response.status_code}\"\n error_message += f\"\\nResponse body: {e.response.text}\"\n logger.error(error_message)\n return error_message\n except requests.exceptions.RequestException as e:\n error_message = f\"An error occurred while making the request: {str(e)}\"\n logger.error(error_message)\n return error_message\n except Exception as e:\n error_message = f\"An unexpected error occurred: {str(e)}\"\n logger.error(error_message)\n return error_message\n\n def __call__(self, *args, **kwargs):\n return self._update_notion_page(*args, **kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "page_id": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "page_id",
+ "value": "",
+ "display_name": "Page ID",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The ID of the Notion page to update.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "properties": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "properties",
+ "value": "",
+ "display_name": "Properties",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The properties to update on the page (as a JSON string or a dictionary).",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Update the properties of a Notion page.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "Update Page Property ",
+ "documentation": "https://docs.langflow.org/integrations/notion/page-update",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "page_id",
+ "properties",
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionPageUpdate-bexvy",
+ "description": "Update the properties of a Notion page.",
+ "display_name": "Update Page Property "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 2649.2991466550625,
+ "y": 1385.262204377853
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionSearch-EdSJb",
+ "type": "genericNode",
+ "position": {
+ "x": 2435.4455721283834,
+ "y": 357.45573905064634
+ },
+ "data": {
+ "type": "NotionSearch",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import Dict, Any, List\nfrom pydantic import BaseModel, Field\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput, StrInput, DropdownInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionSearch(LCToolComponent):\n display_name: str = \"Search \"\n description: str = \"Searches all pages and databases that have been shared with an integration. The search field can be an empty value to show all values from that search\"\n documentation: str = \"https://docs.langflow.org/integrations/notion/search\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n StrInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The text that the API compares page and database titles against.\",\n ),\n DropdownInput(\n name=\"filter_value\",\n display_name=\"Filter Type\",\n info=\"Limits the results to either only pages or only databases.\",\n options=[\"page\", \"database\"],\n value=\"page\",\n ),\n DropdownInput(\n name=\"sort_direction\",\n display_name=\"Sort Direction\",\n info=\"The direction to sort the results.\",\n options=[\"ascending\", \"descending\"],\n value=\"descending\",\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionSearchSchema(BaseModel):\n query: str = Field(..., description=\"The search query text.\")\n filter_value: str = Field(default=\"page\", description=\"Filter type: 'page' or 'database'.\")\n sort_direction: str = Field(default=\"descending\", description=\"Sort direction: 'ascending' or 'descending'.\")\n\n def run_model(self) -> List[Data]:\n results = self._search_notion(self.query, self.filter_value, self.sort_direction)\n records = []\n combined_text = f\"Results found: {len(results)}\\n\\n\"\n\n for result in results:\n result_data = {\n \"id\": result[\"id\"],\n \"type\": result[\"object\"],\n \"last_edited_time\": result[\"last_edited_time\"],\n }\n\n if result[\"object\"] == \"page\":\n result_data[\"title_or_url\"] = result[\"url\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['url']}\\n\"\n elif result[\"object\"] == \"database\":\n if \"title\" in result and isinstance(result[\"title\"], list) and len(result[\"title\"]) > 0:\n result_data[\"title_or_url\"] = result[\"title\"][0][\"plain_text\"]\n text = f\"id: {result['id']}\\ntitle_or_url: {result['title'][0]['plain_text']}\\n\"\n else:\n result_data[\"title_or_url\"] = \"N/A\"\n text = f\"id: {result['id']}\\ntitle_or_url: N/A\\n\"\n\n text += f\"type: {result['object']}\\nlast_edited_time: {result['last_edited_time']}\\n\\n\"\n combined_text += text\n records.append(Data(text=text, data=result_data))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_search\",\n description=\"Search Notion pages and databases. Input should include the search query and optionally filter type and sort direction.\",\n func=self._search_notion,\n args_schema=self.NotionSearchSchema,\n )\n\n def _search_notion(\n self, query: str, filter_value: str = \"page\", sort_direction: str = \"descending\"\n ) -> List[Dict[str, Any]]:\n url = \"https://api.notion.com/v1/search\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n data = {\n \"query\": query,\n \"filter\": {\"value\": filter_value, \"property\": \"object\"},\n \"sort\": {\"direction\": sort_direction, \"timestamp\": \"last_edited_time\"},\n }\n\n response = requests.post(url, headers=headers, json=data)\n response.raise_for_status()\n\n results = response.json()\n return results[\"results\"]\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "filter_value": {
+ "trace_as_metadata": true,
+ "options": [
+ "page",
+ "database"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "filter_value",
+ "value": "database",
+ "display_name": "Filter Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Limits the results to either only pages or only databases.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "query": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "query",
+ "value": "",
+ "display_name": "Search Query",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The text that the API compares page and database titles against.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "sort_direction": {
+ "trace_as_metadata": true,
+ "options": [
+ "ascending",
+ "descending"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sort_direction",
+ "value": "descending",
+ "display_name": "Sort Direction",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The direction to sort the results.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "List All Databases",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Databases",
+ "documentation": "https://docs.langflow.org/integrations/notion/search",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": false
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ }
+ ],
+ "field_order": [
+ "notion_secret",
+ "query",
+ "filter_value",
+ "sort_direction"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionSearch-EdSJb",
+ "description": "Searches all pages and databases that have been shared with an integration.",
+ "display_name": "Search "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 2435.4455721283834,
+ "y": 357.45573905064634
+ },
+ "dragging": false
+ },
+ {
+ "id": "ParseData-vYVwu",
+ "type": "genericNode",
+ "position": {
+ "x": 2871.5903532688335,
+ "y": 563.1965154816405
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{text}",
+ "display_name": "Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ParseData-vYVwu",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "positionAbsolute": {
+ "x": 2871.5903532688335,
+ "y": 563.1965154816405
+ },
+ "dragging": false
+ },
+ {
+ "id": "ChatOutput-zBv53",
+ "type": "genericNode",
+ "position": {
+ "x": 4429.812566227955,
+ "y": 940.6072472757681
+ },
+ "data": {
+ "type": "ChatOutput",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if (\n self.session_id\n and isinstance(message, Message)\n and isinstance(message.text, str)\n and self.should_store_message\n ):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "data_template": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data_template",
+ "value": "{text}",
+ "display_name": "Data Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Text",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "sender": {
+ "trace_as_metadata": true,
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender",
+ "value": "Machine",
+ "display_name": "Sender Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Type of sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender_name": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender_name",
+ "value": "AI",
+ "display_name": "Sender Name",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "session_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "session_id",
+ "value": "",
+ "display_name": "Session ID",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "should_store_message": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "should_store_message",
+ "value": true,
+ "display_name": "Store Messages",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Display a chat message in the Playground.",
+ "icon": "ChatOutput",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Chat Output",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "message",
+ "display_name": "Message",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ChatOutput-zBv53"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 4429.812566227955,
+ "y": 940.6072472757681
+ },
+ "dragging": false
+ },
+ {
+ "id": "NotionUserList-wFEb1",
+ "type": "genericNode",
+ "position": {
+ "x": 2390.6365450681037,
+ "y": 694.4867003504073
+ },
+ "data": {
+ "type": "NotionUserList",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import requests\nfrom typing import List, Dict\nfrom pydantic import BaseModel\nfrom langflow.io import Output\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass NotionUserList(LCToolComponent):\n display_name = \"List Users \"\n description = \"Retrieve users from Notion.\"\n documentation = \"https://docs.langflow.org/integrations/notion/list-users\"\n icon = \"NotionDirectoryLoader\"\n\n inputs = [\n SecretStrInput(\n name=\"notion_secret\",\n display_name=\"Notion Secret\",\n info=\"The Notion integration token.\",\n required=True,\n ),\n ]\n outputs = [\n Output(name=\"example_output\", display_name=\"Data\", method=\"run_model\"),\n Output(name=\"example_tool_output\", display_name=\"Tool\", method=\"build_tool\"),\n ]\n\n class NotionUserListSchema(BaseModel):\n pass\n\n def run_model(self) -> List[Data]:\n users = self._list_users()\n records = []\n combined_text = \"\"\n\n for user in users:\n output = \"User:\\n\"\n for key, value in user.items():\n output += f\"{key.replace('_', ' ').title()}: {value}\\n\"\n output += \"________________________\\n\"\n\n combined_text += output\n records.append(Data(text=output, data=user))\n\n self.status = records\n return records\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"notion_list_users\",\n description=\"Retrieve users from Notion.\",\n func=self._list_users,\n args_schema=self.NotionUserListSchema,\n )\n\n def _list_users(self) -> List[Dict]:\n url = \"https://api.notion.com/v1/users\"\n headers = {\n \"Authorization\": f\"Bearer {self.notion_secret}\",\n \"Notion-Version\": \"2022-06-28\",\n }\n\n response = requests.get(url, headers=headers)\n response.raise_for_status()\n\n data = response.json()\n results = data[\"results\"]\n\n users = []\n for user in results:\n user_data = {\n \"id\": user[\"id\"],\n \"type\": user[\"type\"],\n \"name\": user.get(\"name\", \"\"),\n \"avatar_url\": user.get(\"avatar_url\", \"\"),\n }\n users.append(user_data)\n\n return users\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "notion_secret": {
+ "load_from_db": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "notion_secret",
+ "value": "",
+ "display_name": "Notion Secret",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The Notion integration token.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Retrieve users from Notion.",
+ "icon": "NotionDirectoryLoader",
+ "base_classes": [
+ "Data",
+ "Tool"
+ ],
+ "display_name": "List Users ",
+ "documentation": "https://docs.langflow.org/integrations/notion/list-users",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "example_output",
+ "display_name": "Data",
+ "method": "run_model",
+ "value": "__UNDEFINED__",
+ "cache": true
+ },
+ {
+ "types": [
+ "Tool"
+ ],
+ "selected": "Tool",
+ "name": "example_tool_output",
+ "display_name": "Tool",
+ "method": "build_tool",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "hidden": true
+ }
+ ],
+ "field_order": [
+ "notion_secret"
+ ],
+ "beta": false,
+ "edited": true,
+ "lf_version": "1.0.17"
+ },
+ "id": "NotionUserList-wFEb1",
+ "description": "Retrieve users from Notion.",
+ "display_name": "List Users "
+ },
+ "selected": false,
+ "width": 384,
+ "height": 302,
+ "positionAbsolute": {
+ "x": 2390.6365450681037,
+ "y": 694.4867003504073
+ },
+ "dragging": false
+ },
+ {
+ "id": "ParseData-WKjW6",
+ "type": "genericNode",
+ "position": {
+ "x": 2877.571533084884,
+ "y": 856.8480898893301
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{text}",
+ "display_name": "Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "lf_version": "1.0.17"
+ },
+ "id": "ParseData-WKjW6",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "positionAbsolute": {
+ "x": 2877.571533084884,
+ "y": 856.8480898893301
+ },
+ "dragging": false
+ },
+ {
+ "id": "CurrentDateComponent-WOwNq",
+ "type": "genericNode",
+ "position": {
+ "x": 536.7929500860405,
+ "y": 617.6055631700241
+ },
+ "data": {
+ "type": "CurrentDateComponent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from datetime import datetime\r\nfrom zoneinfo import ZoneInfo\r\nfrom typing import List\r\n\r\nfrom langflow.custom import Component\r\nfrom langflow.io import DropdownInput, Output\r\nfrom langflow.schema.message import Message\r\n\r\nclass CurrentDateComponent(Component):\r\n display_name = \"Current Date 🕰️\"\r\n description = \"Returns the current date and time in the selected timezone.\"\r\n icon = \"clock\"\r\n\r\n inputs = [\r\n DropdownInput(\r\n name=\"timezone\",\r\n display_name=\"Timezone\",\r\n options=[\r\n \"UTC\",\r\n \"US/Eastern\",\r\n \"US/Central\",\r\n \"US/Mountain\",\r\n \"US/Pacific\",\r\n \"Europe/London\",\r\n \"Europe/Paris\",\r\n \"Asia/Tokyo\",\r\n \"Australia/Sydney\",\r\n \"America/Sao_Paulo\",\r\n \"America/Cuiaba\",\r\n ],\r\n value=\"UTC\",\r\n info=\"Select the timezone for the current date and time.\",\r\n ),\r\n ]\r\n\r\n outputs = [\r\n Output(display_name=\"Current Date\", name=\"current_date\", method=\"get_current_date\"),\r\n ]\r\n\r\n def get_current_date(self) -> Message:\r\n try:\r\n tz = ZoneInfo(self.timezone)\r\n current_date = datetime.now(tz).strftime(\"%Y-%m-%d %H:%M:%S %Z\")\r\n result = f\"Current date and time in {self.timezone}: {current_date}\"\r\n self.status = result\r\n return Message(text=result)\r\n except Exception as e:\r\n error_message = f\"Error: {str(e)}\"\r\n self.status = error_message\r\n return Message(text=error_message)",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "timezone": {
+ "trace_as_metadata": true,
+ "options": [
+ "UTC",
+ "US/Eastern",
+ "US/Central",
+ "US/Mountain",
+ "US/Pacific",
+ "Europe/London",
+ "Europe/Paris",
+ "Asia/Tokyo",
+ "Australia/Sydney",
+ "America/Sao_Paulo",
+ "America/Cuiaba"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "timezone",
+ "value": "UTC",
+ "display_name": "Timezone",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Select the timezone for the current date and time.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Returns the current date and time in the selected timezone.",
+ "icon": "clock",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Current Date",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "current_date",
+ "display_name": "Current Date",
+ "method": "get_current_date",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "timezone"
+ ],
+ "beta": false,
+ "edited": true
+ },
+ "id": "CurrentDateComponent-WOwNq",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 536.7929500860405,
+ "y": 617.6055631700241
+ }
+ },
+ {
+ "id": "CurrentDateComponent-PZ8xJ",
+ "type": "genericNode",
+ "position": {
+ "x": 2871.6341688682833,
+ "y": 453.3374434097356
+ },
+ "data": {
+ "type": "CurrentDateComponent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from datetime import datetime\r\nfrom zoneinfo import ZoneInfo\r\nfrom typing import List\r\n\r\nfrom langflow.custom import Component\r\nfrom langflow.io import DropdownInput, Output\r\nfrom langflow.schema.message import Message\r\n\r\nclass CurrentDateComponent(Component):\r\n display_name = \"Current Date 🕰️\"\r\n description = \"Returns the current date and time in the selected timezone.\"\r\n icon = \"clock\"\r\n\r\n inputs = [\r\n DropdownInput(\r\n name=\"timezone\",\r\n display_name=\"Timezone\",\r\n options=[\r\n \"UTC\",\r\n \"US/Eastern\",\r\n \"US/Central\",\r\n \"US/Mountain\",\r\n \"US/Pacific\",\r\n \"Europe/London\",\r\n \"Europe/Paris\",\r\n \"Asia/Tokyo\",\r\n \"Australia/Sydney\",\r\n \"America/Sao_Paulo\",\r\n \"America/Cuiaba\",\r\n ],\r\n value=\"UTC\",\r\n info=\"Select the timezone for the current date and time.\",\r\n ),\r\n ]\r\n\r\n outputs = [\r\n Output(display_name=\"Current Date\", name=\"current_date\", method=\"get_current_date\"),\r\n ]\r\n\r\n def get_current_date(self) -> Message:\r\n try:\r\n tz = ZoneInfo(self.timezone)\r\n current_date = datetime.now(tz).strftime(\"%Y-%m-%d %H:%M:%S %Z\")\r\n result = f\"Current date and time in {self.timezone}: {current_date}\"\r\n self.status = result\r\n return Message(text=result)\r\n except Exception as e:\r\n error_message = f\"Error: {str(e)}\"\r\n self.status = error_message\r\n return Message(text=error_message)",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "timezone": {
+ "trace_as_metadata": true,
+ "options": [
+ "UTC",
+ "US/Eastern",
+ "US/Central",
+ "US/Mountain",
+ "US/Pacific",
+ "Europe/London",
+ "Europe/Paris",
+ "Asia/Tokyo",
+ "Australia/Sydney",
+ "America/Sao_Paulo",
+ "America/Cuiaba"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "timezone",
+ "value": "UTC",
+ "display_name": "Timezone",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Select the timezone for the current date and time.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ }
+ },
+ "description": "Returns the current date and time in the selected timezone.",
+ "icon": "clock",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Current Date",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "current_date",
+ "display_name": "Current Date",
+ "method": "get_current_date",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "timezone"
+ ],
+ "beta": false,
+ "edited": true,
+ "official": false
+ },
+ "id": "CurrentDateComponent-PZ8xJ",
+ "showNode": false
+ },
+ "selected": false,
+ "width": 96,
+ "height": 96,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 2871.6341688682833,
+ "y": 453.3374434097356
+ }
+ }
+ ],
+ "edges": [
+ {
+ "source": "TextInput-iJPEJ",
+ "sourceHandle": "{œdataTypeœ:œTextInputœ,œidœ:œTextInput-iJPEJœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-19rub",
+ "targetHandle": "{œfieldNameœ:œTRANSCRIPTœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "TRANSCRIPT",
+ "id": "Prompt-19rub",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "TextInput",
+ "id": "TextInput-iJPEJ",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-TextInput-iJPEJ{œdataTypeœ:œTextInputœ,œidœ:œTextInput-iJPEJœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-19rub{œfieldNameœ:œTRANSCRIPTœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionUserList-TvIKS",
+ "sourceHandle": "{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-TvIKSœ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ParseData-aNk1v",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-aNk1vœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-aNk1v",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionUserList",
+ "id": "NotionUserList-TvIKS",
+ "name": "example_output",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionUserList-TvIKS{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-TvIKSœ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-aNk1v{œfieldNameœ:œdataœ,œidœ:œParseData-aNk1vœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ParseData-aNk1v",
+ "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-aNk1vœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-19rub",
+ "targetHandle": "{œfieldNameœ:œUSERSœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "USERS",
+ "id": "Prompt-19rub",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-aNk1v",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ParseData-aNk1v{œdataTypeœ:œParseDataœ,œidœ:œParseData-aNk1vœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-19rub{œfieldNameœ:œUSERSœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "Prompt-19rub",
+ "sourceHandle": "{œdataTypeœ:œPromptœ,œidœ:œPrompt-19rubœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "ToolCallingAgent-rVWeq",
+ "targetHandle": "{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "system_prompt",
+ "id": "ToolCallingAgent-rVWeq",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-19rub",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Prompt-19rub{œdataTypeœ:œPromptœ,œidœ:œPrompt-19rubœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ToolCallingAgent-rVWeq{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionSearch-M66HF",
+ "sourceHandle": "{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-M66HFœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolCallingAgent-rVWeq",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-rVWeq",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionSearch",
+ "id": "NotionSearch-M66HF",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionSearch-M66HF{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-M66HFœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolCallingAgent-rVWeq{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "OpenAIModel-Ht8xI",
+ "sourceHandle": "{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Ht8xIœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}",
+ "target": "ToolCallingAgent-rVWeq",
+ "targetHandle": "{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "ToolCallingAgent-rVWeq",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-Ht8xI",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-Ht8xI{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Ht8xIœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-ToolCallingAgent-rVWeq{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-rVWeqœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ToolCallingAgent-rVWeq",
+ "sourceHandle": "{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-rVWeqœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-Lbxk6",
+ "targetHandle": "{œfieldNameœ:œTASK_LISTœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "TASK_LIST",
+ "id": "Prompt-Lbxk6",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ToolCallingAgent",
+ "id": "ToolCallingAgent-rVWeq",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ToolCallingAgent-rVWeq{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-rVWeqœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-Lbxk6{œfieldNameœ:œTASK_LISTœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "OpenAIModel-OTfnt",
+ "sourceHandle": "{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-OTfntœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-OTfnt",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-OTfnt{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-OTfntœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œllmœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "Prompt-Lbxk6",
+ "sourceHandle": "{œdataTypeœ:œPromptœ,œidœ:œPrompt-Lbxk6œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "system_prompt",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-Lbxk6",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Prompt-Lbxk6{œdataTypeœ:œPromptœ,œidœ:œPrompt-Lbxk6œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œsystem_promptœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "AddContentToPage-vrAvx",
+ "sourceHandle": "{œdataTypeœ:œAddContentToPageœ,œidœ:œAddContentToPage-vrAvxœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "AddContentToPage",
+ "id": "AddContentToPage-vrAvx",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-AddContentToPage-vrAvx{œdataTypeœ:œAddContentToPageœ,œidœ:œAddContentToPage-vrAvxœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionPageCreator-Exc7f",
+ "sourceHandle": "{œdataTypeœ:œNotionPageCreatorœ,œidœ:œNotionPageCreator-Exc7fœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionPageCreator",
+ "id": "NotionPageCreator-Exc7f",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionPageCreator-Exc7f{œdataTypeœ:œNotionPageCreatorœ,œidœ:œNotionPageCreator-Exc7fœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionDatabaseProperties-IjzLV",
+ "sourceHandle": "{œdataTypeœ:œNotionDatabasePropertiesœ,œidœ:œNotionDatabaseProperties-IjzLVœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionDatabaseProperties",
+ "id": "NotionDatabaseProperties-IjzLV",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionDatabaseProperties-IjzLV{œdataTypeœ:œNotionDatabasePropertiesœ,œidœ:œNotionDatabaseProperties-IjzLVœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionPageUpdate-bexvy",
+ "sourceHandle": "{œdataTypeœ:œNotionPageUpdateœ,œidœ:œNotionPageUpdate-bexvyœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}",
+ "target": "ToolCallingAgent-GurdE",
+ "targetHandle": "{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "ToolCallingAgent-GurdE",
+ "inputTypes": [
+ "Tool",
+ "BaseTool"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionPageUpdate",
+ "id": "NotionPageUpdate-bexvy",
+ "name": "example_tool_output",
+ "output_types": [
+ "Tool"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionPageUpdate-bexvy{œdataTypeœ:œNotionPageUpdateœ,œidœ:œNotionPageUpdate-bexvyœ,œnameœ:œexample_tool_outputœ,œoutput_typesœ:[œToolœ]}-ToolCallingAgent-GurdE{œfieldNameœ:œtoolsœ,œidœ:œToolCallingAgent-GurdEœ,œinputTypesœ:[œToolœ,œBaseToolœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionSearch-EdSJb",
+ "sourceHandle": "{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-EdSJbœ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ParseData-vYVwu",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-vYVwuœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-vYVwu",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionSearch",
+ "id": "NotionSearch-EdSJb",
+ "name": "example_output",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionSearch-EdSJb{œdataTypeœ:œNotionSearchœ,œidœ:œNotionSearch-EdSJbœ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-vYVwu{œfieldNameœ:œdataœ,œidœ:œParseData-vYVwuœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ParseData-vYVwu",
+ "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-vYVwuœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-Lbxk6",
+ "targetHandle": "{œfieldNameœ:œDATABASESœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "DATABASES",
+ "id": "Prompt-Lbxk6",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-vYVwu",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ParseData-vYVwu{œdataTypeœ:œParseDataœ,œidœ:œParseData-vYVwuœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-Lbxk6{œfieldNameœ:œDATABASESœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "ToolCallingAgent-GurdE",
+ "sourceHandle": "{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-GurdEœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "ChatOutput-zBv53",
+ "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-zBv53œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-zBv53",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ToolCallingAgent",
+ "id": "ToolCallingAgent-GurdE",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ToolCallingAgent-GurdE{œdataTypeœ:œToolCallingAgentœ,œidœ:œToolCallingAgent-GurdEœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-zBv53{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-zBv53œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "className": ""
+ },
+ {
+ "source": "NotionUserList-wFEb1",
+ "sourceHandle": "{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-wFEb1œ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ParseData-WKjW6",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-WKjW6œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-WKjW6",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "NotionUserList",
+ "id": "NotionUserList-wFEb1",
+ "name": "example_output",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-NotionUserList-wFEb1{œdataTypeœ:œNotionUserListœ,œidœ:œNotionUserList-wFEb1œ,œnameœ:œexample_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-WKjW6{œfieldNameœ:œdataœ,œidœ:œParseData-WKjW6œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "className": ""
+ },
+ {
+ "source": "ParseData-WKjW6",
+ "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-WKjW6œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-Lbxk6",
+ "targetHandle": "{œfieldNameœ:œUSERSœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "USERS",
+ "id": "Prompt-Lbxk6",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-WKjW6",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ParseData-WKjW6{œdataTypeœ:œParseDataœ,œidœ:œParseData-WKjW6œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-Lbxk6{œfieldNameœ:œUSERSœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "className": ""
+ },
+ {
+ "source": "CurrentDateComponent-WOwNq",
+ "sourceHandle": "{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-WOwNqœ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-19rub",
+ "targetHandle": "{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "CURRENT_DATE",
+ "id": "Prompt-19rub",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "CurrentDateComponent",
+ "id": "CurrentDateComponent-WOwNq",
+ "name": "current_date",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-CurrentDateComponent-WOwNq{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-WOwNqœ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}-Prompt-19rub{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-19rubœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "className": ""
+ },
+ {
+ "source": "CurrentDateComponent-PZ8xJ",
+ "sourceHandle": "{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-PZ8xJœ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-Lbxk6",
+ "targetHandle": "{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "CURRENT_DATE",
+ "id": "Prompt-Lbxk6",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "CurrentDateComponent",
+ "id": "CurrentDateComponent-PZ8xJ",
+ "name": "current_date",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-CurrentDateComponent-PZ8xJ{œdataTypeœ:œCurrentDateComponentœ,œidœ:œCurrentDateComponent-PZ8xJœ,œnameœ:œcurrent_dateœ,œoutput_typesœ:[œMessageœ]}-Prompt-Lbxk6{œfieldNameœ:œCURRENT_DATEœ,œidœ:œPrompt-Lbxk6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "className": ""
+ }
+ ],
+ "viewport": {
+ "x": -65.48833753518215,
+ "y": 119.49034539812101,
+ "zoom": 0.5588906662759379
+ }
+ },
+ "description": "The Notion Agent for Meeting Notes is an AI-powered tool that automatically processes meeting transcripts and updates your Notion workspace accordingly. It identifies tasks, action items, and key points from your meetings, then creates new tasks or updates existing ones in Notion without manual input.\n\nTo use it, simply add your API Keys and provide a meeting transcript. The agent will analyze it, interact with your Notion workspace to make necessary updates, and give you a summary of actions taken. This streamlines your workflow, ensuring important meeting outcomes are captured and organized in Notion effortlessly.",
+ "name": "Notion Agent - Meeting Notes ",
+ "last_tested_version": "1.0.17.dev8",
+ "endpoint_name": null,
+ "is_component": false
+}
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Notion/integrations-notion.md b/langflow/docs/docs/Integrations/Notion/integrations-notion.md
new file mode 100644
index 0000000..c9b504c
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Notion/integrations-notion.md
@@ -0,0 +1,88 @@
+---
+title: Setup
+slug: /integrations/notion/setup
+---
+
+# Set up a Notion App
+
+To use Notion components in Langflow, you first need to create a Notion integration and configure it with the necessary capabilities. This guide will walk you through the process of setting up a Notion integration and granting it access to your Notion databases.
+
+## Prerequisites
+
+- A Notion account with access to the workspace where you want to use the integration.
+- Admin permissions in the Notion workspace to create and manage integrations.
+
+## Create a Notion Integration
+
+1. Go to the [Notion Integrations](https://www.notion.com/my-integrations) page.
+2. Click on the "New integration" button.
+3. Give your integration a name and select the workspace where you want to use it.
+4. Click "Submit" to create the integration.
+
+:::info
+When creating the integration, make sure to enable the necessary capabilities based on your requirements. Refer to the [Notion Integration Capabilities](https://developers.notion.com/reference/capabilities) documentation for more information on each capability.
+:::
+
+
+## Configure Integration Capabilities
+
+After creating the integration, you need to configure its capabilities to define what actions it can perform and what data it can access.
+
+1. In the integration settings page, go to the **Capabilities** tab.
+2. Enable the required capabilities for your integration. For example:
+ - If your integration needs to read data from Notion, enable the "Read content" capability.
+ - If your integration needs to create new content in Notion, enable the "Insert content" capability.
+ - If your integration needs to update existing content in Notion, enable the "Update content" capability.
+3. Configure the user information access level based on your integration's requirements.
+4. Save the changes.
+
+## Obtain Integration Token
+
+To authenticate your integration with Notion, you need to obtain an integration token.
+
+1. In the integration settings page, go to the "Secrets" tab.
+2. Copy the "Internal Integration Token" value. This token will be used to authenticate your integration with Notion.
+
+:::warning
+Your integration token is a sensitive piece of information. Make sure to keep it secure and never share it publicly. Store it safely in your Langflow configuration or environment variables.
+:::
+
+## Grant Integration Access to Notion Databases
+
+For your integration to interact with Notion databases, you need to grant it access to the specific databases it will be working with.
+
+1. Open the Notion database that you want your integration to access.
+2. Click on the "Share" button in the top-right corner of the page.
+3. In the "Invite" section, select your integration from the list.
+4. Click "Invite" to grant the integration access to the database.
+
+:::info
+If your database contains references to other databases, you need to grant the integration access to those referenced databases as well. Repeat step 4 for each referenced database to ensure your integration has the necessary access.
+:::
+
+## Build with Notion Components in Langflow
+
+Once you have set up your Notion integration and granted it access to the required databases, you can start using the Notion components in Langflow.
+
+Langflow provides the following Notion components:
+
+- **Search**: Searches all pages and databases that have been shared with the integration. You can filter results to either pages or databases and specify the sort direction.
+- **List Users**: Retrieves a list of users from the Notion workspace.
+- **List Database Properties**: Retrieves the properties of a specified Notion database.
+- **Create Page**: Creates a new page in a specified Notion database with the provided properties.
+- **Update Page Property**: Updates the properties of an existing Notion page.
+- **Add Content to Page**: Converts markdown text to Notion blocks and appends them to a specified Notion page.
+- **List Pages**: Queries a Notion database with filtering and sorting options.
+- **Page Content Viewer**: Retrieves the content of a Notion page as plain text.
+
+Each of these components output both "Data" and "Tool":
+- The "Data" output can be used directly in your Langflow for further processing or display.
+- The "Tool" output can be utilized in Langflow Agents, allowing them to interact with Notion programmatically.
+
+
+## Additional Resources
+
+- [Notion API Documentation](https://developers.notion.com/docs/getting-started)
+- [Notion Integration Capabilities](https://developers.notion.com/reference/capabilities)
+
+If you encounter any issues or have questions, please reach out to our support team or consult the Langflow community forums.
diff --git a/langflow/docs/docs/Integrations/Notion/notion-agent-conversational.md b/langflow/docs/docs/Integrations/Notion/notion-agent-conversational.md
new file mode 100644
index 0000000..1d5fd16
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Notion/notion-agent-conversational.md
@@ -0,0 +1,144 @@
+---
+title: Notion Conversational Agent
+slug: /integrations/notion/notion-agent-conversational
+---
+
+The Notion Conversational Agent is an AI-powered assistant that interacts with your Notion workspace through natural language conversations. This flow performs Notion-related tasks like creating pages, searching for information, and managing content, all through a chat interface.
+
+## Prerequisites
+
+---
+
+- [Notion App](/integrations/notion/setup)
+- [Notion account and API key](https://www.notion.so/my-integrations)
+- [OpenAI API key](https://platform.openai.com/account/api-keys)
+- [Download Flow Conversation Agent Flow](./Conversational_Notion_Agent.json)(Download link)
+
+
+
+## Flow Components
+
+---
+
+
+### Input and Output
+- **Chat Input**: Accepts user queries and commands
+- **Chat Output**: Displays the agent's responses
+
+### Language Model
+- **OpenAI Model**: Processes user input and generates responses
+
+### Agent and Tools
+- **Tool Calling Agent**: Coordinates the use of various Notion tools based on user input
+- **Toolkit**: Combines multiple Notion-specific tools into a single toolkit
+- **Notion Tools**: Various components for interacting with Notion, including:
+ - List Users
+ - List Database Properties
+ - List Pages
+ - Page Content Viewer
+ - Create Page
+ - Update Page Property
+ - Add Content to Page
+ - Search
+
+### Memory and Prompt
+- **Chat Memory**: Stores conversation history
+- **Prompt**: Provides system instructions and context for the agent
+- **Current Date**: Supplies the current date and time for context
+
+
+## Run the Conversational Notion Agent
+
+---
+
+1. Open Langflow and create a new project.
+2. Add the components listed above to your flow canvas, or Download the [Conversation Agent Flow](./Conversational_Notion_Agent.json)(Download link) and **Import** the JSON file into Langflow.
+3. Connect the components as shown in the flow diagram.
+4. Input the Notion and OpenAI API keys in their respective components.
+5. To start the flow, click the "Play" button in Langflow.
+6. In the chat interface, enter a Notion-related request or question. For example:
+ - "Create a new page in my task database"
+ - "Search for pages about project management"
+ - "List all users in my Notion workspace"
+7. The agent will process your request, use the appropriate Notion tools, and provide a response.
+8. Continue the conversation, asking follow-up questions or giving new commands as needed.
+
+## Example Interactions
+
+---
+
+```
+User: List all the users in my Notion workspace.
+
+Agent: Certainly! I'll use the Notion User List tool to retrieve that information for you. One moment please.
+
+[Agent uses the Notion User List tool]
+
+Here's a list of users in your Notion workspace:
+
+1. John Doe (john@example.com)
+2. Jane Smith (jane@example.com)
+3. Alex Johnson (alex@example.com)
+
+Is there anything specific you'd like to know about these users or any other Notion-related task I can help you with?
+
+User: Create a new page in my Projects database with the title "Website Redesign".
+
+Agent: Certainly! I'd be happy to create a new page in your Projects database with the title "Website Redesign". I'll use the Notion Page Creator tool to do this. First, let me check the properties of your Projects database to ensure we include all necessary information.
+
+[Agent uses the Notion Database Properties tool]
+
+I've retrieved the properties for your Projects database. Now, I'll create the new page with the title "Website Redesign". One moment please.
+
+[Agent uses the Notion Page Creator tool]
+
+Great news! I've successfully created a new page in your Projects database with the title "Website Redesign". Here are the details:
+
+- Title: Website Redesign
+- Page ID: [page_id_here]
+- URL: [page_url_here]
+
+Is there any additional information you'd like me to add to this new project page, such as a description, due date, or assigned team members?
+
+User: Yes, please add a description: "Redesign company website to improve user experience and modernize the look."
+
+Agent: Certainly! I'd be happy to add that description to the "Website Redesign" project page. I'll use the Add Content to Page tool to update the page with this information.
+
+[Agent uses the Add Content to Page tool]
+
+I've successfully added the description to your "Website Redesign" project page. Here's a summary of what I've done:
+
+1. Located the "Website Redesign" page using the page ID from our previous interaction.
+2. Added the following content to the page:
+
+ Description: Redesign company website to improve user experience and modernize the look.
+
+The description has been added as a new text block on the page. Is there anything else you'd like me to add or modify on this project page?
+
+```
+
+
+## Customization
+
+---
+
+The flow can be customized to meet your team's specific needs.
+
+Customize this flow by:
+
+1. Adjusting the system prompt to change the agent's behavior or knowledge base.
+2. Adding or removing Notion tools based on your specific needs.
+3. Modifying the OpenAI model parameters (e.g., temperature) to adjust the agent's response style.
+
+## Troubleshooting
+
+---
+
+If you encounter issues:
+
+1. Ensure all API keys are correctly set and have the necessary permissions.
+2. Check that your Notion integration has access to the relevant pages and databases.
+3. Verify that all components are properly connected in the flow.
+4. Review the Langflow logs for any error messages.
+
+For more advanced usage and integration options, refer to the [Notion API documentation](https://developers.notion.com/) and [Langflow documentation](/).
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Notion/notion-agent-meeting-notes.md b/langflow/docs/docs/Integrations/Notion/notion-agent-meeting-notes.md
new file mode 100644
index 0000000..dfd1cde
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Notion/notion-agent-meeting-notes.md
@@ -0,0 +1,175 @@
+---
+title: Notion Meeting Notes Agent
+slug: /integrations/notion/notion-agent-meeting-notes
+---
+
+The Notion Agent for Meeting Notes is an AI-powered tool that automatically processes meeting transcripts and updates your Notion workspace. It identifies tasks, action items, and key points from your meetings, then creates new tasks or updates existing ones in Notion without manual input.
+
+## Prerequisites
+---
+
+- [Notion App](/integrations/notion/setup)
+- [Notion API key](https://www.notion.so/my-integrations)
+- [OpenAI API key](https://platform.openai.com/account/api-keys)
+- [Download Flow Meeting Agent Flow](./Meeting_Notes_Agent.json)(Download link)
+
+:::warning
+
+Before using this flow, ensure you have obtained the necessary API keys from Notion and OpenAI. These keys are essential for the flow to function properly. Keep them secure and do not share them publicly.
+
+:::
+
+## Components
+
+---
+
+
+
+
+
+### Meeting Transcript (Text Input)
+
+This component allows users to input the meeting transcript directly into the flow.
+
+### List Users (Notion Component)
+
+- **Purpose**: Retrieves a list of users from the Notion workspace.
+- **Input**: Notion Secret (API key)
+- **Output**: List of user data
+
+### List Databases (Notion Component)
+
+- **Purpose**: Searches and lists all databases in the Notion workspace.
+- **Input**:
+ - Notion Secret (API key)
+ - Query (optional)
+ - Filter Type (default: database)
+ - Sort Direction
+- **Output**: List of database data
+
+### Prompt
+
+This component creates a dynamic prompt template using the following inputs:
+- Meeting Transcript
+- List of Users
+- List of Databases
+- Current Date
+
+### Meeting Summarizer (Tool Calling Agent)
+
+- **Purpose**: Analyzes the meeting transcript and identifies tasks and action items.
+- **Inputs**:
+ - System Prompt (from the Prompt component)
+ - Language Model (OpenAI)
+ - Tools:
+ - Notion Search
+ - List Database Properties
+ - Create Page
+ - Update Page Property
+ - Add Content to Page
+
+
+
+### Notion Agent (Tool Calling Agent)
+
+- **Purpose**: Executes actions in Notion based on the meeting summary.
+- **Inputs**:
+ - System Prompt (from the second Prompt component)
+ - Language Model (OpenAI)
+ - Tools:
+ - List Database Properties
+ - Create Page
+ - Update Page Property
+ - Add Content to Page
+
+### Notion Components (Tools)
+
+#### List Database Properties
+
+- **Purpose**: Retrieves the properties of a specified Notion database.
+- **Input**:
+ - Database ID
+ - Notion Secret (API key)
+
+#### Create Page
+
+- **Purpose**: Creates a new page in a Notion database.
+- **Inputs**:
+ - Database ID
+ - Notion Secret (API key)
+ - Properties (JSON)
+
+#### Update Page Property
+
+- **Purpose**: Updates the properties of an existing Notion page.
+- **Inputs**:
+ - Page ID
+ - Notion Secret (API key)
+ - Properties to update
+
+#### Add Content to Page
+
+- **Purpose**: Converts markdown text to Notion blocks and appends them to a specified Notion page.
+- **Inputs**:
+ - Page/Block ID
+ - Notion Secret (API key)
+ - Markdown text
+
+### Chat Output
+
+Displays the final output of the Notion Agent in the Playground.
+
+## Flow Process
+
+---
+
+1. The user inputs a meeting transcript.
+2. The flow retrieves the list of Notion users and databases.
+3. A prompt is generated using the transcript, user list, database list, and current date.
+4. The Meeting Summarizer analyzes the transcript and identifies tasks and action items.
+5. The Notion Agent uses the meeting summary to:
+ - Create new pages for new tasks
+ - Update existing pages for existing tasks
+ - Add content to pages with meeting notes
+6. The Chat Output displays a summary of actions taken in Notion.
+
+## Run the Notion Meeting Notes flow
+
+---
+
+To run the Notion Agent for Meeting Notes:
+
+1. Open Langflow and create a new project.
+2. Add the components listed above to your flow canvas, or download the [Flow Meeting Agent Flow](./Meeting_Notes_Agent.json)(Download link) and **Import** the JSON file into Langflow.
+3. Connect the components as shown in the flow diagram.
+4. Input the Notion and OpenAI API keys in their respective components.
+5. Paste your meeting transcript into the Meeting Transcript component.
+6. Run the flow by clicking **Play** on the **Chat Output** component.
+7. Review the output in the Chat Output component, which will summarize the actions taken in your Notion workspace.
+
+For optimal results, use detailed meeting transcripts. The quality of the output depends on the comprehensiveness of the input provided.
+
+## Customization
+
+---
+
+The flow can be customized to meet your team's specific needs.
+
+Customize this flow by:
+
+1. Adjusting the system prompt to change the agent's behavior or knowledge base.
+2. Adding or removing Notion tools based on your specific needs.
+3. Modifying the OpenAI model parameters (e.g., temperature) to adjust the agent's response style.
+
+## Troubleshooting
+
+---
+
+If you encounter issues:
+
+1. Ensure all API keys are correctly set and have the necessary permissions.
+2. Check that your Notion integration has access to the relevant pages and databases.
+3. Verify that all components are properly connected in the flow.
+4. Review the Langflow logs for any error messages.
+
+For more advanced usage and integration options, refer to the [Notion API documentation](https://developers.notion.com/) and [Langflow documentation](/).
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Notion/notion_conversational_agent_tools.png b/langflow/docs/docs/Integrations/Notion/notion_conversational_agent_tools.png
new file mode 100644
index 0000000..c981e6c
Binary files /dev/null and b/langflow/docs/docs/Integrations/Notion/notion_conversational_agent_tools.png differ
diff --git a/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_1.png b/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_1.png
new file mode 100644
index 0000000..db32f5b
Binary files /dev/null and b/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_1.png differ
diff --git a/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_2.png b/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_2.png
new file mode 100644
index 0000000..387d6f8
Binary files /dev/null and b/langflow/docs/docs/Integrations/Notion/notion_meeting_agent_part_2.png differ
diff --git a/langflow/docs/docs/Integrations/Nvidia/integrations-nvidia-ingest.md b/langflow/docs/docs/Integrations/Nvidia/integrations-nvidia-ingest.md
new file mode 100644
index 0000000..09c6df2
--- /dev/null
+++ b/langflow/docs/docs/Integrations/Nvidia/integrations-nvidia-ingest.md
@@ -0,0 +1,106 @@
+---
+title: Integrate Nvidia Ingest with Langflow
+slug: /integrations-nvidia-ingest
+---
+
+The **NVIDIA Ingest** component integrates with the [NVIDIA nv-ingest](https://github.com/NVIDIA/nv-ingest) microservice for data ingestion, processing, and extraction of text files.
+
+The `nv-ingest` service supports multiple extraction methods for PDF, DOCX, and PPTX file types, and includes pre- and post-processing services like splitting, chunking, and embedding generation.
+
+The **NVIDIA Ingest** component imports the NVIDIA `Ingestor` client, ingests files with requests to the NVIDIA ingest endpoint, and outputs the processed content as a list of [Data](/concepts-objects#data-object) objects. `Ingestor` accepts additional configuration options for data extraction from other text formats. To configure these options, see the [component parameters](/integrations-nvidia-ingest#parameters).
+
+## Prerequisites
+
+* An NVIDIA Ingest endpoint. For more information on setting up an NVIDIA Ingest endpoint, see the [NVIDIA Ingest quickstart](https://github.com/NVIDIA/nv-ingest?tab=readme-ov-file#quickstart).
+
+* The **NVIDIA Ingest** component requires the installation of additional dependencies to your Langflow environment. To install the dependencies in a virtual environment, run the following commands.
+
+ * If you have the Langflow repository cloned and installed from source:
+ ```bash
+ source **YOUR_LANGFLOW_VENV**/bin/activate
+ uv sync --extra nv-ingest
+ uv run langflow run
+ ```
+
+ * If you are installing Langflow from the Python Package Index:
+ ```bash
+ source **YOUR_LANGFLOW_VENV**/bin/activate
+ uv pip install 'langflow[nv-ingest]'
+ uv run langflow run
+ ```
+
+## Use the NVIDIA Ingest component in a flow
+
+The **NVIDIA Ingest** component accepts **Message** inputs and outputs **Data**. The component calls a NVIDIA Ingest microservice's endpoint to ingest a local file and extract the text.
+
+To use the NVIDIA Ingest component in your flow, follow these steps:
+1. In the component library, click the **NVIDIA Ingest** component, and then drag it onto the canvas.
+2. In the **NVIDIA Ingestion URL** field, enter the URL of the NVIDIA Ingest endpoint.
+Optionally, add the endpoint URL as a **Global variable**:
+ 1. Click **Settings**, and then click **Global Variables**.
+ 2. Click **Add New**.
+ 3. Name your variable. Paste your endpoint in the **Value** field.
+ 4. In the **Apply To Fields** field, select the field you want to globally apply this variable to. In this case, select **NVIDIA Ingestion URL**.
+ 5. Click **Save Variable**.
+3. In the **Path** field, enter the path to the file you want to ingest.
+4. Select which text type to extract from the file.
+The component supports text, charts, and tables.
+5. Select whether to split the text into chunks.
+Modify the splitting parameters in the component's **Configuration** tab.
+7. Click **Run** to ingest the file.
+8. To confirm the component is ingesting the file, open the **Logs** pane to view the output of the flow.
+9. To store the processed data in a vector database, add an **AstraDB Vector** component to your flow, and connect the **NVIDIA Ingest** component to the **AstraDB Vector** component with a **Data** output.
+
+
+
+10. Run the flow.
+Inspect your Astra DB vector database to view the processed data.
+
+## NVIDIA Ingest component parameters {#parameters}
+
+The **NVIDIA Ingest** component has the following parameters.
+
+For more information, see the [NV-Ingest documentation](https://nvidia.github.io/nv-ingest/user-guide/).
+
+### Inputs
+
+| Name | Display Name | Info |
+|------|--------------|------|
+| base_url | NVIDIA Ingestion URL | The URL of the NVIDIA Ingestion API. |
+| path | Path | File path to process. |
+| extract_text | Extract Text | Extract text from documents. Default: `True`. |
+| extract_charts | Extract Charts | Extract text from charts. Default: `False`. |
+| extract_tables | Extract Tables | Extract text from tables. Default: `True`. |
+| text_depth | Text Depth | The level at which text is extracted. Support for 'block', 'line', and 'span' varies by document type. Default: `document`. |
+| split_text | Split Text | Split text into smaller chunks. Default: `True`. |
+| split_by | Split By | How to split into chunks. 'size' splits by number of characters. Default: `word`. |
+| split_length | Split Length | The size of each chunk based on the 'split_by' method. Default: `200`. |
+| split_overlap | Split Overlap | The number of segments to overlap from the previous chunk. Default: `20`. |
+| max_character_length | Max Character Length | The maximum number of characters in each chunk. Default: `1000`. |
+| sentence_window_size | Sentence Window Size | The number of sentences to include from previous and following chunks when `split_by=sentence`. Default: `0`. |
+
+### Outputs
+
+The **NVIDIA Ingest** component outputs a list of [Data](/concepts-objects#data-object) objects where each object contains:
+- `text`: The extracted content.
+ - For text documents: The extracted text content.
+ - For tables and charts: The extracted table/chart content.
+- `file_path`: The source file name and path.
+- `document_type`: The type of the document ("text" or "structured").
+- `description`: Additional description of the content.
+
+The output varies based on the `document_type`:
+
+- Documents with `document_type: "text"` contain:
+ - Raw text content extracted from documents, for example, paragraphs from PDFs or DOCX files.
+ - Content stored directly in the `text` field.
+ - Content extracted using the `extract_text` parameter.
+
+- Documents with `document_type: "structured"` contain:
+ - Text extracted from tables and charts and processed to preserve structural information.
+ - Content extracted using the `extract_tables` and `extract_charts` parameters.
+ - Content stored in the `text` field after being processed from the `table_content` metadata.
+
+:::note
+Images are currently not supported and will be skipped during processing.
+:::
\ No newline at end of file
diff --git a/langflow/docs/docs/Integrations/Nvidia/nvidia-component-ingest-astra.png b/langflow/docs/docs/Integrations/Nvidia/nvidia-component-ingest-astra.png
new file mode 100644
index 0000000..1d0af51
Binary files /dev/null and b/langflow/docs/docs/Integrations/Nvidia/nvidia-component-ingest-astra.png differ
diff --git a/langflow/docs/docs/Integrations/assemblyai-components.png b/langflow/docs/docs/Integrations/assemblyai-components.png
new file mode 100644
index 0000000..788feb0
Binary files /dev/null and b/langflow/docs/docs/Integrations/assemblyai-components.png differ
diff --git a/langflow/docs/docs/Integrations/integrations-assemblyai.md b/langflow/docs/docs/Integrations/integrations-assemblyai.md
new file mode 100644
index 0000000..a2609e5
--- /dev/null
+++ b/langflow/docs/docs/Integrations/integrations-assemblyai.md
@@ -0,0 +1,164 @@
+---
+title: AssemblyAI
+slug: /integrations-assemblyai
+---
+
+
+
+# AssemblyAI
+
+The AssemblyAI components allow you to apply powerful Speech AI models to your app for tasks like:
+
+- Transcribing audio and video files
+- Formatting transcripts
+- Generating subtitles
+- Applying LLMs to audio files
+
+More info about AssemblyAI:
+
+- [Website](https://www.assemblyai.com/)
+- [AssemblyAI API Docs](https://www.assemblyai.com/docs)
+- [Get a Free API key](https://www.assemblyai.com/dashboard/signup)
+
+
+## Prerequisites
+
+You need an **AssemblyAI API key**. After creating a free account, you'll find the API key in your dashboard. [Get a Free API key here](https://www.assemblyai.com/dashboard/signup).
+
+Enter the key in the *AssemblyAI API Key* field in all components that require the key.
+
+(Optional): To use LeMUR, you need to upgrade your AssemblyAI account, since this is not included in the free account.
+
+## Components
+
+
+
+### AssemblyAI Start Transcript
+
+This component allows you to submit an audio or video file for transcription.
+
+**Tip**: You can freeze the path of this component to only submit the file once.
+
+- **Input**:
+ - AssemblyAI API Key: Your API key.
+ - Audio File: The audio or video file to transcribe.
+ - Speech Model (Optional): Select the class of models. Default is *Best*. See [speech models](https://www.assemblyai.com/docs/speech-to-text/speech-recognition#select-the-speech-model-with-best-and-nano) for more info.
+ - Automatic Language Detection (Optional): Enable automatic language detection.
+ - Language (Optional): The language of the audio file. Can be set manually if automatic language detection is disabled.
+ See [supported languages](https://www.assemblyai.com/docs/getting-started/supported-languages) for a list of supported language codes.
+ - Enable Speaker Labels (Optional): Detect speakers in an audio file and what each speaker said.
+ - Expected Number of Speakers (Optional): Set the expected number of speakers, if Speaker Labels is enabled.
+ - Audio File URL (Optional): The URL of the audio or video file to transcribe. Can be used instead of *Audio File*.
+ - Punctuate (Optional): Apply punctuation. Default is true.
+ - Format Text (Optional): Apply casing and text formatting. Default is true.
+
+- **Output**:
+ - Transcript ID: The id of the transcript
+
+
+### AssemblyAI Poll Transcript
+
+This components allows you to poll the transcripts. It checks the status of the transcript every few seconds until the transcription is completed.
+
+- **Input**:
+ - AssemblyAI API Key: Your API key.
+ - Polling Interval (Optional): The polling interval in seconds. Default is 3.
+
+- **Output**:
+ - Transcription Result: The AssemblyAI JSON response of a completed transcript. Contains the text and other info.
+
+
+### AssemblyAI Get Subtitles
+
+This component allows you to generate subtitles in SRT or VTT format.
+
+- **Input**:
+ - AssemblyAI API Key: Your API key.
+ - Transcription Result: The output of the *Poll Transcript* component.
+ - Subtitle Format: The format of the captions (SRT or VTT).
+ - Character per Caption (Optional): The maximum number of characters per caption (0 for no limit).
+
+- **Output**:
+ - Subtitles: A JSON response with the `subtitles` field containing the captions in SRT or VTT format.
+
+
+### AssemblyAI LeMUR
+
+This component allows you to apply Large Language Models to spoken data using the [AssemblyAI LeMUR framework](https://www.assemblyai.com/docs/lemur).
+
+LeMUR automatically ingests the transcript as additional context, making it easy to apply LLMs to audio data. You can use it for tasks like summarizing audio, extracting insights, or asking questions.
+
+- **Input**:
+ - AssemblyAI API Key: Your API key.
+ - Transcription Result: The output of the *Poll Transcript* component.
+ - Input Prompt: The text to prompt the model. You can type your prompt in this field or connect it to a *Prompt* component.
+ - Final Model: The model that is used for the final prompt after compression is performed. Default is Claude 3.5 Sonnet.
+ - Temperature (Optional): The temperature to use for the model. Default is 0.0.
+ - Max Output Size (Optional): Max output size in tokens, up to 4000. Default is 2000.
+ - Endpoint (Optional): The LeMUR endpoint to use. Default is "task". For "summary" and "question-answer", no prompt input is needed. See [LeMUR API docs](https://www.assemblyai.com/docs/api-reference/lemur/) for more info.
+ - Questions (Optional): Comma-separated list of your questions. Only used if *Endpoint* is "question-answer".
+ - Transcript IDs (Optional): Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts. If provided, the *Transcription Result* is ignored.
+
+- **Output**:
+ - LeMUR Response: The generated LLM response.
+
+### AssemblyAI List Transcripts
+
+This component can be used as a standalone component to list all previously generated transcripts.
+
+- **Input**:
+ - AssemblyAI API Key: Your API key.
+ - Limit (Optional): Maximum number of transcripts to retrieve. Default is 20, use 0 for all.
+ - Filter (Optional): Filter by transcript status.
+ - Created On (Optional): Only get transcripts created on this date (YYYY-MM-DD).
+ - Throttled Only (Optional): Only get throttled transcripts, overrides the status filter
+
+- **Output**:
+ - Transcript List: A list of all transcripts with info such as the transcript ID, the status, and the data.
+
+
+## Flow Process
+
+1. The user inputs an audio or video file.
+2. The user can also input an LLM prompt. In this example, we want to generate a summary of the transcript.
+3. The flow submits the audio file for transcription.
+4. The flow checks the status of the transcript every few seconds until transcription is completed.
+5. The flow parses the transcription result and outputs the transcribed text.
+6. The flow also generates subtitles.
+7. The flow applies the LLM prompt to generate a summary.
+8. As a standalone component, all transcripts can be listed.
+
+## Run the Transcription and Speech AI Flow
+
+To run the Transcription and Speech AI Flow:
+
+1. Open Langflow and create a new project.
+2. Add the components listed above to your flow canvas, or download the [AssemblyAI Transcription and Speech AI Flow](./AssemblyAI_Flow.json)(Download link) and **Import** the JSON file into Langflow.
+3. Connect the components as shown in the flow diagram. **Tip**: Freeze the path of the *Start Transcript* component to only submit the file once.
+4. Input the AssemblyAI API key in in all components that require the key (Start Transcript, Poll Transcript, Get Subtitles, LeMUR, List Transcripts).
+5. Select an audio or video file in the *Start Transcript* component.
+6. Run the flow by clicking **Play** on the *Parse Data* component. Make sure that the specified template is `{text}`.
+7. To generate subtitles, click **Play** on the *Get Subtitles* component.
+8. To apply an LLM to your audio file, click **Play** on the *LeMUR* component. Note that you need an upgraded AssemblyAI account to use LeMUR.
+9. To list all transcripts, click **Play** on the *List Transcript* component.
+
+
+## Customization
+
+The flow can be customized by:
+
+1. Modifying the parameters in the *Start Transcript* component.
+2. Modifying the subtitle format in the *Get Subtitles* component.
+3. Modifying the LLM prompt for input of the *LeMUR* component.
+4. Modifying the LLM parameters (e.g., temperature) in the *LeMUR* component.
+
+## Troubleshooting
+
+If you encounter issues:
+
+1. Ensure the API key is correctly set in all components that require the key.
+2. To use LeMUR, you need to upgrade your AssemblyAI account, since this is not included in the free account.
+3. Verify that all components are properly connected in the flow.
+4. Review the Langflow logs for any error messages.
+
+For more advanced usage, refer to the [AssemblyAI API documentation](https://www.assemblyai.com/docs/). If you need more help, you can reach out to the [AssemblyAI support](https://www.assemblyai.com/contact/support).
diff --git a/langflow/docs/docs/Integrations/integrations-langfuse.md b/langflow/docs/docs/Integrations/integrations-langfuse.md
new file mode 100644
index 0000000..6b19748
--- /dev/null
+++ b/langflow/docs/docs/Integrations/integrations-langfuse.md
@@ -0,0 +1,77 @@
+---
+title: Langfuse
+slug: /integrations-langfuse
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Integrate Langfuse with Langflow
+
+[Langfuse](https://langfuse.com/) is an observability and analytics platform specifically designed for language models and AI applications.
+
+This guide walks you through how to configure Langflow to collect [tracing](https://langfuse.com/docs/tracing) data about your flow executions and automatically send the data to Langfuse.
+
+## Prerequisites
+
+- A project in Langflow with a runnable flow
+- A Langfuse Cloud account in any [data region](https://langfuse.com/faq/all/cloud-data-regions)
+- A Langfuse organization and project
+
+## Create Langfuse project credentials
+
+1. In Langfuse, go to your project settings, and then create a new set of API keys.
+
+2. Copy the following API key information:
+
+ - Secret Key
+ - Public Key
+ - Host URL
+
+## Set your Langfuse credentials as environment variables
+
+Set your Langfuse project credentials as environment variables in the same environment where you run Langflow.
+
+You can use any method you prefer to set environment variables.
+The following examples show how to set environment variables in a terminal session (Linux or macOS) and in a command prompt session (Windows):
+
+
+
+
+```
+export LANGFUSE_SECRET_KEY=SECRET_KEY
+export LANGFUSE_PUBLIC_KEY=PUBLIC_KEY
+export LANGFUSE_HOST=HOST_URL
+```
+
+
+
+```
+set LANGFUSE_SECRET_KEY=SECRET_KEY
+set LANGFUSE_PUBLIC_KEY=PUBLIC_KEY
+set LANGFUSE_HOST=HOST_URL
+```
+
+
+
+
+Replace `SECRET_KEY`, `PUBLIC_KEY`, and `HOST_URL` with the API key information you copied from Langfuse.
+
+## Start Langflow and run a flow
+
+1. Start Langflow in the same terminal or environment where you set the environment variables:
+
+ ```bash
+ python -m langflow run
+ ```
+
+2. In Langflow, open and existing project, and then run a flow.
+
+## View tracing data in Langfuse
+
+Langflow automatically collects and sends tracing data about the flow execution to Langfuse.
+You can view the collected data in your Langfuse project dashboard.
+
+## Disable the Langfuse integration
+
+To disable the Langfuse integration, remove the environment variables you set in the previous steps and restart Langflow.
diff --git a/langflow/docs/docs/Integrations/integrations-langsmith.md b/langflow/docs/docs/Integrations/integrations-langsmith.md
new file mode 100644
index 0000000..b656624
--- /dev/null
+++ b/langflow/docs/docs/Integrations/integrations-langsmith.md
@@ -0,0 +1,32 @@
+---
+title: LangSmith
+slug: /integrations-langsmith
+---
+
+
+
+LangSmith is a full-lifecycle DevOps service from LangChain that provides monitoring and observability. To integrate with Langflow, just add your LangChain API key as a Langflow environment variable and you are good to go!
+
+
+## Step-by-step Configuration {#b912579a43984f9a92921232b67c885d}
+
+
+---
+
+1. Obtain your LangChain API key from [https://smith.langchain.com](https://smith.langchain.com/)
+2. Add the following keys to Langflow .env file:
+
+`LANGCHAIN_API_KEY="your-api-key"LANGCHAIN_PROJECT="your-project-name"`
+
+
+or export the environment variables in your terminal:
+
+
+`export LANGCHAIN_API_KEY="your-api-key"export LANGCHAIN_PROJECT="your-project-name"`
+
+3. Restart Langflow using `langflow run --env-file .env`
+4. Run a project in Langflow.
+5. View the Langsmith dashboard for monitoring and observability.
+
+
+
diff --git a/langflow/docs/docs/Integrations/integrations-langwatch.md b/langflow/docs/docs/Integrations/integrations-langwatch.md
new file mode 100644
index 0000000..85a735e
--- /dev/null
+++ b/langflow/docs/docs/Integrations/integrations-langwatch.md
@@ -0,0 +1,39 @@
+---
+title: LangWatch
+slug: /integrations-langwatch
+---
+
+
+
+# LangWatch {#938674091aac4d9d9aa4aa6eb5c215b4}
+
+
+LangWatch is an all-in-one LLMOps platform for monitoring, observability, analytics, evaluations and alerting for getting user insights and improve your LLM workflows.
+
+
+To integrate with Langflow, just add your LangWatch API as a Langflow environment variable and you are good to go!
+
+
+## Step-by-step Configuration {#6f1d56ff6063417491d100d522dfcf1a}
+
+1. Obtain your LangWatch API key from [https://app.langwatch.ai/](https://app.langwatch.ai/)
+2. Add the following key to Langflow .env file:
+
+```shell
+LANGWATCH_API_KEY="your-api-key"
+```
+
+
+or export it in your terminal:
+
+
+```shell
+export LANGWATCH_API_KEY="your-api-key"
+```
+
+3. Restart Langflow using `langflow run --env-file .env`
+4. Run a project in Langflow.
+5. View the LangWatch dashboard for monitoring and observability.
+
+
+
diff --git a/langflow/docs/docs/Starter-Projects/starter-projects-basic-prompting.md b/langflow/docs/docs/Starter-Projects/starter-projects-basic-prompting.md
new file mode 100644
index 0000000..154f42d
--- /dev/null
+++ b/langflow/docs/docs/Starter-Projects/starter-projects-basic-prompting.md
@@ -0,0 +1,56 @@
+---
+title: Basic prompting
+slug: /starter-projects-basic-prompting
+---
+
+import Icon from "@site/src/components/icon";
+
+Prompts serve as the inputs to a large language model (LLM), acting as the interface between human instructions and computational tasks.
+
+By submitting natural language requests in a prompt to an LLM, you can obtain answers, generate text, and solve problems.
+
+This article demonstrates how to use Langflow's prompt tools to issue basic prompts to an LLM, and how various prompting strategies can affect your outcomes.
+
+
+## Prerequisites
+
+- [Langflow installed and running](/get-started-installation)
+- [OpenAI API key created](https://platform.openai.com/)
+
+## Create the basic prompting flow
+
+1. From the Langflow dashboard, click **New Flow**.
+
+2. Select **Basic Prompting**.
+
+3. The **Basic Prompting** flow is created.
+
+
+
+
+
+This flow allows you to chat with the **OpenAI model** component.
+The model will respond according to the prompt constructed in the **Prompt** component.
+
+4. To examine the **Template**, in the **Prompt** component, click the **Template** field.
+
+```plain
+Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh.
+```
+
+5. To create an environment variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
+
+ 1. In the **Variable Name** field, enter `openai_api_key`.
+ 2. In the **Value** field, paste your OpenAI API Key (`sk-...`).
+ 3. Click **Save Variable**.
+
+## Run the basic prompting flow
+
+1. Click the **Playground** button.
+2. Type a message and press Enter. The bot should respond in a markedly piratical manner!
+
+## Modify the prompt for a different result
+
+1. To modify your prompt results, in the **Prompt** component, click the **Template** field. The **Edit Prompt** window opens.
+2. Change the existing prompt to a different character, perhaps `Answer the user as if you were Hermione Granger.`
+3. Run the workflow again and notice how the prompt changes the model's response.
diff --git a/langflow/docs/docs/Starter-Projects/starter-projects-simple-agent.md b/langflow/docs/docs/Starter-Projects/starter-projects-simple-agent.md
new file mode 100644
index 0000000..e786507
--- /dev/null
+++ b/langflow/docs/docs/Starter-Projects/starter-projects-simple-agent.md
@@ -0,0 +1,66 @@
+---
+title: Simple agent
+slug: /starter-projects-simple-agent
+---
+
+Build a **Simple Agent** flow for an agentic application using the **Tool-calling agent** component.
+
+An **agent** uses an LLM as its "brain" to select among the connected tools and complete its tasks.
+
+In this flow, the **Tool-calling agent** reasons using an **Open AI** LLM.
+The agent selects the **Calculator** tool for simple math problems and the **URL** tool to search a URL for content.
+
+## Prerequisites
+
+To use this flow, you need an OpenAI API key.
+
+## Open Langflow and start a new flow
+
+Click **New Flow**, and then select the **Simple Agent** flow.
+
+This opens a starter flow with the necessary components to run an agentic application using the Tool-calling agent.
+
+## Simple Agent flow
+
+
+
+The **Simple Agent** flow consists of these components:
+
+* The **Tool calling agent** component uses the connected LLM to reason through the user's input and select among the connected tools to complete its task.
+* The **URL** tool component searches a list of URLs for content.
+* The **Calculator** component performs basic arithmetic operations.
+* The **Chat Input** component accepts user input to the chat.
+* The **Prompt** component combines the user input with a user-defined prompt.
+* The **Chat Output** component prints the flow's output to the chat.
+* The **OpenAI** model component sends the user input and prompt to the OpenAI API and receives a response.
+
+## Run the Simple Agent flow
+
+1. Add your credentials to the Open AI component.
+2. Click **Playground** to start a chat session.
+3. To confirm the tools are connected, ask the agent, `What tools are available to you?`
+The response is similar to the following:
+```plain
+I have access to the following tools:
+Calculator: Perform basic arithmetic operations.
+fetch_content: Load and retrieve data from specified URLs.
+fetch_content_text: Load and retrieve text data from specified URLs.
+as_dataframe: Load and retrieve data in a structured format (dataframe) from specified URLs.
+get_current_date: Returns the current date and time in a selected timezone.
+```
+4. Ask the agent a question. For example, ask it to create a tabletop character using your favorite rules set.
+The agent will tell you when it's using the `URL-fetch_content_text` tool to search for rules information, and when it's using `CalculatorComponent-evaluate_expression` to generate attributes with dice rolls.
+The final output should be similar to this:
+
+```plain
+Final Attributes
+Strength (STR): 10
+Constitution (CON): 12
+Size (SIZ): 14
+Dexterity (DEX): 9
+Intelligence (INT): 11
+Power (POW): 13
+Charisma (CHA): 8
+```
+
+Now that your query has completed the journey from **Chat input** to **Chat output**, you have completed the **Simple Agent** flow.
diff --git a/langflow/docs/docs/Starter-Projects/starter-projects-vector-store-rag.md b/langflow/docs/docs/Starter-Projects/starter-projects-vector-store-rag.md
new file mode 100644
index 0000000..a833bed
--- /dev/null
+++ b/langflow/docs/docs/Starter-Projects/starter-projects-vector-store-rag.md
@@ -0,0 +1,89 @@
+---
+title: Vector store RAG
+slug: /starter-projects-vector-store-rag
+---
+
+import Icon from "@site/src/components/icon";
+
+Retrieval Augmented Generation, or RAG, is a pattern for training LLMs on your data and querying it.
+
+
+RAG is backed by a **vector store**, a vector database which stores embeddings of the ingested data.
+
+
+This enables **vector search**, a more powerful and context-aware search.
+
+
+We've chosen [Astra DB](https://astra.datastax.com/signup?utm_source=langflow-pre-release&utm_medium=referral&utm_campaign=langflow-announcement&utm_content=create-a-free-astra-db-account) as the vector database for this starter flow, but you can follow along with any of Langflow's vector database options.
+
+
+## Prerequisites
+
+* [An OpenAI API key](https://platform.openai.com/)
+* [An Astra DB vector database](https://docs.datastax.com/en/astra-db-serverless/get-started/quickstart.html) with the following:
+ * An Astra DB application token scoped to read and write to the database
+ * A collection created in [Astra](https://docs.datastax.com/en/astra-db-serverless/databases/manage-collections.html#create-collection) or a new collection created in the **Astra DB** component
+
+
+## Open Langflow and start a new project
+
+1. From the Langflow dashboard, click **New Flow**.
+2. Select **Vector Store RAG**.
+3. The **Vector Store RAG** flow is created.
+
+## Build the vector RAG flow
+
+The vector store RAG flow is built of two separate flows for ingestion and query.
+
+
+
+The **Load Data Flow** (bottom of the screen) creates a searchable index to be queried for contextual similarity.
+This flow populates the vector store with data from a local file.
+It ingests data from a local file, splits it into chunks, indexes it in Astra DB, and computes embeddings for the chunks using the OpenAI embeddings model.
+
+The **Retriever Flow** (top of the screen) embeds the user's queries into vectors, which are compared to the vector store data from the **Load Data Flow** for contextual similarity.
+
+- **Chat Input** receives user input from the **Playground**.
+- **OpenAI Embeddings** converts the user query into vector form.
+- **Astra DB** performs similarity search using the query vector.
+- **Parse Data** processes the retrieved chunks.
+- **Prompt** combines the user query with relevant context.
+- **OpenAI** generates the response using the prompt.
+- **Chat Output** returns the response to the **Playground**.
+
+1. Configure the **OpenAI** model component.
+ 1. To create a global variable for the **OpenAI** component, in the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
+ 2. In the **Variable Name** field, enter `openai_api_key`.
+ 3. In the **Value** field, paste your OpenAI API Key (`sk-...`).
+ 4. Click **Save Variable**.
+2. Configure the **Astra DB** component.
+ 1. In the **Astra DB Application Token** field, add your **Astra DB** application token.
+ The component connects to your database and populates the menus with existing databases and collections.
+ 2. Select your **Database**.
+ If you don't have a collection, select **New database**.
+ Complete the **Name**, **Cloud provider**, and **Region** fields, and then click **Create**. **Database creation takes a few minutes**.
+ 3. Select your **Collection**. Collections are created in your [Astra DB deployment](https://astra.datastax.com) for storing vector data.
+ :::info
+ If you select a collection embedded with Nvidia through Astra's vectorize service, the **Embedding Model** port is removed, because you have already generated embeddings for this collection with the Nvidia `NV-Embed-QA` model. The component fetches the data from the collection, and uses the same embeddings for queries.
+ :::
+
+3. If you don't have a collection, create a new one within the component.
+ 1. Select **New collection**.
+ 2. Complete the **Name**, **Embedding generation method**, **Embedding model**, and **Dimensions** fields, and then click **Create**.
+
+ Your choice for the **Embedding generation method** and **Embedding model** depends on whether you want to use embeddings generated by a provider through Astra's vectorize service, or generated by a component in Langflow.
+
+ * To use embeddings generated by a provider through Astra's vectorize service, select the model from the **Embedding generation method** dropdown menu, and then select the model from the **Embedding model** dropdown menu.
+ * To use embeddings generated by a component in Langflow, select **Bring your own** for both the **Embedding generation method** and **Embedding model** fields. In this starter project, the option for the embeddings method and model is the **OpenAI Embeddings** component connected to the **Astra DB** component.
+ * The **Dimensions** value must match the dimensions of your collection. This field is **not required** if you use embeddings generated through Astra's vectorize service. You can find this value in the **Collection** in your [Astra DB deployment](https://astra.datastax.com).
+
+ For more information, see the [DataStax Astra DB Serverless documentation](https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html).
+
+
+If you used Langflow's **Global Variables** feature, the RAG application flow components are already configured with the necessary credentials.
+
+## Run the Vector Store RAG flow
+
+1. Click the **Playground** button. Here you can chat with the AI that uses context from the database you created.
+2. Type a message and press Enter. (Try something like "What topics do you know about?")
+3. The bot will respond with a summary of the data you've embedded.
diff --git a/langflow/docs/docs/Tutorials/tutorials-blog-writer.md b/langflow/docs/docs/Tutorials/tutorials-blog-writer.md
new file mode 100644
index 0000000..3485b59
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-blog-writer.md
@@ -0,0 +1,58 @@
+---
+title: Blog writer
+slug: /tutorials-blog-writer
+---
+
+Build a Blog Writer flow for a one-shot application using OpenAI.
+
+This flow extends the Basic Prompting flow with the **URL** and **Parse data** components that fetch content from multiple URLs and convert the loaded data into plain text.
+
+OpenAI uses this loaded data to generate a blog post, as instructed by the **Text input** component.
+
+
+## Prerequisites {#899268e6c12c49b59215373a38287507}
+
+
+---
+
+- [Langflow installed and running](/get-started-installation)
+- [OpenAI API key created](https://platform.openai.com/)
+
+
+## Create the blog writer flow {#0c1a9c65b7d640f693ec3aad963416ff}
+
+1. From the Langflow dashboard, click **New Flow**.
+2. Select **Blog Writer**.
+3. The **Blog Writer** flow is created.
+
+
+
+
+This flow creates a one-shot article generator with **Prompt**, **OpenAI**, and **Chat Output** components, augmented with reference content and instructions from the **URL** and **Text Input** components.
+
+The **URL** component extracts raw text and metadata from one or more web links.
+The **Parse Data** component converts the data coming from the **URL** component into plain text to feed the prompt.
+
+To examine the flow's prompt, click the **Template** field of the **Prompt** component.
+
+```plain
+Reference 1:
+
+{references}
+
+---
+
+{instructions}
+
+Blog:
+```
+
+The `{instructions}` value is received from the **Text input** component, and one or more `{references}` are received from a list of URLs parsed from the **URL** component.
+
+
+### Run the blog writer flow {#b93be7a567f5400293693b31b8d0f81a}
+
+1. Click the **Playground** button. Here you can chat with the AI that has access to the **URL** content.
+2. Click the **Lighting Bolt** icon to run it.
+3. To write about something different, change the values in the **URL** component and adjust the instructions on the left side bar of the **Playground**. Try again and see what the LLM constructs.
+
diff --git a/langflow/docs/docs/Tutorials/tutorials-document-qa.md b/langflow/docs/docs/Tutorials/tutorials-document-qa.md
new file mode 100644
index 0000000..614ecef
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-document-qa.md
@@ -0,0 +1,41 @@
+---
+title: Document QA
+slug: /tutorials-document-qa
+---
+
+
+
+Build a question-and-answer chatbot with a document loaded from local memory.
+
+
+## Prerequisites {#6555c100a30e4a21954af25e2e05403a}
+
+
+---
+
+- [Langflow installed and running](/get-started-installation)
+- [OpenAI API key created](https://platform.openai.com/)
+
+
+## Create the document QA flow {#204500104f024553aab2b633bb99f603}
+
+1. From the Langflow dashboard, click **New Flow**.
+2. Select **Document QA**.
+3. The **Document QA** flow is created.
+
+
+
+
+This flow is composed of a standard chatbot with the **Chat Input**, **Prompt**, **OpenAI**, and **Chat Output** components, but it also incorporates a **File** component, which loads a file from your local machine. **Parse Data** is used to convert the data from **File** into the **Prompt** component as `{Document}`. The **Prompt** component is instructed to answer questions based on the contents of `{Document}`. This gives the **OpenAI** component context it would not otherwise have access to.
+
+
+### Run the document QA flow {#f58fcc2b9e594156a829b1772b6a7191}
+
+
+1. To select a document to load, in the **File** component, click the **Path** field. Select a local file, and then click **Open**. The file name appears in the field.
+
+2. Click the **Playground** button. Here you can chat with the AI that has access to your document's content.
+
+
+3. Type in a question about the document content and press Enter. You should see a contextual response.
+
diff --git a/langflow/docs/docs/Tutorials/tutorials-math-agent.md b/langflow/docs/docs/Tutorials/tutorials-math-agent.md
new file mode 100644
index 0000000..857df25
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-math-agent.md
@@ -0,0 +1,56 @@
+---
+title: Math agent
+slug: /tutorials-math-agent
+---
+
+import Icon from "@site/src/components/icon";
+
+Build a **Math Agent** flow for an agentic application using the **Tool-calling agent** component.
+
+In this flow, the **Tool-calling agent** reasons using an **Open AI** LLM to solve math problems.
+It selects the **Calculator** tool for simpler math and the **Python REPL** tool (with the Python `math` library) for more complex problems.
+
+## Prerequisites
+
+To use this flow, you need an OpenAI API key.
+
+## Open Langflow and start a new flow
+
+Click **New Flow**, and then select the **Math Agent** flow.
+
+This opens a starter flow with the necessary components to run an agentic application using the Tool-calling agent.
+
+## Math Agent flow
+
+
+
+The **Math Agent** flow consists of these components:
+
+* The **Tool calling agent** component uses the connected LLM to reason through the user's input and select among the connected tools to complete its task.
+* The **Python REPL tool** component executes Python code in a REPL (Read-Evaluate-Print Loop) interpreter.
+* The **Calculator** component performs basic arithmetic operations.
+* The **Chat Input** component accepts user input to the chat.
+* The **Prompt** component combines the user input with a user-defined prompt.
+* The **Chat Output** component prints the flow's output to the chat.
+* The **OpenAI** model component sends the user input and prompt to the OpenAI API and receives a response.
+
+## Run the Math Agent flow
+
+1. Add your credentials to the Open AI component.
+2. Click **Playground** to start a chat session.
+3. Enter a simple math problem, like `2 + 2`, and then make sure the bot responds with the correct answer.
+4. To confirm the REPL interpreter is working, prompt the `math` library directly with `math.sqrt(4)` and see if the bot responds with `4`.
+5. The agent will also reason through more complex word problems. For example, prompt the agent with the following math problem:
+
+```plain
+The equation 24x2+25x−47ax−2=−8x−3−53ax−2 is true for all values of x≠2a, where a is a constant.
+What is the value of a?
+A) -16
+B) -3
+C) 3
+D) 16
+```
+
+The agent should respond with `B`.
+
+Now that your query has completed the journey from **Chat input** to **Chat output**, you have completed the **Math Agent** flow.
diff --git a/langflow/docs/docs/Tutorials/tutorials-memory-chatbot.md b/langflow/docs/docs/Tutorials/tutorials-memory-chatbot.md
new file mode 100644
index 0000000..ff0b87d
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-memory-chatbot.md
@@ -0,0 +1,73 @@
+---
+title: Memory chatbot
+slug: /tutorials-memory-chatbot
+---
+
+import Icon from "@site/src/components/icon";
+
+This flow extends the [basic prompting flow](/starter-projects-basic-prompting) with a **Chat memory** component that stores up to 100 previous chat messages and uses them to provide context for the current conversation.
+
+## Prerequisites
+
+- [Langflow installed and running](/get-started-installation)
+- [OpenAI API key created](https://platform.openai.com/)
+
+## Create the memory chatbot flow
+
+1. From the Langflow dashboard, click **New Flow**.
+2. Select **Memory Chatbot**.
+3. The **Memory Chatbot** flow is created.
+
+
+
+This flow adds a **Chat Memory** component to the Basic Prompting flow.
+This component retrieves previous messages and sends them to the **Prompt** component to fill a part of the **Template** with context.
+
+To examine the template, click the **Template** field in the **Prompt** component.
+The **Prompt** tells the **OpenAI model** component how to respond to input.
+
+```plain
+You are a helpful assistant that answers questions.
+
+Use markdown to format your answer, properly embedding images and urls.
+
+History:
+
+{memory}
+```
+
+The `{memory}` code in the prompt creates a new input port in the component called **memory**.
+The **Chat Memory** component is connected to this port to store chat messages from the **Playground**.
+
+This gives the **OpenAI** component a memory of previous chat messages.
+
+## Run the memory chatbot flow
+
+1. Open the **Playground**.
+2. Type multiple questions. For example, try entering this conversation:
+
+```plain
+Hi, my name is Luca.
+Please tell me about PostgreSQL.
+What is my name?
+What is the second subject I asked you about?
+```
+
+The chatbot remembers your name and previous questions.
+
+3. To view the **Message Logs** pane, click , and then click **Message Logs**.
+The **Message Logs** pane displays all previous messages, with each conversation sorted by `session_id`.
+
+
+
+## Use Session ID with the memory chatbot flow
+
+`session_id` is a unique identifier in Langflow that stores conversation sessions between the AI and a user. A `session_id` is created when a conversation is initiated, and then associated with all subsequent messages during that session.
+
+In the **Memory Chatbot** flow you created, the **Chat Memory** component references past interactions by **Session ID**. You can demonstrate this by modifying the **Session ID** value to switch between conversation histories.
+
+1. In the **Session ID** field of the **Chat Memory** and **Chat Input** components, add a **Session ID** value like `MySessionID`.
+2. Now, once you send a new message the **Playground**, you should have a new memory created in the **Message Logs** pane.
+3. Notice how your conversation is being stored in different memory sessions.
+
+Learn more about chat memories in the [Memory](/components-memories) section.
diff --git a/langflow/docs/docs/Tutorials/tutorials-sequential-agent.md b/langflow/docs/docs/Tutorials/tutorials-sequential-agent.md
new file mode 100644
index 0000000..24b092a
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-sequential-agent.md
@@ -0,0 +1,52 @@
+---
+title: Sequential tasks agent
+slug: /tutorials-sequential-agent
+---
+
+Build a **Sequential Tasks Agent** flow for a multi-agent application using multiple **Agent** components.
+
+Each agent has an LLM model and a unique set of tools at its disposal, with **Prompt** components connected to the **Agent Instructions** fields to control the agent's behavior. For example, the **Researcher Agent** has a **Tavily AI Search** component connected as a tool. The **Prompt** instructs the agent how to answer your query, format the response, and pass the query and research results on to the next agent in the flow.
+
+Each successive agent in the flow builds on the work of the previous agent, creating a chain of reasoning for solving complex problems.
+
+## Prerequisites
+- [An OpenAI API key](https://platform.openai.com/)
+- [A Tavily AI API key](https://www.tavily.com/)
+
+## Open Langflow and create a new flow
+
+1. Click **New Flow**, and then select **Sequential Tasks Agent**.
+This opens a starter template with the necessary components to run the flow.
+
+
+
+The Sequential Tasks Agent flow consists of these components:
+
+* The **Agent** components use the connected LLM to analyze the user's input and select among the connected tools to complete the tasks.
+* The **Chat Input** component accepts user input to the chat.
+* The **Prompt** component combines the user input with a user-defined prompt.
+* The **Chat Output** component prints the flow's output to the chat.
+* The **YFinance** tool component provides access to financial data from Yahoo Finance.
+* The **Tavily AI Search** tool component performs AI-powered web searches.
+* The **Calculator** tool component performs mathematical calculations.
+
+## Run the Sequential Tasks Agent flow
+
+1. Add your OpenAI API key to the **Agent** components.
+2. Add your Tavily API key to the **Tavily** component.
+3. Click **Playground** to start a chat session with the template's default question.
+
+```plain
+Should I invest in Tesla (TSLA) stock right now?
+Please analyze the company's current position, market trends,
+financial health, and provide a clear investment recommendation.
+```
+
+This question provides clear instructions to the agents about how to proceed and what question to answer.
+
+4. In the **Playground**, inspect the answers to see how the agents use the **Tavily AI Search** tool to research the query, the **YFinance** tool to analyze the stock data, and the **Calculator** to determine if the stock is a wise investment.
+5. Ask similar questions to see how the agents use the tools to answer your queries.
+
+## Next steps
+
+To create your own multi-agent flow, see [Create a problem solving agent](/agents-tool-calling-agent-component).
\ No newline at end of file
diff --git a/langflow/docs/docs/Tutorials/tutorials-travel-planning-agent.md b/langflow/docs/docs/Tutorials/tutorials-travel-planning-agent.md
new file mode 100644
index 0000000..3733323
--- /dev/null
+++ b/langflow/docs/docs/Tutorials/tutorials-travel-planning-agent.md
@@ -0,0 +1,45 @@
+---
+title: Travel planning agent
+slug: /tutorials-travel-planning-agent
+---
+
+Build a **Travel Planning Agent** flow for an agentic application using the multiple Tool-calling agents.
+
+An **agent** uses an LLM as its "brain" to select among the connected tools and complete its tasks.
+
+In this flow, multiple **Tool-calling agents** reason using an **Open AI** LLM to plan a travel journey. Each agent is given a different responsibility defined by its **System Prompt** field.
+
+The **Chat input** defines where the user wants to go, and passes the result to the **City Selection** agent. The **Local Expert** agent then adds information based on the selected cities, and the **Travel Concierge** assembles a seven day travel plan in Markdown.
+
+All agents have access to the **Search API** and **URL Content Fetcher** components, while only the Travel Concierge can use the **Calculator** for computing the trip costs.
+
+## Prerequisites
+
+To use this flow, you need an [OpenAI API key](https://platform.openai.com/) and a [Search API key](https://www.searchapi.io/).
+
+## Open Langflow and start a new flow
+
+Click **New Flow**, and then select the **Travel Planning Agent** flow.
+
+This opens a starter flow with the necessary components to run an agentic application using multiple Tool-calling agents.
+
+## Create the travel planning agent flow
+
+
+
+The **Travel Planning Agent** flow consists of these components:
+
+* Multiple **Tool calling agent** components that use the connected LLM to reason through the user's input and select among the connected tools to complete their tasks.
+* The **Calculator** component performs basic arithmetic operations.
+* The **URL Content Fetcher** component scrapes content from a given URL.
+* The **Chat Input** component accepts user input to the chat.
+* The **Chat Output** component prints the flow's output to the chat.
+* The **OpenAI** model component sends the user input and prompt to the OpenAI API and receives a response.
+
+## Run the travel planning agent flow
+
+1. Add your credentials to the Open AI and Search API components.
+2. Click **Playground** to start a chat session.
+You should receive a detailed, helpful answer to the journey defined in the **Chat input** component.
+
+Now that your query has completed the journey from **Chat input** to **Chat output**, you have completed the **Travel Planning Agent** flow.
diff --git a/langflow/docs/docusaurus.config.js b/langflow/docs/docusaurus.config.js
new file mode 100644
index 0000000..c400a8c
--- /dev/null
+++ b/langflow/docs/docusaurus.config.js
@@ -0,0 +1,270 @@
+// @ts-check
+// Note: type annotations allow type checking and IDEs autocompletion
+
+const lightCodeTheme = require("prism-react-renderer/themes/github");
+const darkCodeTheme = require("prism-react-renderer/themes/dracula");
+const { remarkCodeHike } = require("@code-hike/mdx");
+
+/** @type {import('@docusaurus/types').Config} */
+const config = {
+ title: "Langflow Documentation",
+ tagline:
+ "Langflow is a low-code app builder for RAG and multi-agent AI applications.",
+ favicon: "img/favicon.ico",
+ url: "https://docs.langflow.org",
+ baseUrl: "/",
+ onBrokenLinks: "throw",
+ onBrokenMarkdownLinks: "warn",
+ onBrokenAnchors: "warn",
+ organizationName: "langflow-ai",
+ projectName: "langflow",
+ trailingSlash: false,
+ staticDirectories: ["static"],
+ i18n: {
+ defaultLocale: "en",
+ locales: ["en"],
+ },
+
+ presets: [
+ [
+ "docusaurus-preset-openapi",
+ /** @type {import('@docusaurus/preset-classic').Options} */
+ ({
+ api: {
+ path: "openapi.json", // Path to your OpenAPI file
+ routeBasePath: "/api", // The base URL for your API docs
+ },
+ docs: {
+ routeBasePath: "/", // Serve the docs at the site's root
+ sidebarPath: require.resolve("./sidebars.js"), // Use sidebars.js file
+ sidebarCollapsed: true,
+ beforeDefaultRemarkPlugins: [
+ [
+ remarkCodeHike,
+ {
+ theme: "github-dark",
+ showCopyButton: true,
+ lineNumbers: true,
+ },
+ ],
+ ],
+ },
+ sitemap: {
+ // https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-sitemap
+ // https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
+ lastmod: "datetime",
+ changefreq: null,
+ priority: null,
+ },
+ gtag: {
+ trackingID: "G-XHC7G628ZP",
+ anonymizeIP: true,
+ },
+ googleTagManager: {
+ containerId: "GTM-NK5M4ZT8",
+ },
+ blog: false,
+ theme: {
+ customCss: [
+ require.resolve("@code-hike/mdx/styles.css"),
+ require.resolve("./css/custom.css"),
+ require.resolve("./css/docu-notion-styles.css"),
+ require.resolve(
+ "./css/gifplayer.css"
+ //"./node_modules/react-gif-player/dist/gifplayer.css" // this gave a big red compile warning which is seaming unrelated " Replace Autoprefixer browsers option to Browserslist config..."
+ ),
+ ],
+ },
+ }),
+ ],
+ ],
+ plugins: [
+ ["docusaurus-node-polyfills", { excludeAliases: ["console"] }],
+ "docusaurus-plugin-image-zoom",
+ [
+ "@docusaurus/plugin-client-redirects",
+ {
+ redirects: [
+ {
+ to: "/",
+ from: [
+ "/whats-new-a-new-chapter-langflow",
+ "/👋 Welcome-to-Langflow",
+ "/getting-started-welcome-to-langflow",
+ "/guides-new-to-llms"
+ ],
+ },
+ {
+ to: "/get-started-installation",
+ from: [
+ "/getting-started-installation",
+ "/getting-started-common-installation-issues",
+ ],
+ },
+ {
+ to: "/get-started-quickstart",
+ from: "/getting-started-quickstart",
+ },
+ {
+ to: "/tutorials-travel-planning-agent",
+ from: [
+ "/starter-projects-dynamic-agent/",
+ "/starter-projects-travel-planning-agent",
+ ],
+ },
+ {
+ to: "concepts-overview",
+ from: [
+ "/workspace-overview",
+ "/365085a8-a90a-43f9-a779-f8769ec7eca1",
+ "/My-Collection",
+ "/workspace",
+ "/settings-project-general-settings",
+ ],
+ },
+ {
+ to: "/concepts-components",
+ from: [
+ "/components",
+ "/components-overview"
+ ],
+ },
+ {
+ to: "/configuration-global-variables",
+ from: "/settings-global-variables",
+ },
+ {
+ to: "/concepts-playground",
+ from: [
+ "/workspace-playground",
+ "/workspace-logs",
+ "/guides-chat-memory",
+ ],
+ },
+ {
+ to: "/concepts-objects",
+ from: [
+ "/guides-data-message",
+ "/configuration-objects",
+ ]
+ },
+ {
+ to: "/tutorials-sequential-agent",
+ from: "/starter-projects-sequential-agent",
+ },
+ {
+ to: "/tutorials-blog-writer",
+ from: "/starter-projects-blog-writer",
+ },
+ {
+ to: "/tutorials-memory-chatbot",
+ from: "/starter-projects-memory-chatbot",
+ },
+ {
+ to: "/tutorials-document-qa",
+ from: "/starter-projects-document-qa",
+ },
+ {
+ to: "/components-vector-stores",
+ from: "/components-rag",
+ },
+ {
+ to: "/concepts-api",
+ from: "/workspace-api",
+ },
+ {
+ to: "/components-custom-components",
+ from: "/components/custom",
+ },
+ // add more redirects like this
+ // {
+ // to: '/docs/anotherpage',
+ // from: ['/docs/legacypage1', '/docs/legacypage2'],
+ // },
+ ],
+ },
+ ],
+ // ....
+ async function myPlugin(context, options) {
+ return {
+ name: "docusaurus-tailwindcss",
+ configurePostCss(postcssOptions) {
+ // Appends TailwindCSS and AutoPrefixer.
+ postcssOptions.plugins.push(require("tailwindcss"));
+ postcssOptions.plugins.push(require("autoprefixer"));
+ return postcssOptions;
+ },
+ };
+ },
+ ],
+ themeConfig:
+ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
+ ({
+ navbar: {
+ hideOnScroll: true,
+ logo: {
+ alt: "Langflow",
+ src: "img/langflow-logo-black.svg",
+ srcDark: "img/langflow-logo-white.svg",
+ },
+ items: [
+ // right
+ {
+ position: "right",
+ href: "https://github.com/langflow-ai/langflow",
+ className: "header-github-link",
+ target: "_blank",
+ rel: null,
+ },
+ {
+ position: "right",
+ href: "https://twitter.com/langflow_ai",
+ className: "header-twitter-link",
+ target: "_blank",
+ rel: null,
+ },
+ {
+ position: "right",
+ href: "https://discord.gg/EqksyE2EX9",
+ className: "header-discord-link",
+ target: "_blank",
+ rel: null,
+ },
+ ],
+ },
+ colorMode: {
+ defaultMode: "light",
+ /* Allow users to chose light or dark mode. */
+ disableSwitch: false,
+ /* Respect user preferences, such as low light mode in the evening */
+ respectPrefersColorScheme: true,
+ },
+ prism: {
+ theme: lightCodeTheme,
+ darkTheme: darkCodeTheme,
+ },
+ zoom: {
+ selector: ".markdown :not(a) > img:not(.no-zoom)",
+ background: {
+ light: "rgba(240, 240, 240, 0.9)",
+ },
+ config: {},
+ },
+ docs: {
+ sidebar: {
+ hideable: false,
+ },
+ },
+ algolia: {
+ appId: 'UZK6BDPCVY',
+ // public key, safe to commit
+ apiKey: 'adbd7686dceb1cd510d5ce20d04bf74c',
+ indexName: 'langflow',
+ contextualSearch: true,
+ searchParameters: {},
+ searchPagePath: 'search',
+ },
+ }),
+};
+
+module.exports = config;
diff --git a/langflow/docs/i18n/es/docusaurus-plugin-content-docs/current/Examples/527257662.png b/langflow/docs/i18n/es/docusaurus-plugin-content-docs/current/Examples/527257662.png
new file mode 100644
index 0000000..0c8d3e5
Binary files /dev/null and b/langflow/docs/i18n/es/docusaurus-plugin-content-docs/current/Examples/527257662.png differ
diff --git a/langflow/docs/i18n/fr/docusaurus-plugin-content-docs/current/Examples/527257662.png b/langflow/docs/i18n/fr/docusaurus-plugin-content-docs/current/Examples/527257662.png
new file mode 100644
index 0000000..3e02e55
Binary files /dev/null and b/langflow/docs/i18n/fr/docusaurus-plugin-content-docs/current/Examples/527257662.png differ
diff --git a/langflow/docs/index.d.ts b/langflow/docs/index.d.ts
new file mode 100644
index 0000000..686a5df
--- /dev/null
+++ b/langflow/docs/index.d.ts
@@ -0,0 +1,10 @@
+declare module "*.module.scss" {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+
+declare module "@theme/*";
+
+declare module "@components/*";
+
+declare module "@docusaurus/*";
diff --git a/langflow/docs/openapi.json b/langflow/docs/openapi.json
new file mode 100644
index 0000000..117ceba
--- /dev/null
+++ b/langflow/docs/openapi.json
@@ -0,0 +1,9998 @@
+{
+ "openapi": "3.1.0",
+ "info": {
+ "title": "Langflow",
+ "version": "1.2.0"
+ },
+ "paths": {
+ "/api/v1/build/{flow_id}/vertices": {
+ "post": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Retrieve Vertices Order",
+ "description": "Retrieve the vertices order for a given flow.\n\nArgs:\n flow_id (str): The ID of the flow.\n background_tasks (BackgroundTasks): The background tasks.\n data (Optional[FlowDataRequest], optional): The flow data. Defaults to None.\n stop_component_id (str, optional): The ID of the stop component. Defaults to None.\n start_component_id (str, optional): The ID of the start component. Defaults to None.\n session (AsyncSession, optional): The session dependency.\n\nReturns:\n VerticesOrderResponse: The response containing the ordered vertex IDs and the run ID.\n\nRaises:\n HTTPException: If there is an error checking the build status.",
+ "operationId": "retrieve_vertices_order_api_v1_build__flow_id__vertices_post",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "data",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FlowDataRequest"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ }
+ },
+ {
+ "name": "stop_component_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Stop Component Id"
+ }
+ },
+ {
+ "name": "start_component_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Start Component Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerticesOrderResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/build/{flow_id}/flow": {
+ "post": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Build Flow",
+ "description": "Build and process a flow, returning a job ID for event polling.",
+ "operationId": "build_flow_api_v1_build__flow_id__flow_post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "stop_component_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Stop Component Id"
+ }
+ },
+ {
+ "name": "start_component_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Start Component Id"
+ }
+ },
+ {
+ "name": "log_builds",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true,
+ "title": "Log Builds"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_build_flow_api_v1_build__flow_id__flow_post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/build/{job_id}/events": {
+ "get": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Get Build Events",
+ "description": "Get events for a specific build job.",
+ "operationId": "get_build_events_api_v1_build__job_id__events_get",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Job Id"
+ }
+ },
+ {
+ "name": "stream",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true,
+ "title": "Stream"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/build/{job_id}/cancel": {
+ "post": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Cancel Build",
+ "description": "Cancel a specific build job.",
+ "operationId": "cancel_build_api_v1_build__job_id__cancel_post",
+ "parameters": [
+ {
+ "name": "job_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Job Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CancelFlowResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/build/{flow_id}/vertices/{vertex_id}": {
+ "post": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Build Vertex",
+ "description": "Build a vertex instead of the entire graph.\n\nArgs:\n flow_id (str): The ID of the flow.\n vertex_id (str): The ID of the vertex to build.\n background_tasks (BackgroundTasks): The background tasks dependency.\n inputs (Optional[InputValueRequest], optional): The input values for the vertex. Defaults to None.\n files (List[str], optional): The files to use. Defaults to None.\n current_user (Any, optional): The current user dependency. Defaults to Depends(get_current_active_user).\n\nReturns:\n VertexBuildResponse: The response containing the built vertex information.\n\nRaises:\n HTTPException: If there is an error building the vertex.",
+ "operationId": "build_vertex_api_v1_build__flow_id__vertices__vertex_id__post",
+ "deprecated": true,
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "vertex_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Vertex Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_build_vertex_api_v1_build__flow_id__vertices__vertex_id__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VertexBuildResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/build/{flow_id}/{vertex_id}/stream": {
+ "get": {
+ "tags": [
+ "Chat"
+ ],
+ "summary": "Build Vertex Stream",
+ "description": "Build a vertex instead of the entire graph.\n\nThis function is responsible for building a single vertex instead of the entire graph.\nIt takes the `flow_id` and `vertex_id` as required parameters, and an optional `session_id`.\nIt also depends on the `ChatService` and `SessionService` services.\n\nIf `session_id` is not provided, it retrieves the graph from the cache using the `chat_service`.\nIf `session_id` is provided, it loads the session data using the `session_service`.\n\nOnce the graph is obtained, it retrieves the specified vertex using the `vertex_id`.\nIf the vertex does not support streaming, an error is raised.\nIf the vertex has a built result, it sends the result as a chunk.\nIf the vertex is not frozen or not built, it streams the vertex data.\nIf the vertex has a result, it sends the result as a chunk.\nIf none of the above conditions are met, an error is raised.\n\nIf any exception occurs during the process, an error message is sent.\nFinally, the stream is closed.\n\nReturns:\n A `StreamingResponse` object with the streamed vertex data in text/event-stream format.\n\nRaises:\n HTTPException: If an error occurs while building the vertex.",
+ "operationId": "build_vertex_stream_api_v1_build__flow_id___vertex_id__stream_get",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "vertex_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Vertex Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/all": {
+ "get": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Get All",
+ "operationId": "get_all_api_v1_all_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/run/{flow_id_or_name}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Simplified Run Flow",
+ "description": "Executes a specified flow by ID with support for streaming and telemetry.\n\nThis endpoint executes a flow identified by ID or name, with options for streaming the response\nand tracking execution metrics. It handles both streaming and non-streaming execution modes.\n\nArgs:\n background_tasks (BackgroundTasks): FastAPI background task manager\n flow (FlowRead | None): The flow to execute, loaded via dependency\n input_request (SimplifiedAPIRequest | None): Input parameters for the flow\n stream (bool): Whether to stream the response\n api_key_user (UserRead): Authenticated user from API key\n request (Request): The incoming HTTP request\n\nReturns:\n Union[StreamingResponse, RunResponse]: Either a streaming response for real-time results\n or a RunResponse with the complete execution results\n\nRaises:\n HTTPException: For flow not found (404) or invalid input (400)\n APIException: For internal execution errors (500)\n\nNotes:\n - Supports both streaming and non-streaming execution modes\n - Tracks execution time and success/failure via telemetry\n - Handles graceful client disconnection in streaming mode\n - Provides detailed error handling with appropriate HTTP status codes\n - In streaming mode, uses EventManager to handle events:\n - \"add_message\": New messages during execution\n - \"token\": Individual tokens during streaming\n - \"end\": Final execution result",
+ "operationId": "simplified_run_flow_api_v1_run__flow_id_or_name__post",
+ "security": [
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id_or_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Flow Id Or Name"
+ }
+ },
+ {
+ "name": "stream",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Stream"
+ }
+ },
+ {
+ "name": "user_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SimplifiedAPIRequest"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input Request"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/webhook/{flow_id_or_name}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Webhook Run Flow",
+ "description": "Run a flow using a webhook request.\n\nArgs:\n flow (Flow, optional): The flow to be executed. Defaults to Depends(get_flow_by_id).\n user (User): The flow user.\n request (Request): The incoming HTTP request.\n background_tasks (BackgroundTasks): The background tasks manager.\n\nReturns:\n dict: A dictionary containing the status of the task.\n\nRaises:\n HTTPException: If the flow is not found or if there is an error processing the request.",
+ "operationId": "webhook_run_flow_api_v1_webhook__flow_id_or_name__post",
+ "parameters": [
+ {
+ "name": "flow_id_or_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Flow Id Or Name"
+ }
+ },
+ {
+ "name": "user_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "title": "Response Webhook Run Flow Api V1 Webhook Flow Id Or Name Post"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/run/advanced/{flow_id}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Experimental Run Flow",
+ "description": "Executes a specified flow by ID with optional input values, output selection, tweaks, and streaming capability.\n\nThis endpoint supports running flows with caching to enhance performance and efficiency.\n\n### Parameters:\n- `flow_id` (str): The unique identifier of the flow to be executed.\n- `inputs` (List[InputValueRequest], optional): A list of inputs specifying the input values and components\n for the flow. Each input can target specific components and provide custom values.\n- `outputs` (List[str], optional): A list of output names to retrieve from the executed flow.\n If not provided, all outputs are returned.\n- `tweaks` (Optional[Tweaks], optional): A dictionary of tweaks to customize the flow execution.\n The tweaks can be used to modify the flow's parameters and components.\n Tweaks can be overridden by the input values.\n- `stream` (bool, optional): Specifies whether the results should be streamed. Defaults to False.\n- `session_id` (Union[None, str], optional): An optional session ID to utilize existing session data for the flow\n execution.\n- `api_key_user` (User): The user associated with the current API key. Automatically resolved from the API key.\n\n### Returns:\nA `RunResponse` object containing the selected outputs (or all if not specified) of the executed flow\nand the session ID.\nThe structure of the response accommodates multiple inputs, providing a nested list of outputs for each input.\n\n### Raises:\nHTTPException: Indicates issues with finding the specified flow, invalid input formats, or internal errors during\nflow execution.\n\n### Example usage:\n```json\nPOST /run/{flow_id}\nx-api-key: YOUR_API_KEY\nPayload:\n{\n \"inputs\": [\n {\"components\": [\"component1\"], \"input_value\": \"value1\"},\n {\"components\": [\"component3\"], \"input_value\": \"value2\"}\n ],\n \"outputs\": [\"Component Name\", \"component_id\"],\n \"tweaks\": {\"parameter_name\": \"value\", \"Component Name\": {\"parameter_name\": \"value\"}, \"component_id\": {\"parameter_name\": \"value\"}}\n \"stream\": false\n}\n```\n\nThis endpoint facilitates complex flow executions with customized inputs, outputs, and configurations,\ncatering to diverse application requirements.",
+ "operationId": "experimental_run_flow_api_v1_run_advanced__flow_id__post",
+ "security": [
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_experimental_run_flow_api_v1_run_advanced__flow_id__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RunResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/process/{flow_id}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Process",
+ "description": "Endpoint to process an input with a given flow_id.",
+ "operationId": "process_api_v1_process__flow_id__post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "null",
+ "title": "Response Process Api V1 Process Flow Id Post"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/predict/{flow_id}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Process",
+ "description": "Endpoint to process an input with a given flow_id.",
+ "operationId": "process_api_v1_predict__flow_id__post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "null",
+ "title": "Response Process Api V1 Predict Flow Id Post"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/task/{_task_id}": {
+ "get": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Get Task Status",
+ "description": "Get the status of a task by ID (Deprecated).\n\nThis endpoint is deprecated and will be removed in a future version.",
+ "operationId": "get_task_status_api_v1_task___task_id__get",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "_task_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": " Task Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/TaskStatusResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/upload/{flow_id}": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Create Upload File",
+ "description": "Upload a file for a specific flow (Deprecated).\n\nThis endpoint is deprecated and will be removed in a future version.",
+ "operationId": "create_upload_file_api_v1_upload__flow_id__post",
+ "deprecated": true,
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_create_upload_file_api_v1_upload__flow_id__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/langflow__api__v1__schemas__UploadFileResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/version": {
+ "get": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Get Version",
+ "operationId": "get_version_api_v1_version_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/custom_component": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Custom Component",
+ "operationId": "custom_component_api_v1_custom_component_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CustomComponentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CustomComponentResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/custom_component/update": {
+ "post": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Custom Component Update",
+ "description": "Update a custom component with the provided code request.\n\nThis endpoint generates the CustomComponentFrontendNode normally but then runs the `update_build_config` method\non the latest version of the template.\nThis ensures that every time it runs, it has the latest version of the template.\n\nArgs:\n code_request (CustomComponentRequest): The code request containing the updated code for the custom component.\n user (User, optional): The user making the request. Defaults to the current active user.\n\nReturns:\n dict: The updated custom component node.\n\nRaises:\n HTTPException: If there's an error building or updating the component\n SerializationError: If there's an error serializing the component to JSON",
+ "operationId": "custom_component_update_api_v1_custom_component_update_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateCustomComponentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/config": {
+ "get": {
+ "tags": [
+ "Base"
+ ],
+ "summary": "Get Config",
+ "operationId": "get_config_api_v1_config_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConfigResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/validate/code": {
+ "post": {
+ "tags": [
+ "Validate"
+ ],
+ "summary": "Post Validate Code",
+ "operationId": "post_validate_code_api_v1_validate_code_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Code"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CodeValidationResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/validate/prompt": {
+ "post": {
+ "tags": [
+ "Validate"
+ ],
+ "summary": "Post Validate Prompt",
+ "operationId": "post_validate_prompt_api_v1_validate_prompt_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ValidatePromptRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PromptValidationResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/store/check/": {
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Check If Store Is Enabled",
+ "operationId": "check_if_store_is_enabled_api_v1_store_check__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/store/check/api_key": {
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Check If Store Has Api Key",
+ "operationId": "check_if_store_has_api_key_api_v1_store_check_api_key_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/store/components/": {
+ "post": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Share Component",
+ "operationId": "share_component_api_v1_store_components__post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StoreComponentCreate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateComponentResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Get Components",
+ "operationId": "get_components_api_v1_store_components__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "component_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Component Id"
+ }
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Search"
+ }
+ },
+ {
+ "name": "private",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Private"
+ }
+ },
+ {
+ "name": "is_component",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component"
+ }
+ },
+ {
+ "name": "tags",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags"
+ }
+ },
+ {
+ "name": "sort",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sort"
+ }
+ },
+ {
+ "name": "liked",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Liked"
+ }
+ },
+ {
+ "name": "filter_by_user",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Filter By User"
+ }
+ },
+ {
+ "name": "fields",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Fields"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 1,
+ "title": "Page"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 10,
+ "title": "Limit"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListComponentResponseModel"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/store/components/{component_id}": {
+ "patch": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Update Shared Component",
+ "operationId": "update_shared_component_api_v1_store_components__component_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "component_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Component Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StoreComponentCreate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateComponentResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Download Component",
+ "operationId": "download_component_api_v1_store_components__component_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "component_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Component Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DownloadComponentResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/store/tags": {
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Get Tags",
+ "operationId": "get_tags_api_v1_store_tags_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/TagResponse"
+ },
+ "type": "array",
+ "title": "Response Get Tags Api V1 Store Tags Get"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/store/users/likes": {
+ "get": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Get List Of Components Liked By User",
+ "operationId": "get_list_of_components_liked_by_user_api_v1_store_users_likes_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/UsersLikesResponse"
+ },
+ "type": "array",
+ "title": "Response Get List Of Components Liked By User Api V1 Store Users Likes Get"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/store/users/likes/{component_id}": {
+ "post": {
+ "tags": [
+ "Components Store"
+ ],
+ "summary": "Like Component",
+ "operationId": "like_component_api_v1_store_users_likes__component_id__post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "component_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Component Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UsersLikesResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/flows/": {
+ "post": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Create Flow",
+ "operationId": "create_flow_api_v1_flows__post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowCreate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Read Flows",
+ "description": "Retrieve a list of flows with pagination support.\n\nArgs:\n current_user (User): The current authenticated user.\n session (Session): The database session.\n settings_service (SettingsService): The settings service.\n components_only (bool, optional): Whether to return only components. Defaults to False.\n\n get_all (bool, optional): Whether to return all flows without pagination. Defaults to True.\n **This field must be True because of backward compatibility with the frontend - Release: 1.0.20**\n\n folder_id (UUID, optional): The folder ID. Defaults to None.\n params (Params): Pagination parameters.\n remove_example_flows (bool, optional): Whether to remove example flows. Defaults to False.\n header_flows (bool, optional): Whether to return only specific headers of the flows. Defaults to False.\n\nReturns:\n list[FlowRead] | Page[FlowRead] | list[FlowHeader]\n A list of flows or a paginated response containing the list of flows or a list of flow headers.",
+ "operationId": "read_flows_api_v1_flows__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "remove_example_flows",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Remove Example Flows"
+ }
+ },
+ {
+ "name": "components_only",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Components Only"
+ }
+ },
+ {
+ "name": "get_all",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true,
+ "title": "Get All"
+ }
+ },
+ {
+ "name": "folder_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ }
+ },
+ {
+ "name": "header_flows",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Header Flows"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "minimum": 1,
+ "default": 1,
+ "title": "Page"
+ }
+ },
+ {
+ "name": "size",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "default": 50,
+ "title": "Size"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ }
+ },
+ {
+ "$ref": "#/components/schemas/Page_FlowRead_"
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FlowHeader"
+ }
+ }
+ ],
+ "title": "Response Read Flows Api V1 Flows Get"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Delete Multiple Flows",
+ "description": "Delete multiple flows by their IDs.\n\nArgs:\n flow_ids (List[str]): The list of flow IDs to delete.\n user (User, optional): The user making the request. Defaults to the current active user.\n db (Session, optional): The database session.\n\nReturns:\n dict: A dictionary containing the number of flows deleted.",
+ "operationId": "delete_multiple_flows_api_v1_flows__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "title": "Flow Ids"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/flows/{flow_id}": {
+ "get": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Read Flow",
+ "description": "Read a flow.",
+ "operationId": "read_flow_api_v1_flows__flow_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "patch": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Update Flow",
+ "description": "Update a flow.",
+ "operationId": "update_flow_api_v1_flows__flow_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Delete Flow",
+ "description": "Delete a flow.",
+ "operationId": "delete_flow_api_v1_flows__flow_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/flows/batch/": {
+ "post": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Create Flows",
+ "description": "Create multiple new flows.",
+ "operationId": "create_flows_api_v1_flows_batch__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FlowListCreate"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "type": "array",
+ "title": "Response Create Flows Api V1 Flows Batch Post"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/flows/upload/": {
+ "post": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Upload File",
+ "description": "Upload flows from a file.",
+ "operationId": "upload_file_api_v1_flows_upload__post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "folder_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_file_api_v1_flows_upload__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "title": "Response Upload File Api V1 Flows Upload Post"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/flows/download/": {
+ "post": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Download Multiple File",
+ "description": "Download all flows as a zip file.",
+ "operationId": "download_multiple_file_api_v1_flows_download__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": "array",
+ "title": "Flow Ids"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/flows/basic_examples/": {
+ "get": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Read Basic Examples",
+ "description": "Retrieve a list of basic example flows.\n\nArgs:\n session (Session): The database session.\n\nReturns:\n list[FlowRead]: A list of basic example flows.",
+ "operationId": "read_basic_examples_api_v1_flows_basic_examples__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "type": "array",
+ "title": "Response Read Basic Examples Api V1 Flows Basic Examples Get"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/users/": {
+ "post": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Add User",
+ "description": "Add a new user to the database.",
+ "operationId": "add_user_api_v1_users__post",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserCreate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Read All Users",
+ "description": "Retrieve a list of users from the database with pagination.",
+ "operationId": "read_all_users_api_v1_users__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "skip",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 0,
+ "title": "Skip"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 10,
+ "title": "Limit"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UsersResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/users/whoami": {
+ "get": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Read Current User",
+ "description": "Retrieve the current user's data.",
+ "operationId": "read_current_user_api_v1_users_whoami_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRead"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/users/{user_id}": {
+ "patch": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Patch User",
+ "description": "Update an existing user's data.",
+ "operationId": "patch_user_api_v1_users__user_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Delete User",
+ "description": "Delete a user from the database.",
+ "operationId": "delete_user_api_v1_users__user_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "title": "Response Delete User Api V1 Users User Id Delete"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/users/{user_id}/reset-password": {
+ "patch": {
+ "tags": [
+ "Users"
+ ],
+ "summary": "Reset Password",
+ "description": "Reset a user's password.",
+ "operationId": "reset_password_api_v1_users__user_id__reset_password_patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "user_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UserRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/api_key/": {
+ "get": {
+ "tags": [
+ "APIKey"
+ ],
+ "summary": "Get Api Keys Route",
+ "operationId": "get_api_keys_route_api_v1_api_key__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiKeysResponse"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "APIKey"
+ ],
+ "summary": "Create Api Key Route",
+ "operationId": "create_api_key_route_api_v1_api_key__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiKeyCreate"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnmaskedApiKeyRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/api_key/{api_key_id}": {
+ "delete": {
+ "tags": [
+ "APIKey"
+ ],
+ "summary": "Delete Api Key Route",
+ "operationId": "delete_api_key_route_api_v1_api_key__api_key_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "api_key_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Api Key Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/api_key/store": {
+ "post": {
+ "tags": [
+ "APIKey"
+ ],
+ "summary": "Save Store Api Key",
+ "operationId": "save_store_api_key_api_v1_api_key_store_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiKeyCreateRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/login": {
+ "post": {
+ "tags": [
+ "Login"
+ ],
+ "summary": "Login To Get Access Token",
+ "operationId": "login_to_get_access_token_api_v1_login_post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_login_to_get_access_token_api_v1_login_post"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Token"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/auto_login": {
+ "get": {
+ "tags": [
+ "Login"
+ ],
+ "summary": "Auto Login",
+ "operationId": "auto_login_api_v1_auto_login_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/refresh": {
+ "post": {
+ "tags": [
+ "Login"
+ ],
+ "summary": "Refresh Token",
+ "operationId": "refresh_token_api_v1_refresh_post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/logout": {
+ "post": {
+ "tags": [
+ "Login"
+ ],
+ "summary": "Logout",
+ "operationId": "logout_api_v1_logout_post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/variables/": {
+ "get": {
+ "tags": [
+ "Variables"
+ ],
+ "summary": "Read Variables",
+ "description": "Read all variables.",
+ "operationId": "read_variables_api_v1_variables__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/VariableRead"
+ },
+ "type": "array",
+ "title": "Response Read Variables Api V1 Variables Get"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Variables"
+ ],
+ "summary": "Create Variable",
+ "description": "Create a new variable.",
+ "operationId": "create_variable_api_v1_variables__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VariableCreate"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VariableRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/variables/{variable_id}": {
+ "patch": {
+ "tags": [
+ "Variables"
+ ],
+ "summary": "Update Variable",
+ "description": "Update a variable.",
+ "operationId": "update_variable_api_v1_variables__variable_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "variable_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Variable Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VariableUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VariableRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Variables"
+ ],
+ "summary": "Delete Variable",
+ "description": "Delete a variable.",
+ "operationId": "delete_variable_api_v1_variables__variable_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "variable_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Variable Id"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/upload/{flow_id}": {
+ "post": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Upload File",
+ "operationId": "upload_file_api_v1_files_upload__flow_id__post",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_file_api_v1_files_upload__flow_id__post"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/langflow__api__v1__schemas__UploadFileResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/download/{flow_id}/{file_name}": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Download File",
+ "operationId": "download_file_api_v1_files_download__flow_id___file_name__get",
+ "parameters": [
+ {
+ "name": "file_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Name"
+ }
+ },
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/images/{flow_id}/{file_name}": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Download Image",
+ "operationId": "download_image_api_v1_files_images__flow_id___file_name__get",
+ "parameters": [
+ {
+ "name": "file_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Name"
+ }
+ },
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/profile_pictures/{folder_name}/{file_name}": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Download Profile Picture",
+ "operationId": "download_profile_picture_api_v1_files_profile_pictures__folder_name___file_name__get",
+ "parameters": [
+ {
+ "name": "folder_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Folder Name"
+ }
+ },
+ {
+ "name": "file_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/profile_pictures/list": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "List Profile Pictures",
+ "operationId": "list_profile_pictures_api_v1_files_profile_pictures_list_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/list/{flow_id}": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "List Files",
+ "operationId": "list_files_api_v1_files_list__flow_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/files/delete/{flow_id}/{file_name}": {
+ "delete": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Delete File",
+ "operationId": "delete_file_api_v1_files_delete__flow_id___file_name__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "file_name",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "File Name"
+ }
+ },
+ {
+ "name": "flow_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/builds": {
+ "get": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Get Vertex Builds",
+ "operationId": "get_vertex_builds_api_v1_monitor_builds_get",
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VertexBuildMapModel"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Delete Vertex Builds",
+ "operationId": "delete_vertex_builds_api_v1_monitor_builds_delete",
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/messages": {
+ "get": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Get Messages",
+ "operationId": "get_messages_api_v1_monitor_messages_get",
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "session_id",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id"
+ }
+ },
+ {
+ "name": "sender",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender"
+ }
+ },
+ {
+ "name": "sender_name",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender Name"
+ }
+ },
+ {
+ "name": "order_by",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": "timestamp",
+ "title": "Order By"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/MessageResponse"
+ },
+ "title": "Response Get Messages Api V1 Monitor Messages Get"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Delete Messages",
+ "operationId": "delete_messages_api_v1_monitor_messages_delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "title": "Message Ids"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/messages/{message_id}": {
+ "put": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Update Message",
+ "operationId": "update_message_api_v1_monitor_messages__message_id__put",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "message_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Message Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MessageUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MessageRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/messages/session/{old_session_id}": {
+ "patch": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Update Session Id",
+ "operationId": "update_session_id_api_v1_monitor_messages_session__old_session_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "old_session_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Old Session Id"
+ }
+ },
+ {
+ "name": "new_session_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "description": "The new session ID to update to",
+ "title": "New Session Id"
+ },
+ "description": "The new session ID to update to"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/MessageResponse"
+ },
+ "title": "Response Update Session Id Api V1 Monitor Messages Session Old Session Id Patch"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/messages/session/{session_id}": {
+ "delete": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Delete Messages Session",
+ "operationId": "delete_messages_session_api_v1_monitor_messages_session__session_id__delete",
+ "parameters": [
+ {
+ "name": "session_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Session Id"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/monitor/transactions": {
+ "get": {
+ "tags": [
+ "Monitor"
+ ],
+ "summary": "Get Transactions",
+ "operationId": "get_transactions_api_v1_monitor_transactions_get",
+ "parameters": [
+ {
+ "name": "flow_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Page number",
+ "default": 1,
+ "title": "Page"
+ },
+ "description": "Page number"
+ },
+ {
+ "name": "size",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "maximum": 100,
+ "minimum": 1,
+ "description": "Page size",
+ "default": 50,
+ "title": "Size"
+ },
+ "description": "Page size"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Page_TransactionTable_"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/folders/": {
+ "get": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Read Folders",
+ "operationId": "read_folders_api_v1_folders__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/FolderRead"
+ },
+ "type": "array",
+ "title": "Response Read Folders Api V1 Folders Get"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Create Folder",
+ "operationId": "create_folder_api_v1_folders__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FolderCreate"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FolderRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/folders/{folder_id}": {
+ "get": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Read Folder",
+ "operationId": "read_folder_api_v1_folders__folder_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "folder_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Folder Id"
+ }
+ },
+ {
+ "name": "is_component",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Is Component"
+ }
+ },
+ {
+ "name": "is_flow",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "title": "Is Flow"
+ }
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "",
+ "title": "Search"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Page"
+ }
+ },
+ {
+ "name": "size",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Size"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FolderWithPaginatedFlows"
+ },
+ {
+ "$ref": "#/components/schemas/FolderReadWithFlows"
+ }
+ ],
+ "title": "Response Read Folder Api V1 Folders Folder Id Get"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "patch": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Update Folder",
+ "operationId": "update_folder_api_v1_folders__folder_id__patch",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "folder_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Folder Id"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FolderUpdate"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FolderRead"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Delete Folder",
+ "operationId": "delete_folder_api_v1_folders__folder_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "folder_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Folder Id"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/folders/download/{folder_id}": {
+ "get": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Download File",
+ "description": "Download all flows from folder as a zip file.",
+ "operationId": "download_file_api_v1_folders_download__folder_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "folder_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Folder Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/folders/upload/": {
+ "post": {
+ "tags": [
+ "Folders"
+ ],
+ "summary": "Upload File",
+ "description": "Upload flows from a file.",
+ "operationId": "upload_file_api_v1_folders_upload__post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_file_api_v1_folders_upload__post"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "type": "array",
+ "title": "Response Upload File Api V1 Folders Upload Post"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/starter-projects/": {
+ "get": {
+ "tags": [
+ "Flows"
+ ],
+ "summary": "Get Starter Projects",
+ "description": "Get a list of starter projects.",
+ "operationId": "get_starter_projects_api_v1_starter_projects__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/GraphDump"
+ },
+ "type": "array",
+ "title": "Response Get Starter Projects Api V1 Starter Projects Get"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/mcp/sse": {
+ "get": {
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Handle Sse",
+ "operationId": "handle_sse_api_v1_mcp_sse_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response"
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v1/mcp/": {
+ "post": {
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Handle Messages",
+ "operationId": "handle_messages_api_v1_mcp__post",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v2/files": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "List Files",
+ "description": "List the files available to the current user.",
+ "operationId": "list_files_api_v2_files_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/langflow__services__database__models__file__model__File"
+ },
+ "type": "array",
+ "title": "Response List Files Api V2 Files Get"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Upload User File",
+ "description": "Upload a file for the current user and track it in the database.",
+ "operationId": "upload_user_file_api_v2_files_post",
+ "requestBody": {
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_upload_user_file_api_v2_files_post"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/langflow__api__schemas__UploadFileResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ },
+ "delete": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Delete All Files",
+ "description": "Delete all files for the current user.",
+ "operationId": "delete_all_files_api_v2_files_delete",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ]
+ }
+ },
+ "/api/v2/files/{file_id}": {
+ "get": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Download File",
+ "description": "Download a file by its ID.",
+ "operationId": "download_file_api_v2_files__file_id__get",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "File Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Edit File Name",
+ "description": "Edit the name of a file by its ID.",
+ "operationId": "edit_file_name_api_v2_files__file_id__put",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "File Id"
+ }
+ },
+ {
+ "name": "name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "title": "Name"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/langflow__api__schemas__UploadFileResponse"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "Files"
+ ],
+ "summary": "Delete File",
+ "description": "Delete a file by its ID.",
+ "operationId": "delete_file_api_v2_files__file_id__delete",
+ "security": [
+ {
+ "OAuth2PasswordBearer": []
+ },
+ {
+ "API key query": []
+ },
+ {
+ "API key header": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "file_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "title": "File Id"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/health": {
+ "get": {
+ "tags": [
+ "Health Check"
+ ],
+ "summary": "Health",
+ "operationId": "health_health_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/health_check": {
+ "get": {
+ "tags": [
+ "Health Check"
+ ],
+ "summary": "Health Check",
+ "operationId": "health_check_health_check_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HealthResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/logs-stream": {
+ "get": {
+ "tags": [
+ "Log"
+ ],
+ "summary": "Stream Logs",
+ "description": "HTTP/2 Server-Sent-Event (SSE) endpoint for streaming logs.\n\nIt establishes a long-lived connection to the server and receives log messages in real-time.\nThe client should use the header \"Accept: text/event-stream\".",
+ "operationId": "stream_logs_logs_stream_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "/logs": {
+ "get": {
+ "tags": [
+ "Log"
+ ],
+ "summary": "Logs",
+ "operationId": "logs_logs_get",
+ "parameters": [
+ {
+ "name": "lines_before",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "description": "The number of logs before the timestamp or the last log",
+ "default": 0,
+ "title": "Lines Before"
+ },
+ "description": "The number of logs before the timestamp or the last log"
+ },
+ {
+ "name": "lines_after",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "description": "The number of logs after the timestamp",
+ "default": 0,
+ "title": "Lines After"
+ },
+ "description": "The number of logs after the timestamp"
+ },
+ {
+ "name": "timestamp",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "description": "The timestamp to start getting logs from",
+ "default": 0,
+ "title": "Timestamp"
+ },
+ "description": "The timestamp to start getting logs from"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "ApiKeyCreate": {
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "last_used_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Used At"
+ },
+ "total_uses": {
+ "type": "integer",
+ "title": "Total Uses",
+ "default": 0
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Is Active",
+ "default": true
+ },
+ "api_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Api Key"
+ },
+ "user_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ },
+ "created_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Created At"
+ }
+ },
+ "type": "object",
+ "title": "ApiKeyCreate"
+ },
+ "ApiKeyCreateRequest": {
+ "properties": {
+ "api_key": {
+ "type": "string",
+ "title": "Api Key"
+ }
+ },
+ "type": "object",
+ "required": [
+ "api_key"
+ ],
+ "title": "ApiKeyCreateRequest"
+ },
+ "ApiKeyRead": {
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "last_used_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Used At"
+ },
+ "total_uses": {
+ "type": "integer",
+ "title": "Total Uses",
+ "default": 0
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Is Active",
+ "default": true
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "api_key": {
+ "type": "string",
+ "title": "Api Key"
+ },
+ "user_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Created At"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "api_key",
+ "user_id",
+ "created_at"
+ ],
+ "title": "ApiKeyRead"
+ },
+ "ApiKeysResponse": {
+ "properties": {
+ "total_count": {
+ "type": "integer",
+ "title": "Total Count"
+ },
+ "user_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ },
+ "api_keys": {
+ "items": {
+ "$ref": "#/components/schemas/ApiKeyRead"
+ },
+ "type": "array",
+ "title": "Api Keys"
+ }
+ },
+ "type": "object",
+ "required": [
+ "total_count",
+ "user_id",
+ "api_keys"
+ ],
+ "title": "ApiKeysResponse"
+ },
+ "BaseModel": {
+ "properties": {},
+ "type": "object",
+ "title": "BaseModel"
+ },
+ "Body_build_flow_api_v1_build__flow_id__flow_post": {
+ "properties": {
+ "inputs": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/InputValueRequest"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "data": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FlowDataRequest"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "files": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Files"
+ }
+ },
+ "type": "object",
+ "title": "Body_build_flow_api_v1_build__flow_id__flow_post"
+ },
+ "Body_build_vertex_api_v1_build__flow_id__vertices__vertex_id__post": {
+ "properties": {
+ "inputs": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/InputValueRequest"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "files": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Files"
+ }
+ },
+ "type": "object",
+ "title": "Body_build_vertex_api_v1_build__flow_id__vertices__vertex_id__post"
+ },
+ "Body_create_upload_file_api_v1_upload__flow_id__post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_create_upload_file_api_v1_upload__flow_id__post"
+ },
+ "Body_experimental_run_flow_api_v1_run_advanced__flow_id__post": {
+ "properties": {
+ "inputs": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/InputValueRequest"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Inputs"
+ },
+ "outputs": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Outputs"
+ },
+ "tweaks": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Tweaks"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "stream": {
+ "type": "boolean",
+ "title": "Stream",
+ "default": false
+ },
+ "session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id"
+ }
+ },
+ "type": "object",
+ "title": "Body_experimental_run_flow_api_v1_run_advanced__flow_id__post"
+ },
+ "Body_login_to_get_access_token_api_v1_login_post": {
+ "properties": {
+ "grant_type": {
+ "anyOf": [
+ {
+ "type": "string",
+ "pattern": "^password$"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Grant Type"
+ },
+ "username": {
+ "type": "string",
+ "title": "Username"
+ },
+ "password": {
+ "type": "string",
+ "title": "Password"
+ },
+ "scope": {
+ "type": "string",
+ "title": "Scope",
+ "default": ""
+ },
+ "client_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Client Id"
+ },
+ "client_secret": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Client Secret"
+ }
+ },
+ "type": "object",
+ "required": [
+ "username",
+ "password"
+ ],
+ "title": "Body_login_to_get_access_token_api_v1_login_post"
+ },
+ "Body_upload_file_api_v1_files_upload__flow_id__post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_file_api_v1_files_upload__flow_id__post"
+ },
+ "Body_upload_file_api_v1_flows_upload__post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_file_api_v1_flows_upload__post"
+ },
+ "Body_upload_file_api_v1_folders_upload__post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_file_api_v1_folders_upload__post"
+ },
+ "Body_upload_user_file_api_v2_files_post": {
+ "properties": {
+ "file": {
+ "type": "string",
+ "format": "binary",
+ "title": "File"
+ }
+ },
+ "type": "object",
+ "required": [
+ "file"
+ ],
+ "title": "Body_upload_user_file_api_v2_files_post"
+ },
+ "CancelFlowResponse": {
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "title": "Success"
+ },
+ "message": {
+ "type": "string",
+ "title": "Message"
+ }
+ },
+ "type": "object",
+ "required": [
+ "success",
+ "message"
+ ],
+ "title": "CancelFlowResponse",
+ "description": "Response model for flow build cancellation."
+ },
+ "ChatOutputResponse": {
+ "properties": {
+ "message": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ },
+ "type": "array"
+ }
+ ],
+ "title": "Message"
+ },
+ "sender": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender",
+ "default": "Machine"
+ },
+ "sender_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender Name",
+ "default": "AI"
+ },
+ "session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id"
+ },
+ "stream_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Stream Url"
+ },
+ "component_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Component Id"
+ },
+ "files": {
+ "items": {
+ "$ref": "#/components/schemas/langflow__utils__schemas__File"
+ },
+ "type": "array",
+ "title": "Files",
+ "default": []
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "message",
+ "type"
+ ],
+ "title": "ChatOutputResponse",
+ "description": "Chat output response schema."
+ },
+ "Code": {
+ "properties": {
+ "code": {
+ "type": "string",
+ "title": "Code"
+ }
+ },
+ "type": "object",
+ "required": [
+ "code"
+ ],
+ "title": "Code"
+ },
+ "CodeContent": {
+ "type": "object"
+ },
+ "CodeValidationResponse": {
+ "properties": {
+ "imports": {
+ "type": "object",
+ "title": "Imports"
+ },
+ "function": {
+ "type": "object",
+ "title": "Function"
+ }
+ },
+ "type": "object",
+ "required": [
+ "imports",
+ "function"
+ ],
+ "title": "CodeValidationResponse"
+ },
+ "ConfigResponse": {
+ "properties": {
+ "feature_flags": {
+ "$ref": "#/components/schemas/FeatureFlags"
+ },
+ "frontend_timeout": {
+ "type": "integer",
+ "title": "Frontend Timeout"
+ },
+ "auto_saving": {
+ "type": "boolean",
+ "title": "Auto Saving"
+ },
+ "auto_saving_interval": {
+ "type": "integer",
+ "title": "Auto Saving Interval"
+ },
+ "health_check_max_retries": {
+ "type": "integer",
+ "title": "Health Check Max Retries"
+ },
+ "max_file_size_upload": {
+ "type": "integer",
+ "title": "Max File Size Upload"
+ },
+ "event_delivery": {
+ "type": "string",
+ "enum": [
+ "polling",
+ "streaming"
+ ],
+ "title": "Event Delivery"
+ }
+ },
+ "type": "object",
+ "required": [
+ "feature_flags",
+ "frontend_timeout",
+ "auto_saving",
+ "auto_saving_interval",
+ "health_check_max_retries",
+ "max_file_size_upload",
+ "event_delivery"
+ ],
+ "title": "ConfigResponse"
+ },
+ "ContentBlock": {
+ "properties": {
+ "title": {
+ "type": "string",
+ "title": "Title"
+ },
+ "contents": {
+ "items": {
+ "type": "object"
+ },
+ "type": "array",
+ "title": "Contents"
+ },
+ "allow_markdown": {
+ "type": "boolean",
+ "title": "Allow Markdown",
+ "default": true
+ },
+ "media_url": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Media Url"
+ }
+ },
+ "type": "object",
+ "required": [
+ "title",
+ "contents"
+ ],
+ "title": "ContentBlock",
+ "description": "A block of content that can contain different types of content."
+ },
+ "CreateComponentResponse": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "title": "CreateComponentResponse"
+ },
+ "CustomComponentRequest": {
+ "properties": {
+ "code": {
+ "type": "string",
+ "title": "Code"
+ },
+ "frontend_node": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Frontend Node"
+ }
+ },
+ "type": "object",
+ "required": [
+ "code"
+ ],
+ "title": "CustomComponentRequest"
+ },
+ "CustomComponentResponse": {
+ "properties": {
+ "data": {
+ "type": "object",
+ "title": "Data"
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "data",
+ "type"
+ ],
+ "title": "CustomComponentResponse"
+ },
+ "DownloadComponentResponse": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component"
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Metadata",
+ "default": {}
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "description",
+ "data",
+ "is_component"
+ ],
+ "title": "DownloadComponentResponse"
+ },
+ "EdgeData": {
+ "properties": {
+ "source": {
+ "type": "string",
+ "title": "Source"
+ },
+ "target": {
+ "type": "string",
+ "title": "Target"
+ },
+ "data": {
+ "$ref": "#/components/schemas/EdgeDataDetails"
+ }
+ },
+ "type": "object",
+ "title": "EdgeData"
+ },
+ "EdgeDataDetails": {
+ "properties": {
+ "sourceHandle": {
+ "$ref": "#/components/schemas/SourceHandleDict"
+ },
+ "targetHandle": {
+ "$ref": "#/components/schemas/TargetHandleDict"
+ }
+ },
+ "type": "object",
+ "required": [
+ "sourceHandle",
+ "targetHandle"
+ ],
+ "title": "EdgeDataDetails"
+ },
+ "ErrorContent": {
+ "type": "object"
+ },
+ "ErrorLog": {
+ "properties": {
+ "errorMessage": {
+ "type": "string",
+ "title": "Errormessage"
+ },
+ "stackTrace": {
+ "type": "string",
+ "title": "Stacktrace"
+ }
+ },
+ "type": "object",
+ "required": [
+ "errorMessage",
+ "stackTrace"
+ ],
+ "title": "ErrorLog"
+ },
+ "FeatureFlags": {
+ "properties": {
+ "mvp_components": {
+ "type": "boolean",
+ "title": "Mvp Components",
+ "default": false
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "title": "FeatureFlags"
+ },
+ "Flow": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "icon_bg_color": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon Bg Color"
+ },
+ "gradient": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Gradient"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component",
+ "default": false
+ },
+ "updated_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Updated At"
+ },
+ "webhook": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Webhook",
+ "description": "Can be used on the webhook endpoint",
+ "default": false
+ },
+ "endpoint_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Endpoint Name"
+ },
+ "tags": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags",
+ "default": []
+ },
+ "locked": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Locked",
+ "default": false
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "user_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ },
+ "folder_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "user_id"
+ ],
+ "title": "Flow"
+ },
+ "FlowCreate": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "icon_bg_color": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon Bg Color"
+ },
+ "gradient": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Gradient"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component",
+ "default": false
+ },
+ "updated_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Updated At"
+ },
+ "webhook": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Webhook",
+ "description": "Can be used on the webhook endpoint",
+ "default": false
+ },
+ "endpoint_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Endpoint Name"
+ },
+ "tags": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags"
+ },
+ "locked": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Locked",
+ "default": false
+ },
+ "user_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ },
+ "folder_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "title": "FlowCreate"
+ },
+ "FlowDataRequest": {
+ "properties": {
+ "nodes": {
+ "items": {
+ "type": "object"
+ },
+ "type": "array",
+ "title": "Nodes"
+ },
+ "edges": {
+ "items": {
+ "type": "object"
+ },
+ "type": "array",
+ "title": "Edges"
+ },
+ "viewport": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Viewport"
+ }
+ },
+ "type": "object",
+ "required": [
+ "nodes",
+ "edges"
+ ],
+ "title": "FlowDataRequest"
+ },
+ "FlowHeader": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id",
+ "description": "Unique identifier for the flow"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "description": "The name of the flow"
+ },
+ "folder_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id",
+ "description": "The ID of the folder containing the flow. None if not associated with a folder"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component",
+ "description": "Flag indicating whether the flow is a component"
+ },
+ "endpoint_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Endpoint Name",
+ "description": "The name of the endpoint associated with this flow"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description",
+ "description": "A description of the flow"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data",
+ "description": "The data of the component, if is_component is True"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "title": "FlowHeader",
+ "description": "Model representing a header for a flow - Without the data."
+ },
+ "FlowListCreate": {
+ "properties": {
+ "flows": {
+ "items": {
+ "$ref": "#/components/schemas/FlowCreate"
+ },
+ "type": "array",
+ "title": "Flows"
+ }
+ },
+ "type": "object",
+ "required": [
+ "flows"
+ ],
+ "title": "FlowListCreate"
+ },
+ "FlowRead": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "icon_bg_color": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon Bg Color"
+ },
+ "gradient": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Gradient"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component",
+ "default": false
+ },
+ "updated_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Updated At"
+ },
+ "webhook": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Webhook",
+ "description": "Can be used on the webhook endpoint",
+ "default": false
+ },
+ "endpoint_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Endpoint Name"
+ },
+ "tags": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags"
+ },
+ "locked": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Locked",
+ "default": false
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "user_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Id"
+ },
+ "folder_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "id",
+ "user_id",
+ "folder_id"
+ ],
+ "title": "FlowRead"
+ },
+ "FlowUpdate": {
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Data"
+ },
+ "folder_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Folder Id"
+ },
+ "endpoint_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Endpoint Name"
+ },
+ "locked": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Locked"
+ }
+ },
+ "type": "object",
+ "title": "FlowUpdate"
+ },
+ "FolderCreate": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "components_list": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Components List"
+ },
+ "flows_list": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Flows List"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "title": "FolderCreate"
+ },
+ "FolderRead": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "parent_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Parent Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "id",
+ "parent_id"
+ ],
+ "title": "FolderRead"
+ },
+ "FolderReadWithFlows": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "parent_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Parent Id"
+ },
+ "flows": {
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "type": "array",
+ "title": "Flows",
+ "default": []
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "id",
+ "parent_id"
+ ],
+ "title": "FolderReadWithFlows"
+ },
+ "FolderUpdate": {
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "parent_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Parent Id"
+ },
+ "components": {
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": "array",
+ "title": "Components"
+ },
+ "flows": {
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": "array",
+ "title": "Flows"
+ }
+ },
+ "type": "object",
+ "title": "FolderUpdate"
+ },
+ "FolderWithPaginatedFlows": {
+ "properties": {
+ "folder": {
+ "$ref": "#/components/schemas/FolderRead"
+ },
+ "flows": {
+ "$ref": "#/components/schemas/Page_Flow_"
+ }
+ },
+ "type": "object",
+ "required": [
+ "folder",
+ "flows"
+ ],
+ "title": "FolderWithPaginatedFlows"
+ },
+ "FrontendNodeRequest-Input": {
+ "properties": {
+ "template": {
+ "type": "object",
+ "title": "Template"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "is_input": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Input"
+ },
+ "is_output": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Output"
+ },
+ "is_composition": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Composition"
+ },
+ "base_classes": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Base Classes"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "default": ""
+ },
+ "display_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Display Name",
+ "default": ""
+ },
+ "documentation": {
+ "type": "string",
+ "title": "Documentation",
+ "default": ""
+ },
+ "minimized": {
+ "type": "boolean",
+ "title": "Minimized",
+ "default": false
+ },
+ "custom_fields": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Custom Fields"
+ },
+ "output_types": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Output Types",
+ "default": []
+ },
+ "full_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Full Path"
+ },
+ "pinned": {
+ "type": "boolean",
+ "title": "Pinned",
+ "default": false
+ },
+ "conditional_paths": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Conditional Paths",
+ "default": []
+ },
+ "frozen": {
+ "type": "boolean",
+ "title": "Frozen",
+ "default": false
+ },
+ "outputs": {
+ "items": {
+ "$ref": "#/components/schemas/Output"
+ },
+ "type": "array",
+ "title": "Outputs",
+ "default": []
+ },
+ "field_order": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Field Order",
+ "default": []
+ },
+ "beta": {
+ "type": "boolean",
+ "title": "Beta",
+ "default": false
+ },
+ "legacy": {
+ "type": "boolean",
+ "title": "Legacy",
+ "default": false
+ },
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Error"
+ },
+ "edited": {
+ "type": "boolean",
+ "title": "Edited",
+ "default": false
+ },
+ "metadata": {
+ "type": "object",
+ "title": "Metadata",
+ "default": {}
+ },
+ "tool_mode": {
+ "type": "boolean",
+ "title": "Tool Mode",
+ "default": false
+ }
+ },
+ "type": "object",
+ "required": [
+ "template",
+ "base_classes"
+ ],
+ "title": "FrontendNodeRequest"
+ },
+ "FrontendNodeRequest-Output": {
+ "properties": {
+ "template": {
+ "type": "object",
+ "title": "Template"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "is_input": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Input"
+ },
+ "is_output": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Output"
+ },
+ "is_composition": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Composition"
+ },
+ "base_classes": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Base Classes"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "default": ""
+ },
+ "display_name": {
+ "type": "string",
+ "title": "Display Name"
+ },
+ "documentation": {
+ "type": "string",
+ "title": "Documentation",
+ "default": ""
+ },
+ "minimized": {
+ "type": "boolean",
+ "title": "Minimized",
+ "default": false
+ },
+ "custom_fields": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Custom Fields"
+ },
+ "output_types": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Output Types",
+ "default": []
+ },
+ "full_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Full Path"
+ },
+ "pinned": {
+ "type": "boolean",
+ "title": "Pinned",
+ "default": false
+ },
+ "conditional_paths": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Conditional Paths",
+ "default": []
+ },
+ "frozen": {
+ "type": "boolean",
+ "title": "Frozen",
+ "default": false
+ },
+ "outputs": {
+ "items": {
+ "$ref": "#/components/schemas/Output"
+ },
+ "type": "array",
+ "title": "Outputs",
+ "default": []
+ },
+ "field_order": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Field Order",
+ "default": []
+ },
+ "beta": {
+ "type": "boolean",
+ "title": "Beta",
+ "default": false
+ },
+ "legacy": {
+ "type": "boolean",
+ "title": "Legacy",
+ "default": false
+ },
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Error"
+ },
+ "edited": {
+ "type": "boolean",
+ "title": "Edited",
+ "default": false
+ },
+ "metadata": {
+ "type": "object",
+ "title": "Metadata",
+ "default": {}
+ },
+ "tool_mode": {
+ "type": "boolean",
+ "title": "Tool Mode",
+ "default": false
+ }
+ },
+ "type": "object",
+ "required": [
+ "template",
+ "base_classes"
+ ],
+ "title": "FrontendNodeRequest"
+ },
+ "GraphData": {
+ "properties": {
+ "nodes": {
+ "items": {
+ "$ref": "#/components/schemas/NodeData"
+ },
+ "type": "array",
+ "title": "Nodes"
+ },
+ "edges": {
+ "items": {
+ "$ref": "#/components/schemas/EdgeData"
+ },
+ "type": "array",
+ "title": "Edges"
+ },
+ "viewport": {
+ "$ref": "#/components/schemas/ViewPort"
+ }
+ },
+ "type": "object",
+ "required": [
+ "nodes",
+ "edges"
+ ],
+ "title": "GraphData"
+ },
+ "GraphDump": {
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/GraphData"
+ },
+ "is_component": {
+ "type": "boolean",
+ "title": "Is Component"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "type": "string",
+ "title": "Description"
+ },
+ "endpoint_name": {
+ "type": "string",
+ "title": "Endpoint Name"
+ }
+ },
+ "type": "object",
+ "title": "GraphDump"
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "type": "array",
+ "title": "Detail"
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError"
+ },
+ "HeaderDict": {
+ "properties": {
+ "title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Title"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ }
+ },
+ "type": "object",
+ "title": "HeaderDict"
+ },
+ "HealthResponse": {
+ "properties": {
+ "status": {
+ "type": "string",
+ "title": "Status",
+ "default": "nok"
+ },
+ "chat": {
+ "type": "string",
+ "title": "Chat",
+ "default": "error check the server logs"
+ },
+ "db": {
+ "type": "string",
+ "title": "Db",
+ "default": "error check the server logs"
+ }
+ },
+ "type": "object",
+ "title": "HealthResponse"
+ },
+ "InputValueRequest": {
+ "properties": {
+ "components": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Components",
+ "default": []
+ },
+ "input_value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input Value"
+ },
+ "session": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "chat",
+ "text",
+ "any"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Type",
+ "description": "Defines on which components the input value should be applied. 'any' applies to all input components.",
+ "default": "any"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object",
+ "title": "InputValueRequest",
+ "examples": [
+ {
+ "components": [
+ "components_id",
+ "Component Name"
+ ],
+ "input_value": "input_value",
+ "session": "session_id"
+ },
+ {
+ "components": [
+ "Component Name"
+ ],
+ "input_value": "input_value"
+ },
+ {
+ "input_value": "input_value"
+ },
+ {
+ "components": [
+ "Component Name"
+ ],
+ "input_value": "input_value",
+ "session": "session_id"
+ },
+ {
+ "input_value": "input_value",
+ "session": "session_id"
+ },
+ {
+ "input_value": "input_value",
+ "type": "chat"
+ },
+ {
+ "input_value": "{\"key\": \"value\"}",
+ "type": "json"
+ }
+ ]
+ },
+ "JSONContent": {
+ "type": "object"
+ },
+ "ListComponentResponse": {
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "liked_by_count": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Liked By Count"
+ },
+ "liked_by_user": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Liked By User"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component"
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Metadata",
+ "default": {}
+ },
+ "user_created": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "User Created",
+ "default": {}
+ },
+ "tags": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/TagResponse"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags"
+ },
+ "downloads_count": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Downloads Count"
+ },
+ "last_tested_version": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Tested Version"
+ },
+ "private": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Private"
+ }
+ },
+ "type": "object",
+ "title": "ListComponentResponse"
+ },
+ "ListComponentResponseModel": {
+ "properties": {
+ "count": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Count",
+ "default": 0
+ },
+ "authorized": {
+ "type": "boolean",
+ "title": "Authorized"
+ },
+ "results": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/ListComponentResponse"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Results"
+ }
+ },
+ "type": "object",
+ "required": [
+ "authorized",
+ "results"
+ ],
+ "title": "ListComponentResponseModel"
+ },
+ "Log": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "message": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object"
+ },
+ {
+ "items": {},
+ "type": "array"
+ },
+ {
+ "type": "integer"
+ },
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#/components/schemas/BaseModel"
+ },
+ {
+ "$ref": "#/components/schemas/PlaygroundEvent"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Message"
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "message",
+ "type"
+ ],
+ "title": "Log"
+ },
+ "MediaContent": {
+ "type": "object"
+ },
+ "MessageRead": {
+ "properties": {
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Timestamp"
+ },
+ "sender": {
+ "type": "string",
+ "title": "Sender"
+ },
+ "sender_name": {
+ "type": "string",
+ "title": "Sender Name"
+ },
+ "session_id": {
+ "type": "string",
+ "title": "Session Id"
+ },
+ "text": {
+ "type": "string",
+ "title": "Text"
+ },
+ "files": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Files"
+ },
+ "error": {
+ "type": "boolean",
+ "title": "Error",
+ "default": false
+ },
+ "edit": {
+ "type": "boolean",
+ "title": "Edit",
+ "default": false
+ },
+ "properties": {
+ "$ref": "#/components/schemas/Properties"
+ },
+ "category": {
+ "type": "string",
+ "title": "Category",
+ "default": "message"
+ },
+ "content_blocks": {
+ "items": {
+ "$ref": "#/components/schemas/ContentBlock"
+ },
+ "type": "array",
+ "title": "Content Blocks"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "flow_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Flow Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "sender",
+ "sender_name",
+ "session_id",
+ "text",
+ "id",
+ "flow_id"
+ ],
+ "title": "MessageRead"
+ },
+ "MessageResponse": {
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id"
+ },
+ "flow_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Flow Id"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Timestamp"
+ },
+ "sender": {
+ "type": "string",
+ "title": "Sender"
+ },
+ "sender_name": {
+ "type": "string",
+ "title": "Sender Name"
+ },
+ "session_id": {
+ "type": "string",
+ "title": "Session Id"
+ },
+ "text": {
+ "type": "string",
+ "title": "Text"
+ },
+ "files": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Files",
+ "default": []
+ },
+ "edit": {
+ "type": "boolean",
+ "title": "Edit"
+ },
+ "properties": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Properties"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "category": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Category"
+ },
+ "content_blocks": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/ContentBlock"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Content Blocks"
+ }
+ },
+ "type": "object",
+ "required": [
+ "sender",
+ "sender_name",
+ "session_id",
+ "text",
+ "edit"
+ ],
+ "title": "MessageResponse"
+ },
+ "MessageUpdate": {
+ "properties": {
+ "text": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Text"
+ },
+ "sender": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender"
+ },
+ "sender_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender Name"
+ },
+ "session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id"
+ },
+ "files": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Files"
+ },
+ "edit": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Edit"
+ },
+ "error": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Error"
+ },
+ "properties": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Properties"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object",
+ "title": "MessageUpdate"
+ },
+ "NodeData": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "data": {
+ "type": "object",
+ "title": "Data"
+ },
+ "dragging": {
+ "type": "boolean",
+ "title": "Dragging"
+ },
+ "height": {
+ "type": "integer",
+ "title": "Height"
+ },
+ "width": {
+ "type": "integer",
+ "title": "Width"
+ },
+ "position": {
+ "$ref": "#/components/schemas/Position"
+ },
+ "positionAbsolute": {
+ "$ref": "#/components/schemas/Position"
+ },
+ "selected": {
+ "type": "boolean",
+ "title": "Selected"
+ },
+ "parent_node_id": {
+ "type": "string",
+ "title": "Parent Node Id"
+ },
+ "type": {
+ "$ref": "#/components/schemas/NodeTypeEnum"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "data"
+ ],
+ "title": "NodeData"
+ },
+ "NodeTypeEnum": {
+ "type": "string",
+ "enum": [
+ "noteNode",
+ "genericNode"
+ ],
+ "title": "NodeTypeEnum"
+ },
+ "Output": {
+ "properties": {
+ "types": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Types",
+ "default": []
+ },
+ "selected": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Selected"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "description": "The name of the field."
+ },
+ "hidden": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Hidden"
+ },
+ "display_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Display Name"
+ },
+ "method": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Method"
+ },
+ "value": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Value",
+ "default": "__UNDEFINED__"
+ },
+ "cache": {
+ "type": "boolean",
+ "title": "Cache",
+ "default": true
+ },
+ "required_inputs": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Required Inputs"
+ },
+ "allows_loop": {
+ "type": "boolean",
+ "title": "Allows Loop",
+ "default": false
+ },
+ "tool_mode": {
+ "type": "boolean",
+ "title": "Tool Mode",
+ "default": true
+ }
+ },
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "title": "Output"
+ },
+ "OutputValue": {
+ "properties": {
+ "message": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ErrorLog"
+ },
+ {
+ "$ref": "#/components/schemas/StreamURL"
+ },
+ {
+ "type": "object"
+ },
+ {
+ "items": {},
+ "type": "array"
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "title": "Message"
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "message",
+ "type"
+ ],
+ "title": "OutputValue"
+ },
+ "Page_FlowRead_": {
+ "properties": {
+ "items": {
+ "items": {
+ "$ref": "#/components/schemas/FlowRead"
+ },
+ "type": "array",
+ "title": "Items"
+ },
+ "total": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Total"
+ },
+ "page": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Page"
+ },
+ "size": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Size"
+ },
+ "pages": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Pages"
+ }
+ },
+ "type": "object",
+ "required": [
+ "items",
+ "total",
+ "page",
+ "size"
+ ],
+ "title": "Page[FlowRead]"
+ },
+ "Page_Flow_": {
+ "properties": {
+ "items": {
+ "items": {
+ "$ref": "#/components/schemas/Flow"
+ },
+ "type": "array",
+ "title": "Items"
+ },
+ "total": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Total"
+ },
+ "page": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Page"
+ },
+ "size": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Size"
+ },
+ "pages": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Pages"
+ }
+ },
+ "type": "object",
+ "required": [
+ "items",
+ "total",
+ "page",
+ "size"
+ ],
+ "title": "Page[Flow]"
+ },
+ "Page_TransactionTable_": {
+ "properties": {
+ "items": {
+ "items": {
+ "$ref": "#/components/schemas/TransactionTable"
+ },
+ "type": "array",
+ "title": "Items"
+ },
+ "total": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Total"
+ },
+ "page": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Page"
+ },
+ "size": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Size"
+ },
+ "pages": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0.0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Pages"
+ }
+ },
+ "type": "object",
+ "required": [
+ "items",
+ "total",
+ "page",
+ "size"
+ ],
+ "title": "Page[TransactionTable]"
+ },
+ "PlaygroundEvent": {
+ "properties": {
+ "properties": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Properties"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "sender_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sender Name"
+ },
+ "content_blocks": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/ContentBlock"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Content Blocks"
+ },
+ "format_type": {
+ "type": "string",
+ "enum": [
+ "default",
+ "error",
+ "warning",
+ "info"
+ ],
+ "title": "Format Type",
+ "default": "default"
+ },
+ "files": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Files"
+ },
+ "text": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Text"
+ },
+ "timestamp": {
+ "type": "string",
+ "title": "Timestamp"
+ },
+ "id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id"
+ }
+ },
+ "additionalProperties": true,
+ "type": "object",
+ "title": "PlaygroundEvent"
+ },
+ "Position": {
+ "properties": {
+ "x": {
+ "type": "number",
+ "title": "X"
+ },
+ "y": {
+ "type": "number",
+ "title": "Y"
+ }
+ },
+ "type": "object",
+ "required": [
+ "x",
+ "y"
+ ],
+ "title": "Position"
+ },
+ "PromptValidationResponse": {
+ "properties": {
+ "input_variables": {
+ "items": {},
+ "type": "array",
+ "title": "Input Variables"
+ },
+ "frontend_node": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FrontendNodeRequest-Output"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object",
+ "required": [
+ "input_variables"
+ ],
+ "title": "PromptValidationResponse"
+ },
+ "Properties": {
+ "properties": {
+ "text_color": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Text Color"
+ },
+ "background_color": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Background Color"
+ },
+ "edited": {
+ "type": "boolean",
+ "title": "Edited",
+ "default": false
+ },
+ "source": {
+ "$ref": "#/components/schemas/Source"
+ },
+ "icon": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Icon"
+ },
+ "allow_markdown": {
+ "type": "boolean",
+ "title": "Allow Markdown",
+ "default": false
+ },
+ "positive_feedback": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Positive Feedback"
+ },
+ "state": {
+ "type": "string",
+ "enum": [
+ "partial",
+ "complete"
+ ],
+ "title": "State",
+ "default": "complete"
+ },
+ "targets": {
+ "items": {},
+ "type": "array",
+ "title": "Targets",
+ "default": []
+ }
+ },
+ "type": "object",
+ "title": "Properties"
+ },
+ "ResultData": {
+ "properties": {
+ "results": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Results"
+ },
+ "artifacts": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Artifacts"
+ },
+ "outputs": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Outputs"
+ },
+ "logs": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Logs"
+ },
+ "messages": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/ChatOutputResponse"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Messages"
+ },
+ "timedelta": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Timedelta"
+ },
+ "duration": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Duration"
+ },
+ "component_display_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Component Display Name"
+ },
+ "component_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Component Id"
+ },
+ "used_frozen_result": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Used Frozen Result",
+ "default": false
+ }
+ },
+ "type": "object",
+ "title": "ResultData"
+ },
+ "ResultDataResponse": {
+ "type": "object"
+ },
+ "RunOutputs": {
+ "properties": {
+ "inputs": {
+ "type": "object",
+ "title": "Inputs"
+ },
+ "outputs": {
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ResultData"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "type": "array",
+ "title": "Outputs"
+ }
+ },
+ "type": "object",
+ "title": "RunOutputs"
+ },
+ "RunResponse": {
+ "properties": {
+ "outputs": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/components/schemas/RunOutputs"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Outputs",
+ "default": []
+ },
+ "session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id"
+ }
+ },
+ "type": "object",
+ "title": "RunResponse",
+ "description": "Run response schema."
+ },
+ "SimplifiedAPIRequest": {
+ "properties": {
+ "input_value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input Value",
+ "description": "The input value"
+ },
+ "input_type": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "chat",
+ "text",
+ "any"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Input Type",
+ "description": "The input type",
+ "default": "chat"
+ },
+ "output_type": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "chat",
+ "text",
+ "any",
+ "debug"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Output Type",
+ "description": "The output type",
+ "default": "chat"
+ },
+ "output_component": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Output Component",
+ "description": "If there are multiple output components, you can specify the component to get the output from.",
+ "default": ""
+ },
+ "tweaks": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Tweaks"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The tweaks"
+ },
+ "session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Session Id",
+ "description": "The session id"
+ }
+ },
+ "type": "object",
+ "title": "SimplifiedAPIRequest"
+ },
+ "Source": {
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id",
+ "description": "The id of the source component."
+ },
+ "display_name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Display Name",
+ "description": "The display name of the source component."
+ },
+ "source": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Source",
+ "description": "The source of the message. Normally used to display the model name (e.g. 'gpt-4o')"
+ }
+ },
+ "type": "object",
+ "title": "Source"
+ },
+ "SourceHandleDict": {
+ "properties": {
+ "baseClasses": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Baseclasses"
+ },
+ "dataType": {
+ "type": "string",
+ "title": "Datatype"
+ },
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "output_types": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Output Types"
+ }
+ },
+ "type": "object",
+ "title": "SourceHandleDict"
+ },
+ "StoreComponentCreate": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Description"
+ },
+ "data": {
+ "type": "object",
+ "title": "Data"
+ },
+ "tags": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Tags"
+ },
+ "parent": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Parent"
+ },
+ "is_component": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Component"
+ },
+ "last_tested_version": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Tested Version"
+ },
+ "private": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Private",
+ "default": true
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "description",
+ "data",
+ "tags",
+ "is_component"
+ ],
+ "title": "StoreComponentCreate"
+ },
+ "StreamURL": {
+ "properties": {
+ "location": {
+ "type": "string",
+ "title": "Location"
+ }
+ },
+ "type": "object",
+ "required": [
+ "location"
+ ],
+ "title": "StreamURL"
+ },
+ "TagResponse": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name"
+ ],
+ "title": "TagResponse"
+ },
+ "TargetHandleDict": {
+ "properties": {
+ "fieldName": {
+ "type": "string",
+ "title": "Fieldname"
+ },
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "inputTypes": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Inputtypes"
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "fieldName",
+ "id",
+ "inputTypes",
+ "type"
+ ],
+ "title": "TargetHandleDict"
+ },
+ "TaskStatusResponse": {
+ "properties": {
+ "status": {
+ "type": "string",
+ "title": "Status"
+ },
+ "result": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Result"
+ }
+ },
+ "type": "object",
+ "required": [
+ "status"
+ ],
+ "title": "TaskStatusResponse",
+ "description": "Task status response schema."
+ },
+ "TextContent": {
+ "type": "object"
+ },
+ "Token": {
+ "properties": {
+ "access_token": {
+ "type": "string",
+ "title": "Access Token"
+ },
+ "refresh_token": {
+ "type": "string",
+ "title": "Refresh Token"
+ },
+ "token_type": {
+ "type": "string",
+ "title": "Token Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "access_token",
+ "refresh_token",
+ "token_type"
+ ],
+ "title": "Token"
+ },
+ "ToolContent": {
+ "type": "object"
+ },
+ "TransactionTable": {
+ "properties": {
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Timestamp"
+ },
+ "vertex_id": {
+ "type": "string",
+ "title": "Vertex Id"
+ },
+ "target_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Target Id"
+ },
+ "inputs": {
+ "type": "object",
+ "title": "Inputs"
+ },
+ "outputs": {
+ "type": "object",
+ "title": "Outputs"
+ },
+ "status": {
+ "type": "string",
+ "title": "Status"
+ },
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Error"
+ },
+ "flow_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ },
+ "id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "vertex_id",
+ "status",
+ "flow_id"
+ ],
+ "title": "TransactionTable"
+ },
+ "Tweaks": {
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ },
+ "type": "object",
+ "title": "Tweaks",
+ "description": "A dictionary of tweaks to adjust the flow's execution. Allows customizing flow behavior dynamically. All tweaks are overridden by the input values.",
+ "examples": [
+ {
+ "Component Name": {
+ "parameter_name": "value"
+ },
+ "component_id": {
+ "parameter_name": "value"
+ },
+ "parameter_name": "value"
+ }
+ ]
+ },
+ "UnmaskedApiKeyRead": {
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name"
+ },
+ "last_used_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Used At"
+ },
+ "total_uses": {
+ "type": "integer",
+ "title": "Total Uses",
+ "default": 0
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Is Active",
+ "default": true
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "api_key": {
+ "type": "string",
+ "title": "Api Key"
+ },
+ "user_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "api_key",
+ "user_id"
+ ],
+ "title": "UnmaskedApiKeyRead"
+ },
+ "UpdateCustomComponentRequest": {
+ "properties": {
+ "code": {
+ "type": "string",
+ "title": "Code"
+ },
+ "frontend_node": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Frontend Node"
+ },
+ "field": {
+ "type": "string",
+ "title": "Field"
+ },
+ "field_value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer"
+ },
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "object"
+ },
+ {
+ "items": {},
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Field Value"
+ },
+ "template": {
+ "type": "object",
+ "title": "Template"
+ },
+ "tool_mode": {
+ "type": "boolean",
+ "title": "Tool Mode",
+ "default": false
+ }
+ },
+ "type": "object",
+ "required": [
+ "code",
+ "field",
+ "template"
+ ],
+ "title": "UpdateCustomComponentRequest"
+ },
+ "UserCreate": {
+ "properties": {
+ "username": {
+ "type": "string",
+ "title": "Username"
+ },
+ "password": {
+ "type": "string",
+ "title": "Password"
+ }
+ },
+ "type": "object",
+ "required": [
+ "username",
+ "password"
+ ],
+ "title": "UserCreate"
+ },
+ "UserRead": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "username": {
+ "type": "string",
+ "title": "Username"
+ },
+ "profile_image": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Profile Image"
+ },
+ "store_api_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Store Api Key"
+ },
+ "is_active": {
+ "type": "boolean",
+ "title": "Is Active"
+ },
+ "is_superuser": {
+ "type": "boolean",
+ "title": "Is Superuser"
+ },
+ "create_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Create At"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Updated At"
+ },
+ "last_login_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Login At"
+ }
+ },
+ "type": "object",
+ "required": [
+ "username",
+ "profile_image",
+ "store_api_key",
+ "is_active",
+ "is_superuser",
+ "create_at",
+ "updated_at",
+ "last_login_at"
+ ],
+ "title": "UserRead"
+ },
+ "UserUpdate": {
+ "properties": {
+ "username": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Username"
+ },
+ "profile_image": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Profile Image"
+ },
+ "password": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Password"
+ },
+ "is_active": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Active"
+ },
+ "is_superuser": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Is Superuser"
+ },
+ "last_login_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Last Login At"
+ }
+ },
+ "type": "object",
+ "title": "UserUpdate"
+ },
+ "UsersLikesResponse": {
+ "properties": {
+ "likes_count": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Likes Count"
+ },
+ "liked_by_user": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Liked By User"
+ }
+ },
+ "type": "object",
+ "required": [
+ "likes_count",
+ "liked_by_user"
+ ],
+ "title": "UsersLikesResponse"
+ },
+ "UsersResponse": {
+ "properties": {
+ "total_count": {
+ "type": "integer",
+ "title": "Total Count"
+ },
+ "users": {
+ "items": {
+ "$ref": "#/components/schemas/UserRead"
+ },
+ "type": "array",
+ "title": "Users"
+ }
+ },
+ "type": "object",
+ "required": [
+ "total_count",
+ "users"
+ ],
+ "title": "UsersResponse"
+ },
+ "ValidatePromptRequest": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "template": {
+ "type": "string",
+ "title": "Template"
+ },
+ "custom_fields": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Custom Fields"
+ },
+ "frontend_node": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FrontendNodeRequest-Input"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "template"
+ ],
+ "title": "ValidatePromptRequest"
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer"
+ }
+ ]
+ },
+ "type": "array",
+ "title": "Location"
+ },
+ "msg": {
+ "type": "string",
+ "title": "Message"
+ },
+ "type": {
+ "type": "string",
+ "title": "Error Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "title": "ValidationError"
+ },
+ "VariableCreate": {
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "Name",
+ "description": "Name of the variable"
+ },
+ "value": {
+ "type": "string",
+ "title": "Value",
+ "description": "Encrypted value of the variable"
+ },
+ "default_fields": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Default Fields"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Type",
+ "description": "Type of the variable"
+ },
+ "created_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Created At",
+ "description": "Creation time of the variable"
+ },
+ "updated_at": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Updated At",
+ "description": "Creation time of the variable"
+ }
+ },
+ "type": "object",
+ "required": [
+ "name",
+ "value",
+ "default_fields"
+ ],
+ "title": "VariableCreate"
+ },
+ "VariableRead": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name",
+ "description": "Name of the variable"
+ },
+ "type": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Type",
+ "description": "Type of the variable"
+ },
+ "value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Value",
+ "description": "Encrypted value of the variable"
+ },
+ "default_fields": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Default Fields",
+ "description": "Default fields for the variable"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "title": "VariableRead"
+ },
+ "VariableUpdate": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Name",
+ "description": "Name of the variable"
+ },
+ "value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Value",
+ "description": "Encrypted value of the variable"
+ },
+ "default_fields": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Default Fields",
+ "description": "Default fields for the variable"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "title": "VariableUpdate"
+ },
+ "VertexBuildMapModel": {
+ "properties": {
+ "vertex_builds": {
+ "additionalProperties": {
+ "items": {
+ "$ref": "#/components/schemas/VertexBuildTable"
+ },
+ "type": "array"
+ },
+ "type": "object",
+ "title": "Vertex Builds"
+ }
+ },
+ "type": "object",
+ "required": [
+ "vertex_builds"
+ ],
+ "title": "VertexBuildMapModel"
+ },
+ "VertexBuildResponse": {
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Id"
+ },
+ "inactivated_vertices": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Inactivated Vertices"
+ },
+ "next_vertices_ids": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Next Vertices Ids"
+ },
+ "top_level_vertices": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Top Level Vertices"
+ },
+ "valid": {
+ "type": "boolean",
+ "title": "Valid"
+ },
+ "params": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Params"
+ },
+ "data": {
+ "type": "object"
+ },
+ "timestamp": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Timestamp"
+ }
+ },
+ "type": "object",
+ "required": [
+ "valid",
+ "data"
+ ],
+ "title": "VertexBuildResponse"
+ },
+ "VertexBuildTable": {
+ "properties": {
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Timestamp"
+ },
+ "id": {
+ "type": "string",
+ "title": "Id"
+ },
+ "data": {
+ "type": "object",
+ "title": "Data"
+ },
+ "artifacts": {
+ "type": "object",
+ "title": "Artifacts"
+ },
+ "params": {
+ "type": "string",
+ "title": "Params"
+ },
+ "valid": {
+ "type": "boolean",
+ "title": "Valid"
+ },
+ "flow_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Flow Id"
+ },
+ "build_id": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uuid"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Build Id"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "valid",
+ "flow_id"
+ ],
+ "title": "VertexBuildTable"
+ },
+ "VerticesOrderResponse": {
+ "properties": {
+ "ids": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Ids"
+ },
+ "run_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Run Id"
+ },
+ "vertices_to_run": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "Vertices To Run"
+ }
+ },
+ "type": "object",
+ "required": [
+ "ids",
+ "run_id",
+ "vertices_to_run"
+ ],
+ "title": "VerticesOrderResponse"
+ },
+ "ViewPort": {
+ "properties": {
+ "x": {
+ "type": "number",
+ "title": "X"
+ },
+ "y": {
+ "type": "number",
+ "title": "Y"
+ },
+ "zoom": {
+ "type": "number",
+ "title": "Zoom"
+ }
+ },
+ "type": "object",
+ "required": [
+ "x",
+ "y",
+ "zoom"
+ ],
+ "title": "ViewPort"
+ },
+ "langflow__api__schemas__UploadFileResponse": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "path": {
+ "type": "string",
+ "format": "path",
+ "title": "Path"
+ },
+ "size": {
+ "type": "integer",
+ "title": "Size"
+ },
+ "provider": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Provider"
+ }
+ },
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "path",
+ "size"
+ ],
+ "title": "UploadFileResponse",
+ "description": "File upload response schema."
+ },
+ "langflow__api__v1__schemas__UploadFileResponse": {
+ "properties": {
+ "flowId": {
+ "type": "string",
+ "title": "Flowid"
+ },
+ "file_path": {
+ "type": "string",
+ "format": "path",
+ "title": "File Path"
+ }
+ },
+ "type": "object",
+ "required": [
+ "flowId",
+ "file_path"
+ ],
+ "title": "UploadFileResponse",
+ "description": "Upload file response schema."
+ },
+ "langflow__services__database__models__file__model__File": {
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "Id"
+ },
+ "user_id": {
+ "type": "string",
+ "format": "uuid",
+ "title": "User Id"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "path": {
+ "type": "string",
+ "title": "Path"
+ },
+ "size": {
+ "type": "integer",
+ "title": "Size"
+ },
+ "provider": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Provider"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Created At"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "title": "Updated At"
+ }
+ },
+ "type": "object",
+ "required": [
+ "user_id",
+ "name",
+ "path",
+ "size"
+ ],
+ "title": "File"
+ },
+ "langflow__utils__schemas__File": {
+ "properties": {
+ "path": {
+ "type": "string",
+ "title": "Path"
+ },
+ "name": {
+ "type": "string",
+ "title": "Name"
+ },
+ "type": {
+ "type": "string",
+ "title": "Type"
+ }
+ },
+ "type": "object",
+ "required": [
+ "path",
+ "name",
+ "type"
+ ],
+ "title": "File",
+ "description": "File schema."
+ }
+ },
+ "securitySchemes": {
+ "OAuth2PasswordBearer": {
+ "type": "oauth2",
+ "flows": {
+ "password": {
+ "scopes": {},
+ "tokenUrl": "api/v1/login"
+ }
+ }
+ },
+ "API key query": {
+ "type": "apiKey",
+ "in": "query",
+ "name": "x-api-key"
+ },
+ "API key header": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "x-api-key"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/langflow/docs/package.json b/langflow/docs/package.json
new file mode 100644
index 0000000..cb437e3
--- /dev/null
+++ b/langflow/docs/package.json
@@ -0,0 +1,68 @@
+{
+ "name": "langflow-docs",
+ "version": "0.0.0",
+ "private": false,
+ "scripts": {
+ "clear-docs": "rimraf ./docs/",
+ "pull": "yarn clear-docs && yarn dlx @sillsdev/docu-notion -n $NOTION_TOKEN -r $NOTION_DOCS_ROOT_PAGE_ID",
+ "start": "docusaurus start",
+ "build": "docusaurus build",
+ "deploy": "docusaurus deploy",
+ "serve": "docusaurus serve"
+ },
+ "dependencies": {
+ "@code-hike/mdx": "^0.9.0",
+ "@docusaurus/core": "3.7.0",
+ "@docusaurus/plugin-client-redirects": "^3.7.0",
+ "@docusaurus/plugin-google-tag-manager": "^3.7.0",
+ "@docusaurus/preset-classic": "3.7.0",
+ "@easyops-cn/docusaurus-search-local": "^0.45.0",
+ "@mdx-js/react": "^3.0.1",
+ "@mendable/search": "^0.0.206",
+ "@sillsdev/docu-notion": "^0.15.0",
+ "clsx": "^1.2.1",
+ "docusaurus-node-polyfills": "^1.0.0",
+ "docusaurus-plugin-image-zoom": "^2.0.0",
+ "docusaurus-plugin-openapi": "^0.7.6",
+ "docusaurus-plugin-openapi-docs": "^4.3.1",
+ "docusaurus-preset-openapi": "^0.7.6",
+ "docusaurus-theme-openapi-docs": "^4.3.1",
+ "lucide-react": "^0.460.0",
+ "prism-react-renderer": "^1.3.5",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-player": "^2.10.1",
+ "rimraf": "^4.1.2",
+ "tailwindcss": "^3.4.4"
+ },
+ "devDependencies": {
+ "@docusaurus/module-type-aliases": "^3.7.0",
+ "@tsconfig/docusaurus": "^2.0.3",
+ "cross-var": "^1.1.0",
+ "typescript": "^5.2.2"
+ },
+ "browserslist": {
+ "production": [
+ ">0.5%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "packageManager": "yarn@1.22.19",
+ "volta": {
+ "node": "18.18.0",
+ "yarn": "1.22.19"
+ },
+ "resolutions": {
+ "lodash": "4.17.21",
+ "openapi-to-postmanv2/lodash": "4.17.21",
+ "postman-collection/lodash": "4.17.21",
+ "json-schema-resolve-allof/lodash": "4.17.21",
+ "json-refs/lodash": "4.17.21"
+ }
+}
diff --git a/langflow/docs/sidebars.js b/langflow/docs/sidebars.js
new file mode 100644
index 0000000..1bab044
--- /dev/null
+++ b/langflow/docs/sidebars.js
@@ -0,0 +1,200 @@
+module.exports = {
+ docs: [
+ "Get-Started/welcome-to-langflow",
+ {
+ type: "category",
+ label: "Get started",
+ items: [
+ "Get-Started/get-started-installation",
+ "Get-Started/get-started-quickstart",
+ ],
+ },
+ {
+ type: "category",
+ label: "Starter projects",
+ items: [
+ 'Starter-Projects/starter-projects-basic-prompting',
+ 'Starter-Projects/starter-projects-vector-store-rag',
+ 'Starter-Projects/starter-projects-simple-agent',
+ ],
+ },
+ {
+ type: "category",
+ label: "Tutorials",
+ items: [
+ 'Tutorials/tutorials-blog-writer',
+ 'Tutorials/tutorials-document-qa',
+ 'Tutorials/tutorials-memory-chatbot',
+ 'Tutorials/tutorials-math-agent',
+ 'Tutorials/tutorials-sequential-agent',
+ 'Tutorials/tutorials-travel-planning-agent',
+ ],
+ },
+ {
+ type: "category",
+ label: "Concepts",
+ items: [
+ "Concepts/concepts-overview",
+ "Concepts/concepts-playground",
+ "Concepts/concepts-components",
+ "Concepts/concepts-flows",
+ "Concepts/concepts-objects",
+ "Concepts/concepts-api",
+ ],
+ },
+ {
+ type: "category",
+ label: "Components",
+ items: [
+ "Components/components-agents",
+ "Components/components-custom-components",
+ "Components/components-data",
+ "Components/components-embedding-models",
+ "Components/components-helpers",
+ "Components/components-io",
+ "Components/components-loaders",
+ "Components/components-logic",
+ "Components/components-memories",
+ "Components/components-models",
+ "Components/components-processing",
+ "Components/components-prompts",
+ "Components/components-tools",
+ "Components/components-vector-stores",
+ ],
+ },
+ {
+ type: "category",
+ label: "Agents",
+ items: [
+ "Agents/agents-overview",
+ "Agents/agent-tool-calling-agent-component",
+ ],
+ },
+ {
+ type: "category",
+ label: "Configuration",
+ items: [
+ "Configuration/configuration-api-keys",
+ "Configuration/configuration-authentication",
+ "Configuration/configuration-auto-saving",
+ "Configuration/configuration-backend-only",
+ "Configuration/configuration-cli",
+ "Configuration/configuration-global-variables",
+ "Configuration/environment-variables",
+ "Configuration/configuration-security-best-practices"
+ ],
+ },
+ {
+ type: "category",
+ label: "Deployment",
+ items: [
+ {
+ type: "doc",
+ id: "Deployment/deployment-docker",
+ label: "Docker"
+ },
+ {
+ type: "doc",
+ id: "Deployment/deployment-gcp",
+ label: "Google Cloud Platform"
+ },
+ {
+ type: "doc",
+ id: "Deployment/deployment-hugging-face-spaces",
+ label: "Hugging Face Spaces"
+ },
+ {
+ type: "doc",
+ id: "Deployment/deployment-kubernetes",
+ label: "Kubernetes"
+ },
+ {
+ type: "doc",
+ id: "Deployment/deployment-railway",
+ label: "Railway"
+ },
+ {
+ type: "doc",
+ id: "Deployment/deployment-render",
+ label: "Render"
+ }
+ ],
+ },
+ {
+ type: "category",
+ label: "API reference",
+ items: [
+ {
+ type: "link",
+ label: "API documentation",
+ href: "/api",
+ },
+ {
+ type: "doc",
+ id: "API-Reference/api-reference-api-examples",
+ label: "API examples",
+ },
+ ],
+ },
+ {
+ type: "category",
+ label: "Integrations",
+ items: [
+ "Integrations/Apify/integrations-apify",
+ "Integrations/Arize/integrations-arize",
+ "Integrations/integrations-assemblyai",
+ "Integrations/Composio/integrations-composio",
+ "Integrations/integrations-langfuse",
+ "Integrations/integrations-langsmith",
+ "Integrations/integrations-langwatch",
+ {
+ type: 'category',
+ label: 'Google',
+ items: [
+ 'Integrations/Google/integrations-setup-google-oauth-langflow',
+ 'Integrations/Google/integrations-setup-google-cloud-vertex-ai-langflow',
+ ],
+ },
+ {
+ type: "category",
+ label: "Notion",
+ items: [
+ "Integrations/Notion/integrations-notion",
+ "Integrations/Notion/notion-agent-conversational",
+ "Integrations/Notion/notion-agent-meeting-notes",
+ ],
+ },
+ {
+ type: "category",
+ label: "NVIDIA",
+ items: [
+ "Integrations/Nvidia/integrations-nvidia-ingest",
+ ],
+ },
+ ],
+ },
+ {
+ type: "category",
+ label: "Contributing",
+ items: [
+ "Contributing/contributing-community",
+ "Contributing/contributing-components",
+ "Contributing/contributing-github-discussion-board",
+ "Contributing/contributing-github-issues",
+ "Contributing/contributing-how-to-contribute",
+ "Contributing/contributing-telemetry",
+ ],
+ },
+ {
+ type: "category",
+ label: "Changelog",
+ items: [
+ {
+ type: "link",
+ label: "Changelog",
+ href: "https://github.com/langflow-ai/langflow/releases/latest",
+ },
+ ],
+ },
+ ],
+};
diff --git a/langflow/docs/src/components/icon/index.tsx b/langflow/docs/src/components/icon/index.tsx
new file mode 100644
index 0000000..de230ae
--- /dev/null
+++ b/langflow/docs/src/components/icon/index.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import * as LucideIcons from "lucide-react";
+
+/*
+How to use this component:
+
+import Icon from "@site/src/components/icon";
+
+
+*/
+
+type IconProps = {
+ name: string;
+};
+
+export default function Icon({ name, ...props }: IconProps) {
+ const Icon = LucideIcons[name];
+ return Icon ? : null;
+}
diff --git a/langflow/docs/src/theme/DownloadableJsonFile.js b/langflow/docs/src/theme/DownloadableJsonFile.js
new file mode 100644
index 0000000..7b5466e
--- /dev/null
+++ b/langflow/docs/src/theme/DownloadableJsonFile.js
@@ -0,0 +1,29 @@
+const DownloadableJsonFile = ({ source, title }) => {
+ const handleDownload = (event) => {
+ event.preventDefault();
+ fetch(source)
+ .then((response) => response.blob())
+ .then((blob) => {
+ const url = window.URL.createObjectURL(
+ new Blob([blob], { type: "application/json" })
+ );
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", title);
+ document.body.appendChild(link);
+ link.click();
+ link.parentNode.removeChild(link);
+ })
+ .catch((error) => {
+ console.error("Error downloading file:", error);
+ });
+ };
+
+ return (
+
+ {title}
+
+ );
+};
+
+export default DownloadableJsonFile;
diff --git a/langflow/docs/src/theme/Footer.js b/langflow/docs/src/theme/Footer.js
new file mode 100644
index 0000000..aed2e94
--- /dev/null
+++ b/langflow/docs/src/theme/Footer.js
@@ -0,0 +1,77 @@
+import React, { useState } from "react";
+import Footer from "@theme-original/Footer";
+import { useDocSearchKeyboardEvents } from '@docsearch/react';
+
+export default function FooterWrapper(props) {
+ const [isHovered, setIsHovered] = useState(false);
+ const searchButtonRef = React.useRef(null);
+
+ useDocSearchKeyboardEvents({
+ isOpen: false,
+ onOpen: () => {
+ searchButtonRef.current?.click();
+ },
+ });
+
+ const searchButton = (
+ {
+ // This will trigger Docusaurus's default search modal
+ document.querySelector('.DocSearch-Button')?.click();
+ }}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ style={{
+ position: 'fixed',
+ right: '20px',
+ bottom: '20px',
+ zIndex: 100,
+ display: 'flex',
+ alignItems: 'center',
+ gap: '10px',
+ cursor: 'pointer',
+ }}
+ >
+ {isHovered && (
+
+ Hi, how can I help you?
+
+ )}
+
+
+
+
+ );
+
+ return (
+ <>
+
+ {searchButton}
+ >
+ );
+}
diff --git a/langflow/docs/src/theme/ZoomableImage.js b/langflow/docs/src/theme/ZoomableImage.js
new file mode 100644
index 0000000..aeeb045
--- /dev/null
+++ b/langflow/docs/src/theme/ZoomableImage.js
@@ -0,0 +1,57 @@
+import React, { useState, useEffect } from "react";
+import ThemedImage from "@theme/ThemedImage";
+import useBaseUrl from "@docusaurus/useBaseUrl";
+
+const ZoomableImage = ({ alt, sources, style }) => {
+ // add style here
+ const [isFullscreen, setIsFullscreen] = useState(false);
+
+ const toggleFullscreen = () => {
+ setIsFullscreen(!isFullscreen);
+ };
+
+ const handleKeyPress = (event) => {
+ if (event.key === "Escape") {
+ setIsFullscreen(false);
+ }
+ };
+
+ useEffect(() => {
+ if (isFullscreen) {
+ document.addEventListener("keydown", handleKeyPress);
+ } else {
+ document.removeEventListener("keydown", handleKeyPress);
+ }
+
+ return () => {
+ document.removeEventListener("keydown", handleKeyPress);
+ };
+ }, [isFullscreen]);
+
+ // Default style
+ const defaultStyle = {
+ width: "50%",
+ margin: "0 auto",
+ display: "flex",
+ justifyContent: "center",
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default ZoomableImage;
diff --git a/langflow/docs/static/.nojekyll b/langflow/docs/static/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/docs/static/CNAME b/langflow/docs/static/CNAME
new file mode 100644
index 0000000..ab1d0c8
--- /dev/null
+++ b/langflow/docs/static/CNAME
@@ -0,0 +1 @@
+docs.langflow.org
\ No newline at end of file
diff --git a/langflow/docs/static/files/Google_Drive_Docs_Translations_Example.json b/langflow/docs/static/files/Google_Drive_Docs_Translations_Example.json
new file mode 100644
index 0000000..ca3c6dc
--- /dev/null
+++ b/langflow/docs/static/files/Google_Drive_Docs_Translations_Example.json
@@ -0,0 +1,1580 @@
+{
+ "id": "bc2cbf16-ed92-4ef6-a4a5-1526af21d044",
+ "data": {
+ "nodes": [
+ {
+ "id": "ParseData-LFq8b",
+ "type": "genericNode",
+ "position": {
+ "x": 1068.0112494649502,
+ "y": 237.00462961615915
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{data}",
+ "display_name": "Template",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "id": "ParseData-LFq8b"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 353,
+ "positionAbsolute": {
+ "x": 1068.0112494649502,
+ "y": 237.00462961615915
+ },
+ "dragging": false
+ },
+ {
+ "id": "GoogleDriveComponent-x1jHz",
+ "type": "genericNode",
+ "position": {
+ "x": 1919.003246541804,
+ "y": 230.60766717987485
+ },
+ "data": {
+ "type": "GoogleDriveComponent",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nfrom json.decoder import JSONDecodeError\n\nfrom google.auth.exceptions import RefreshError\nfrom google.oauth2.credentials import Credentials\nfrom langchain_google_community import GoogleDriveLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import docs_to_data\nfrom langflow.inputs import MessageTextInput\nfrom langflow.io import SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.template import Output\n\n\nclass GoogleDriveComponent(Component):\n display_name = \"Google Drive Loader\"\n description = \"Loads documents from Google Drive using provided credentials.\"\n icon = \"Google\"\n\n inputs = [\n SecretStrInput(\n name=\"json_string\",\n display_name=\"JSON String of the Service Account Token\",\n info=\"JSON string containing OAuth 2.0 access token information for service account access\",\n required=True,\n ),\n MessageTextInput(\n name=\"document_id\", display_name=\"Document ID\", info=\"Single Google Drive document ID\", required=True\n ),\n ]\n\n outputs = [\n Output(display_name=\"Loaded Documents\", name=\"docs\", method=\"load_documents\"),\n ]\n\n def load_documents(self) -> Data:\n class CustomGoogleDriveLoader(GoogleDriveLoader):\n creds: Credentials | None = None\n \"\"\"Credentials object to be passed directly.\"\"\"\n\n def _load_credentials(self):\n \"\"\"Load credentials from the provided creds attribute or fallback to the original method.\"\"\"\n if self.creds:\n return self.creds\n msg = \"No credentials provided.\"\n raise ValueError(msg)\n\n class Config:\n arbitrary_types_allowed = True\n\n json_string = self.json_string\n\n document_ids = [self.document_id]\n if len(document_ids) != 1:\n msg = \"Expected a single document ID\"\n raise ValueError(msg)\n\n # TODO: Add validation to check if the document ID is valid\n\n # Load the token information from the JSON string\n try:\n token_info = json.loads(json_string)\n except JSONDecodeError as e:\n msg = \"Invalid JSON string\"\n raise ValueError(msg) from e\n\n # Initialize the custom loader with the provided credentials and document IDs\n loader = CustomGoogleDriveLoader(\n creds=Credentials.from_authorized_user_info(token_info), document_ids=document_ids\n )\n\n # Load the documents\n try:\n docs = loader.load()\n # catch google.auth.exceptions.RefreshError\n except RefreshError as e:\n msg = \"Authentication error: Unable to refresh authentication token. Please try to reauthenticate.\"\n raise ValueError(msg) from e\n except Exception as e:\n msg = f\"Error loading documents: {e}\"\n raise ValueError(msg) from e\n\n assert len(docs) == 1, \"Expected a single document to be loaded.\"\n\n data = docs_to_data(docs)\n # Return the loaded documents\n self.status = data\n return Data(data={\"text\": data})\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "document_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "document_id",
+ "value": "YOUR-DOCUMENT-ID-HERE",
+ "display_name": "Document ID",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Single Google Drive document ID",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "json_string": {
+ "load_from_db": true,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "json_string",
+ "value": "",
+ "display_name": "JSON String of the Service Account Token",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "JSON string containing OAuth 2.0 access token information for service account access",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ }
+ },
+ "description": "Loads documents from Google Drive using provided credentials.",
+ "icon": "Google",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "Google Drive Loader",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "docs",
+ "display_name": "Loaded Documents",
+ "method": "load_documents",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "json_string",
+ "document_id"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "id": "GoogleDriveComponent-x1jHz",
+ "description": "Loads documents from Google Drive using provided credentials.",
+ "display_name": "Google Drive Loader"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 389,
+ "positionAbsolute": {
+ "x": 1919.003246541804,
+ "y": 230.60766717987485
+ },
+ "dragging": false
+ },
+ {
+ "id": "ParseData-7qhmP",
+ "type": "genericNode",
+ "position": {
+ "x": 2341.9094676839277,
+ "y": 249.73963276782303
+ },
+ "data": {
+ "type": "ParseData",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "data": {
+ "trace_as_metadata": true,
+ "list": false,
+ "trace_as_input": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data",
+ "value": "",
+ "display_name": "Data",
+ "advanced": false,
+ "input_types": [
+ "Data"
+ ],
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "DataInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "sep": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sep",
+ "value": "\n",
+ "display_name": "Separator",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "template": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{text}",
+ "display_name": "Template",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Convert Data into plain text following a specified template.",
+ "icon": "braces",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Parse Data",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text",
+ "display_name": "Text",
+ "method": "parse_data",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "id": "ParseData-7qhmP"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 353,
+ "positionAbsolute": {
+ "x": 2341.9094676839277,
+ "y": 249.73963276782303
+ },
+ "dragging": false
+ },
+ {
+ "id": "OpenAIModel-YUM1a",
+ "type": "genericNode",
+ "position": {
+ "x": 3231.963340176361,
+ "y": 94.70653241512102
+ },
+ "data": {
+ "type": "OpenAIModel",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "output_parser": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "output_parser",
+ "value": "",
+ "display_name": "Output Parser",
+ "advanced": true,
+ "input_types": [
+ "OutputParser"
+ ],
+ "dynamic": false,
+ "info": "The parser to use to parse the output of the model",
+ "title_case": false,
+ "type": "other",
+ "_input_type": "HandleInput"
+ },
+ "api_key": {
+ "load_from_db": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "api_key",
+ "value": "",
+ "display_name": "OpenAI API Key",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "title_case": false,
+ "password": true,
+ "type": "str",
+ "_input_type": "SecretStrInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.inputs.inputs import HandleInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. \"\n \"You must pass the word JSON in the prompt. \"\n \"If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n HandleInput(\n name=\"output_parser\",\n display_name=\"Output Parser\",\n info=\"The parser to use to parse the output of the model\",\n advanced=True,\n input_types=[\"OutputParser\"],\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # self.output_schema is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n\n api_key = SecretStr(openai_api_key) if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\")\n else:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageInput"
+ },
+ "json_mode": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "json_mode",
+ "value": false,
+ "display_name": "JSON Mode",
+ "advanced": true,
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "max_tokens": {
+ "trace_as_metadata": true,
+ "range_spec": {
+ "step_type": "float",
+ "min": 0,
+ "max": 128000,
+ "step": 0.1
+ },
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "max_tokens",
+ "value": "",
+ "display_name": "Max Tokens",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "model_kwargs": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_kwargs",
+ "value": {},
+ "display_name": "Model Kwargs",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "model_name": {
+ "trace_as_metadata": true,
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-0125"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_name",
+ "value": "gpt-4o-mini",
+ "display_name": "Model Name",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "openai_api_base": {
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "openai_api_base",
+ "value": "",
+ "display_name": "OpenAI API Base",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "StrInput"
+ },
+ "output_schema": {
+ "trace_as_input": true,
+ "list": true,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "output_schema",
+ "value": {},
+ "display_name": "Schema",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "seed": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "seed",
+ "value": 1,
+ "display_name": "Seed",
+ "advanced": true,
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "stream": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stream",
+ "value": false,
+ "display_name": "Stream",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "system_message": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_message",
+ "value": "",
+ "display_name": "System Message",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "temperature": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": 0.1,
+ "display_name": "Temperature",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ }
+ },
+ "description": "Generates text using OpenAI LLMs.",
+ "icon": "OpenAI",
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "display_name": "OpenAI",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "text_output",
+ "display_name": "Text",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "required_inputs": [
+ "input_value",
+ "stream",
+ "system_message"
+ ]
+ },
+ {
+ "types": [
+ "LanguageModel"
+ ],
+ "selected": "LanguageModel",
+ "name": "model_output",
+ "display_name": "Language Model",
+ "method": "build_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "required_inputs": [
+ "api_key",
+ "json_mode",
+ "max_tokens",
+ "model_kwargs",
+ "model_name",
+ "openai_api_base",
+ "output_schema",
+ "seed",
+ "temperature"
+ ]
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "output_parser"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "id": "OpenAIModel-YUM1a",
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 587,
+ "positionAbsolute": {
+ "x": 3231.963340176361,
+ "y": 94.70653241512102
+ },
+ "dragging": false
+ },
+ {
+ "id": "ChatOutput-c4k0L",
+ "type": "genericNode",
+ "position": {
+ "x": 3669.141671875754,
+ "y": 248.4257650261032
+ },
+ "data": {
+ "type": "ChatOutput",
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if (\n self.session_id\n and isinstance(message, Message)\n and isinstance(message.text, str)\n and self.should_store_message\n ):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "data_template": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "data_template",
+ "value": "{text}",
+ "display_name": "Data Template",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Text",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "sender": {
+ "trace_as_metadata": true,
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "combobox": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender",
+ "value": "Machine",
+ "display_name": "Sender Type",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Type of sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "sender_name": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "sender_name",
+ "value": "AI",
+ "display_name": "Sender Name",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "session_id": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "session_id",
+ "value": "",
+ "display_name": "Session ID",
+ "advanced": true,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "should_store_message": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "should_store_message",
+ "value": true,
+ "display_name": "Store Messages",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Display a chat message in the Playground.",
+ "icon": "ChatOutput",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Chat Output",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "message",
+ "display_name": "Message",
+ "method": "message_response",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "id": "ChatOutput-c4k0L",
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 289,
+ "positionAbsolute": {
+ "x": 3669.141671875754,
+ "y": 248.4257650261032
+ },
+ "dragging": false
+ },
+ {
+ "id": "note-9ynW0",
+ "type": "noteNode",
+ "position": {
+ "x": -4,
+ "y": 38
+ },
+ "data": {
+ "node": {
+ "description": "**Google Drive Example Scopes**\n\n**Langflow - OAuth Integration Documentation**\n\n```\ndocs.langflow.org/integrations-setup-google-oauth-langflow\n```\n\n**Drive API Documentation**\n\nhttps://developers.google.com/drive/api/guides/api-specific-auth\n\n**Scope Used in This Example**\n\nPermission to view and download all your Drive files.\n\n```\nhttps://www.googleapis.com/auth/drive.readonly\n```\n\n**Example of How to Enter Scopes**\n\n```\nhttps://www.googleapis.com/auth/drive.apps.readonly, https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive.activity\n```",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "indigo"
+ }
+ },
+ "type": "note",
+ "id": "note-9ynW0"
+ },
+ "selected": false,
+ "width": 584,
+ "height": 673,
+ "dragging": false,
+ "style": {
+ "width": 584,
+ "height": 673
+ },
+ "resizing": false
+ },
+ {
+ "id": "GoogleOAuthToken-piWFF",
+ "type": "genericNode",
+ "position": {
+ "x": 609.7629463431374,
+ "y": 218.39474366635045
+ },
+ "data": {
+ "node": {
+ "template": {
+ "_type": "Component",
+ "oauth_credentials": {
+ "trace_as_metadata": true,
+ "file_path": "bc2cbf16-ed92-4ef6-a4a5-1526af21d044/2024-11-06_15-06-14_client_secret_998884801917-hsjq3alo0vfqih74fe635k01khqi74d3.apps.googleusercontent.com.json",
+ "fileTypes": [
+ "json"
+ ],
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "oauth_credentials",
+ "value": "",
+ "display_name": "Credentials File",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Input OAuth Credentials file (e.g. credentials.json).",
+ "title_case": false,
+ "type": "file",
+ "_input_type": "FileInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nimport re\nfrom pathlib import Path\n\nfrom google.auth.transport.requests import Request\nfrom google.oauth2.credentials import Credentials\nfrom google_auth_oauthlib.flow import InstalledAppFlow\n\nfrom langflow.custom import Component\nfrom langflow.io import FileInput, MultilineInput, Output\nfrom langflow.schema import Data\n\n\nclass GoogleOAuthToken(Component):\n display_name = \"Google OAuth Token\"\n description = \"Generates a JSON string with your Google OAuth token.\"\n documentation: str = \"https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1\"\n icon = \"Google\"\n name = \"GoogleOAuthToken\"\n\n inputs = [\n MultilineInput(\n name=\"scopes\",\n display_name=\"Scopes\",\n info=\"Input scopes for your application.\",\n required=True,\n ),\n FileInput(\n name=\"oauth_credentials\",\n display_name=\"Credentials File\",\n info=\"Input OAuth Credentials file (e.g. credentials.json).\",\n file_types=[\"json\"],\n required=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n def validate_scopes(self, scopes):\n pattern = (\n r\"^(https://www\\.googleapis\\.com/auth/[\\w\\.\\-]+\"\n r\"|mail\\.google\\.com/\"\n r\"|www\\.google\\.com/calendar/feeds\"\n r\"|www\\.google\\.com/m8/feeds)\"\n r\"(,\\s*https://www\\.googleapis\\.com/auth/[\\w\\.\\-]+\"\n r\"|mail\\.google\\.com/\"\n r\"|www\\.google\\.com/calendar/feeds\"\n r\"|www\\.google\\.com/m8/feeds)*$\"\n )\n if not re.match(pattern, scopes):\n error_message = \"Invalid scope format.\"\n raise ValueError(error_message)\n\n def build_output(self) -> Data:\n self.validate_scopes(self.scopes)\n\n user_scopes = [scope.strip() for scope in self.scopes.split(\",\")]\n if self.scopes:\n scopes = user_scopes\n else:\n error_message = \"Incorrect scope, check the scopes field.\"\n raise ValueError(error_message)\n\n creds = None\n token_path = Path(\"token.json\")\n\n if token_path.exists():\n with token_path.open(mode=\"r\", encoding=\"utf-8\") as token_file:\n creds = Credentials.from_authorized_user_file(\n str(token_path), scopes)\n\n if not creds or not creds.valid:\n if creds and creds.expired and creds.refresh_token:\n creds.refresh(Request())\n else:\n if self.oauth_credentials:\n client_secret_file = self.oauth_credentials\n else:\n error_message = \"OAuth 2.0 Credentials file not provided.\"\n raise ValueError(error_message)\n\n flow = InstalledAppFlow.from_client_secrets_file(\n client_secret_file, scopes)\n creds = flow.run_local_server(port=0)\n\n with token_path.open(mode=\"w\", encoding=\"utf-8\") as token_file:\n token_file.write(creds.to_json())\n\n creds_json = json.loads(creds.to_json())\n\n return Data(data=creds_json)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "scopes": {
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "scopes",
+ "value": "https://www.googleapis.com/auth/drive.readonly",
+ "display_name": "Scopes",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "Input scopes for your application.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ }
+ },
+ "description": "Generates a JSON string with your Google OAuth token.",
+ "icon": "Google",
+ "base_classes": [
+ "Data"
+ ],
+ "display_name": "Google OAuth Token",
+ "documentation": "https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Data"
+ ],
+ "selected": "Data",
+ "name": "output",
+ "display_name": "Output",
+ "method": "build_output",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "scopes",
+ "oauth_credentials"
+ ],
+ "beta": false,
+ "edited": true,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "type": "GoogleOAuthToken",
+ "id": "GoogleOAuthToken-piWFF",
+ "description": "A component to generate a json string containing your Google OAuth token.",
+ "display_name": "Google OAuth Token"
+ },
+ "selected": true,
+ "width": 384,
+ "height": 391,
+ "dragging": false,
+ "positionAbsolute": {
+ "x": 609.7629463431374,
+ "y": 218.39474366635045
+ }
+ },
+ {
+ "id": "JSONCleaner-N4p7F",
+ "type": "genericNode",
+ "position": {
+ "x": 1484.3316811088714,
+ "y": 174.88328534754476
+ },
+ "data": {
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import json\nimport re\nimport unicodedata\n\nfrom langflow.custom import Component\nfrom langflow.inputs import BoolInput, MessageTextInput\nfrom langflow.schema.message import Message\nfrom langflow.template import Output\n\n\nclass JSONCleaner(Component):\n display_name = \"JSON Cleaner\"\n description = (\n \"Cleans the messy and sometimes incorrect JSON strings produced by LLMs \"\n \"so that they are fully compliant with the JSON spec.\"\n )\n icon = \"custom_components\"\n\n inputs = [\n MessageTextInput(\n name=\"json_str\", display_name=\"JSON String\", info=\"The JSON string to be cleaned.\", required=True\n ),\n BoolInput(\n name=\"remove_control_chars\",\n display_name=\"Remove Control Characters\",\n info=\"Remove control characters from the JSON string.\",\n required=False,\n ),\n BoolInput(\n name=\"normalize_unicode\",\n display_name=\"Normalize Unicode\",\n info=\"Normalize Unicode characters in the JSON string.\",\n required=False,\n ),\n BoolInput(\n name=\"validate_json\",\n display_name=\"Validate JSON\",\n info=\"Validate the JSON string to ensure it is well-formed.\",\n required=False,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Cleaned JSON String\", name=\"output\", method=\"clean_json\"),\n ]\n\n def clean_json(self) -> Message:\n try:\n from json_repair import repair_json\n except ImportError as e:\n msg = \"Could not import the json_repair package. Please install it with `pip install json_repair`.\"\n raise ImportError(msg) from e\n\n \"\"\"Clean the input JSON string based on provided options and return the cleaned JSON string.\"\"\"\n json_str = self.json_str\n remove_control_chars = self.remove_control_chars\n normalize_unicode = self.normalize_unicode\n validate_json = self.validate_json\n\n try:\n start = json_str.find(\"{\")\n end = json_str.rfind(\"}\")\n if start == -1 or end == -1:\n msg = \"Invalid JSON string: Missing '{' or '}'\"\n raise ValueError(msg)\n json_str = json_str[start : end + 1]\n\n if remove_control_chars:\n json_str = self._remove_control_characters(json_str)\n if normalize_unicode:\n json_str = self._normalize_unicode(json_str)\n if validate_json:\n json_str = self._validate_json(json_str)\n\n cleaned_json_str = repair_json(json_str)\n result = str(cleaned_json_str)\n\n self.status = result\n return Message(text=result)\n except Exception as e:\n msg = f\"Error cleaning JSON string: {e}\"\n raise ValueError(msg) from e\n\n def _remove_control_characters(self, s: str) -> str:\n \"\"\"Remove control characters from the string.\"\"\"\n return re.sub(r\"[\\x00-\\x1F\\x7F]\", \"\", s)\n\n def _normalize_unicode(self, s: str) -> str:\n \"\"\"Normalize Unicode characters in the string.\"\"\"\n return unicodedata.normalize(\"NFC\", s)\n\n def _validate_json(self, s: str) -> str:\n \"\"\"Validate the JSON string.\"\"\"\n try:\n json.loads(s)\n return s\n except json.JSONDecodeError as e:\n msg = f\"Invalid JSON string: {e}\"\n raise ValueError(msg) from e\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "json_str": {
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "required": true,
+ "placeholder": "",
+ "show": true,
+ "name": "json_str",
+ "value": "",
+ "display_name": "JSON String",
+ "advanced": false,
+ "input_types": [
+ "Message"
+ ],
+ "dynamic": false,
+ "info": "The JSON string to be cleaned.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "normalize_unicode": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "normalize_unicode",
+ "value": true,
+ "display_name": "Normalize Unicode",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Normalize Unicode characters in the JSON string.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput",
+ "load_from_db": false
+ },
+ "remove_control_chars": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "remove_control_chars",
+ "value": false,
+ "display_name": "Remove Control Characters",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Remove control characters from the JSON string.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "validate_json": {
+ "trace_as_metadata": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "validate_json",
+ "value": false,
+ "display_name": "Validate JSON",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Validate the JSON string to ensure it is well-formed.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Cleans the messy and sometimes incorrect JSON strings produced by LLMs so that they are fully compliant with the JSON spec.",
+ "icon": "custom_components",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "JSON Cleaner",
+ "documentation": "",
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "output",
+ "display_name": "Cleaned JSON String",
+ "method": "clean_json",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "json_str",
+ "remove_control_chars",
+ "normalize_unicode",
+ "validate_json"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "type": "JSONCleaner",
+ "id": "JSONCleaner-N4p7F",
+ "description": "Cleans the messy and sometimes incorrect JSON strings produced by LLMs so that they are fully compliant with the JSON spec.",
+ "display_name": "JSON Cleaner"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 537,
+ "positionAbsolute": {
+ "x": 1484.3316811088714,
+ "y": 174.88328534754476
+ },
+ "dragging": false
+ },
+ {
+ "id": "Prompt-uk0Fr",
+ "type": "genericNode",
+ "position": {
+ "x": 2784.1158749493534,
+ "y": 225.01053782589702
+ },
+ "data": {
+ "node": {
+ "template": {
+ "_type": "Component",
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = await Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "template": {
+ "trace_as_input": true,
+ "list": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "{context}\n\nTranslate the text you receive into Spanish!",
+ "display_name": "Template",
+ "advanced": false,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "prompt",
+ "_input_type": "PromptInput",
+ "load_from_db": false
+ },
+ "context": {
+ "field_type": "str",
+ "required": false,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "",
+ "fileTypes": [],
+ "file_path": "",
+ "name": "context",
+ "display_name": "context",
+ "advanced": false,
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false,
+ "type": "str"
+ }
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "icon": "prompts",
+ "base_classes": [
+ "Message"
+ ],
+ "display_name": "Prompt",
+ "documentation": "",
+ "custom_fields": {
+ "template": [
+ "context"
+ ]
+ },
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": [
+ "Message"
+ ],
+ "selected": "Message",
+ "name": "prompt",
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "value": "__UNDEFINED__",
+ "cache": true
+ }
+ ],
+ "field_order": [
+ "template"
+ ],
+ "beta": false,
+ "edited": false,
+ "metadata": {},
+ "lf_version": "1.0.19.post2"
+ },
+ "type": "Prompt",
+ "id": "Prompt-uk0Fr",
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt"
+ },
+ "selected": false,
+ "width": 384,
+ "height": 391,
+ "positionAbsolute": {
+ "x": 2784.1158749493534,
+ "y": 225.01053782589702
+ },
+ "dragging": false
+ }
+ ],
+ "edges": [
+ {
+ "source": "GoogleDriveComponent-x1jHz",
+ "target": "ParseData-7qhmP",
+ "sourceHandle": "{œdataTypeœ:œGoogleDriveComponentœ,œidœ:œGoogleDriveComponent-x1jHzœ,œnameœ:œdocsœ,œoutput_typesœ:[œDataœ]}",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-7qhmPœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "id": "reactflow__edge-GoogleDriveComponent-x1jHz{œdataTypeœ:œGoogleDriveComponentœ,œidœ:œGoogleDriveComponent-x1jHzœ,œnameœ:œdocsœ,œoutput_typesœ:[œDataœ]}-ParseData-7qhmP{œfieldNameœ:œdataœ,œidœ:œParseData-7qhmPœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-7qhmP",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "GoogleDriveComponent",
+ "id": "GoogleDriveComponent-x1jHz",
+ "name": "docs",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "selected": false,
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "OpenAIModel-YUM1a",
+ "target": "ChatOutput-c4k0L",
+ "sourceHandle": "{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-YUM1aœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}",
+ "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-c4k0Lœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "id": "reactflow__edge-OpenAIModel-YUM1a{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-YUM1aœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-c4k0L{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-c4k0Lœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-c4k0L",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-YUM1a",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "selected": false,
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "GoogleOAuthToken-piWFF",
+ "sourceHandle": "{œdataTypeœ:œGoogleOAuthTokenœ,œidœ:œGoogleOAuthToken-piWFFœ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}",
+ "target": "ParseData-LFq8b",
+ "targetHandle": "{œfieldNameœ:œdataœ,œidœ:œParseData-LFq8bœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-LFq8b",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ },
+ "sourceHandle": {
+ "dataType": "GoogleOAuthToken",
+ "id": "GoogleOAuthToken-piWFF",
+ "name": "output",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "reactflow__edge-GoogleOAuthToken-piWFF{œdataTypeœ:œGoogleOAuthTokenœ,œidœ:œGoogleOAuthToken-piWFFœ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}-ParseData-LFq8b{œfieldNameœ:œdataœ,œidœ:œParseData-LFq8bœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "ParseData-LFq8b",
+ "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-LFq8bœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "JSONCleaner-N4p7F",
+ "targetHandle": "{œfieldNameœ:œjson_strœ,œidœ:œJSONCleaner-N4p7Fœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "json_str",
+ "id": "JSONCleaner-N4p7F",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-LFq8b",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ParseData-LFq8b{œdataTypeœ:œParseDataœ,œidœ:œParseData-LFq8bœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-JSONCleaner-N4p7F{œfieldNameœ:œjson_strœ,œidœ:œJSONCleaner-N4p7Fœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "JSONCleaner-N4p7F",
+ "sourceHandle": "{œdataTypeœ:œJSONCleanerœ,œidœ:œJSONCleaner-N4p7Fœ,œnameœ:œoutputœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "GoogleDriveComponent-x1jHz",
+ "targetHandle": "{œfieldNameœ:œjson_stringœ,œidœ:œGoogleDriveComponent-x1jHzœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "json_string",
+ "id": "GoogleDriveComponent-x1jHz",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "JSONCleaner",
+ "id": "JSONCleaner-N4p7F",
+ "name": "output",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-JSONCleaner-N4p7F{œdataTypeœ:œJSONCleanerœ,œidœ:œJSONCleaner-N4p7Fœ,œnameœ:œoutputœ,œoutput_typesœ:[œMessageœ]}-GoogleDriveComponent-x1jHz{œfieldNameœ:œjson_stringœ,œidœ:œGoogleDriveComponent-x1jHzœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "ParseData-7qhmP",
+ "sourceHandle": "{œdataTypeœ:œParseDataœ,œidœ:œParseData-7qhmPœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "Prompt-uk0Fr",
+ "targetHandle": "{œfieldNameœ:œcontextœ,œidœ:œPrompt-uk0Frœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "context",
+ "id": "Prompt-uk0Fr",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-7qhmP",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-ParseData-7qhmP{œdataTypeœ:œParseDataœ,œidœ:œParseData-7qhmPœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-uk0Fr{œfieldNameœ:œcontextœ,œidœ:œPrompt-uk0Frœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "animated": false,
+ "className": ""
+ },
+ {
+ "source": "Prompt-uk0Fr",
+ "sourceHandle": "{œdataTypeœ:œPromptœ,œidœ:œPrompt-uk0Frœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}",
+ "target": "OpenAIModel-YUM1a",
+ "targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-YUM1aœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "data": {
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-YUM1a",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ },
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-uk0Fr",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ }
+ },
+ "id": "reactflow__edge-Prompt-uk0Fr{œdataTypeœ:œPromptœ,œidœ:œPrompt-uk0Frœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-YUM1a{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-YUM1aœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "animated": false,
+ "className": ""
+ }
+ ],
+ "viewport": {
+ "x": -1019.4126433636247,
+ "y": 197.09015595775952,
+ "zoom": 0.6352002867729776
+ }
+ },
+ "description": "An example of a flow that connects to Google Drive to access a text document, reads the content, translates it into the desired language, and returns the translated text in the chat, allowing for quick and efficient automation of the Google Docs file translation process.",
+ "name": "Google Drive Docs Translations Example",
+ "last_tested_version": "1.0.19.post2",
+ "endpoint_name": null,
+ "is_component": false
+}
\ No newline at end of file
diff --git a/langflow/docs/static/img/api-pane.png b/langflow/docs/static/img/api-pane.png
new file mode 100644
index 0000000..fcab418
Binary files /dev/null and b/langflow/docs/static/img/api-pane.png differ
diff --git a/langflow/docs/static/img/astra_db_chat_memory_rounded.png b/langflow/docs/static/img/astra_db_chat_memory_rounded.png
new file mode 100644
index 0000000..630e74b
Binary files /dev/null and b/langflow/docs/static/img/astra_db_chat_memory_rounded.png differ
diff --git a/langflow/docs/static/img/chat-input-controls-pane.png b/langflow/docs/static/img/chat-input-controls-pane.png
new file mode 100644
index 0000000..4deda8a
Binary files /dev/null and b/langflow/docs/static/img/chat-input-controls-pane.png differ
diff --git a/langflow/docs/static/img/component-run-flow.png b/langflow/docs/static/img/component-run-flow.png
new file mode 100644
index 0000000..185a36e
Binary files /dev/null and b/langflow/docs/static/img/component-run-flow.png differ
diff --git a/langflow/docs/static/img/component-structured-output.png b/langflow/docs/static/img/component-structured-output.png
new file mode 100644
index 0000000..859982a
Binary files /dev/null and b/langflow/docs/static/img/component-structured-output.png differ
diff --git a/langflow/docs/static/img/composio/composio-create-flow.png b/langflow/docs/static/img/composio/composio-create-flow.png
new file mode 100644
index 0000000..b26c68c
Binary files /dev/null and b/langflow/docs/static/img/composio/composio-create-flow.png differ
diff --git a/langflow/docs/static/img/connect-component.png b/langflow/docs/static/img/connect-component.png
new file mode 100644
index 0000000..f76f20d
Binary files /dev/null and b/langflow/docs/static/img/connect-component.png differ
diff --git a/langflow/docs/static/img/connect-handles.png b/langflow/docs/static/img/connect-handles.png
new file mode 100644
index 0000000..bf11804
Binary files /dev/null and b/langflow/docs/static/img/connect-handles.png differ
diff --git a/langflow/docs/static/img/ds-lf-docs.png b/langflow/docs/static/img/ds-lf-docs.png
new file mode 100644
index 0000000..46fc704
Binary files /dev/null and b/langflow/docs/static/img/ds-lf-docs.png differ
diff --git a/langflow/docs/static/img/ds-lf-zoom.png b/langflow/docs/static/img/ds-lf-zoom.png
new file mode 100644
index 0000000..53f78b4
Binary files /dev/null and b/langflow/docs/static/img/ds-lf-zoom.png differ
diff --git a/langflow/docs/static/img/favicon.ico b/langflow/docs/static/img/favicon.ico
new file mode 100644
index 0000000..0eee393
Binary files /dev/null and b/langflow/docs/static/img/favicon.ico differ
diff --git a/langflow/docs/static/img/flows/import.gif b/langflow/docs/static/img/flows/import.gif
new file mode 100644
index 0000000..ed1dc29
Binary files /dev/null and b/langflow/docs/static/img/flows/import.gif differ
diff --git a/langflow/docs/static/img/google/configure-vertex-ai-credentials-in-langflow.gif b/langflow/docs/static/img/google/configure-vertex-ai-credentials-in-langflow.gif
new file mode 100644
index 0000000..b42e8e1
Binary files /dev/null and b/langflow/docs/static/img/google/configure-vertex-ai-credentials-in-langflow.gif differ
diff --git a/langflow/docs/static/img/google/create-a-google-cloud-project.gif b/langflow/docs/static/img/google/create-a-google-cloud-project.gif
new file mode 100644
index 0000000..177225a
Binary files /dev/null and b/langflow/docs/static/img/google/create-a-google-cloud-project.gif differ
diff --git a/langflow/docs/static/img/google/create-oauth-client-id.gif b/langflow/docs/static/img/google/create-oauth-client-id.gif
new file mode 100644
index 0000000..8579769
Binary files /dev/null and b/langflow/docs/static/img/google/create-oauth-client-id.gif differ
diff --git a/langflow/docs/static/img/hero.png b/langflow/docs/static/img/hero.png
new file mode 100644
index 0000000..3118ea3
Binary files /dev/null and b/langflow/docs/static/img/hero.png differ
diff --git a/langflow/docs/static/img/hugging-face-deployment.png b/langflow/docs/static/img/hugging-face-deployment.png
new file mode 100644
index 0000000..d1adaa2
Binary files /dev/null and b/langflow/docs/static/img/hugging-face-deployment.png differ
diff --git a/langflow/docs/static/img/integrations.png b/langflow/docs/static/img/integrations.png
new file mode 100644
index 0000000..8ded830
Binary files /dev/null and b/langflow/docs/static/img/integrations.png differ
diff --git a/langflow/docs/static/img/langflow-astradb-component.png b/langflow/docs/static/img/langflow-astradb-component.png
new file mode 100644
index 0000000..aa772aa
Binary files /dev/null and b/langflow/docs/static/img/langflow-astradb-component.png differ
diff --git a/langflow/docs/static/img/langflow-icon-black-transparent.svg b/langflow/docs/static/img/langflow-icon-black-transparent.svg
new file mode 100644
index 0000000..1c7b36c
--- /dev/null
+++ b/langflow/docs/static/img/langflow-icon-black-transparent.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/langflow/docs/static/img/langflow-logo-black.svg b/langflow/docs/static/img/langflow-logo-black.svg
new file mode 100644
index 0000000..a79abb2
--- /dev/null
+++ b/langflow/docs/static/img/langflow-logo-black.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/docs/static/img/langflow-logo-white.svg b/langflow/docs/static/img/langflow-logo-white.svg
new file mode 100644
index 0000000..8188bad
--- /dev/null
+++ b/langflow/docs/static/img/langflow-logo-white.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/docs/static/img/langsmith-dashboard.png b/langflow/docs/static/img/langsmith-dashboard.png
new file mode 100644
index 0000000..47e007a
Binary files /dev/null and b/langflow/docs/static/img/langsmith-dashboard.png differ
diff --git a/langflow/docs/static/img/langwatch-dashboard.png b/langflow/docs/static/img/langwatch-dashboard.png
new file mode 100644
index 0000000..9cf362a
Binary files /dev/null and b/langflow/docs/static/img/langwatch-dashboard.png differ
diff --git a/langflow/docs/static/img/logo.svg b/langflow/docs/static/img/logo.svg
new file mode 100644
index 0000000..3665f82
--- /dev/null
+++ b/langflow/docs/static/img/logo.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/langflow/docs/static/img/logs.png b/langflow/docs/static/img/logs.png
new file mode 100644
index 0000000..0eee63d
Binary files /dev/null and b/langflow/docs/static/img/logs.png differ
diff --git a/langflow/docs/static/img/loop-text-summarizer.png b/langflow/docs/static/img/loop-text-summarizer.png
new file mode 100644
index 0000000..bae75f6
Binary files /dev/null and b/langflow/docs/static/img/loop-text-summarizer.png differ
diff --git a/langflow/docs/static/img/mcp-stdio-component.png b/langflow/docs/static/img/mcp-stdio-component.png
new file mode 100644
index 0000000..f6d4338
Binary files /dev/null and b/langflow/docs/static/img/mcp-stdio-component.png differ
diff --git a/langflow/docs/static/img/messages-logs.png b/langflow/docs/static/img/messages-logs.png
new file mode 100644
index 0000000..356e79b
Binary files /dev/null and b/langflow/docs/static/img/messages-logs.png differ
diff --git a/langflow/docs/static/img/my-projects.png b/langflow/docs/static/img/my-projects.png
new file mode 100644
index 0000000..2054dcb
Binary files /dev/null and b/langflow/docs/static/img/my-projects.png differ
diff --git a/langflow/docs/static/img/notion/AddContentToPage_flow_example.png b/langflow/docs/static/img/notion/AddContentToPage_flow_example.png
new file mode 100644
index 0000000..31aadb0
Binary files /dev/null and b/langflow/docs/static/img/notion/AddContentToPage_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/AddContentToPage_flow_example_dark.png b/langflow/docs/static/img/notion/AddContentToPage_flow_example_dark.png
new file mode 100644
index 0000000..0d88092
Binary files /dev/null and b/langflow/docs/static/img/notion/AddContentToPage_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example.png b/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example.png
new file mode 100644
index 0000000..6ec3d7a
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example_dark.png b/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example_dark.png
new file mode 100644
index 0000000..4d3a5a2
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionDatabaseProperties_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionListPages_flow_example.png b/langflow/docs/static/img/notion/NotionListPages_flow_example.png
new file mode 100644
index 0000000..c04e3d8
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionListPages_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionListPages_flow_example_dark.png b/langflow/docs/static/img/notion/NotionListPages_flow_example_dark.png
new file mode 100644
index 0000000..ee041bd
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionListPages_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageContent_flow_example.png b/langflow/docs/static/img/notion/NotionPageContent_flow_example.png
new file mode 100644
index 0000000..5d89af1
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageContent_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageContent_flow_example_dark.png b/langflow/docs/static/img/notion/NotionPageContent_flow_example_dark.png
new file mode 100644
index 0000000..144c1e0
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageContent_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageCreator_flow_example.png b/langflow/docs/static/img/notion/NotionPageCreator_flow_example.png
new file mode 100644
index 0000000..1cc1478
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageCreator_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageCreator_flow_example_dark.png b/langflow/docs/static/img/notion/NotionPageCreator_flow_example_dark.png
new file mode 100644
index 0000000..97788db
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageCreator_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageUpdate_flow_example.png b/langflow/docs/static/img/notion/NotionPageUpdate_flow_example.png
new file mode 100644
index 0000000..dd02f9b
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageUpdate_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionPageUpdate_flow_example_dark.png b/langflow/docs/static/img/notion/NotionPageUpdate_flow_example_dark.png
new file mode 100644
index 0000000..bc37ff2
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionPageUpdate_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionSearch_flow_example.png b/langflow/docs/static/img/notion/NotionSearch_flow_example.png
new file mode 100644
index 0000000..95e6c72
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionSearch_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionSearch_flow_example_dark.png b/langflow/docs/static/img/notion/NotionSearch_flow_example_dark.png
new file mode 100644
index 0000000..924aff5
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionSearch_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/NotionUserList_flow_example.png b/langflow/docs/static/img/notion/NotionUserList_flow_example.png
new file mode 100644
index 0000000..e0fbd85
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionUserList_flow_example.png differ
diff --git a/langflow/docs/static/img/notion/NotionUserList_flow_example_dark.png b/langflow/docs/static/img/notion/NotionUserList_flow_example_dark.png
new file mode 100644
index 0000000..d59e7d9
Binary files /dev/null and b/langflow/docs/static/img/notion/NotionUserList_flow_example_dark.png differ
diff --git a/langflow/docs/static/img/notion/notion_bundle.jpg b/langflow/docs/static/img/notion/notion_bundle.jpg
new file mode 100644
index 0000000..b6dc62d
Binary files /dev/null and b/langflow/docs/static/img/notion/notion_bundle.jpg differ
diff --git a/langflow/docs/static/img/notion/notion_components_bundle.png b/langflow/docs/static/img/notion/notion_components_bundle.png
new file mode 100644
index 0000000..f924ed1
Binary files /dev/null and b/langflow/docs/static/img/notion/notion_components_bundle.png differ
diff --git a/langflow/docs/static/img/notion/notion_components_bundle_dark.png b/langflow/docs/static/img/notion/notion_components_bundle_dark.png
new file mode 100644
index 0000000..048646b
Binary files /dev/null and b/langflow/docs/static/img/notion/notion_components_bundle_dark.png differ
diff --git a/langflow/docs/static/img/open-logs-pane.png b/langflow/docs/static/img/open-logs-pane.png
new file mode 100644
index 0000000..c7e8b6e
Binary files /dev/null and b/langflow/docs/static/img/open-logs-pane.png differ
diff --git a/langflow/docs/static/img/openai-model-component.png b/langflow/docs/static/img/openai-model-component.png
new file mode 100644
index 0000000..3c1145d
Binary files /dev/null and b/langflow/docs/static/img/openai-model-component.png differ
diff --git a/langflow/docs/static/img/playground-response.png b/langflow/docs/static/img/playground-response.png
new file mode 100644
index 0000000..15f7cc4
Binary files /dev/null and b/langflow/docs/static/img/playground-response.png differ
diff --git a/langflow/docs/static/img/playground.png b/langflow/docs/static/img/playground.png
new file mode 100644
index 0000000..ab79ff1
Binary files /dev/null and b/langflow/docs/static/img/playground.png differ
diff --git a/langflow/docs/static/img/prompt-component.png b/langflow/docs/static/img/prompt-component.png
new file mode 100644
index 0000000..dbc9fb2
Binary files /dev/null and b/langflow/docs/static/img/prompt-component.png differ
diff --git a/langflow/docs/static/img/quickstart-add-document-ingestion.png b/langflow/docs/static/img/quickstart-add-document-ingestion.png
new file mode 100644
index 0000000..9bda445
Binary files /dev/null and b/langflow/docs/static/img/quickstart-add-document-ingestion.png differ
diff --git a/langflow/docs/static/img/quickstart-basic-prompt-no-connections.png b/langflow/docs/static/img/quickstart-basic-prompt-no-connections.png
new file mode 100644
index 0000000..f767d00
Binary files /dev/null and b/langflow/docs/static/img/quickstart-basic-prompt-no-connections.png differ
diff --git a/langflow/docs/static/img/settings-messages.png b/langflow/docs/static/img/settings-messages.png
new file mode 100644
index 0000000..bbbcd71
Binary files /dev/null and b/langflow/docs/static/img/settings-messages.png differ
diff --git a/langflow/docs/static/img/starter-flow-basic-prompting.png b/langflow/docs/static/img/starter-flow-basic-prompting.png
new file mode 100644
index 0000000..29b87cb
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-basic-prompting.png differ
diff --git a/langflow/docs/static/img/starter-flow-blog-writer.png b/langflow/docs/static/img/starter-flow-blog-writer.png
new file mode 100644
index 0000000..ef2efba
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-blog-writer.png differ
diff --git a/langflow/docs/static/img/starter-flow-document-qa.png b/langflow/docs/static/img/starter-flow-document-qa.png
new file mode 100644
index 0000000..819f07b
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-document-qa.png differ
diff --git a/langflow/docs/static/img/starter-flow-memory-chatbot.png b/langflow/docs/static/img/starter-flow-memory-chatbot.png
new file mode 100644
index 0000000..9c29df4
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-memory-chatbot.png differ
diff --git a/langflow/docs/static/img/starter-flow-sequential-agent.png b/langflow/docs/static/img/starter-flow-sequential-agent.png
new file mode 100644
index 0000000..989bc10
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-sequential-agent.png differ
diff --git a/langflow/docs/static/img/starter-flow-simple-agent-repl.png b/langflow/docs/static/img/starter-flow-simple-agent-repl.png
new file mode 100644
index 0000000..e6799c1
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-simple-agent-repl.png differ
diff --git a/langflow/docs/static/img/starter-flow-simple-agent.png b/langflow/docs/static/img/starter-flow-simple-agent.png
new file mode 100644
index 0000000..064dbb9
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-simple-agent.png differ
diff --git a/langflow/docs/static/img/starter-flow-travel-agent.png b/langflow/docs/static/img/starter-flow-travel-agent.png
new file mode 100644
index 0000000..3a1c849
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-travel-agent.png differ
diff --git a/langflow/docs/static/img/starter-flow-unstructured-qa.png b/langflow/docs/static/img/starter-flow-unstructured-qa.png
new file mode 100644
index 0000000..631038c
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-unstructured-qa.png differ
diff --git a/langflow/docs/static/img/starter-flow-vector-rag.png b/langflow/docs/static/img/starter-flow-vector-rag.png
new file mode 100644
index 0000000..d7830a6
Binary files /dev/null and b/langflow/docs/static/img/starter-flow-vector-rag.png differ
diff --git a/langflow/docs/static/img/tool-calling-agent-add-chat.png b/langflow/docs/static/img/tool-calling-agent-add-chat.png
new file mode 100644
index 0000000..e9817f8
Binary files /dev/null and b/langflow/docs/static/img/tool-calling-agent-add-chat.png differ
diff --git a/langflow/docs/static/img/tool-calling-agent-add-tools.png b/langflow/docs/static/img/tool-calling-agent-add-tools.png
new file mode 100644
index 0000000..1435b25
Binary files /dev/null and b/langflow/docs/static/img/tool-calling-agent-add-tools.png differ
diff --git a/langflow/docs/static/img/tool-calling-agent-as-tool.png b/langflow/docs/static/img/tool-calling-agent-as-tool.png
new file mode 100644
index 0000000..73c5a72
Binary files /dev/null and b/langflow/docs/static/img/tool-calling-agent-as-tool.png differ
diff --git a/langflow/docs/static/img/tool-calling-agent-component.png b/langflow/docs/static/img/tool-calling-agent-component.png
new file mode 100644
index 0000000..f6e8c5e
Binary files /dev/null and b/langflow/docs/static/img/tool-calling-agent-component.png differ
diff --git a/langflow/docs/static/img/url-component.png b/langflow/docs/static/img/url-component.png
new file mode 100644
index 0000000..a0e9733
Binary files /dev/null and b/langflow/docs/static/img/url-component.png differ
diff --git a/langflow/docs/static/img/vector-store-agent-retrieval-tool.png b/langflow/docs/static/img/vector-store-agent-retrieval-tool.png
new file mode 100644
index 0000000..d8aaa49
Binary files /dev/null and b/langflow/docs/static/img/vector-store-agent-retrieval-tool.png differ
diff --git a/langflow/docs/static/img/vector-store-document-ingestion.png b/langflow/docs/static/img/vector-store-document-ingestion.png
new file mode 100644
index 0000000..8157dc6
Binary files /dev/null and b/langflow/docs/static/img/vector-store-document-ingestion.png differ
diff --git a/langflow/docs/static/img/vector-store-retrieval.png b/langflow/docs/static/img/vector-store-retrieval.png
new file mode 100644
index 0000000..132ba25
Binary files /dev/null and b/langflow/docs/static/img/vector-store-retrieval.png differ
diff --git a/langflow/docs/static/img/workspace-basic-prompting.png b/langflow/docs/static/img/workspace-basic-prompting.png
new file mode 100644
index 0000000..d9530c4
Binary files /dev/null and b/langflow/docs/static/img/workspace-basic-prompting.png differ
diff --git a/langflow/docs/static/img/workspace.png b/langflow/docs/static/img/workspace.png
new file mode 100644
index 0000000..98b6ba7
Binary files /dev/null and b/langflow/docs/static/img/workspace.png differ
diff --git a/langflow/docs/static/logos/botmessage.svg b/langflow/docs/static/logos/botmessage.svg
new file mode 100644
index 0000000..ab468da
--- /dev/null
+++ b/langflow/docs/static/logos/botmessage.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/cloud_deploy.svg b/langflow/docs/static/logos/cloud_deploy.svg
new file mode 100644
index 0000000..b55ef3c
--- /dev/null
+++ b/langflow/docs/static/logos/cloud_deploy.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/discordDark.svg b/langflow/docs/static/logos/discordDark.svg
new file mode 100644
index 0000000..5f782ed
--- /dev/null
+++ b/langflow/docs/static/logos/discordDark.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/discordLight.svg b/langflow/docs/static/logos/discordLight.svg
new file mode 100644
index 0000000..86084bc
--- /dev/null
+++ b/langflow/docs/static/logos/discordLight.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/gitDark.svg b/langflow/docs/static/logos/gitDark.svg
new file mode 100644
index 0000000..51944a7
--- /dev/null
+++ b/langflow/docs/static/logos/gitDark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/gitLight.svg b/langflow/docs/static/logos/gitLight.svg
new file mode 100644
index 0000000..ed869f4
--- /dev/null
+++ b/langflow/docs/static/logos/gitLight.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/greencheck.svg b/langflow/docs/static/logos/greencheck.svg
new file mode 100644
index 0000000..842be95
--- /dev/null
+++ b/langflow/docs/static/logos/greencheck.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/langflow/docs/static/logos/playbutton.svg b/langflow/docs/static/logos/playbutton.svg
new file mode 100644
index 0000000..9784074
--- /dev/null
+++ b/langflow/docs/static/logos/playbutton.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/langflow/docs/static/logos/railway-deploy.svg b/langflow/docs/static/logos/railway-deploy.svg
new file mode 100644
index 0000000..73df2c1
--- /dev/null
+++ b/langflow/docs/static/logos/railway-deploy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/render-deploy.svg b/langflow/docs/static/logos/render-deploy.svg
new file mode 100644
index 0000000..7e13763
--- /dev/null
+++ b/langflow/docs/static/logos/render-deploy.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/langflow/docs/static/logos/xDark.svg b/langflow/docs/static/logos/xDark.svg
new file mode 100644
index 0000000..f40b99e
--- /dev/null
+++ b/langflow/docs/static/logos/xDark.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/logos/xLight.svg b/langflow/docs/static/logos/xLight.svg
new file mode 100644
index 0000000..9a15d23
--- /dev/null
+++ b/langflow/docs/static/logos/xLight.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/langflow/docs/static/notion_imgs/1911112500.png b/langflow/docs/static/notion_imgs/1911112500.png
new file mode 100644
index 0000000..5c998d4
Binary files /dev/null and b/langflow/docs/static/notion_imgs/1911112500.png differ
diff --git a/langflow/docs/static/notion_imgs/2091252224.png b/langflow/docs/static/notion_imgs/2091252224.png
new file mode 100644
index 0000000..8e9b004
Binary files /dev/null and b/langflow/docs/static/notion_imgs/2091252224.png differ
diff --git a/langflow/docs/static/videos/chat_memory.mp4 b/langflow/docs/static/videos/chat_memory.mp4
new file mode 100644
index 0000000..ffed26a
Binary files /dev/null and b/langflow/docs/static/videos/chat_memory.mp4 differ
diff --git a/langflow/docs/static/videos/combine_text.mp4 b/langflow/docs/static/videos/combine_text.mp4
new file mode 100644
index 0000000..7e48303
Binary files /dev/null and b/langflow/docs/static/videos/combine_text.mp4 differ
diff --git a/langflow/docs/static/videos/create_record.mp4 b/langflow/docs/static/videos/create_record.mp4
new file mode 100644
index 0000000..558f702
Binary files /dev/null and b/langflow/docs/static/videos/create_record.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_api.mp4 b/langflow/docs/static/videos/langflow_api.mp4
new file mode 100644
index 0000000..f0daa52
Binary files /dev/null and b/langflow/docs/static/videos/langflow_api.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_build.mp4 b/langflow/docs/static/videos/langflow_build.mp4
new file mode 100644
index 0000000..9d068fa
Binary files /dev/null and b/langflow/docs/static/videos/langflow_build.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_collection.mp4 b/langflow/docs/static/videos/langflow_collection.mp4
new file mode 100644
index 0000000..69d1727
Binary files /dev/null and b/langflow/docs/static/videos/langflow_collection.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_collection_example.mp4 b/langflow/docs/static/videos/langflow_collection_example.mp4
new file mode 100644
index 0000000..e58ea31
Binary files /dev/null and b/langflow/docs/static/videos/langflow_collection_example.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_fork.mp4 b/langflow/docs/static/videos/langflow_fork.mp4
new file mode 100644
index 0000000..03c280c
Binary files /dev/null and b/langflow/docs/static/videos/langflow_fork.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_global_variables.mp4 b/langflow/docs/static/videos/langflow_global_variables.mp4
new file mode 100644
index 0000000..8be58e7
Binary files /dev/null and b/langflow/docs/static/videos/langflow_global_variables.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_parameters.mp4 b/langflow/docs/static/videos/langflow_parameters.mp4
new file mode 100644
index 0000000..370ca5f
Binary files /dev/null and b/langflow/docs/static/videos/langflow_parameters.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_playground.mp4 b/langflow/docs/static/videos/langflow_playground.mp4
new file mode 100644
index 0000000..aa7488c
Binary files /dev/null and b/langflow/docs/static/videos/langflow_playground.mp4 differ
diff --git a/langflow/docs/static/videos/langflow_widget.mp4 b/langflow/docs/static/videos/langflow_widget.mp4
new file mode 100644
index 0000000..7894316
Binary files /dev/null and b/langflow/docs/static/videos/langflow_widget.mp4 differ
diff --git a/langflow/docs/static/videos/pass.mp4 b/langflow/docs/static/videos/pass.mp4
new file mode 100644
index 0000000..bb06236
Binary files /dev/null and b/langflow/docs/static/videos/pass.mp4 differ
diff --git a/langflow/docs/static/videos/store_message.mp4 b/langflow/docs/static/videos/store_message.mp4
new file mode 100644
index 0000000..c8352da
Binary files /dev/null and b/langflow/docs/static/videos/store_message.mp4 differ
diff --git a/langflow/docs/static/videos/sub_flow.mp4 b/langflow/docs/static/videos/sub_flow.mp4
new file mode 100644
index 0000000..24222e8
Binary files /dev/null and b/langflow/docs/static/videos/sub_flow.mp4 differ
diff --git a/langflow/docs/static/videos/text_operator.mp4 b/langflow/docs/static/videos/text_operator.mp4
new file mode 100644
index 0000000..3124e6b
Binary files /dev/null and b/langflow/docs/static/videos/text_operator.mp4 differ
diff --git a/langflow/docs/tsconfig.json b/langflow/docs/tsconfig.json
new file mode 100644
index 0000000..6f47569
--- /dev/null
+++ b/langflow/docs/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ // This file is not used in compilation. It is here just for a nice editor experience.
+ "extends": "@tsconfig/docusaurus/tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": "."
+ }
+}
diff --git a/langflow/docs/yarn.lock b/langflow/docs/yarn.lock
new file mode 100644
index 0000000..e3251ce
--- /dev/null
+++ b/langflow/docs/yarn.lock
@@ -0,0 +1,13609 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@algolia/autocomplete-core@1.17.9":
+ version "1.17.9"
+ resolved "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz"
+ integrity sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==
+ dependencies:
+ "@algolia/autocomplete-plugin-algolia-insights" "1.17.9"
+ "@algolia/autocomplete-shared" "1.17.9"
+
+"@algolia/autocomplete-plugin-algolia-insights@1.17.9":
+ version "1.17.9"
+ resolved "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz"
+ integrity sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.17.9"
+
+"@algolia/autocomplete-preset-algolia@1.17.9":
+ version "1.17.9"
+ resolved "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz"
+ integrity sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.17.9"
+
+"@algolia/autocomplete-shared@1.17.9":
+ version "1.17.9"
+ resolved "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz"
+ integrity sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==
+
+"@algolia/client-abtesting@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.20.3.tgz"
+ integrity sha512-wPOzHYSsW+H97JkBLmnlOdJSpbb9mIiuNPycUCV5DgzSkJFaI/OFxXfZXAh1gqxK+hf0miKue1C9bltjWljrNA==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/client-analytics@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.20.3.tgz"
+ integrity sha512-XE3iduH9lA7iTQacDGofBQyIyIgaX8qbTRRdj1bOCmfzc9b98CoiMwhNwdTifmmMewmN0EhVF3hP8KjKWwX7Yw==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/client-common@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.20.3.tgz"
+ integrity sha512-IYRd/A/R3BXeaQVT2805lZEdWo54v39Lqa7ABOxIYnUvX2vvOMW1AyzCuT0U7Q+uPdD4UW48zksUKRixShcWxA==
+
+"@algolia/client-insights@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.20.3.tgz"
+ integrity sha512-QGc/bmDUBgzB71rDL6kihI2e1Mx6G6PxYO5Ks84iL3tDcIel1aFuxtRF14P8saGgdIe1B6I6QkpkeIddZ6vWQw==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/client-personalization@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.20.3.tgz"
+ integrity sha512-zuM31VNPDJ1LBIwKbYGz/7+CSm+M8EhlljDamTg8AnDilnCpKjBebWZR5Tftv/FdWSro4tnYGOIz1AURQgZ+tQ==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/client-query-suggestions@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.20.3.tgz"
+ integrity sha512-Nn872PuOI8qzi1bxMMhJ0t2AzVBqN01jbymBQOkypvZHrrjZPso3iTpuuLLo9gi3yc/08vaaWTAwJfPhxPwJUw==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/client-search@>= 4.9.1 < 6", "@algolia/client-search@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.20.3.tgz"
+ integrity sha512-9+Fm1ahV8/2goSIPIqZnVitV5yHW5E5xTdKy33xnqGd45A9yVv5tTkudWzEXsbfBB47j9Xb3uYPZjAvV5RHbKA==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/events@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz"
+ integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==
+
+"@algolia/ingestion@1.20.3":
+ version "1.20.3"
+ resolved "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.20.3.tgz"
+ integrity sha512-5GHNTiZ3saLjTNyr6WkP5hzDg2eFFAYWomvPcm9eHWskjzXt8R0IOiW9kkTS6I6hXBwN5H9Zna5mZDSqqJdg+g==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/monitoring@1.20.3":
+ version "1.20.3"
+ resolved "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.20.3.tgz"
+ integrity sha512-KUWQbTPoRjP37ivXSQ1+lWMfaifCCMzTnEcEnXwAmherS5Tp7us6BAqQDMGOD4E7xyaS2I8pto6WlOzxH+CxmA==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/recommend@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.20.3.tgz"
+ integrity sha512-oo/gG77xTTTclkrdFem0Kmx5+iSRFiwuRRdxZETDjwzCI7svutdbwBgV/Vy4D4QpYaX4nhY/P43k84uEowCE4Q==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+"@algolia/requester-browser-xhr@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.20.3.tgz"
+ integrity sha512-BkkW7otbiI/Er1AiEPZs1h7lxbtSO9p09jFhv3/iT8/0Yz0CY79VJ9iq+Wv1+dq/l0OxnMpBy8mozrieGA3mXQ==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+
+"@algolia/requester-fetch@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.20.3.tgz"
+ integrity sha512-eAVlXz7UNzTsA1EDr+p0nlIH7WFxo7k3NMxYe8p38DH8YVWLgm2MgOVFUMNg9HCi6ZNOi/A2w/id2ZZ4sKgUOw==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+
+"@algolia/requester-node-http@5.20.3":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.20.3.tgz"
+ integrity sha512-FqR3pQPfHfQyX1wgcdK6iyqu86yP76MZd4Pzj1y/YLMj9rRmRCY0E0AffKr//nrOFEwv6uY8BQY4fd9/6b0ZCg==
+ dependencies:
+ "@algolia/client-common" "5.20.3"
+
+"@alloc/quick-lru@^5.2.0":
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz"
+ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
+
+"@ampproject/remapping@^2.2.0":
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz"
+ integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@apidevtools/json-schema-ref-parser@^11.5.4":
+ version "11.9.1"
+ resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.1.tgz"
+ integrity sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==
+ dependencies:
+ "@jsdevtools/ono" "^7.1.3"
+ "@types/json-schema" "^7.0.15"
+ js-yaml "^4.1.0"
+
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3":
+ version "7.26.2"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz"
+ integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz"
+ integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
+
+"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.21.3", "@babel/core@^7.25.9", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz"
+ integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.26.9"
+ "@babel/helper-compilation-targets" "^7.26.5"
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helpers" "^7.26.9"
+ "@babel/parser" "^7.26.9"
+ "@babel/template" "^7.26.9"
+ "@babel/traverse" "^7.26.9"
+ "@babel/types" "^7.26.9"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.25.9", "@babel/generator@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz"
+ integrity sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==
+ dependencies:
+ "@babel/parser" "^7.26.9"
+ "@babel/types" "^7.26.9"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
+"@babel/helper-annotate-as-pure@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz"
+ integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
+ dependencies:
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz"
+ integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==
+ dependencies:
+ "@babel/compat-data" "^7.26.5"
+ "@babel/helper-validator-option" "^7.25.9"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-create-class-features-plugin@^7.25.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz"
+ integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-member-expression-to-functions" "^7.25.9"
+ "@babel/helper-optimise-call-expression" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.26.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+ "@babel/traverse" "^7.26.9"
+ semver "^6.3.1"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9":
+ version "7.26.3"
+ resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz"
+ integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ regexpu-core "^6.2.0"
+ semver "^6.3.1"
+
+"@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3":
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz"
+ integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+
+"@babel/helper-member-expression-to-functions@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz"
+ integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==
+ dependencies:
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-module-imports@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz"
+ integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
+ dependencies:
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz"
+ integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/helper-optimise-call-expression@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz"
+ integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==
+ dependencies:
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0":
+ version "7.26.5"
+ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz"
+ integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
+
+"@babel/helper-remap-async-to-generator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz"
+ integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-wrap-function" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/helper-replace-supers@^7.25.9", "@babel/helper-replace-supers@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz"
+ integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.25.9"
+ "@babel/helper-optimise-call-expression" "^7.25.9"
+ "@babel/traverse" "^7.26.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz"
+ integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==
+ dependencies:
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-string-parser@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
+ integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
+
+"@babel/helper-validator-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
+"@babel/helper-validator-option@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz"
+ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+
+"@babel/helper-wrap-function@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz"
+ integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==
+ dependencies:
+ "@babel/template" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helpers@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz"
+ integrity sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==
+ dependencies:
+ "@babel/template" "^7.26.9"
+ "@babel/types" "^7.26.9"
+
+"@babel/parser@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz"
+ integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
+ dependencies:
+ "@babel/types" "^7.26.9"
+
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz"
+ integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz"
+ integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz"
+ integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz"
+ integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+ "@babel/plugin-transform-optional-chaining" "^7.25.9"
+
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz"
+ integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
+ version "7.21.0-placeholder-for-preset-env.2"
+ resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz"
+ integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==
+
+"@babel/plugin-syntax-dynamic-import@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz"
+ integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-import-assertions@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz"
+ integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-syntax-import-attributes@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz"
+ integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-syntax-jsx@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz"
+ integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-syntax-typescript@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz"
+ integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz"
+ integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-arrow-functions@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz"
+ integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-async-generator-functions@^7.26.8":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz"
+ integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+ "@babel/helper-remap-async-to-generator" "^7.25.9"
+ "@babel/traverse" "^7.26.8"
+
+"@babel/plugin-transform-async-to-generator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz"
+ integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-remap-async-to-generator" "^7.25.9"
+
+"@babel/plugin-transform-block-scoped-functions@^7.26.5":
+ version "7.26.5"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz"
+ integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+
+"@babel/plugin-transform-block-scoping@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz"
+ integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-class-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz"
+ integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-class-static-block@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz"
+ integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-classes@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz"
+ integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz"
+ integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/template" "^7.25.9"
+
+"@babel/plugin-transform-destructuring@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz"
+ integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-dotall-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz"
+ integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-duplicate-keys@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz"
+ integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz"
+ integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-dynamic-import@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz"
+ integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-exponentiation-operator@^7.26.3":
+ version "7.26.3"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz"
+ integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-export-namespace-from@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz"
+ integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-for-of@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz"
+ integrity sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+
+"@babel/plugin-transform-function-name@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz"
+ integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/plugin-transform-json-strings@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz"
+ integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz"
+ integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-logical-assignment-operators@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz"
+ integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-member-expression-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz"
+ integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-modules-amd@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz"
+ integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3":
+ version "7.26.3"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz"
+ integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-modules-systemjs@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz"
+ integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/plugin-transform-modules-umd@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz"
+ integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz"
+ integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-new-target@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz"
+ integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6":
+ version "7.26.6"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz"
+ integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+
+"@babel/plugin-transform-numeric-separator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz"
+ integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-object-rest-spread@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz"
+ integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/plugin-transform-parameters" "^7.25.9"
+
+"@babel/plugin-transform-object-super@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz"
+ integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-replace-supers" "^7.25.9"
+
+"@babel/plugin-transform-optional-catch-binding@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz"
+ integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-optional-chaining@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz"
+ integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+
+"@babel/plugin-transform-parameters@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz"
+ integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-private-methods@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz"
+ integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-private-property-in-object@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz"
+ integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-property-literals@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz"
+ integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-react-constant-elements@^7.21.3":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz"
+ integrity sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-react-display-name@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz"
+ integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-react-jsx-development@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz"
+ integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.25.9"
+
+"@babel/plugin-transform-react-jsx@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz"
+ integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/plugin-syntax-jsx" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/plugin-transform-react-pure-annotations@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz"
+ integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-regenerator@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz"
+ integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ regenerator-transform "^0.15.2"
+
+"@babel/plugin-transform-regexp-modifiers@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz"
+ integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-reserved-words@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz"
+ integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-runtime@^7.25.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.9.tgz"
+ integrity sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.26.5"
+ babel-plugin-polyfill-corejs2 "^0.4.10"
+ babel-plugin-polyfill-corejs3 "^0.10.6"
+ babel-plugin-polyfill-regenerator "^0.6.1"
+ semver "^6.3.1"
+
+"@babel/plugin-transform-shorthand-properties@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz"
+ integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-spread@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz"
+ integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+
+"@babel/plugin-transform-sticky-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz"
+ integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-template-literals@^7.26.8":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz"
+ integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+
+"@babel/plugin-transform-typeof-symbol@^7.26.7":
+ version "7.26.7"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz"
+ integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.26.5"
+
+"@babel/plugin-transform-typescript@^7.25.9":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz"
+ integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.25.9"
+ "@babel/helper-create-class-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.26.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
+ "@babel/plugin-syntax-typescript" "^7.25.9"
+
+"@babel/plugin-transform-unicode-escapes@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz"
+ integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-property-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz"
+ integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz"
+ integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz"
+ integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.25.9"
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/preset-env@^7.20.2", "@babel/preset-env@^7.25.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz"
+ integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==
+ dependencies:
+ "@babel/compat-data" "^7.26.8"
+ "@babel/helper-compilation-targets" "^7.26.5"
+ "@babel/helper-plugin-utils" "^7.26.5"
+ "@babel/helper-validator-option" "^7.25.9"
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9"
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9"
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9"
+ "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2"
+ "@babel/plugin-syntax-import-assertions" "^7.26.0"
+ "@babel/plugin-syntax-import-attributes" "^7.26.0"
+ "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
+ "@babel/plugin-transform-arrow-functions" "^7.25.9"
+ "@babel/plugin-transform-async-generator-functions" "^7.26.8"
+ "@babel/plugin-transform-async-to-generator" "^7.25.9"
+ "@babel/plugin-transform-block-scoped-functions" "^7.26.5"
+ "@babel/plugin-transform-block-scoping" "^7.25.9"
+ "@babel/plugin-transform-class-properties" "^7.25.9"
+ "@babel/plugin-transform-class-static-block" "^7.26.0"
+ "@babel/plugin-transform-classes" "^7.25.9"
+ "@babel/plugin-transform-computed-properties" "^7.25.9"
+ "@babel/plugin-transform-destructuring" "^7.25.9"
+ "@babel/plugin-transform-dotall-regex" "^7.25.9"
+ "@babel/plugin-transform-duplicate-keys" "^7.25.9"
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9"
+ "@babel/plugin-transform-dynamic-import" "^7.25.9"
+ "@babel/plugin-transform-exponentiation-operator" "^7.26.3"
+ "@babel/plugin-transform-export-namespace-from" "^7.25.9"
+ "@babel/plugin-transform-for-of" "^7.26.9"
+ "@babel/plugin-transform-function-name" "^7.25.9"
+ "@babel/plugin-transform-json-strings" "^7.25.9"
+ "@babel/plugin-transform-literals" "^7.25.9"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.25.9"
+ "@babel/plugin-transform-member-expression-literals" "^7.25.9"
+ "@babel/plugin-transform-modules-amd" "^7.25.9"
+ "@babel/plugin-transform-modules-commonjs" "^7.26.3"
+ "@babel/plugin-transform-modules-systemjs" "^7.25.9"
+ "@babel/plugin-transform-modules-umd" "^7.25.9"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9"
+ "@babel/plugin-transform-new-target" "^7.25.9"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6"
+ "@babel/plugin-transform-numeric-separator" "^7.25.9"
+ "@babel/plugin-transform-object-rest-spread" "^7.25.9"
+ "@babel/plugin-transform-object-super" "^7.25.9"
+ "@babel/plugin-transform-optional-catch-binding" "^7.25.9"
+ "@babel/plugin-transform-optional-chaining" "^7.25.9"
+ "@babel/plugin-transform-parameters" "^7.25.9"
+ "@babel/plugin-transform-private-methods" "^7.25.9"
+ "@babel/plugin-transform-private-property-in-object" "^7.25.9"
+ "@babel/plugin-transform-property-literals" "^7.25.9"
+ "@babel/plugin-transform-regenerator" "^7.25.9"
+ "@babel/plugin-transform-regexp-modifiers" "^7.26.0"
+ "@babel/plugin-transform-reserved-words" "^7.25.9"
+ "@babel/plugin-transform-shorthand-properties" "^7.25.9"
+ "@babel/plugin-transform-spread" "^7.25.9"
+ "@babel/plugin-transform-sticky-regex" "^7.25.9"
+ "@babel/plugin-transform-template-literals" "^7.26.8"
+ "@babel/plugin-transform-typeof-symbol" "^7.26.7"
+ "@babel/plugin-transform-unicode-escapes" "^7.25.9"
+ "@babel/plugin-transform-unicode-property-regex" "^7.25.9"
+ "@babel/plugin-transform-unicode-regex" "^7.25.9"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.25.9"
+ "@babel/preset-modules" "0.1.6-no-external-plugins"
+ babel-plugin-polyfill-corejs2 "^0.4.10"
+ babel-plugin-polyfill-corejs3 "^0.11.0"
+ babel-plugin-polyfill-regenerator "^0.6.1"
+ core-js-compat "^3.40.0"
+ semver "^6.3.1"
+
+"@babel/preset-modules@0.1.6-no-external-plugins":
+ version "0.1.6-no-external-plugins"
+ resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz"
+ integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@babel/preset-react@^7.18.6", "@babel/preset-react@^7.25.9":
+ version "7.26.3"
+ resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz"
+ integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-validator-option" "^7.25.9"
+ "@babel/plugin-transform-react-display-name" "^7.25.9"
+ "@babel/plugin-transform-react-jsx" "^7.25.9"
+ "@babel/plugin-transform-react-jsx-development" "^7.25.9"
+ "@babel/plugin-transform-react-pure-annotations" "^7.25.9"
+
+"@babel/preset-typescript@^7.21.0", "@babel/preset-typescript@^7.25.9":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz"
+ integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-validator-option" "^7.25.9"
+ "@babel/plugin-syntax-jsx" "^7.25.9"
+ "@babel/plugin-transform-modules-commonjs" "^7.25.9"
+ "@babel/plugin-transform-typescript" "^7.25.9"
+
+"@babel/runtime-corejs3@^7.25.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.9.tgz"
+ integrity sha512-5EVjbTegqN7RSJle6hMWYxO4voo4rI+9krITk+DWR+diJgGrjZjrIBnJhjrHYYQsFgI7j1w1QnrvV7YSKBfYGg==
+ dependencies:
+ core-js-pure "^3.30.2"
+ regenerator-runtime "^0.14.0"
+
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz"
+ integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@babel/template@^7.25.9", "@babel/template@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz"
+ integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/parser" "^7.26.9"
+ "@babel/types" "^7.26.9"
+
+"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz"
+ integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.26.9"
+ "@babel/parser" "^7.26.9"
+ "@babel/template" "^7.26.9"
+ "@babel/types" "^7.26.9"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.4.4":
+ version "7.26.9"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz"
+ integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+
+"@code-hike/lighter@0.7.0":
+ version "0.7.0"
+ resolved "https://registry.npmjs.org/@code-hike/lighter/-/lighter-0.7.0.tgz"
+ integrity sha512-64O07rIORKQLB+5T/GKAmKcD9sC0N9yHFJXa0Hs+0Aee1G+I4bSXxTccuDFP6c/G/3h5Pk7yv7PoX9/SpzaeiQ==
+
+"@code-hike/mdx@^0.9.0":
+ version "0.9.0"
+ resolved "https://registry.npmjs.org/@code-hike/mdx/-/mdx-0.9.0.tgz"
+ integrity sha512-0wg68ZCjVWAkWT4gBUZJ8Mwktjen/XeWyqBQCrhA2IZSbZZnMYsEI6JJEFb/nZoNI3comB3JdxPLykZRq3qT2A==
+ dependencies:
+ "@code-hike/lighter" "0.7.0"
+ node-fetch "^2.0.0"
+
+"@colors/colors@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz"
+ integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+
+"@cspotcode/source-map-support@^0.8.0":
+ version "0.8.1"
+ resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz"
+ integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
+ dependencies:
+ "@jridgewell/trace-mapping" "0.3.9"
+
+"@csstools/cascade-layer-name-parser@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz"
+ integrity sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==
+
+"@csstools/color-helpers@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz"
+ integrity sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==
+
+"@csstools/css-calc@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.1.tgz"
+ integrity sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==
+
+"@csstools/css-color-parser@^3.0.7":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz"
+ integrity sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==
+ dependencies:
+ "@csstools/color-helpers" "^5.0.1"
+ "@csstools/css-calc" "^2.1.1"
+
+"@csstools/css-parser-algorithms@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz"
+ integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==
+
+"@csstools/css-tokenizer@^3.0.3":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz"
+ integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==
+
+"@csstools/media-query-list-parser@^4.0.2":
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz"
+ integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==
+
+"@csstools/postcss-cascade-layers@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz"
+ integrity sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==
+ dependencies:
+ "@csstools/selector-specificity" "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+
+"@csstools/postcss-color-function@^4.0.7":
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.7.tgz"
+ integrity sha512-aDHYmhNIHR6iLw4ElWhf+tRqqaXwKnMl0YsQ/X105Zc4dQwe6yJpMrTN6BwOoESrkDjOYMOfORviSSLeDTJkdQ==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-color-mix-function@^3.0.7":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.7.tgz"
+ integrity sha512-e68Nev4CxZYCLcrfWhHH4u/N1YocOfTmw67/kVX5Rb7rnguqqLyxPjhHWjSBX8o4bmyuukmNf3wrUSU3//kT7g==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-content-alt-text@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz"
+ integrity sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==
+ dependencies:
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-exponential-functions@^2.0.6":
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.6.tgz"
+ integrity sha512-IgJA5DQsQLu/upA3HcdvC6xEMR051ufebBTIXZ5E9/9iiaA7juXWz1ceYj814lnDYP/7eWjZnw0grRJlX4eI6g==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-font-format-keywords@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz"
+ integrity sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==
+ dependencies:
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-gamut-mapping@^2.0.7":
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.7.tgz"
+ integrity sha512-gzFEZPoOkY0HqGdyeBXR3JP218Owr683u7KOZazTK7tQZBE8s2yhg06W1tshOqk7R7SWvw9gkw2TQogKpIW8Xw==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-gradients-interpolation-method@^5.0.7":
+ version "5.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.7.tgz"
+ integrity sha512-WgEyBeg6glUeTdS2XT7qeTFBthTJuXlS9GFro/DVomj7W7WMTamAwpoP4oQCq/0Ki2gvfRYFi/uZtmRE14/DFA==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-hwb-function@^4.0.7":
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.7.tgz"
+ integrity sha512-LKYqjO+wGwDCfNIEllessCBWfR4MS/sS1WXO+j00KKyOjm7jDW2L6jzUmqASEiv/kkJO39GcoIOvTTfB3yeBUA==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-ic-unit@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz"
+ integrity sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==
+ dependencies:
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-initial@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz"
+ integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==
+
+"@csstools/postcss-is-pseudo-class@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz"
+ integrity sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==
+ dependencies:
+ "@csstools/selector-specificity" "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+
+"@csstools/postcss-light-dark-function@^2.0.7":
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz"
+ integrity sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==
+ dependencies:
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-logical-float-and-clear@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz"
+ integrity sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==
+
+"@csstools/postcss-logical-overflow@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz"
+ integrity sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==
+
+"@csstools/postcss-logical-overscroll-behavior@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz"
+ integrity sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==
+
+"@csstools/postcss-logical-resize@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz"
+ integrity sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-logical-viewport-units@^3.0.3":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz"
+ integrity sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==
+ dependencies:
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-media-minmax@^2.0.6":
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.6.tgz"
+ integrity sha512-J1+4Fr2W3pLZsfxkFazK+9kr96LhEYqoeBszLmFjb6AjYs+g9oDAw3J5oQignLKk3rC9XHW+ebPTZ9FaW5u5pg==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/media-query-list-parser" "^4.0.2"
+
+"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz"
+ integrity sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==
+ dependencies:
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/media-query-list-parser" "^4.0.2"
+
+"@csstools/postcss-nested-calc@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz"
+ integrity sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==
+ dependencies:
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-normalize-display-values@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz"
+ integrity sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-oklab-function@^4.0.7":
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.7.tgz"
+ integrity sha512-I6WFQIbEKG2IO3vhaMGZDkucbCaUSXMxvHNzDdnfsTCF5tc0UlV3Oe2AhamatQoKFjBi75dSEMrgWq3+RegsOQ==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-progressive-custom-properties@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz"
+ integrity sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-random-function@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.2.tgz"
+ integrity sha512-vBCT6JvgdEkvRc91NFoNrLjgGtkLWt47GKT6E2UDn3nd8ZkMBiziQ1Md1OiKoSsgzxsSnGKG3RVdhlbdZEkHjA==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-relative-color-syntax@^3.0.7":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.7.tgz"
+ integrity sha512-apbT31vsJVd18MabfPOnE977xgct5B1I+Jpf+Munw3n6kKb1MMuUmGGH+PT9Hm/fFs6fe61Q/EWnkrb4bNoNQw==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+"@csstools/postcss-scope-pseudo-class@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz"
+ integrity sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+"@csstools/postcss-sign-functions@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.1.tgz"
+ integrity sha512-MslYkZCeMQDxetNkfmmQYgKCy4c+w9pPDfgOBCJOo/RI1RveEUdZQYtOfrC6cIZB7sD7/PHr2VGOcMXlZawrnA==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-stepped-value-functions@^4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.6.tgz"
+ integrity sha512-/dwlO9w8vfKgiADxpxUbZOWlL5zKoRIsCymYoh1IPuBsXODKanKnfuZRr32DEqT0//3Av1VjfNZU9yhxtEfIeA==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-text-decoration-shorthand@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz"
+ integrity sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==
+ dependencies:
+ "@csstools/color-helpers" "^5.0.1"
+ postcss-value-parser "^4.2.0"
+
+"@csstools/postcss-trigonometric-functions@^4.0.6":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.6.tgz"
+ integrity sha512-c4Y1D2Why/PeccaSouXnTt6WcNHJkoJRidV2VW9s5gJ97cNxnLgQ4Qj8qOqkIR9VmTQKJyNcbF4hy79ZQnWD7A==
+ dependencies:
+ "@csstools/css-calc" "^2.1.1"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+
+"@csstools/postcss-unset-value@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz"
+ integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==
+
+"@csstools/selector-resolve-nested@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz"
+ integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==
+
+"@csstools/selector-specificity@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz"
+ integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
+
+"@csstools/utilities@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz"
+ integrity sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==
+
+"@discoveryjs/json-ext@0.5.7":
+ version "0.5.7"
+ resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz"
+ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+
+"@docsearch/css@3.8.3":
+ version "3.8.3"
+ resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.8.3.tgz"
+ integrity sha512-1nELpMV40JDLJ6rpVVFX48R1jsBFIQ6RnEQDsLFGmzOjPWTOMlZqUcXcvRx8VmYV/TqnS1l784Ofz+ZEb+wEOQ==
+
+"@docsearch/react@^3.8.1":
+ version "3.8.3"
+ resolved "https://registry.npmjs.org/@docsearch/react/-/react-3.8.3.tgz"
+ integrity sha512-6UNrg88K7lJWmuS6zFPL/xgL+n326qXqZ7Ybyy4E8P/6Rcblk3GE8RXxeol4Pd5pFpKMhOhBhzABKKwHtbJCIg==
+ dependencies:
+ "@algolia/autocomplete-core" "1.17.9"
+ "@algolia/autocomplete-preset-algolia" "1.17.9"
+ "@docsearch/css" "3.8.3"
+ algoliasearch "^5.14.2"
+
+"@docusaurus/babel@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.7.0.tgz"
+ integrity sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ==
+ dependencies:
+ "@babel/core" "^7.25.9"
+ "@babel/generator" "^7.25.9"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/plugin-transform-runtime" "^7.25.9"
+ "@babel/preset-env" "^7.25.9"
+ "@babel/preset-react" "^7.25.9"
+ "@babel/preset-typescript" "^7.25.9"
+ "@babel/runtime" "^7.25.9"
+ "@babel/runtime-corejs3" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ babel-plugin-dynamic-import-node "^2.3.3"
+ fs-extra "^11.1.1"
+ tslib "^2.6.0"
+
+"@docusaurus/bundler@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.7.0.tgz"
+ integrity sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg==
+ dependencies:
+ "@babel/core" "^7.25.9"
+ "@docusaurus/babel" "3.7.0"
+ "@docusaurus/cssnano-preset" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ babel-loader "^9.2.1"
+ clean-css "^5.3.2"
+ copy-webpack-plugin "^11.0.0"
+ css-loader "^6.8.1"
+ css-minimizer-webpack-plugin "^5.0.1"
+ cssnano "^6.1.2"
+ file-loader "^6.2.0"
+ html-minifier-terser "^7.2.0"
+ mini-css-extract-plugin "^2.9.1"
+ null-loader "^4.0.1"
+ postcss "^8.4.26"
+ postcss-loader "^7.3.3"
+ postcss-preset-env "^10.1.0"
+ react-dev-utils "^12.0.1"
+ terser-webpack-plugin "^5.3.9"
+ tslib "^2.6.0"
+ url-loader "^4.1.1"
+ webpack "^5.95.0"
+ webpackbar "^6.0.1"
+
+"@docusaurus/core@^2.0.0-beta || ^3.0.0-alpha", "@docusaurus/core@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/core/-/core-3.7.0.tgz"
+ integrity sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==
+ dependencies:
+ "@docusaurus/babel" "3.7.0"
+ "@docusaurus/bundler" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ boxen "^6.2.1"
+ chalk "^4.1.2"
+ chokidar "^3.5.3"
+ cli-table3 "^0.6.3"
+ combine-promises "^1.1.0"
+ commander "^5.1.0"
+ core-js "^3.31.1"
+ del "^6.1.1"
+ detect-port "^1.5.1"
+ escape-html "^1.0.3"
+ eta "^2.2.0"
+ eval "^0.1.8"
+ fs-extra "^11.1.1"
+ html-tags "^3.3.1"
+ html-webpack-plugin "^5.6.0"
+ leven "^3.1.0"
+ lodash "^4.17.21"
+ p-map "^4.0.0"
+ prompts "^2.4.2"
+ react-dev-utils "^12.0.1"
+ react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
+ react-loadable "npm:@docusaurus/react-loadable@6.0.0"
+ react-loadable-ssr-addon-v5-slorber "^1.0.1"
+ react-router "^5.3.4"
+ react-router-config "^5.1.1"
+ react-router-dom "^5.3.4"
+ semver "^7.5.4"
+ serve-handler "^6.1.6"
+ shelljs "^0.8.5"
+ tslib "^2.6.0"
+ update-notifier "^6.0.2"
+ webpack "^5.95.0"
+ webpack-bundle-analyzer "^4.10.2"
+ webpack-dev-server "^4.15.2"
+ webpack-merge "^6.0.1"
+
+"@docusaurus/cssnano-preset@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.7.0.tgz"
+ integrity sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ==
+ dependencies:
+ cssnano-preset-advanced "^6.1.2"
+ postcss "^8.4.38"
+ postcss-sort-media-queries "^5.2.0"
+ tslib "^2.6.0"
+
+"@docusaurus/logger@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.7.0.tgz"
+ integrity sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA==
+ dependencies:
+ chalk "^4.1.2"
+ tslib "^2.6.0"
+
+"@docusaurus/mdx-loader@^3.6.0", "@docusaurus/mdx-loader@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.7.0.tgz"
+ integrity sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA==
+ dependencies:
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ "@mdx-js/mdx" "^3.0.0"
+ "@slorber/remark-comment" "^1.0.0"
+ escape-html "^1.0.3"
+ estree-util-value-to-estree "^3.0.1"
+ file-loader "^6.2.0"
+ fs-extra "^11.1.1"
+ image-size "^1.0.2"
+ mdast-util-mdx "^3.0.0"
+ mdast-util-to-string "^4.0.0"
+ rehype-raw "^7.0.0"
+ remark-directive "^3.0.0"
+ remark-emoji "^4.0.0"
+ remark-frontmatter "^5.0.0"
+ remark-gfm "^4.0.0"
+ stringify-object "^3.3.0"
+ tslib "^2.6.0"
+ unified "^11.0.3"
+ unist-util-visit "^5.0.0"
+ url-loader "^4.1.1"
+ vfile "^6.0.1"
+ webpack "^5.88.1"
+
+"@docusaurus/module-type-aliases@^3.7.0", "@docusaurus/module-type-aliases@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz"
+ integrity sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==
+ dependencies:
+ "@docusaurus/types" "3.7.0"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router-config" "*"
+ "@types/react-router-dom" "*"
+ react-helmet-async "npm:@slorber/react-helmet-async@*"
+ react-loadable "npm:@docusaurus/react-loadable@6.0.0"
+
+"@docusaurus/plugin-client-redirects@^3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.7.0.tgz"
+ integrity sha512-6B4XAtE5ZVKOyhPgpgMkb7LwCkN+Hgd4vOnlbwR8nCdTQhLjz8MHbGlwwvZ/cay2SPNRX5KssqKAlcHVZP2m8g==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ eta "^2.2.0"
+ fs-extra "^11.1.1"
+ lodash "^4.17.21"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-content-blog@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.7.0.tgz"
+ integrity sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/theme-common" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ cheerio "1.0.0-rc.12"
+ feed "^4.2.2"
+ fs-extra "^11.1.1"
+ lodash "^4.17.21"
+ reading-time "^1.5.0"
+ srcset "^4.0.0"
+ tslib "^2.6.0"
+ unist-util-visit "^5.0.0"
+ utility-types "^3.10.0"
+ webpack "^5.88.1"
+
+"@docusaurus/plugin-content-docs@*", "@docusaurus/plugin-content-docs@^2 || ^3", "@docusaurus/plugin-content-docs@^3.5.0", "@docusaurus/plugin-content-docs@^3.6.0", "@docusaurus/plugin-content-docs@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0.tgz"
+ integrity sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/module-type-aliases" "3.7.0"
+ "@docusaurus/theme-common" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ "@types/react-router-config" "^5.0.7"
+ combine-promises "^1.1.0"
+ fs-extra "^11.1.1"
+ js-yaml "^4.1.0"
+ lodash "^4.17.21"
+ tslib "^2.6.0"
+ utility-types "^3.10.0"
+ webpack "^5.88.1"
+
+"@docusaurus/plugin-content-pages@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0.tgz"
+ integrity sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ fs-extra "^11.1.1"
+ tslib "^2.6.0"
+ webpack "^5.88.1"
+
+"@docusaurus/plugin-debug@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.7.0.tgz"
+ integrity sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ fs-extra "^11.1.1"
+ react-json-view-lite "^1.2.0"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-google-analytics@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.7.0.tgz"
+ integrity sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-google-gtag@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.7.0.tgz"
+ integrity sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ "@types/gtag.js" "^0.0.12"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-google-tag-manager@^3.7.0", "@docusaurus/plugin-google-tag-manager@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.7.0.tgz"
+ integrity sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-sitemap@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.7.0.tgz"
+ integrity sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ fs-extra "^11.1.1"
+ sitemap "^7.1.1"
+ tslib "^2.6.0"
+
+"@docusaurus/plugin-svgr@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.7.0.tgz"
+ integrity sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ "@svgr/core" "8.1.0"
+ "@svgr/webpack" "^8.1.0"
+ tslib "^2.6.0"
+ webpack "^5.88.1"
+
+"@docusaurus/preset-classic@^3.6.0", "@docusaurus/preset-classic@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.7.0.tgz"
+ integrity sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/plugin-content-blog" "3.7.0"
+ "@docusaurus/plugin-content-docs" "3.7.0"
+ "@docusaurus/plugin-content-pages" "3.7.0"
+ "@docusaurus/plugin-debug" "3.7.0"
+ "@docusaurus/plugin-google-analytics" "3.7.0"
+ "@docusaurus/plugin-google-gtag" "3.7.0"
+ "@docusaurus/plugin-google-tag-manager" "3.7.0"
+ "@docusaurus/plugin-sitemap" "3.7.0"
+ "@docusaurus/plugin-svgr" "3.7.0"
+ "@docusaurus/theme-classic" "3.7.0"
+ "@docusaurus/theme-common" "3.7.0"
+ "@docusaurus/theme-search-algolia" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+
+"@docusaurus/theme-classic@>=3.0.0", "@docusaurus/theme-classic@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.7.0.tgz"
+ integrity sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ==
+ dependencies:
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/module-type-aliases" "3.7.0"
+ "@docusaurus/plugin-content-blog" "3.7.0"
+ "@docusaurus/plugin-content-docs" "3.7.0"
+ "@docusaurus/plugin-content-pages" "3.7.0"
+ "@docusaurus/theme-common" "3.7.0"
+ "@docusaurus/theme-translations" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ "@mdx-js/react" "^3.0.0"
+ clsx "^2.0.0"
+ copy-text-to-clipboard "^3.2.0"
+ infima "0.2.0-alpha.45"
+ lodash "^4.17.21"
+ nprogress "^0.2.0"
+ postcss "^8.4.26"
+ prism-react-renderer "^2.3.0"
+ prismjs "^1.29.0"
+ react-router-dom "^5.3.4"
+ rtlcss "^4.1.0"
+ tslib "^2.6.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-common@^2 || ^3", "@docusaurus/theme-common@^3.5.0", "@docusaurus/theme-common@^3.6.0", "@docusaurus/theme-common@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.7.0.tgz"
+ integrity sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A==
+ dependencies:
+ "@docusaurus/mdx-loader" "3.7.0"
+ "@docusaurus/module-type-aliases" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router-config" "*"
+ clsx "^2.0.0"
+ parse-numeric-range "^1.3.0"
+ prism-react-renderer "^2.3.0"
+ tslib "^2.6.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-search-algolia@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.7.0.tgz"
+ integrity sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g==
+ dependencies:
+ "@docsearch/react" "^3.8.1"
+ "@docusaurus/core" "3.7.0"
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/plugin-content-docs" "3.7.0"
+ "@docusaurus/theme-common" "3.7.0"
+ "@docusaurus/theme-translations" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-validation" "3.7.0"
+ algoliasearch "^5.17.1"
+ algoliasearch-helper "^3.22.6"
+ clsx "^2.0.0"
+ eta "^2.2.0"
+ fs-extra "^11.1.1"
+ lodash "^4.17.21"
+ tslib "^2.6.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-translations@^2 || ^3", "@docusaurus/theme-translations@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.7.0.tgz"
+ integrity sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g==
+ dependencies:
+ fs-extra "^11.1.1"
+ tslib "^2.6.0"
+
+"@docusaurus/types@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz"
+ integrity sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==
+ dependencies:
+ "@mdx-js/mdx" "^3.0.0"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ commander "^5.1.0"
+ joi "^17.9.2"
+ react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
+ utility-types "^3.10.0"
+ webpack "^5.95.0"
+ webpack-merge "^5.9.0"
+
+"@docusaurus/utils-common@^2 || ^3", "@docusaurus/utils-common@^3.6.0", "@docusaurus/utils-common@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.7.0.tgz"
+ integrity sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA==
+ dependencies:
+ "@docusaurus/types" "3.7.0"
+ tslib "^2.6.0"
+
+"@docusaurus/utils-validation@^2 || ^3", "@docusaurus/utils-validation@^3.5.0", "@docusaurus/utils-validation@^3.6.0", "@docusaurus/utils-validation@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.7.0.tgz"
+ integrity sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA==
+ dependencies:
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/utils" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ fs-extra "^11.2.0"
+ joi "^17.9.2"
+ js-yaml "^4.1.0"
+ lodash "^4.17.21"
+ tslib "^2.6.0"
+
+"@docusaurus/utils@^2 || ^3", "@docusaurus/utils@^3.5.0", "@docusaurus/utils@^3.6.0", "@docusaurus/utils@3.7.0":
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.7.0.tgz"
+ integrity sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA==
+ dependencies:
+ "@docusaurus/logger" "3.7.0"
+ "@docusaurus/types" "3.7.0"
+ "@docusaurus/utils-common" "3.7.0"
+ escape-string-regexp "^4.0.0"
+ file-loader "^6.2.0"
+ fs-extra "^11.1.1"
+ github-slugger "^1.5.0"
+ globby "^11.1.0"
+ gray-matter "^4.0.3"
+ jiti "^1.20.0"
+ js-yaml "^4.1.0"
+ lodash "^4.17.21"
+ micromatch "^4.0.5"
+ prompts "^2.4.2"
+ resolve-pathname "^3.0.0"
+ shelljs "^0.8.5"
+ tslib "^2.6.0"
+ url-loader "^4.1.1"
+ utility-types "^3.10.0"
+ webpack "^5.88.1"
+
+"@easyops-cn/autocomplete.js@^0.38.1":
+ version "0.38.1"
+ resolved "https://registry.npmjs.org/@easyops-cn/autocomplete.js/-/autocomplete.js-0.38.1.tgz"
+ integrity sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==
+ dependencies:
+ cssesc "^3.0.0"
+ immediate "^3.2.3"
+
+"@easyops-cn/docusaurus-search-local@^0.45.0":
+ version "0.45.0"
+ resolved "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.45.0.tgz"
+ integrity sha512-ccJjeYmBHrv2v8Y9eQnH79S0PEKcogACKkEatEKPcad7usQj/14jA9POUUUYW/yougLSXghwe+uIncbuUBuBFg==
+ dependencies:
+ "@docusaurus/plugin-content-docs" "^2 || ^3"
+ "@docusaurus/theme-translations" "^2 || ^3"
+ "@docusaurus/utils" "^2 || ^3"
+ "@docusaurus/utils-common" "^2 || ^3"
+ "@docusaurus/utils-validation" "^2 || ^3"
+ "@easyops-cn/autocomplete.js" "^0.38.1"
+ "@node-rs/jieba" "^1.6.0"
+ cheerio "^1.0.0"
+ clsx "^1.1.1"
+ debug "^4.2.0"
+ fs-extra "^10.0.0"
+ klaw-sync "^6.0.0"
+ lunr "^2.3.9"
+ lunr-languages "^1.4.0"
+ mark.js "^8.11.1"
+ tslib "^2.4.0"
+
+"@exodus/schemasafe@^1.0.0-rc.2":
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz"
+ integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==
+
+"@faker-js/faker@5.5.3":
+ version "5.5.3"
+ resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz"
+ integrity sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==
+
+"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
+ version "9.3.0"
+ resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz"
+ integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
+
+"@hapi/topo@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz"
+ integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
+ dependencies:
+ "@hapi/hoek" "^9.0.0"
+
+"@hookform/error-message@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/@hookform/error-message/-/error-message-2.0.1.tgz"
+ integrity sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==
+
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
+"@jest/schemas@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz"
+ integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
+ dependencies:
+ "@sinclair/typebox" "^0.27.8"
+
+"@jest/types@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz"
+ integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.8"
+ chalk "^4.0.0"
+
+"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.8"
+ resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz"
+ integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==
+ dependencies:
+ "@jridgewell/set-array" "^1.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/set-array@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz"
+ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.6"
+ resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz"
+ integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
+"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+ version "0.3.25"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
+ integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@jridgewell/trace-mapping@0.3.9":
+ version "0.3.9"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
+ integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@jsdevtools/ono@^7.1.3":
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz"
+ integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==
+
+"@leichtgewicht/ip-codec@^2.0.1":
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz"
+ integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==
+
+"@mdx-js/mdx@^3.0.0":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz"
+ integrity sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdx" "^2.0.0"
+ collapse-white-space "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-util-scope "^1.0.0"
+ estree-walker "^3.0.0"
+ hast-util-to-jsx-runtime "^2.0.0"
+ markdown-extensions "^2.0.0"
+ recma-build-jsx "^1.0.0"
+ recma-jsx "^1.0.0"
+ recma-stringify "^1.0.0"
+ rehype-recma "^1.0.0"
+ remark-mdx "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-rehype "^11.0.0"
+ source-map "^0.7.0"
+ unified "^11.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.0.1":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz"
+ integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==
+ dependencies:
+ "@types/mdx" "^2.0.0"
+
+"@mendable/search@^0.0.206":
+ version "0.0.206"
+ resolved "https://registry.npmjs.org/@mendable/search/-/search-0.0.206.tgz"
+ integrity sha512-T1qvSL4S0YXnQXaBjJ7DVzBDv+EpaPOm7ovacjL6qg5AtxdK8csF6T2rxj82hJBLcFzKmghEq8A8dQkfNiHLLw==
+ dependencies:
+ html-react-parser "^4.2.0"
+ posthog-js "^1.45.1"
+
+"@monaco-editor/loader@^1.5.0":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz"
+ integrity sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==
+ dependencies:
+ state-local "^1.0.6"
+
+"@monaco-editor/react@^4.3.1":
+ version "4.7.0"
+ resolved "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz"
+ integrity sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==
+ dependencies:
+ "@monaco-editor/loader" "^1.5.0"
+
+"@node-rs/jieba-darwin-arm64@1.10.4":
+ version "1.10.4"
+ resolved "https://registry.npmjs.org/@node-rs/jieba-darwin-arm64/-/jieba-darwin-arm64-1.10.4.tgz"
+ integrity sha512-G++RYEJ2jo0rxF9626KUy90wp06TRUjAsvY/BrIzEOX/ingQYV/HjwQzNPRR1P1o32a6/U8RGo7zEBhfdybL6w==
+
+"@node-rs/jieba@^1.6.0":
+ version "1.10.4"
+ resolved "https://registry.npmjs.org/@node-rs/jieba/-/jieba-1.10.4.tgz"
+ integrity sha512-GvDgi8MnBiyWd6tksojej8anIx18244NmIOc1ovEw8WKNUejcccLfyu8vj66LWSuoZuKILVtNsOy4jvg3aoxIw==
+ optionalDependencies:
+ "@node-rs/jieba-android-arm-eabi" "1.10.4"
+ "@node-rs/jieba-android-arm64" "1.10.4"
+ "@node-rs/jieba-darwin-arm64" "1.10.4"
+ "@node-rs/jieba-darwin-x64" "1.10.4"
+ "@node-rs/jieba-freebsd-x64" "1.10.4"
+ "@node-rs/jieba-linux-arm-gnueabihf" "1.10.4"
+ "@node-rs/jieba-linux-arm64-gnu" "1.10.4"
+ "@node-rs/jieba-linux-arm64-musl" "1.10.4"
+ "@node-rs/jieba-linux-x64-gnu" "1.10.4"
+ "@node-rs/jieba-linux-x64-musl" "1.10.4"
+ "@node-rs/jieba-wasm32-wasi" "1.10.4"
+ "@node-rs/jieba-win32-arm64-msvc" "1.10.4"
+ "@node-rs/jieba-win32-ia32-msvc" "1.10.4"
+ "@node-rs/jieba-win32-x64-msvc" "1.10.4"
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@notionhq/client@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.npmjs.org/@notionhq/client/-/client-2.2.3.tgz"
+ integrity sha512-ZqUzY0iRg/LIrwS+wzz/6osSB2nxIpmqdAtdUwzpcimc9Jlu1j85FeYdaU26Shr193CFrl2TLFeKqpk/APRQ4g==
+ dependencies:
+ "@types/node-fetch" "^2.5.10"
+ node-fetch "^2.6.1"
+
+"@parcel/watcher-darwin-arm64@2.5.1":
+ version "2.5.1"
+ resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz"
+ integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
+
+"@parcel/watcher@^2.4.1":
+ version "2.5.1"
+ resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
+ integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==
+ dependencies:
+ detect-libc "^1.0.3"
+ is-glob "^4.0.3"
+ micromatch "^4.0.5"
+ node-addon-api "^7.0.0"
+ optionalDependencies:
+ "@parcel/watcher-android-arm64" "2.5.1"
+ "@parcel/watcher-darwin-arm64" "2.5.1"
+ "@parcel/watcher-darwin-x64" "2.5.1"
+ "@parcel/watcher-freebsd-x64" "2.5.1"
+ "@parcel/watcher-linux-arm-glibc" "2.5.1"
+ "@parcel/watcher-linux-arm-musl" "2.5.1"
+ "@parcel/watcher-linux-arm64-glibc" "2.5.1"
+ "@parcel/watcher-linux-arm64-musl" "2.5.1"
+ "@parcel/watcher-linux-x64-glibc" "2.5.1"
+ "@parcel/watcher-linux-x64-musl" "2.5.1"
+ "@parcel/watcher-win32-arm64" "2.5.1"
+ "@parcel/watcher-win32-ia32" "2.5.1"
+ "@parcel/watcher-win32-x64" "2.5.1"
+
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
+"@pnpm/config.env-replace@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz"
+ integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==
+
+"@pnpm/network.ca-file@^1.0.1":
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz"
+ integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==
+ dependencies:
+ graceful-fs "4.2.10"
+
+"@pnpm/npm-conf@^2.1.0":
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz"
+ integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==
+ dependencies:
+ "@pnpm/config.env-replace" "^1.1.0"
+ "@pnpm/network.ca-file" "^1.0.1"
+ config-chain "^1.1.11"
+
+"@polka/url@^1.0.0-next.24":
+ version "1.0.0-next.28"
+ resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz"
+ integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
+
+"@redocly/ajv@^8.11.2":
+ version "8.11.2"
+ resolved "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz"
+ integrity sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js-replace "^1.0.1"
+
+"@redocly/config@^0.20.1":
+ version "0.20.3"
+ resolved "https://registry.npmjs.org/@redocly/config/-/config-0.20.3.tgz"
+ integrity sha512-Nyyv1Bj7GgYwj/l46O0nkH1GTKWbO3Ixe7KFcn021aZipkZd+z8Vlu1BwkhqtVgivcKaClaExtWU/lDHkjBzag==
+
+"@redocly/openapi-core@^1.10.5":
+ version "1.29.0"
+ resolved "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.29.0.tgz"
+ integrity sha512-Ju8POuRjYLTl6JfaSMq5exzhw4E/f1Qb7fGxgS4/PDSTzS1jzZ/UUJRBPeiQ1Ag7yuxH6JwltOr2iiltnBey1w==
+ dependencies:
+ "@redocly/ajv" "^8.11.2"
+ "@redocly/config" "^0.20.1"
+ colorette "^1.2.0"
+ https-proxy-agent "^7.0.5"
+ js-levenshtein "^1.1.6"
+ js-yaml "^4.1.0"
+ minimatch "^5.0.1"
+ pluralize "^8.0.0"
+ yaml-ast-parser "0.0.43"
+
+"@reduxjs/toolkit@^1.7.1":
+ version "1.9.7"
+ resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz"
+ integrity sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==
+ dependencies:
+ immer "^9.0.21"
+ redux "^4.2.1"
+ redux-thunk "^2.4.2"
+ reselect "^4.1.8"
+
+"@sideway/address@^4.1.5":
+ version "4.1.5"
+ resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz"
+ integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==
+ dependencies:
+ "@hapi/hoek" "^9.0.0"
+
+"@sideway/formula@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz"
+ integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
+
+"@sideway/pinpoint@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz"
+ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
+
+"@sillsdev/docu-notion@^0.15.0":
+ version "0.15.0"
+ resolved "https://registry.npmjs.org/@sillsdev/docu-notion/-/docu-notion-0.15.0.tgz"
+ integrity sha512-CwT/N/2UXS3/o7Mxst1ZL451nR9Zx6HxoOdYMrAM9V0mn5btb47X7eDVtjpgDKjobJyHo/+THkB9o9jUcJQyxw==
+ dependencies:
+ "@notionhq/client" "2.2.3"
+ chalk "^4.1.2"
+ commander "^9.2.0"
+ cosmiconfig "^8.0.0"
+ cosmiconfig-typescript-loader "^4.3.0"
+ file-type "16.5.1"
+ fs-extra "^10.1.0"
+ limiter "^2.1.0"
+ markdown-table "^2.0.0"
+ node-fetch "2.6.6"
+ notion-client "^4"
+ notion-to-md "3.1.1"
+ path "^0.12.7"
+ sanitize-filename "^1.6.3"
+ ts-node "^10.2.1"
+
+"@sinclair/typebox@^0.27.8":
+ version "0.27.8"
+ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz"
+ integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+
+"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0":
+ version "4.6.0"
+ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz"
+ integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+
+"@sindresorhus/is@^5.2.0":
+ version "5.6.0"
+ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz"
+ integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==
+
+"@slorber/remark-comment@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz"
+ integrity sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.1.0"
+ micromark-util-symbol "^1.0.1"
+
+"@svgr/babel-plugin-add-jsx-attribute@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz"
+ integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==
+
+"@svgr/babel-plugin-remove-jsx-attribute@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz"
+ integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==
+
+"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz"
+ integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==
+
+"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz"
+ integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==
+
+"@svgr/babel-plugin-svg-dynamic-title@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz"
+ integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==
+
+"@svgr/babel-plugin-svg-em-dimensions@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz"
+ integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==
+
+"@svgr/babel-plugin-transform-react-native-svg@8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz"
+ integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==
+
+"@svgr/babel-plugin-transform-svg-component@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz"
+ integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==
+
+"@svgr/babel-preset@8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz"
+ integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==
+ dependencies:
+ "@svgr/babel-plugin-add-jsx-attribute" "8.0.0"
+ "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0"
+ "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0"
+ "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0"
+ "@svgr/babel-plugin-svg-dynamic-title" "8.0.0"
+ "@svgr/babel-plugin-svg-em-dimensions" "8.0.0"
+ "@svgr/babel-plugin-transform-react-native-svg" "8.1.0"
+ "@svgr/babel-plugin-transform-svg-component" "8.0.0"
+
+"@svgr/core@*", "@svgr/core@8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz"
+ integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==
+ dependencies:
+ "@babel/core" "^7.21.3"
+ "@svgr/babel-preset" "8.1.0"
+ camelcase "^6.2.0"
+ cosmiconfig "^8.1.3"
+ snake-case "^3.0.4"
+
+"@svgr/hast-util-to-babel-ast@8.0.0":
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz"
+ integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==
+ dependencies:
+ "@babel/types" "^7.21.3"
+ entities "^4.4.0"
+
+"@svgr/plugin-jsx@8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz"
+ integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==
+ dependencies:
+ "@babel/core" "^7.21.3"
+ "@svgr/babel-preset" "8.1.0"
+ "@svgr/hast-util-to-babel-ast" "8.0.0"
+ svg-parser "^2.0.4"
+
+"@svgr/plugin-svgo@8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz"
+ integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==
+ dependencies:
+ cosmiconfig "^8.1.3"
+ deepmerge "^4.3.1"
+ svgo "^3.0.2"
+
+"@svgr/webpack@^8.1.0":
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz"
+ integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==
+ dependencies:
+ "@babel/core" "^7.21.3"
+ "@babel/plugin-transform-react-constant-elements" "^7.21.3"
+ "@babel/preset-env" "^7.20.2"
+ "@babel/preset-react" "^7.18.6"
+ "@babel/preset-typescript" "^7.21.0"
+ "@svgr/core" "8.1.0"
+ "@svgr/plugin-jsx" "8.1.0"
+ "@svgr/plugin-svgo" "8.1.0"
+
+"@szmarczak/http-timer@^4.0.5":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz"
+ integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
+ dependencies:
+ defer-to-connect "^2.0.0"
+
+"@szmarczak/http-timer@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz"
+ integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==
+ dependencies:
+ defer-to-connect "^2.0.1"
+
+"@tokenizer/token@^0.1.1":
+ version "0.1.1"
+ resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.1.1.tgz"
+ integrity sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==
+
+"@tokenizer/token@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz"
+ integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
+
+"@trysound/sax@0.2.0":
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
+ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
+
+"@tsconfig/docusaurus@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-2.0.3.tgz"
+ integrity sha512-3l1L5PzWVa7l0691TjnsZ0yOIEwG9DziSqu5IPZPlI5Dowi7z42cEym8Y35GHbgHvPcBfNxfrbxm7Cncn4nByQ==
+
+"@tsconfig/node10@^1.0.7":
+ version "1.0.11"
+ resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz"
+ integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==
+
+"@tsconfig/node12@^1.0.7":
+ version "1.0.11"
+ resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz"
+ integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
+
+"@tsconfig/node14@^1.0.0":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz"
+ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
+
+"@tsconfig/node16@^1.0.2":
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz"
+ integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+
+"@types/acorn@^4.0.0":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz"
+ integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==
+ dependencies:
+ "@types/estree" "*"
+
+"@types/body-parser@*":
+ version "1.19.5"
+ resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz"
+ integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
+"@types/bonjour@^3.5.9":
+ version "3.5.13"
+ resolved "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz"
+ integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/cacheable-request@^6.0.1":
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz"
+ integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==
+ dependencies:
+ "@types/http-cache-semantics" "*"
+ "@types/keyv" "^3.1.4"
+ "@types/node" "*"
+ "@types/responselike" "^1.0.0"
+
+"@types/connect-history-api-fallback@^1.3.5":
+ version "1.5.4"
+ resolved "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz"
+ integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==
+ dependencies:
+ "@types/express-serve-static-core" "*"
+ "@types/node" "*"
+
+"@types/connect@*":
+ version "3.4.38"
+ resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz"
+ integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
+ dependencies:
+ "@types/node" "*"
+
+"@types/debug@^4.0.0":
+ version "4.1.12"
+ resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz"
+ integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
+ dependencies:
+ "@types/ms" "*"
+
+"@types/eslint-scope@^3.7.7":
+ version "3.7.7"
+ resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz"
+ integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "9.6.1"
+ resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz"
+ integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree-jsx@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz"
+ integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
+ dependencies:
+ "@types/estree" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz"
+ integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
+
+"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
+ version "5.0.6"
+ resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz"
+ integrity sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express-serve-static-core@^4.17.33":
+ version "4.19.6"
+ resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz"
+ integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express@*":
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz"
+ integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^5.0.0"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
+"@types/express@^4.17.13":
+ version "4.17.21"
+ resolved "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz"
+ integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^4.17.33"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
+"@types/gtag.js@^0.0.12":
+ version "0.0.12"
+ resolved "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz"
+ integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==
+
+"@types/hast@^2.0.0":
+ version "2.3.10"
+ resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz"
+ integrity sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==
+ dependencies:
+ "@types/unist" "^2"
+
+"@types/hast@^3.0.0":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz"
+ integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/history@^4.7.11":
+ version "4.7.11"
+ resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz"
+ integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
+
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.6"
+ resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz"
+ integrity sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
+"@types/html-minifier-terser@^6.0.0":
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz"
+ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
+
+"@types/http-cache-semantics@*", "@types/http-cache-semantics@^4.0.2":
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz"
+ integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
+
+"@types/http-errors@*":
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz"
+ integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
+
+"@types/http-proxy@^1.17.8":
+ version "1.17.16"
+ resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz"
+ integrity sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==
+ dependencies:
+ "@types/node" "*"
+
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz"
+ integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==
+
+"@types/istanbul-lib-report@*":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz"
+ integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^3.0.0":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz"
+ integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==
+ dependencies:
+ "@types/istanbul-lib-report" "*"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+ version "7.0.15"
+ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
+ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
+
+"@types/keyv@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz"
+ integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/mdast@^3.0.0":
+ version "3.0.15"
+ resolved "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz"
+ integrity sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==
+ dependencies:
+ "@types/unist" "^2"
+
+"@types/mdast@^4.0.0", "@types/mdast@^4.0.2":
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/mdx@^2.0.0":
+ version "2.0.13"
+ resolved "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz"
+ integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==
+
+"@types/mime@^1":
+ version "1.3.5"
+ resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz"
+ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
+
+"@types/ms@*":
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz"
+ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
+
+"@types/node-fetch@^2.5.10":
+ version "2.6.12"
+ resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz"
+ integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==
+ dependencies:
+ "@types/node" "*"
+ form-data "^4.0.0"
+
+"@types/node-forge@^1.3.0":
+ version "1.3.11"
+ resolved "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz"
+ integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/node@*":
+ version "22.13.4"
+ resolved "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz"
+ integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==
+ dependencies:
+ undici-types "~6.20.0"
+
+"@types/node@^17.0.5":
+ version "17.0.45"
+ resolved "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz"
+ integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
+
+"@types/parse-json@^4.0.0":
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz"
+ integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
+
+"@types/parse5@^6.0.0":
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz"
+ integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
+
+"@types/prismjs@^1.0.0", "@types/prismjs@^1.26.0":
+ version "1.26.5"
+ resolved "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz"
+ integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==
+
+"@types/prop-types@*", "@types/prop-types@^15.0.0":
+ version "15.7.14"
+ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz"
+ integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
+
+"@types/qs@*":
+ version "6.9.18"
+ resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz"
+ integrity sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==
+
+"@types/range-parser@*":
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz"
+ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
+
+"@types/react-redux@^7.1.20":
+ version "7.1.34"
+ resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz"
+ integrity sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
+"@types/react-router-config@*", "@types/react-router-config@^5.0.7":
+ version "5.0.11"
+ resolved "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz"
+ integrity sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router" "^5.1.0"
+
+"@types/react-router-dom@*":
+ version "5.3.3"
+ resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz"
+ integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router" "*"
+
+"@types/react-router@*", "@types/react-router@^5.1.0":
+ version "5.1.20"
+ resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz"
+ integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16":
+ version "18.3.18"
+ resolved "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz"
+ integrity sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^3.0.2"
+
+"@types/responselike@^1.0.0":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz"
+ integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/retry@0.12.0":
+ version "0.12.0"
+ resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz"
+ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+
+"@types/sax@^1.2.1":
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz"
+ integrity sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==
+ dependencies:
+ "@types/node" "*"
+
+"@types/send@*":
+ version "0.17.4"
+ resolved "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz"
+ integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
+"@types/serve-index@^1.9.1":
+ version "1.9.4"
+ resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz"
+ integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==
+ dependencies:
+ "@types/express" "*"
+
+"@types/serve-static@*", "@types/serve-static@^1.13.10":
+ version "1.15.7"
+ resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz"
+ integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
+ dependencies:
+ "@types/http-errors" "*"
+ "@types/node" "*"
+ "@types/send" "*"
+
+"@types/sockjs@^0.3.33":
+ version "0.3.36"
+ resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz"
+ integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==
+ dependencies:
+ "@types/node" "*"
+
+"@types/unist@*", "@types/unist@^3.0.0":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz"
+ integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
+
+"@types/unist@^2", "@types/unist@^2.0.0":
+ version "2.0.11"
+ resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz"
+ integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
+
+"@types/ws@^8.5.5":
+ version "8.5.14"
+ resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz"
+ integrity sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/yargs-parser@*":
+ version "21.0.3"
+ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz"
+ integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==
+
+"@types/yargs@^17.0.8":
+ version "17.0.33"
+ resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz"
+ integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==
+ dependencies:
+ "@types/yargs-parser" "*"
+
+"@ungap/structured-clone@^1.0.0":
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
+ integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+
+"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz"
+ integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==
+ dependencies:
+ "@webassemblyjs/helper-numbers" "1.13.2"
+ "@webassemblyjs/helper-wasm-bytecode" "1.13.2"
+
+"@webassemblyjs/floating-point-hex-parser@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz"
+ integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==
+
+"@webassemblyjs/helper-api-error@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz"
+ integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==
+
+"@webassemblyjs/helper-buffer@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz"
+ integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==
+
+"@webassemblyjs/helper-numbers@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz"
+ integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.13.2"
+ "@webassemblyjs/helper-api-error" "1.13.2"
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz"
+ integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==
+
+"@webassemblyjs/helper-wasm-section@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz"
+ integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@webassemblyjs/helper-buffer" "1.14.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.13.2"
+ "@webassemblyjs/wasm-gen" "1.14.1"
+
+"@webassemblyjs/ieee754@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz"
+ integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==
+ dependencies:
+ "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz"
+ integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==
+ dependencies:
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.13.2":
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz"
+ integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==
+
+"@webassemblyjs/wasm-edit@^1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz"
+ integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@webassemblyjs/helper-buffer" "1.14.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.13.2"
+ "@webassemblyjs/helper-wasm-section" "1.14.1"
+ "@webassemblyjs/wasm-gen" "1.14.1"
+ "@webassemblyjs/wasm-opt" "1.14.1"
+ "@webassemblyjs/wasm-parser" "1.14.1"
+ "@webassemblyjs/wast-printer" "1.14.1"
+
+"@webassemblyjs/wasm-gen@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz"
+ integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@webassemblyjs/helper-wasm-bytecode" "1.13.2"
+ "@webassemblyjs/ieee754" "1.13.2"
+ "@webassemblyjs/leb128" "1.13.2"
+ "@webassemblyjs/utf8" "1.13.2"
+
+"@webassemblyjs/wasm-opt@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz"
+ integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@webassemblyjs/helper-buffer" "1.14.1"
+ "@webassemblyjs/wasm-gen" "1.14.1"
+ "@webassemblyjs/wasm-parser" "1.14.1"
+
+"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz"
+ integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@webassemblyjs/helper-api-error" "1.13.2"
+ "@webassemblyjs/helper-wasm-bytecode" "1.13.2"
+ "@webassemblyjs/ieee754" "1.13.2"
+ "@webassemblyjs/leb128" "1.13.2"
+ "@webassemblyjs/utf8" "1.13.2"
+
+"@webassemblyjs/wast-printer@1.14.1":
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz"
+ integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==
+ dependencies:
+ "@webassemblyjs/ast" "1.14.1"
+ "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz"
+ integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+ version "4.2.2"
+ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
+ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+
+abort-controller@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
+ integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+ dependencies:
+ event-target-shim "^5.0.0"
+
+accepts@~1.3.4, accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+acorn-jsx@^5.0.0:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn-walk@^8.0.0, acorn-walk@^8.1.1:
+ version "8.3.4"
+ resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz"
+ integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
+ dependencies:
+ acorn "^8.11.0"
+
+"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.2:
+ version "8.14.0"
+ resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz"
+ integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
+
+address@^1.0.1, address@^1.1.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/address/-/address-1.2.2.tgz"
+ integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==
+
+agent-base@^7.1.2:
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz"
+ integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
+
+aggregate-error@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz"
+ integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
+ dependencies:
+ clean-stack "^2.0.0"
+ indent-string "^4.0.0"
+
+ajv-draft-04@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz"
+ integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==
+
+ajv-formats@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv-formats@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz"
+ integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv-keywords@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz"
+ integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+
+ajv@^6.12.2, ajv@^6.12.5, ajv@^6.9.1:
+ version "6.12.6"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ajv@^8.0.0, ajv@^8.8.2, ajv@^8.9.0:
+ version "8.17.1"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz"
+ integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ fast-uri "^3.0.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+
+ajv@^8.5.0, ajv@8.11.0:
+ version "8.11.0"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz"
+ integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+algoliasearch-helper@^3.22.6:
+ version "3.24.1"
+ resolved "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.24.1.tgz"
+ integrity sha512-knYRACqLH9UpeR+WRUrBzBFR2ulGuOjI2b525k4PNeqZxeFMHJE7YcL7s6Jh12Qza0rtHqZdgHMfeuaaAkf4wA==
+ dependencies:
+ "@algolia/events" "^4.0.1"
+
+algoliasearch@^5.14.2, algoliasearch@^5.17.1, "algoliasearch@>= 3.1 < 6", "algoliasearch@>= 4.9.1 < 6":
+ version "5.20.3"
+ resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.20.3.tgz"
+ integrity sha512-iNC6BGvipaalFfDfDnXUje8GUlW5asj0cTMsZJwO/0rhsyLx1L7GZFAY8wW+eQ6AM4Yge2p5GSE5hrBlfSD90Q==
+ dependencies:
+ "@algolia/client-abtesting" "5.20.3"
+ "@algolia/client-analytics" "5.20.3"
+ "@algolia/client-common" "5.20.3"
+ "@algolia/client-insights" "5.20.3"
+ "@algolia/client-personalization" "5.20.3"
+ "@algolia/client-query-suggestions" "5.20.3"
+ "@algolia/client-search" "5.20.3"
+ "@algolia/ingestion" "1.20.3"
+ "@algolia/monitoring" "1.20.3"
+ "@algolia/recommend" "5.20.3"
+ "@algolia/requester-browser-xhr" "5.20.3"
+ "@algolia/requester-fetch" "5.20.3"
+ "@algolia/requester-node-http" "5.20.3"
+
+allof-merge@^0.6.6:
+ version "0.6.6"
+ resolved "https://registry.npmjs.org/allof-merge/-/allof-merge-0.6.6.tgz"
+ integrity sha512-116eZBf2he0/J4Tl7EYMz96I5Anaeio+VL0j/H2yxW9CoYQAMMv8gYcwkVRoO7XfIOv/qzSTfVzDVGAYxKFi3g==
+ dependencies:
+ json-crawl "^0.5.3"
+
+ansi-align@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz"
+ integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
+ dependencies:
+ string-width "^4.1.0"
+
+ansi-escapes@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz"
+ integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+ dependencies:
+ type-fest "^0.21.3"
+
+ansi-html-community@^0.0.8:
+ version "0.0.8"
+ resolved "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz"
+ integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz"
+ integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz"
+ integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
+ integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
+any-promise@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz"
+ integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+arg@^4.1.0:
+ version "4.1.3"
+ resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz"
+ integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
+
+arg@^5.0.0, arg@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz"
+ integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
+
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz"
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
+asap@^2.0.0:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"
+ integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
+
+asn1.js@^4.10.1:
+ version "4.10.1"
+ resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz"
+ integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+assert@^2.0.0, assert@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
+astring@^1.8.0:
+ version "1.9.0"
+ resolved "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz"
+ integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==
+
+async@3.2.2:
+ version "3.2.2"
+ resolved "https://registry.npmjs.org/async/-/async-3.2.2.tgz"
+ integrity sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==
+
+async@3.2.4:
+ version "3.2.4"
+ resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz"
+ integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
+autoprefixer@^10.4.19:
+ version "10.4.20"
+ resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz"
+ integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==
+ dependencies:
+ browserslist "^4.23.3"
+ caniuse-lite "^1.0.30001646"
+ fraction.js "^4.3.7"
+ normalize-range "^0.1.2"
+ picocolors "^1.0.1"
+ postcss-value-parser "^4.2.0"
+
+available-typed-arrays@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz"
+ integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
+ dependencies:
+ possible-typed-array-names "^1.0.0"
+
+babel-code-frame@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz"
+ integrity sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==
+ dependencies:
+ chalk "^1.1.3"
+ esutils "^2.0.2"
+ js-tokens "^3.0.2"
+
+babel-core@^6.26.0:
+ version "6.26.3"
+ resolved "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz"
+ integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-generator "^6.26.0"
+ babel-helpers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-register "^6.26.0"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ convert-source-map "^1.5.1"
+ debug "^2.6.9"
+ json5 "^0.5.1"
+ lodash "^4.17.4"
+ minimatch "^3.0.4"
+ path-is-absolute "^1.0.1"
+ private "^0.1.8"
+ slash "^1.0.0"
+ source-map "^0.5.7"
+
+babel-generator@^6.26.0:
+ version "6.26.1"
+ resolved "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz"
+ integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
+ dependencies:
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ detect-indent "^4.0.0"
+ jsesc "^1.3.0"
+ lodash "^4.17.4"
+ source-map "^0.5.7"
+ trim-right "^1.0.1"
+
+babel-helper-bindify-decorators@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz"
+ integrity sha512-TYX2QQATKA6Wssp6j7jqlw4QLmABDN1olRdEHndYvBXdaXM5dcx6j5rN0+nd+aVL+Th40fAEYvvw/Xxd/LETuQ==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz"
+ integrity sha512-gCtfYORSG1fUMX4kKraymq607FWgMWg+j42IFPc18kFQEsmtaibP4UrqsXt8FlEJle25HUd4tsoDR7H2wDhe9Q==
+ dependencies:
+ babel-helper-explode-assignable-expression "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-call-delegate@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz"
+ integrity sha512-RL8n2NiEj+kKztlrVJM9JT1cXzzAdvWFh76xh/H1I4nKwunzE4INBXn8ieCZ+wh4zWszZk7NBS1s/8HR5jDkzQ==
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-define-map@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz"
+ integrity sha512-bHkmjcC9lM1kmZcVpA5t2om2nzT/xiZpo6TJq7UlZ3wqKfzia4veeXbIhKvJXAMzhhEBd3cR1IElL5AenWEUpA==
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-explode-assignable-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz"
+ integrity sha512-qe5csbhbvq6ccry9G7tkXbzNtcDiH4r51rrPUbwwoTzZ18AqxWYRZT6AOmxrpxKnQBW0pYlBI/8vh73Z//78nQ==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-explode-class@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz"
+ integrity sha512-SFbWewr0/0U4AiRzsHqwsbOQeLXVa9T1ELdqEa2efcQB5KopTnunAqoj07TuHlN2lfTQNPGO/rJR4FMln5fVcA==
+ dependencies:
+ babel-helper-bindify-decorators "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-function-name@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz"
+ integrity sha512-Oo6+e2iX+o9eVvJ9Y5eKL5iryeRdsIkwRYheCuhYdVHsdEQysbc2z2QkqCLIYnNxkT5Ss3ggrHdXiDI7Dhrn4Q==
+ dependencies:
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-get-function-arity@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz"
+ integrity sha512-WfgKFX6swFB1jS2vo+DwivRN4NB8XUdM3ij0Y1gnC21y1tdBoe6xjVnd7NSI6alv+gZXCtJqvrTeMW3fR/c0ng==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-hoist-variables@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz"
+ integrity sha512-zAYl3tqerLItvG5cKYw7f1SpvIxS9zi7ohyGHaI9cgDUjAT6YcY9jIEH5CstetP5wHIVSceXwNS7Z5BpJg+rOw==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-optimise-call-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz"
+ integrity sha512-Op9IhEaxhbRT8MDXx2iNuMgciu2V8lDvYCNQbDGjdBNCjaMvyLf4wl4A3b8IgndCyQF8TwfgsQ8T3VD8aX1/pA==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-regex@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz"
+ integrity sha512-VlPiWmqmGJp0x0oK27Out1D+71nVVCTSdlbhIVoaBAj2lUgrNjBCRR9+llO4lTSb2O4r7PJg+RobRkhBrf6ofg==
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-remap-async-to-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz"
+ integrity sha512-RYqaPD0mQyQIFRu7Ho5wE2yvA/5jxqCIj/Lv4BXNq23mHYu/vxikOy2JueLiBxQknwapwrJeNCesvY0ZcfnlHg==
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-replace-supers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz"
+ integrity sha512-sLI+u7sXJh6+ToqDr57Bv973kCepItDhMou0xCP2YPVmR1jkHSCY+p1no8xErbV1Siz5QE8qKT1WIwybSWlqjw==
+ dependencies:
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helpers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz"
+ integrity sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-loader@^9.2.1:
+ version "9.2.1"
+ resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz"
+ integrity sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==
+ dependencies:
+ find-cache-dir "^4.0.0"
+ schema-utils "^4.0.0"
+
+babel-messages@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz"
+ integrity sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-check-es2015-constants@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz"
+ integrity sha512-B1M5KBP29248dViEo1owyY32lk1ZSH2DaNNrXLGt8lyjjHm7pBqAdQ7VKUPR6EEDO323+OvT3MQXbCin8ooWdA==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
+babel-plugin-polyfill-corejs2@^0.4.10:
+ version "0.4.12"
+ resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz"
+ integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==
+ dependencies:
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-define-polyfill-provider" "^0.6.3"
+ semver "^6.3.1"
+
+babel-plugin-polyfill-corejs3@^0.10.6:
+ version "0.10.6"
+ resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz"
+ integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.6.2"
+ core-js-compat "^3.38.0"
+
+babel-plugin-polyfill-corejs3@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz"
+ integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.6.3"
+ core-js-compat "^3.40.0"
+
+babel-plugin-polyfill-regenerator@^0.6.1:
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz"
+ integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.6.3"
+
+babel-plugin-syntax-async-functions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz"
+ integrity sha512-4Zp4unmHgw30A1eWI5EpACji2qMocisdXhAftfhXoSV9j0Tvj6nRFE3tOmRY912E0FMRm/L5xWE7MGVT2FoLnw==
+
+babel-plugin-syntax-async-generators@^6.5.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz"
+ integrity sha512-EbciFN5Jb9iqU9bqaLmmFLx2G8pAUsvpWJ6OzOWBNrSY9qTohXj+7YfZx6Ug1Qqh7tCb1EA7Jvn9bMC1HBiucg==
+
+babel-plugin-syntax-class-constructor-call@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz"
+ integrity sha512-EEuBcXz/wZ81Jaac0LnMHtD4Mfz9XWn2oH2Xj+CHwz2SZWUqqdtR2BgWPSdTGMmxN/5KLSh4PImt9+9ZedDarA==
+
+babel-plugin-syntax-class-properties@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz"
+ integrity sha512-chI3Rt9T1AbrQD1s+vxw3KcwC9yHtF621/MacuItITfZX344uhQoANjpoSJZleAmW2tjlolqB/f+h7jIqXa7pA==
+
+babel-plugin-syntax-decorators@^6.13.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz"
+ integrity sha512-AWj19x2aDm8qFQ5O2JcD6pwJDW1YdcnO+1b81t7gxrGjz5VHiUqeYWAR4h7zueWMalRelrQDXprv2FrY1dbpbw==
+
+babel-plugin-syntax-do-expressions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz"
+ integrity sha512-HD/5qJB9oSXzl0caxM+aRD7ENICXqcc3Up/8toDQk7zNIDE7TzsqtxC5f4t9Rwhu2Ya8l9l4j6b3vOsy+a6qxg==
+
+babel-plugin-syntax-dynamic-import@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz"
+ integrity sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz"
+ integrity sha512-Z/flU+T9ta0aIEKl1tGEmN/pZiI1uXmCiGFRegKacQfEJzp7iNsKloZmyJlQr+75FCJtiFfGIK03SiCvCt9cPQ==
+
+babel-plugin-syntax-export-extensions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz"
+ integrity sha512-Eo0rcRaIDMld/W6mVhePiudIuLW+Cr/8eveW3mBREfZORScZgx4rh6BAPyvzdEc/JZvQ+LkC80t0VGFs6FX+lg==
+
+babel-plugin-syntax-function-bind@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz"
+ integrity sha512-m8yMoh9LIiNyeLdQs5I9G+3YXo4nqVsKQkk7YplrG4qAFbNi9hkZlow8HDHxhH9QOVFPHmy8+03NzRCdyChIKw==
+
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz"
+ integrity sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w==
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz"
+ integrity sha512-Gx9CH3Q/3GKbhs07Bszw5fPTlU+ygrOGfAhEt7W2JICwufpC4SuO0mG0+4NykPBSYPMJhqvVlDBU17qB1D+hMQ==
+
+babel-plugin-transform-async-generator-functions@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz"
+ integrity sha512-uT7eovUxtXe8Q2ufcjRuJIOL0hg6VAUJhiWJBLxH/evYAw+aqoJLcYTR8hqx13iOx/FfbCMHgBmXWZjukbkyPg==
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.24.1"
+ babel-plugin-syntax-async-generators "^6.5.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-async-to-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz"
+ integrity sha512-7BgYJujNCg0Ti3x0c/DL3tStvnKS6ktIYOmo9wginv/dfZOrbSZ+qG4IRRHMBOzZ5Awb1skTiAsQXg/+IWkZYw==
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.24.1"
+ babel-plugin-syntax-async-functions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-class-constructor-call@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz"
+ integrity sha512-RvYukT1Nh7njz8P8326ztpQUGCKwmjgu6aRIx1lkvylWITYcskg29vy1Kp8WXIq7FvhXsz0Crf2kS94bjB690A==
+ dependencies:
+ babel-plugin-syntax-class-constructor-call "^6.18.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-class-properties@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz"
+ integrity sha512-n4jtBA3OYBdvG5PRMKsMXJXHfLYw/ZOmtxCLOOwz6Ro5XlrColkStLnz1AS1L2yfPA9BKJ1ZNlmVCLjAL9DSIg==
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-plugin-syntax-class-properties "^6.8.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-decorators@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz"
+ integrity sha512-skQ2CImwDkCHu0mkWvCOlBCpBIHW4/49IZWVwV4A/EnWjL9bB6UBvLyMNe3Td5XDStSZNhe69j4bfEW8dvUbew==
+ dependencies:
+ babel-helper-explode-class "^6.24.1"
+ babel-plugin-syntax-decorators "^6.13.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-do-expressions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz"
+ integrity sha512-yQwYqYg+Tnj1InA8W1rsItsZVhkv1Euc4KVua9ledtPz5PDWYz7LVyy6rDBpVYUWFZj5k6GUm3YZpCbIm8Tqew==
+ dependencies:
+ babel-plugin-syntax-do-expressions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-arrow-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz"
+ integrity sha512-PCqwwzODXW7JMrzu+yZIaYbPQSKjDTAsNNlK2l5Gg9g4rz2VzLnZsStvp/3c46GfXpwkyufb3NCyG9+50FF1Vg==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz"
+ integrity sha512-2+ujAT2UMBzYFm7tidUsYh+ZoIutxJ3pN9IYrF1/H6dCKtECfhmB8UkHVpyxDwkj0CYbQG35ykoz925TUnBc3A==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoping@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz"
+ integrity sha512-YiN6sFAQ5lML8JjCmr7uerS5Yc/EMbgg9G8ZNmk2E3nYX4ckHR01wrkeeMijEf5WHNK5TW0Sl0Uu3pv3EdOJWw==
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-plugin-transform-es2015-classes@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz"
+ integrity sha512-5Dy7ZbRinGrNtmWpquZKZ3EGY8sDgIVB4CU8Om8q8tnMLrD/m94cKglVcHps0BCTdZ0TJeeAWOq2TK9MIY6cag==
+ dependencies:
+ babel-helper-define-map "^6.24.1"
+ babel-helper-function-name "^6.24.1"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-helper-replace-supers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-computed-properties@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz"
+ integrity sha512-C/uAv4ktFP/Hmh01gMTvYvICrKze0XVX9f2PdIXuriCSvUmV9j+u+BB9f5fJK3+878yMK6dkdcq+Ymr9mrcLzw==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-destructuring@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz"
+ integrity sha512-aNv/GDAW0j/f4Uy1OEPZn1mqD+Nfy9viFGBfQ5bZyT35YqOiqx7/tXdyfZkJ1sC21NyEsBdfDY6PYmLHF4r5iA==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz"
+ integrity sha512-ossocTuPOssfxO2h+Z3/Ea1Vo1wWx31Uqy9vIiJusOP4TbF7tPs9U0sJ9pX9OJPf4lXRGj5+6Gkl/HHKiAP5ug==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-for-of@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz"
+ integrity sha512-DLuRwoygCoXx+YfxHLkVx5/NpeSbVwfoTeBykpJK7JhYWlL/O8hgAK/reforUnZDlxasOrVPPJVI/guE3dCwkw==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-function-name@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz"
+ integrity sha512-iFp5KIcorf11iBqu/y/a7DK3MN5di3pNCzto61FqCNnUX4qeBwcV1SLqe10oXNnCaxBUImX3SckX2/o1nsrTcg==
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz"
+ integrity sha512-tjFl0cwMPpDYyoqYA9li1/7mGFit39XiNX5DKC/uCNjBctMxyL1/PT/l4rSlbvBG1pOKI88STRdUsWXB3/Q9hQ==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-modules-amd@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz"
+ integrity sha512-LnIIdGWIKdw7zwckqx+eGjcS8/cl8D74A3BpJbGjKTFFNJSMrjN4bIh22HY1AlkUbeLG6X6OZj56BDvWD+OeFA==
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+ version "6.26.2"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz"
+ integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-types "^6.26.0"
+
+babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz"
+ integrity sha512-ONFIPsq8y4bls5PPsAWYXH/21Hqv64TBxdje0FvU3MhIV6QM2j5YS7KvAzg/nTIVLot2D2fmFQrFWCbgHlFEjg==
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-umd@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz"
+ integrity sha512-LpVbiT9CLsuAIp3IG0tfbVo81QIhn6pE8xBJ7XSeCtFlMltuar5VuBV6y6Q45tpui9QWcy5i0vLQfCfrnF7Kiw==
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-object-super@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz"
+ integrity sha512-8G5hpZMecb53vpD3mjs64NhI1au24TAmokQ4B+TBFBjN9cVoGoOvotdrMMRmHvVZUEvqGUPWL514woru1ChZMA==
+ dependencies:
+ babel-helper-replace-supers "^6.24.1"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-parameters@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz"
+ integrity sha512-8HxlW+BB5HqniD+nLkQ4xSAVq3bR/pcYW9IigY+2y0dI+Y7INFeTbfAQr+63T3E4UDsZGjyb+l9txUnABWxlOQ==
+ dependencies:
+ babel-helper-call-delegate "^6.24.1"
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz"
+ integrity sha512-mDdocSfUVm1/7Jw/FIRNw9vPrBQNePy6wZJlR8HAUBLybNp1w/6lr6zZ2pjMShee65t/ybR5pT8ulkLzD1xwiw==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-spread@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz"
+ integrity sha512-3Ghhi26r4l3d0Js933E5+IhHwk0A1yiutj9gwvzmFbVV0sPMYk2lekhOufHBswX7NCoSeF4Xrl3sCIuSIa+zOg==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-sticky-regex@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz"
+ integrity sha512-CYP359ADryTo3pCsH0oxRo/0yn6UsEZLqYohHmvLQdfS9xkf+MbCzE3/Kolw9OYIY4ZMilH25z/5CbQbwDD+lQ==
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-template-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz"
+ integrity sha512-x8b9W0ngnKzDMHimVtTfn5ryimars1ByTqsfBDwAqLibmuuQY6pgBQi5z1ErIsUOWBdw1bW9FSz5RZUojM4apg==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
+ version "6.23.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz"
+ integrity sha512-fz6J2Sf4gYN6gWgRZaoFXmq93X+Li/8vf+fb0sGDVtdeWvxC9y5/bTD7bvfWMEq6zetGEHpWjtzRGSugt5kNqw==
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-unicode-regex@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz"
+ integrity sha512-v61Dbbihf5XxnYjtBN04B/JBvsScY37R1cZT5r9permN1cp+b70DY3Ib3fIkgn1DI9U3tGgBJZVD8p/mE/4JbQ==
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ regexpu-core "^2.0.0"
+
+babel-plugin-transform-exponentiation-operator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz"
+ integrity sha512-LzXDmbMkklvNhprr20//RStKVcT8Cu+SQtX18eMHLhjHf2yFzwtQ0S2f0jQ+89rokoNdmwoSqYzAhq86FxlLSQ==
+ dependencies:
+ babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
+ babel-plugin-syntax-exponentiation-operator "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-export-extensions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz"
+ integrity sha512-mtzELzINaYqdVglyZrDDVwkcFRuE7s6QUFWXxwffKAHB/NkfbJ2NJSytugB43ytIC8UVt30Ereyx+7gNyTkDLg==
+ dependencies:
+ babel-plugin-syntax-export-extensions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-function-bind@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz"
+ integrity sha512-9Ec4KYf1GurT39mlUjDSlN7HWSlB3u3mWRMogQbb+Y88lO0ZM3rJ0ADhPnQwWK9TbO6e/4E+Et1rrfGY9mFimA==
+ dependencies:
+ babel-plugin-syntax-function-bind "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-object-rest-spread@^6.22.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz"
+ integrity sha512-ocgA9VJvyxwt+qJB0ncxV8kb/CjfTcECUY4tQ5VT7nP6Aohzobm8CDFaQ5FHdvZQzLmf0sgDxB8iRXZXxwZcyA==
+ dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.8.0"
+ babel-runtime "^6.26.0"
+
+babel-plugin-transform-regenerator@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz"
+ integrity sha512-LS+dBkUGlNR15/5WHKe/8Neawx663qttS6AGqoOUhICc9d1KciBvtrQSuc0PI+CxQ2Q/S1aKuJ+u64GtLdcEZg==
+ dependencies:
+ regenerator-transform "^0.10.0"
+
+babel-plugin-transform-strict-mode@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz"
+ integrity sha512-j3KtSpjyLSJxNoCDrhwiJad8kw0gJ9REGj8/CqL0HeRyLnvUNYV9zcqluL6QJSXh3nfsLEmSLvwRfGzrgR96Pw==
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-preset-es2015@^6.18.0:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz"
+ integrity sha512-XfwUqG1Ry6R43m4Wfob+vHbIVBIqTg/TJY4Snku1iIzeH7mUnwHA8Vagmv+ZQbPwhS8HgsdQvy28Py3k5zpoFQ==
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.24.1"
+ babel-plugin-transform-es2015-classes "^6.24.1"
+ babel-plugin-transform-es2015-computed-properties "^6.24.1"
+ babel-plugin-transform-es2015-destructuring "^6.22.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
+ babel-plugin-transform-es2015-for-of "^6.22.0"
+ babel-plugin-transform-es2015-function-name "^6.24.1"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
+ babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
+ babel-plugin-transform-es2015-modules-umd "^6.24.1"
+ babel-plugin-transform-es2015-object-super "^6.24.1"
+ babel-plugin-transform-es2015-parameters "^6.24.1"
+ babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.24.1"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.24.1"
+ babel-plugin-transform-regenerator "^6.24.1"
+
+babel-preset-stage-0@^6.16.0:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz"
+ integrity sha512-MJD+xBbpsApbKlzAX0sOBF+VeFaUmv5s8FSOO7SSZpes1QgphCjq/UIGRFWSmQ/0i5bqQjLGCTXGGXqcLQ9JDA==
+ dependencies:
+ babel-plugin-transform-do-expressions "^6.22.0"
+ babel-plugin-transform-function-bind "^6.22.0"
+ babel-preset-stage-1 "^6.24.1"
+
+babel-preset-stage-1@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz"
+ integrity sha512-rn+UOcd7BHDniq1SVxv2/AVVSVI1NK+hfS0I/iR6m6KbOi/aeBRcqBilqO73pd9VUpRXF2HFtlDuC9F2BEQqmg==
+ dependencies:
+ babel-plugin-transform-class-constructor-call "^6.24.1"
+ babel-plugin-transform-export-extensions "^6.22.0"
+ babel-preset-stage-2 "^6.24.1"
+
+babel-preset-stage-2@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz"
+ integrity sha512-9F+nquz+37PrlTSBdpeQBKnQfAMNBnryXw+m4qBh35FNbJPfzZz+sjN2G5Uf1CRedU9PH7fJkTbYijxmkLX8Og==
+ dependencies:
+ babel-plugin-syntax-dynamic-import "^6.18.0"
+ babel-plugin-transform-class-properties "^6.24.1"
+ babel-plugin-transform-decorators "^6.24.1"
+ babel-preset-stage-3 "^6.24.1"
+
+babel-preset-stage-3@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz"
+ integrity sha512-eCbEOF8uN0KypFXJmZXn2sTk7bPV9uM5xov7G/7BM08TbQEObsVs0cEWfy6NQySlfk7JBi/t+XJP1JkruYfthA==
+ dependencies:
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-generator-functions "^6.24.1"
+ babel-plugin-transform-async-to-generator "^6.24.1"
+ babel-plugin-transform-exponentiation-operator "^6.24.1"
+ babel-plugin-transform-object-rest-spread "^6.22.0"
+
+babel-register@^6.18.0, babel-register@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz"
+ integrity sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==
+ dependencies:
+ babel-core "^6.26.0"
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.15"
+
+babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz"
+ integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
+babel-template@^6.24.1, babel-template@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz"
+ integrity sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ lodash "^4.17.4"
+
+babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz"
+ integrity sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ debug "^2.6.8"
+ globals "^9.18.0"
+ invariant "^2.2.2"
+ lodash "^4.17.4"
+
+babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz"
+ integrity sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==
+ dependencies:
+ babel-runtime "^6.26.0"
+ esutils "^2.0.2"
+ lodash "^4.17.4"
+ to-fast-properties "^1.0.3"
+
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz"
+ integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
+
+bail@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz"
+ integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base64-js@^1.3.1:
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+batch@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
+ integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
+
+big.js@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz"
+ integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
+binary-extensions@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
+ integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
+ version "4.12.1"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz"
+ integrity sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==
+
+bn.js@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz"
+ integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
+
+body-parser@1.20.3:
+ version "1.20.3"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz"
+ integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.5"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.13.0"
+ raw-body "2.5.2"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+bonjour-service@^1.0.11:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz"
+ integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+ multicast-dns "^7.2.5"
+
+boolbase@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz"
+ integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+boxen@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz"
+ integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==
+ dependencies:
+ ansi-align "^3.0.1"
+ camelcase "^6.2.0"
+ chalk "^4.1.2"
+ cli-boxes "^3.0.0"
+ string-width "^5.0.1"
+ type-fest "^2.5.0"
+ widest-line "^4.0.1"
+ wrap-ansi "^8.0.1"
+
+boxen@^7.0.0:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz"
+ integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==
+ dependencies:
+ ansi-align "^3.0.1"
+ camelcase "^7.0.1"
+ chalk "^5.2.0"
+ cli-boxes "^3.0.0"
+ string-width "^5.1.2"
+ type-fest "^2.13.0"
+ widest-line "^4.0.1"
+ wrap-ansi "^8.1.0"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.3, braces@~3.0.2:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+brorand@^1.0.1, brorand@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
+ integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
+
+browserify-aes@^1.0.4, browserify-aes@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz"
+ integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
+ dependencies:
+ buffer-xor "^1.0.3"
+ cipher-base "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.3"
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+browserify-cipher@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz"
+ integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
+ dependencies:
+ browserify-aes "^1.0.4"
+ browserify-des "^1.0.0"
+ evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz"
+ integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==
+ dependencies:
+ cipher-base "^1.0.1"
+ des.js "^1.0.0"
+ inherits "^2.0.1"
+ safe-buffer "^5.1.2"
+
+browserify-rsa@^4.0.0, browserify-rsa@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz"
+ integrity sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==
+ dependencies:
+ bn.js "^5.2.1"
+ randombytes "^2.1.0"
+ safe-buffer "^5.2.1"
+
+browserify-sign@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz"
+ integrity sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==
+ dependencies:
+ bn.js "^5.2.1"
+ browserify-rsa "^4.1.0"
+ create-hash "^1.2.0"
+ create-hmac "^1.1.7"
+ elliptic "^6.5.5"
+ hash-base "~3.0"
+ inherits "^2.0.4"
+ parse-asn1 "^5.1.7"
+ readable-stream "^2.3.8"
+ safe-buffer "^5.2.1"
+
+browserify-zlib@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz"
+ integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
+ dependencies:
+ pako "~1.0.5"
+
+browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3, browserslist@^4.24.4, "browserslist@>= 4.21.0":
+ version "4.24.4"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz"
+ integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+ dependencies:
+ caniuse-lite "^1.0.30001688"
+ electron-to-chromium "^1.5.73"
+ node-releases "^2.0.19"
+ update-browserslist-db "^1.1.1"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+buffer-xor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz"
+ integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==
+
+buffer@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
+ integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.2.1"
+
+builtin-status-codes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz"
+ integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz"
+ integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+cacheable-lookup@^5.0.3:
+ version "5.0.4"
+ resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz"
+ integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+
+cacheable-lookup@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz"
+ integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==
+
+cacheable-request@^10.2.8:
+ version "10.2.14"
+ resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz"
+ integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==
+ dependencies:
+ "@types/http-cache-semantics" "^4.0.2"
+ get-stream "^6.0.1"
+ http-cache-semantics "^4.1.1"
+ keyv "^4.5.3"
+ mimic-response "^4.0.0"
+ normalize-url "^8.0.0"
+ responselike "^3.0.0"
+
+cacheable-request@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz"
+ integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^4.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^6.0.1"
+ responselike "^2.0.0"
+
+call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
+call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz"
+ integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
+ dependencies:
+ call-bind-apply-helpers "^1.0.0"
+ es-define-property "^1.0.0"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.2"
+
+call-bound@^1.0.2, call-bound@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz"
+ integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ get-intrinsic "^1.2.6"
+
+call-me-maybe@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz"
+ integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+camel-case@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz"
+ integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
+ dependencies:
+ pascal-case "^3.1.2"
+ tslib "^2.0.3"
+
+camelcase-css@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
+ integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
+
+camelcase@^6.2.0:
+ version "6.3.0"
+ resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+camelcase@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz"
+ integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==
+
+caniuse-api@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz"
+ integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==
+ dependencies:
+ browserslist "^4.0.0"
+ caniuse-lite "^1.0.0"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
+ version "1.0.30001700"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz"
+ integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==
+
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
+chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
+ integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chalk@^5.0.1, chalk@^5.2.0:
+ version "5.4.1"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz"
+ integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
+
+char-regex@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
+ integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
+character-entities@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz"
+ integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
+
+character-reference-invalid@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz"
+ integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
+
+charset@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz"
+ integrity sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==
+
+cheerio-select@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz"
+ integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
+ dependencies:
+ boolbase "^1.0.0"
+ css-select "^5.1.0"
+ css-what "^6.1.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+
+cheerio@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz"
+ integrity sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.1.0"
+ encoding-sniffer "^0.2.0"
+ htmlparser2 "^9.1.0"
+ parse5 "^7.1.2"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+ parse5-parser-stream "^7.1.2"
+ undici "^6.19.5"
+ whatwg-mimetype "^4.0.0"
+
+cheerio@1.0.0-rc.12:
+ version "1.0.0-rc.12"
+ resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz"
+ integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ htmlparser2 "^8.0.1"
+ parse5 "^7.0.0"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+
+chokidar@^3.4.2, chokidar@^3.5.3, chokidar@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
+ integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+chokidar@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz"
+ integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
+ dependencies:
+ readdirp "^4.0.1"
+
+chrome-trace-event@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz"
+ integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
+
+ci-info@^3.2.0:
+ version "3.9.0"
+ resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz"
+ integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
+
+cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz"
+ integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==
+ dependencies:
+ inherits "^2.0.4"
+ safe-buffer "^5.2.1"
+
+clean-css@^5.2.2, clean-css@^5.3.2, clean-css@~5.3.2:
+ version "5.3.3"
+ resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz"
+ integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
+ dependencies:
+ source-map "~0.6.0"
+
+clean-stack@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz"
+ integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+
+cli-boxes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz"
+ integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
+
+cli-table3@^0.6.3:
+ version "0.6.5"
+ resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz"
+ integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==
+ dependencies:
+ string-width "^4.2.0"
+ optionalDependencies:
+ "@colors/colors" "1.5.0"
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+ dependencies:
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
+clone-response@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz"
+ integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
+ dependencies:
+ mimic-response "^1.0.0"
+
+clsx@^1.1.1, clsx@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
+ integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
+clsx@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+
+collapse-white-space@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz"
+ integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colord@^2.9.3:
+ version "2.9.3"
+ resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
+ integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
+
+colorette@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz"
+ integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
+
+colorette@^2.0.10:
+ version "2.0.20"
+ resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
+combine-promises@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz"
+ integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==
+
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
+commander@^10.0.0:
+ version "10.0.1"
+ resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz"
+ integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
+
+commander@^2.20.0, commander@2.20.3:
+ version "2.20.3"
+ resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
+ integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+commander@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
+commander@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commander@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz"
+ integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
+commander@^9.2.0:
+ version "9.5.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz"
+ integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
+
+commander@~4.1.1:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
+ integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+common-path-prefix@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz"
+ integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==
+
+component-emitter@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz"
+ integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
+
+compressible@~2.0.18:
+ version "2.0.18"
+ resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz"
+ integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
+ dependencies:
+ mime-db ">= 1.43.0 < 2"
+
+compression@^1.7.4:
+ version "1.8.0"
+ resolved "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz"
+ integrity sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==
+ dependencies:
+ bytes "3.1.2"
+ compressible "~2.0.18"
+ debug "2.6.9"
+ negotiator "~0.6.4"
+ on-headers "~1.0.2"
+ safe-buffer "5.2.1"
+ vary "~1.1.2"
+
+compute-gcd@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz"
+ integrity sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==
+ dependencies:
+ validate.io-array "^1.0.3"
+ validate.io-function "^1.0.2"
+ validate.io-integer-array "^1.0.0"
+
+compute-lcm@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz"
+ integrity sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==
+ dependencies:
+ compute-gcd "^1.2.1"
+ validate.io-array "^1.0.3"
+ validate.io-function "^1.0.2"
+ validate.io-integer-array "^1.0.0"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+config-chain@^1.1.11:
+ version "1.1.13"
+ resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
+ integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
+ dependencies:
+ ini "^1.3.4"
+ proto-list "~1.2.1"
+
+configstore@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz"
+ integrity sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==
+ dependencies:
+ dot-prop "^6.0.1"
+ graceful-fs "^4.2.6"
+ unique-string "^3.0.0"
+ write-file-atomic "^3.0.3"
+ xdg-basedir "^5.0.1"
+
+connect-history-api-fallback@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
+
+consola@^3.2.3:
+ version "3.4.0"
+ resolved "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz"
+ integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==
+
+console-browserify@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz"
+ integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
+
+constants-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz"
+ integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz"
+ integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
+
+content-disposition@0.5.4:
+ version "0.5.4"
+ resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@~1.0.4, content-type@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+convert-source-map@^1.5.1:
+ version "1.9.0"
+ resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz"
+ integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
+
+cookiejar@^2.1.3:
+ version "2.1.4"
+ resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz"
+ integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
+
+copy-text-to-clipboard@^3.1.0, copy-text-to-clipboard@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz"
+ integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==
+
+copy-webpack-plugin@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz"
+ integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==
+ dependencies:
+ fast-glob "^3.2.11"
+ glob-parent "^6.0.1"
+ globby "^13.1.1"
+ normalize-path "^3.0.0"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
+
+core-js-compat@^3.38.0, core-js-compat@^3.40.0:
+ version "3.40.0"
+ resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz"
+ integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
+ dependencies:
+ browserslist "^4.24.3"
+
+core-js-pure@^3.30.2:
+ version "3.40.0"
+ resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz"
+ integrity sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==
+
+core-js@^2.4.0, core-js@^2.5.0:
+ version "2.6.12"
+ resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz"
+ integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
+
+core-js@^3.31.1:
+ version "3.40.0"
+ resolved "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz"
+ integrity sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==
+
+core-js@^3.38.1:
+ version "3.40.0"
+ resolved "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz"
+ integrity sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==
+
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+cosmiconfig-typescript-loader@^4.3.0:
+ version "4.4.0"
+ resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz"
+ integrity sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==
+
+cosmiconfig@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz"
+ integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.7.2"
+
+cosmiconfig@^8.0.0, cosmiconfig@^8.1.3, cosmiconfig@^8.3.5, cosmiconfig@>=7:
+ version "8.3.6"
+ resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz"
+ integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
+ dependencies:
+ import-fresh "^3.3.0"
+ js-yaml "^4.1.0"
+ parse-json "^5.2.0"
+ path-type "^4.0.0"
+
+create-ecdh@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz"
+ integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
+ dependencies:
+ bn.js "^4.1.0"
+ elliptic "^6.5.3"
+
+create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz"
+ integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
+ dependencies:
+ cipher-base "^1.0.1"
+ inherits "^2.0.1"
+ md5.js "^1.3.4"
+ ripemd160 "^2.0.1"
+ sha.js "^2.4.0"
+
+create-hmac@^1.1.4, create-hmac@^1.1.7:
+ version "1.1.7"
+ resolved "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz"
+ integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
+ dependencies:
+ cipher-base "^1.0.3"
+ create-hash "^1.1.0"
+ inherits "^2.0.1"
+ ripemd160 "^2.0.0"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+create-require@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
+ integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz"
+ integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+cross-spawn@^7.0.0, cross-spawn@^7.0.3:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+cross-var@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/cross-var/-/cross-var-1.1.0.tgz"
+ integrity sha512-wIcFax9RNm5ayuORUeJ5MLxPbfh8XdZhhUpKutIszU46Fs9UIhEdPJ7+YguM+7FxEj+68hSQVyathVsIu84SiA==
+ dependencies:
+ babel-preset-es2015 "^6.18.0"
+ babel-preset-stage-0 "^6.16.0"
+ babel-register "^6.18.0"
+ cross-spawn "^5.0.1"
+ exit "^0.1.2"
+
+crypto-browserify@^3.12.0:
+ version "3.12.1"
+ resolved "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz"
+ integrity sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==
+ dependencies:
+ browserify-cipher "^1.0.1"
+ browserify-sign "^4.2.3"
+ create-ecdh "^4.0.4"
+ create-hash "^1.2.0"
+ create-hmac "^1.1.7"
+ diffie-hellman "^5.0.3"
+ hash-base "~3.0.4"
+ inherits "^2.0.4"
+ pbkdf2 "^3.1.2"
+ public-encrypt "^4.0.3"
+ randombytes "^2.1.0"
+ randomfill "^1.0.4"
+
+crypto-js@^4.1.1:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz"
+ integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
+
+crypto-random-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz"
+ integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==
+ dependencies:
+ type-fest "^1.0.1"
+
+css-blank-pseudo@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz"
+ integrity sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+css-declaration-sorter@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz"
+ integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==
+
+css-has-pseudo@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz"
+ integrity sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==
+ dependencies:
+ "@csstools/selector-specificity" "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+ postcss-value-parser "^4.2.0"
+
+css-loader@^6.8.1:
+ version "6.11.0"
+ resolved "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz"
+ integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==
+ dependencies:
+ icss-utils "^5.1.0"
+ postcss "^8.4.33"
+ postcss-modules-extract-imports "^3.1.0"
+ postcss-modules-local-by-default "^4.0.5"
+ postcss-modules-scope "^3.2.0"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.2.0"
+ semver "^7.5.4"
+
+css-minimizer-webpack-plugin@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz"
+ integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.18"
+ cssnano "^6.0.1"
+ jest-worker "^29.4.3"
+ postcss "^8.4.24"
+ schema-utils "^4.0.1"
+ serialize-javascript "^6.0.1"
+
+css-prefers-color-scheme@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz"
+ integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==
+
+css-select@^4.1.3:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz"
+ integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^6.0.1"
+ domhandler "^4.3.1"
+ domutils "^2.8.0"
+ nth-check "^2.0.1"
+
+css-select@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz"
+ integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^6.1.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
+ nth-check "^2.0.1"
+
+css-tree@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz"
+ integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
+ dependencies:
+ mdn-data "2.0.30"
+ source-map-js "^1.0.1"
+
+css-tree@~2.2.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz"
+ integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==
+ dependencies:
+ mdn-data "2.0.28"
+ source-map-js "^1.0.1"
+
+css-what@^6.0.1, css-what@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz"
+ integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+
+cssdb@^8.2.3:
+ version "8.2.3"
+ resolved "https://registry.npmjs.org/cssdb/-/cssdb-8.2.3.tgz"
+ integrity sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==
+
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+cssnano-preset-advanced@^6.1.2:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz"
+ integrity sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==
+ dependencies:
+ autoprefixer "^10.4.19"
+ browserslist "^4.23.0"
+ cssnano-preset-default "^6.1.2"
+ postcss-discard-unused "^6.0.5"
+ postcss-merge-idents "^6.0.3"
+ postcss-reduce-idents "^6.0.3"
+ postcss-zindex "^6.0.2"
+
+cssnano-preset-default@^6.1.2:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz"
+ integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==
+ dependencies:
+ browserslist "^4.23.0"
+ css-declaration-sorter "^7.2.0"
+ cssnano-utils "^4.0.2"
+ postcss-calc "^9.0.1"
+ postcss-colormin "^6.1.0"
+ postcss-convert-values "^6.1.0"
+ postcss-discard-comments "^6.0.2"
+ postcss-discard-duplicates "^6.0.3"
+ postcss-discard-empty "^6.0.3"
+ postcss-discard-overridden "^6.0.2"
+ postcss-merge-longhand "^6.0.5"
+ postcss-merge-rules "^6.1.1"
+ postcss-minify-font-values "^6.1.0"
+ postcss-minify-gradients "^6.0.3"
+ postcss-minify-params "^6.1.0"
+ postcss-minify-selectors "^6.0.4"
+ postcss-normalize-charset "^6.0.2"
+ postcss-normalize-display-values "^6.0.2"
+ postcss-normalize-positions "^6.0.2"
+ postcss-normalize-repeat-style "^6.0.2"
+ postcss-normalize-string "^6.0.2"
+ postcss-normalize-timing-functions "^6.0.2"
+ postcss-normalize-unicode "^6.1.0"
+ postcss-normalize-url "^6.0.2"
+ postcss-normalize-whitespace "^6.0.2"
+ postcss-ordered-values "^6.0.2"
+ postcss-reduce-initial "^6.1.0"
+ postcss-reduce-transforms "^6.0.2"
+ postcss-svgo "^6.0.3"
+ postcss-unique-selectors "^6.0.4"
+
+cssnano-utils@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz"
+ integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==
+
+cssnano@^6.0.1, cssnano@^6.1.2:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz"
+ integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==
+ dependencies:
+ cssnano-preset-default "^6.1.2"
+ lilconfig "^3.1.1"
+
+csso@^5.0.5:
+ version "5.0.5"
+ resolved "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz"
+ integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==
+ dependencies:
+ css-tree "~2.2.0"
+
+csstype@^3.0.2:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+debounce@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz"
+ integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
+
+debug@^2.6.0:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^2.6.8:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^2.6.9:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.4, debug@4:
+ version "4.4.0"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+ dependencies:
+ ms "^2.1.3"
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+decode-named-character-reference@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz"
+ integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
+ dependencies:
+ character-entities "^2.0.0"
+
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+deepmerge@^4.0.0, deepmerge@^4.2.2, deepmerge@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
+ integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
+default-gateway@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz"
+ integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
+ dependencies:
+ execa "^5.0.0"
+
+defer-to-connect@^2.0.0, defer-to-connect@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz"
+ integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+define-data-property@^1.0.1, define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
+
+define-properties@^1.1.3, define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+del@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/del/-/del-6.1.1.tgz"
+ integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==
+ dependencies:
+ globby "^11.0.1"
+ graceful-fs "^4.2.4"
+ is-glob "^4.0.1"
+ is-path-cwd "^2.2.0"
+ is-path-inside "^3.0.2"
+ p-map "^4.0.0"
+ rimraf "^3.0.2"
+ slash "^3.0.0"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"
+ integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+dequal@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz"
+ integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+
+des.js@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz"
+ integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==
+ dependencies:
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+detect-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz"
+ integrity sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==
+ dependencies:
+ repeating "^2.0.0"
+
+detect-libc@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
+ integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
+
+detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+detect-package-manager@3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-3.0.2.tgz"
+ integrity sha512-8JFjJHutStYrfWwzfretQoyNGoZVW1Fsrp4JO9spa7h/fBfwgTMEIy4/LBzRDGsxwVPHU0q+T9YvwLDJoOApLQ==
+ dependencies:
+ execa "^5.1.1"
+
+detect-port-alt@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz"
+ integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==
+ dependencies:
+ address "^1.0.1"
+ debug "^2.6.0"
+
+detect-port@^1.5.1:
+ version "1.6.1"
+ resolved "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz"
+ integrity sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==
+ dependencies:
+ address "^1.0.1"
+ debug "4"
+
+devlop@^1.0.0, devlop@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz"
+ integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
+ dependencies:
+ dequal "^2.0.0"
+
+dezalgo@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz"
+ integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
+ dependencies:
+ asap "^2.0.0"
+ wrappy "1"
+
+didyoumean@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
+ integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
+
+diff@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
+ integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
+diff@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz"
+ integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
+
+diffie-hellman@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz"
+ integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
+ dependencies:
+ bn.js "^4.1.0"
+ miller-rabin "^4.0.0"
+ randombytes "^2.0.0"
+
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+ dependencies:
+ path-type "^4.0.0"
+
+dlv@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz"
+ integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+
+dns-packet@^5.2.2:
+ version "5.6.1"
+ resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz"
+ integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==
+ dependencies:
+ "@leichtgewicht/ip-codec" "^2.0.1"
+
+docusaurus-node-polyfills@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/docusaurus-node-polyfills/-/docusaurus-node-polyfills-1.0.0.tgz"
+ integrity sha512-TUX/smcS0NcoiBKThM3hNlAx7Z8jJr/F5UKR+FiMlsJJbt1KYWgbj3blgUTk/ad0+hfe2vaytJZX4r0GeK6oRQ==
+ dependencies:
+ node-polyfill-webpack-plugin "^1.1.2"
+ os-browserify "^0.3.0"
+ process "^0.11.10"
+
+docusaurus-plugin-image-zoom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/docusaurus-plugin-image-zoom/-/docusaurus-plugin-image-zoom-2.0.0.tgz"
+ integrity sha512-TWHQZeoiged+95CESlZk++lihzl3pqw34n0/fbexx2AocmFhbo9K2scYDgYB8amki4/X6mUCLTPZE1pQvT+00Q==
+ dependencies:
+ medium-zoom "^1.0.8"
+ validate-peer-dependencies "^2.2.0"
+
+docusaurus-plugin-openapi-docs@^4.0.0, docusaurus-plugin-openapi-docs@^4.3.1:
+ version "4.3.4"
+ resolved "https://registry.npmjs.org/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-4.3.4.tgz"
+ integrity sha512-7/MqkdO2ivBDxgpVxFkxsWoehkZfPJ3nzOOgIu/XVNzTytQ2TYFlDoa6GDC5zvwrSDJR7bCpJzeAaVe2Oa+nkg==
+ dependencies:
+ "@apidevtools/json-schema-ref-parser" "^11.5.4"
+ "@redocly/openapi-core" "^1.10.5"
+ allof-merge "^0.6.6"
+ chalk "^4.1.2"
+ clsx "^1.1.1"
+ fs-extra "^9.0.1"
+ json-pointer "^0.6.2"
+ json5 "^2.2.3"
+ lodash "^4.17.20"
+ mustache "^4.2.0"
+ openapi-to-postmanv2 "^4.21.0"
+ postman-collection "^4.4.0"
+ slugify "^1.6.5"
+ swagger2openapi "^7.0.8"
+ xml-formatter "^2.6.1"
+
+docusaurus-plugin-openapi@^0.7.6:
+ version "0.7.6"
+ resolved "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.7.6.tgz"
+ integrity sha512-LR8DI0gO9WFy8K+r0xrVgqDkKKA9zQtDgOnX9CatP3I3Oz5lKegfTJM2fVUIp5m25elzHL+vVKNHS12Jg7sWVA==
+ dependencies:
+ "@docusaurus/mdx-loader" "^3.6.0"
+ "@docusaurus/plugin-content-docs" "^3.6.0"
+ "@docusaurus/utils" "^3.6.0"
+ "@docusaurus/utils-common" "^3.6.0"
+ "@docusaurus/utils-validation" "^3.6.0"
+ chalk "^4.1.2"
+ clsx "^1.2.1"
+ js-yaml "^4.1.0"
+ json-refs "^3.0.15"
+ json-schema-resolve-allof "^1.5.0"
+ lodash "^4.17.20"
+ openapi-to-postmanv2 "^4.20.1"
+ postman-collection "^4.1.0"
+ webpack "^5.95.0"
+
+docusaurus-plugin-proxy@^0.7.6:
+ version "0.7.6"
+ resolved "https://registry.npmjs.org/docusaurus-plugin-proxy/-/docusaurus-plugin-proxy-0.7.6.tgz"
+ integrity sha512-MgjzMEsQOHMljwQGglXXoGjQvs0v1DklhRgzqNLKFwpHB9xLWJZ0KQ3GgbPerW/2vy8tWGJeVhKHy5cPrmweUw==
+
+docusaurus-plugin-sass@^0.2.3:
+ version "0.2.6"
+ resolved "https://registry.npmjs.org/docusaurus-plugin-sass/-/docusaurus-plugin-sass-0.2.6.tgz"
+ integrity sha512-2hKQQDkrufMong9upKoG/kSHJhuwd+FA3iAe/qzS/BmWpbIpe7XKmq5wlz4J5CJaOPu4x+iDJbgAxZqcoQf0kg==
+ dependencies:
+ sass-loader "^16.0.2"
+
+docusaurus-preset-openapi@^0.7.6:
+ version "0.7.6"
+ resolved "https://registry.npmjs.org/docusaurus-preset-openapi/-/docusaurus-preset-openapi-0.7.6.tgz"
+ integrity sha512-QnArH/3X0lePB7667FyNK3EeTS8ZP8V2PQxz5m+3BMO2kIzdXDwfTIQ37boB0BTqsDfUE0yCWTVjB0W/BA1UXA==
+ dependencies:
+ "@docusaurus/preset-classic" "^3.6.0"
+ docusaurus-plugin-openapi "^0.7.6"
+ docusaurus-plugin-proxy "^0.7.6"
+ docusaurus-theme-openapi "^0.7.6"
+
+docusaurus-theme-openapi-docs@^4.3.1:
+ version "4.3.4"
+ resolved "https://registry.npmjs.org/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-4.3.4.tgz"
+ integrity sha512-FcIRQ8FBrM7vaGDL1yN/Sfiyer3d4J7OPF3Xis4ivFlnqWMhZVoOL9A9UYJrq3xDdBWVFF6WkUAmIVrd3Njf8A==
+ dependencies:
+ "@hookform/error-message" "^2.0.1"
+ "@reduxjs/toolkit" "^1.7.1"
+ allof-merge "^0.6.6"
+ clsx "^1.1.1"
+ copy-text-to-clipboard "^3.1.0"
+ crypto-js "^4.1.1"
+ file-saver "^2.0.5"
+ lodash "^4.17.20"
+ node-polyfill-webpack-plugin "^3.0.0"
+ postman-code-generators "^1.10.1"
+ postman-collection "^4.4.0"
+ prism-react-renderer "^2.3.0"
+ react-hook-form "^7.43.8"
+ react-live "^4.0.0"
+ react-magic-dropzone "^1.0.1"
+ react-markdown "^8.0.1"
+ react-modal "^3.15.1"
+ react-redux "^7.2.0"
+ rehype-raw "^6.1.1"
+ remark-gfm "3.0.1"
+ sass "^1.80.4"
+ sass-loader "^16.0.2"
+ unist-util-visit "^5.0.0"
+ webpack "^5.61.0"
+ xml-formatter "^2.6.1"
+
+docusaurus-theme-openapi@^0.7.6:
+ version "0.7.6"
+ resolved "https://registry.npmjs.org/docusaurus-theme-openapi/-/docusaurus-theme-openapi-0.7.6.tgz"
+ integrity sha512-euoEh8tYX/ssQcMQxBOxt3wPttz3zvPu0l5lSe6exiIwMrORB4O2b8XRB7fVa/awF7xzdIkKHMH55uc5zVOKYA==
+ dependencies:
+ "@docusaurus/theme-common" "^3.6.0"
+ "@mdx-js/react" "^3.0.0"
+ "@monaco-editor/react" "^4.3.1"
+ "@reduxjs/toolkit" "^1.7.1"
+ buffer "^6.0.3"
+ clsx "^1.2.1"
+ crypto-js "^4.1.1"
+ docusaurus-plugin-openapi "^0.7.6"
+ immer "^9.0.7"
+ lodash "^4.17.20"
+ marked "^11.0.0"
+ monaco-editor "^0.31.1"
+ postman-code-generators "^1.0.0"
+ postman-collection "^4.1.0"
+ prism-react-renderer "^2.1.0"
+ process "^0.11.10"
+ react-magic-dropzone "^1.0.1"
+ react-redux "^7.2.0"
+ redux-devtools-extension "^2.13.8"
+ refractor "^4.8.1"
+ striptags "^3.2.0"
+ webpack "^5.95.0"
+
+dom-converter@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz"
+ integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
+ dependencies:
+ utila "~0.4"
+
+dom-serializer@^1.0.1:
+ version "1.4.1"
+ resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz"
+ integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.2.0"
+ entities "^2.0.0"
+
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
+
+domain-browser@^4.19.0, domain-browser@^4.22.0:
+ version "4.23.0"
+ resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz"
+ integrity sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==
+
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz"
+ integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz"
+ integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+ dependencies:
+ domelementtype "^2.2.0"
+
+domhandler@^5.0.2, domhandler@^5.0.3, domhandler@5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+ dependencies:
+ domelementtype "^2.3.0"
+
+domutils@^2.5.2, domutils@^2.8.0:
+ version "2.8.0"
+ resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz"
+ integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+ dependencies:
+ dom-serializer "^1.0.1"
+ domelementtype "^2.2.0"
+ domhandler "^4.2.0"
+
+domutils@^3.0.1, domutils@^3.1.0:
+ version "3.2.2"
+ resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz"
+ integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
+ dependencies:
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+
+dot-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz"
+ integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+dot-prop@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz"
+ integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
+ dependencies:
+ is-obj "^2.0.0"
+
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
+duplexer@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz"
+ integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
+
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+electron-to-chromium@^1.5.73:
+ version "1.5.102"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz"
+ integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==
+
+elliptic@^6.5.3, elliptic@^6.5.5:
+ version "6.6.1"
+ resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz"
+ integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==
+ dependencies:
+ bn.js "^4.11.9"
+ brorand "^1.1.0"
+ hash.js "^1.0.0"
+ hmac-drbg "^1.0.1"
+ inherits "^2.0.4"
+ minimalistic-assert "^1.0.1"
+ minimalistic-crypto-utils "^1.0.1"
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
+emojilib@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz"
+ integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==
+
+emojis-list@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz"
+ integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
+
+emoticon@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz"
+ integrity sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+encodeurl@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz"
+ integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
+encoding-sniffer@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz"
+ integrity sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==
+ dependencies:
+ iconv-lite "^0.6.3"
+ whatwg-encoding "^3.1.1"
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+enhanced-resolve@^5.17.1:
+ version "5.18.1"
+ resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz"
+ integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+entities@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
+ integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
+entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+error-ex@^1.3.1:
+ version "1.3.2"
+ resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz"
+ integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-define-property@^1.0.0, es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-module-lexer@^1.2.1:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz"
+ integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==
+
+es-object-atoms@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
+ integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ dependencies:
+ es-errors "^1.3.0"
+
+es-set-tostringtag@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
+ integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
+ dependencies:
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+es6-promise@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz"
+ integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==
+
+esast-util-from-estree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz"
+ integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-visit "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+
+esast-util-from-js@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz"
+ integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ acorn "^8.0.0"
+ esast-util-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+escalade@^3.1.1, escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-goat@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz"
+ integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==
+
+escape-html@^1.0.3, escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+esprima@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
+ integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-util-attach-comments@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz"
+ integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-build-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz"
+ integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ estree-walker "^3.0.0"
+
+estree-util-is-identifier-name@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz"
+ integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
+
+estree-util-scope@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz"
+ integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+
+estree-util-to-js@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz"
+ integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ astring "^1.8.0"
+ source-map "^0.7.0"
+
+estree-util-value-to-estree@^3.0.1:
+ version "3.3.2"
+ resolved "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.2.tgz"
+ integrity sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+estree-util-visit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz"
+ integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/unist" "^3.0.0"
+
+estree-walker@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+eta@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz"
+ integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+eval@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz"
+ integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==
+ dependencies:
+ "@types/node" "*"
+ require-like ">= 0.1.1"
+
+event-target-shim@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
+ integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
+eventemitter3@^4.0.0, eventemitter3@^4.0.4:
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+events@^3.2.0, events@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+
+evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz"
+ integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==
+ dependencies:
+ md5.js "^1.3.4"
+ safe-buffer "^5.1.1"
+
+execa@^5.0.0, execa@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.0"
+ human-signals "^2.1.0"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.1"
+ onetime "^5.1.2"
+ signal-exit "^3.0.3"
+ strip-final-newline "^2.0.0"
+
+exenv@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz"
+ integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==
+
+exit@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
+ integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
+
+express@^4.17.3:
+ version "4.21.2"
+ resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz"
+ integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.3"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.7.1"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.3.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.3"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.12"
+ proxy-addr "~2.0.7"
+ qs "6.13.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.19.0"
+ serve-static "1.16.2"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
+ integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz"
+ integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.8"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz"
+ integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
+
+fast-uri@^3.0.1:
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz"
+ integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
+
+fastq@^1.6.0:
+ version "1.19.0"
+ resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz"
+ integrity sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==
+ dependencies:
+ reusify "^1.0.4"
+
+fault@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz"
+ integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==
+ dependencies:
+ format "^0.2.0"
+
+faye-websocket@^0.11.3:
+ version "0.11.4"
+ resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+feed@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz"
+ integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
+ dependencies:
+ xml-js "^1.6.11"
+
+fflate@^0.4.8:
+ version "0.4.8"
+ resolved "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz"
+ integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
+
+figures@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz"
+ integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
+file-loader@*, file-loader@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz"
+ integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
+file-saver@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz"
+ integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
+
+file-type@16.5.1:
+ version "16.5.1"
+ resolved "https://registry.npmjs.org/file-type/-/file-type-16.5.1.tgz"
+ integrity sha512-Pi1G43smrCy82Q3be3sfKaeS5uHdfj905dP88YqhroG6TYbVY2ljTdDXeXqa6Cn5nOk6znOjWM2uZptA8vH/qQ==
+ dependencies:
+ readable-web-to-node-stream "^3.0.0"
+ strtok3 "^6.0.3"
+ token-types "^2.0.0"
+
+file-type@3.9.0:
+ version "3.9.0"
+ resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz"
+ integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==
+
+filesize@^8.0.6:
+ version "8.0.7"
+ resolved "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz"
+ integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+filter-obj@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-2.0.2.tgz"
+ integrity sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==
+
+finalhandler@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz"
+ integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ statuses "2.0.1"
+ unpipe "~1.0.0"
+
+find-cache-dir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz"
+ integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==
+ dependencies:
+ common-path-prefix "^3.0.0"
+ pkg-dir "^7.0.0"
+
+find-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz"
+ integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+ dependencies:
+ locate-path "^3.0.0"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+find-up@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz"
+ integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==
+ dependencies:
+ locate-path "^7.1.0"
+ path-exists "^5.0.0"
+
+flat@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz"
+ integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
+follow-redirects@^1.0.0:
+ version "1.15.9"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
+for-each@^0.3.3:
+ version "0.3.5"
+ resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz"
+ integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
+ dependencies:
+ is-callable "^1.2.7"
+
+foreach@^2.0.4:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz"
+ integrity sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==
+
+foreground-child@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz"
+ integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
+fork-ts-checker-webpack-plugin@^6.5.0:
+ version "6.5.3"
+ resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz"
+ integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@types/json-schema" "^7.0.5"
+ chalk "^4.1.0"
+ chokidar "^3.4.2"
+ cosmiconfig "^6.0.0"
+ deepmerge "^4.2.2"
+ fs-extra "^9.0.0"
+ glob "^7.1.6"
+ memfs "^3.1.2"
+ minimatch "^3.0.4"
+ schema-utils "2.7.0"
+ semver "^7.3.2"
+ tapable "^1.0.0"
+
+form-data-encoder@^2.1.2:
+ version "2.1.4"
+ resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz"
+ integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==
+
+form-data@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz"
+ integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ es-set-tostringtag "^2.1.0"
+ mime-types "^2.1.12"
+
+format@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz"
+ integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
+
+formidable@^2.0.1:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz"
+ integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==
+ dependencies:
+ dezalgo "^1.0.4"
+ hexoid "^1.0.0"
+ once "^1.4.0"
+ qs "^6.11.0"
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fraction.js@^4.3.7:
+ version "4.3.7"
+ resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
+ integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+
+fs-extra@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^11.1.1, fs-extra@^11.2.0:
+ version "11.3.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz"
+ integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^9.0.1:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-monkey@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz"
+ integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6:
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz"
+ integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ function-bind "^1.1.2"
+ get-proto "^1.0.0"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-own-enumerable-property-symbols@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz"
+ integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
+
+get-proto@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
+get-stdin@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz"
+ integrity sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+get-stream@^6.0.0, get-stream@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+github-slugger@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz"
+ integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==
+
+glob-parent@^5.1.2, glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.1:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@^10.3.10:
+ version "10.4.5"
+ resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz"
+ integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
+glob@^7.0.0, glob@^7.1.3, glob@^7.1.6:
+ version "7.2.3"
+ resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^9.2.0:
+ version "9.3.5"
+ resolved "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz"
+ integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ minimatch "^8.0.2"
+ minipass "^4.2.4"
+ path-scurry "^1.6.1"
+
+global-dirs@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz"
+ integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==
+ dependencies:
+ ini "2.0.0"
+
+global-modules@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz"
+ integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+ dependencies:
+ global-prefix "^3.0.0"
+
+global-prefix@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz"
+ integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
+ dependencies:
+ ini "^1.3.5"
+ kind-of "^6.0.2"
+ which "^1.3.1"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globals@^9.18.0:
+ version "9.18.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz"
+ integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
+
+globby@^11.0.1, globby@^11.0.4, globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
+globby@^13.1.1:
+ version "13.2.2"
+ resolved "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz"
+ integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==
+ dependencies:
+ dir-glob "^3.0.1"
+ fast-glob "^3.3.0"
+ ignore "^5.2.4"
+ merge2 "^1.4.1"
+ slash "^4.0.0"
+
+gopd@^1.0.1, gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
+got@^11.8.1:
+ version "11.8.6"
+ resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz"
+ integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==
+ dependencies:
+ "@sindresorhus/is" "^4.0.0"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.2"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.5.2"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
+got@^12.1.0:
+ version "12.6.1"
+ resolved "https://registry.npmjs.org/got/-/got-12.6.1.tgz"
+ integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==
+ dependencies:
+ "@sindresorhus/is" "^5.2.0"
+ "@szmarczak/http-timer" "^5.0.1"
+ cacheable-lookup "^7.0.0"
+ cacheable-request "^10.2.8"
+ decompress-response "^6.0.0"
+ form-data-encoder "^2.1.2"
+ get-stream "^6.0.1"
+ http2-wrapper "^2.1.10"
+ lowercase-keys "^3.0.0"
+ p-cancelable "^3.0.0"
+ responselike "^3.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+ version "4.2.11"
+ resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+graceful-fs@4.2.10:
+ version "4.2.10"
+ resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz"
+ integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
+
+graphlib@^2.1.8, graphlib@2.1.8:
+ version "2.1.8"
+ resolved "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz"
+ integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
+ dependencies:
+ lodash "^4.17.15"
+
+gray-matter@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz"
+ integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
+ dependencies:
+ js-yaml "^3.13.1"
+ kind-of "^6.0.2"
+ section-matter "^1.0.0"
+ strip-bom-string "^1.0.0"
+
+gzip-size@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz"
+ integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
+ dependencies:
+ duplexer "^0.1.2"
+
+handle-thing@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz"
+ integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
+ integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-symbols@^1.0.3, has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+has-yarn@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz"
+ integrity sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==
+
+hash-base@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz"
+ integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
+ dependencies:
+ inherits "^2.0.4"
+ readable-stream "^3.6.0"
+ safe-buffer "^5.2.0"
+
+hash-base@~3.0, hash-base@~3.0.4:
+ version "3.0.5"
+ resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz"
+ integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==
+ dependencies:
+ inherits "^2.0.4"
+ safe-buffer "^5.2.1"
+
+hash.js@^1.0.0, hash.js@^1.0.3:
+ version "1.1.7"
+ resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz"
+ integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
+ dependencies:
+ inherits "^2.0.3"
+ minimalistic-assert "^1.0.1"
+
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+hast-util-from-parse5@^7.0.0:
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz"
+ integrity sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/unist" "^2.0.0"
+ hastscript "^7.0.0"
+ property-information "^6.0.0"
+ vfile "^5.0.0"
+ vfile-location "^4.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-from-parse5@^8.0.0:
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz"
+ integrity sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ hastscript "^9.0.0"
+ property-information "^6.0.0"
+ vfile "^6.0.0"
+ vfile-location "^5.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-parse-selector@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz"
+ integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==
+ dependencies:
+ "@types/hast" "^2.0.0"
+
+hast-util-parse-selector@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz"
+ integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-raw@^7.2.0:
+ version "7.2.3"
+ resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz"
+ integrity sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/parse5" "^6.0.0"
+ hast-util-from-parse5 "^7.0.0"
+ hast-util-to-parse5 "^7.0.0"
+ html-void-elements "^2.0.0"
+ parse5 "^6.0.0"
+ unist-util-position "^4.0.0"
+ unist-util-visit "^4.0.0"
+ vfile "^5.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-raw@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz"
+ integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ hast-util-from-parse5 "^8.0.0"
+ hast-util-to-parse5 "^8.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ parse5 "^7.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-estree@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.1.tgz"
+ integrity sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-attach-comments "^3.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-object "^1.0.0"
+ unist-util-position "^5.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-jsx-runtime@^2.0.0:
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz"
+ integrity sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-object "^1.0.0"
+ unist-util-position "^5.0.0"
+ vfile-message "^4.0.0"
+
+hast-util-to-parse5@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz"
+ integrity sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-parse5@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz"
+ integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-whitespace@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz"
+ integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==
+
+hast-util-whitespace@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz"
+ integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hastscript@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz"
+ integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-parse-selector "^3.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+
+hastscript@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz"
+ integrity sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-parse-selector "^4.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+
+he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+hexoid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz"
+ integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
+
+history@^4.9.0:
+ version "4.10.1"
+ resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz"
+ integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ loose-envify "^1.2.0"
+ resolve-pathname "^3.0.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+ value-equal "^1.0.1"
+
+hmac-drbg@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz"
+ integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==
+ dependencies:
+ hash.js "^1.0.3"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.1"
+
+hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
+home-or-tmp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz"
+ integrity sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.1"
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz"
+ integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+html-dom-parser@5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.0.3.tgz"
+ integrity sha512-slsc6ipw88OUZjAayRs5NTmfOQCwcUa3hNyk6AdsbQxY09H5Lr1Y3CZ4ZlconMKql3Ga6sWg3HMoUzo7KSItaQ==
+ dependencies:
+ domhandler "5.0.3"
+ htmlparser2 "9.0.0"
+
+html-entities@^2.3.2:
+ version "2.5.2"
+ resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz"
+ integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
+
+html-escaper@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+
+html-minifier-terser@^6.0.2:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz"
+ integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
+ dependencies:
+ camel-case "^4.1.2"
+ clean-css "^5.2.2"
+ commander "^8.3.0"
+ he "^1.2.0"
+ param-case "^3.0.4"
+ relateurl "^0.2.7"
+ terser "^5.10.0"
+
+html-minifier-terser@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz"
+ integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==
+ dependencies:
+ camel-case "^4.1.2"
+ clean-css "~5.3.2"
+ commander "^10.0.0"
+ entities "^4.4.0"
+ param-case "^3.0.4"
+ relateurl "^0.2.7"
+ terser "^5.15.1"
+
+html-react-parser@^4.2.0:
+ version "4.2.10"
+ resolved "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.10.tgz"
+ integrity sha512-JyKZVQ+kQ8PdycISwkuLbEEvV/k4hWhU6cb6TT7yGaYwdqA7cPt4VRYXkCZcix2vlQtgDBSMJUmPI2jpNjPGvg==
+ dependencies:
+ domhandler "5.0.3"
+ html-dom-parser "5.0.3"
+ react-property "2.0.2"
+ style-to-js "1.1.8"
+
+html-tags@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz"
+ integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
+
+html-void-elements@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz"
+ integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
+
+html-void-elements@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz"
+ integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
+
+html-webpack-plugin@^5.6.0:
+ version "5.6.3"
+ resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz"
+ integrity sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==
+ dependencies:
+ "@types/html-minifier-terser" "^6.0.0"
+ html-minifier-terser "^6.0.2"
+ lodash "^4.17.21"
+ pretty-error "^4.0.0"
+ tapable "^2.0.0"
+
+htmlparser2@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz"
+ integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.0.0"
+ domutils "^2.5.2"
+ entities "^2.0.0"
+
+htmlparser2@^8.0.1:
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz"
+ integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ entities "^4.4.0"
+
+htmlparser2@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz"
+ integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.1.0"
+ entities "^4.5.0"
+
+htmlparser2@9.0.0:
+ version "9.0.0"
+ resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz"
+ integrity sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.1.0"
+ entities "^4.5.0"
+
+http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
+
+http-deceiver@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz"
+ integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
+
+http-errors@~1.6.2:
+ version "1.6.3"
+ resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"
+ integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.3"
+ setprototypeof "1.1.0"
+ statuses ">= 1.4.0 < 2"
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+http-parser-js@>=0.5.1:
+ version "0.5.9"
+ resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz"
+ integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==
+
+http-proxy-middleware@^2.0.3:
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz"
+ integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
+ dependencies:
+ "@types/http-proxy" "^1.17.8"
+ http-proxy "^1.18.1"
+ is-glob "^4.0.1"
+ is-plain-obj "^3.0.0"
+ micromatch "^4.0.2"
+
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+http-reasons@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz"
+ integrity sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==
+
+http2-client@^1.2.5:
+ version "1.3.5"
+ resolved "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz"
+ integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==
+
+http2-wrapper@^1.0.0-beta.5.2:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz"
+ integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.0.0"
+
+http2-wrapper@^2.1.10:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz"
+ integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.2.0"
+
+https-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz"
+ integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==
+
+https-proxy-agent@^7.0.5:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
+ integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
+ dependencies:
+ agent-base "^7.1.2"
+ debug "4"
+
+human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+iconv-lite@^0.6.3, iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
+ieee754@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
+ integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+ignore@^5.2.0, ignore@^5.2.4:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+image-size@^1.0.2:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/image-size/-/image-size-1.2.0.tgz"
+ integrity sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==
+ dependencies:
+ queue "6.0.2"
+
+immediate@^3.2.3:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz"
+ integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==
+
+immer@^9.0.21, immer@^9.0.7:
+ version "9.0.21"
+ resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz"
+ integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
+
+immutable@^5.0.2:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz"
+ integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
+
+import-fresh@^3.1.0, import-fresh@^3.3.0:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+import-lazy@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz"
+ integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
+infima@0.2.0-alpha.45:
+ version "0.2.0-alpha.45"
+ resolved "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz"
+ integrity sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4, inherits@2, inherits@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+inherits@2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
+ integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
+
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
+ version "1.3.8"
+ resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+ini@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz"
+ integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+
+inline-style-parser@0.1.1:
+ version "0.1.1"
+ resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz"
+ integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
+
+inline-style-parser@0.2.2:
+ version "0.2.2"
+ resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz"
+ integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==
+
+inline-style-parser@0.2.4:
+ version "0.2.4"
+ resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz"
+ integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
+
+interpret@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz"
+ integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
+
+invariant@^2.2.2, invariant@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz"
+ integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+ dependencies:
+ loose-envify "^1.0.0"
+
+ipaddr.js@^2.0.1:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz"
+ integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-alphabetical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz"
+ integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
+
+is-alphanumerical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz"
+ integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
+ dependencies:
+ is-alphabetical "^2.0.0"
+ is-decimal "^2.0.0"
+
+is-arguments@^1.0.4:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz"
+ integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==
+ dependencies:
+ call-bound "^1.0.2"
+ has-tostringtag "^1.0.2"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-buffer@^2.0.0:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz"
+ integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
+
+is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
+is-ci@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz"
+ integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==
+ dependencies:
+ ci-info "^3.2.0"
+
+is-core-module@^2.16.0:
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
+ integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+ dependencies:
+ hasown "^2.0.2"
+
+is-decimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz"
+ integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
+
+is-docker@^2.0.0, is-docker@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
+is-extendable@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz"
+ integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-finite@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz"
+ integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-generator-function@^1.0.7:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz"
+ integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==
+ dependencies:
+ call-bound "^1.0.3"
+ get-proto "^1.0.0"
+ has-tostringtag "^1.0.2"
+ safe-regex-test "^1.1.0"
+
+is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-hexadecimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz"
+ integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
+
+is-installed-globally@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz"
+ integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
+ dependencies:
+ global-dirs "^3.0.0"
+ is-path-inside "^3.0.2"
+
+is-nan@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
+is-npm@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz"
+ integrity sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz"
+ integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==
+
+is-obj@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz"
+ integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
+
+is-path-cwd@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz"
+ integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
+
+is-path-inside@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-plain-obj@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz"
+ integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
+
+is-plain-obj@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz"
+ integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+
+is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz"
+ integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
+ dependencies:
+ isobject "^3.0.1"
+
+is-regex@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz"
+ integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==
+ dependencies:
+ call-bound "^1.0.2"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+is-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz"
+ integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==
+
+is-root@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz"
+ integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==
+
+is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+is-typed-array@^1.1.3:
+ version "1.1.15"
+ resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz"
+ integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+ dependencies:
+ which-typed-array "^1.1.16"
+
+is-typedarray@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
+ integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+is-yarn-global@^0.4.0:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz"
+ integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
+ integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
+
+jackspeak@^3.1.2:
+ version "3.4.3"
+ resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz"
+ integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
+jest-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz"
+ integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
+
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jest-worker@^29.4.3:
+ version "29.7.0"
+ resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz"
+ integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
+ dependencies:
+ "@types/node" "*"
+ jest-util "^29.7.0"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jiti@^1.20.0, jiti@^1.21.6:
+ version "1.21.7"
+ resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz"
+ integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
+
+joi@^17.9.2:
+ version "17.13.3"
+ resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz"
+ integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==
+ dependencies:
+ "@hapi/hoek" "^9.3.0"
+ "@hapi/topo" "^5.1.0"
+ "@sideway/address" "^4.1.5"
+ "@sideway/formula" "^3.0.1"
+ "@sideway/pinpoint" "^2.0.0"
+
+js-levenshtein@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz"
+ integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-tokens@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
+ integrity sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==
+
+js-yaml@^3.13.1:
+ version "3.14.1"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz"
+ integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+js-yaml@^4.1.0, js-yaml@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+jsesc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz"
+ integrity sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==
+
+jsesc@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"
+ integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
+ integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
+
+jsesc@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz"
+ integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-crawl@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.npmjs.org/json-crawl/-/json-crawl-0.5.3.tgz"
+ integrity sha512-BEjjCw8c7SxzNK4orhlWD5cXQh8vCk2LqDr4WgQq4CV+5dvopeYwt1Tskg67SuSLKvoFH5g0yuYtg7rcfKV6YA==
+
+json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-pointer@^0.6.2, json-pointer@0.6.2:
+ version "0.6.2"
+ resolved "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz"
+ integrity sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==
+ dependencies:
+ foreach "^2.0.4"
+
+json-refs@^3.0.15:
+ version "3.0.15"
+ resolved "https://registry.npmjs.org/json-refs/-/json-refs-3.0.15.tgz"
+ integrity sha512-0vOQd9eLNBL18EGl5yYaO44GhixmImes2wiYn9Z3sag3QnehWrYWlB9AFtMxCL2Bj3fyxgDYkxGFEU/chlYssw==
+ dependencies:
+ commander "~4.1.1"
+ graphlib "^2.1.8"
+ js-yaml "^3.13.1"
+ lodash "^4.17.15"
+ native-promise-only "^0.8.1"
+ path-loader "^1.0.10"
+ slash "^3.0.0"
+ uri-js "^4.2.2"
+
+json-schema-compare@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz"
+ integrity sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==
+ dependencies:
+ lodash "^4.17.4"
+
+json-schema-merge-allof@0.8.1:
+ version "0.8.1"
+ resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz"
+ integrity sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==
+ dependencies:
+ compute-lcm "^1.1.2"
+ json-schema-compare "^0.2.2"
+ lodash "^4.17.20"
+
+json-schema-resolve-allof@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/json-schema-resolve-allof/-/json-schema-resolve-allof-1.5.0.tgz"
+ integrity sha512-Jgn6BQGSLDp3D7bTYrmCbP/p7SRFz5BfpeEJ9A7sXuVADMc14aaDN1a49zqk9D26wwJlcNvjRpT63cz1VgFZeg==
+ dependencies:
+ get-stdin "^5.0.1"
+ lodash "^4.14.0"
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz"
+ integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==
+
+json5@^2.1.2, json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+just-performance@4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz"
+ integrity sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==
+
+keyv@^4.0.0, keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+klaw-sync@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz"
+ integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+
+kleur@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+
+kleur@^4.0.3:
+ version "4.1.5"
+ resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz"
+ integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
+
+latest-version@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz"
+ integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==
+ dependencies:
+ package-json "^8.1.0"
+
+launch-editor@^2.6.0:
+ version "2.10.0"
+ resolved "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz"
+ integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==
+ dependencies:
+ picocolors "^1.0.0"
+ shell-quote "^1.8.1"
+
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
+lilconfig@^3.0.0, lilconfig@^3.1.1, lilconfig@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz"
+ integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
+
+limiter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz"
+ integrity sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==
+ dependencies:
+ just-performance "4.3.0"
+
+lines-and-columns@^1.1.6:
+ version "1.2.4"
+ resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
+ integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+liquid-json@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz"
+ integrity sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==
+
+load-script@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz"
+ integrity sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==
+
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
+loader-utils@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz"
+ integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
+ dependencies:
+ big.js "^5.2.2"
+ emojis-list "^3.0.0"
+ json5 "^2.1.2"
+
+loader-utils@^3.2.0:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz"
+ integrity sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==
+
+locate-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz"
+ integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
+ dependencies:
+ p-locate "^3.0.0"
+ path-exists "^3.0.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+locate-path@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz"
+ integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
+ dependencies:
+ p-locate "^6.0.0"
+
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
+ integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+
+lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
+ integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
+
+lodash@^4.14.0, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@4.17.21:
+ version "4.17.21"
+ resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+longest-streak@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
+ integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
+
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lower-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz"
+ integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
+ dependencies:
+ tslib "^2.0.3"
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lowercase-keys@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz"
+ integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
+
+lru-cache@^10.2.0:
+ version "10.4.3"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
+lru-cache@^4.0.1:
+ version "4.1.5"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+lucide-react@^0.460.0:
+ version "0.460.0"
+ resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz"
+ integrity sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==
+
+lunr-languages@^1.4.0:
+ version "1.14.0"
+ resolved "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz"
+ integrity sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==
+
+lunr@^2.3.9:
+ version "2.3.9"
+ resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz"
+ integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
+
+make-error@^1.1.1:
+ version "1.3.6"
+ resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz"
+ integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
+mark.js@^8.11.1:
+ version "8.11.1"
+ resolved "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz"
+ integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==
+
+markdown-extensions@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz"
+ integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==
+
+markdown-table@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz"
+ integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==
+ dependencies:
+ repeat-string "^1.0.0"
+
+markdown-table@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz"
+ integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==
+
+marked@^11.0.0:
+ version "11.2.0"
+ resolved "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz"
+ integrity sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==
+
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
+md5.js@^1.3.4:
+ version "1.3.5"
+ resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz"
+ integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
+ dependencies:
+ hash-base "^3.0.0"
+ inherits "^2.0.1"
+ safe-buffer "^5.1.2"
+
+mdast-util-definitions@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz"
+ integrity sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ unist-util-visit "^4.0.0"
+
+mdast-util-directive@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz"
+ integrity sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ parse-entities "^4.0.0"
+ stringify-entities "^4.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+mdast-util-find-and-replace@^2.0.0:
+ version "2.2.2"
+ resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz"
+ integrity sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^5.0.0"
+
+mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz"
+ integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ escape-string-regexp "^5.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+mdast-util-from-markdown@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz"
+ integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ mdast-util-to-string "^3.1.0"
+ micromark "^3.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-decode-string "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ unist-util-stringify-position "^3.0.0"
+ uvu "^0.5.0"
+
+mdast-util-from-markdown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz"
+ integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark "^4.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+mdast-util-frontmatter@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz"
+ integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ escape-string-regexp "^5.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+
+mdast-util-gfm-autolink-literal@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz"
+ integrity sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ ccount "^2.0.0"
+ mdast-util-find-and-replace "^2.0.0"
+ micromark-util-character "^1.0.0"
+
+mdast-util-gfm-autolink-literal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz"
+ integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ ccount "^2.0.0"
+ devlop "^1.0.0"
+ mdast-util-find-and-replace "^3.0.0"
+ micromark-util-character "^2.0.0"
+
+mdast-util-gfm-footnote@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz"
+ integrity sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+ micromark-util-normalize-identifier "^1.0.0"
+
+mdast-util-gfm-footnote@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz"
+ integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+
+mdast-util-gfm-strikethrough@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz"
+ integrity sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm-strikethrough@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz"
+ integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-table@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz"
+ integrity sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ markdown-table "^3.0.0"
+ mdast-util-from-markdown "^1.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm-table@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz"
+ integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ markdown-table "^3.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm-task-list-item@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz"
+ integrity sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-markdown "^1.3.0"
+
+mdast-util-gfm-task-list-item@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz"
+ integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-gfm@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz"
+ integrity sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==
+ dependencies:
+ mdast-util-from-markdown "^1.0.0"
+ mdast-util-gfm-autolink-literal "^1.0.0"
+ mdast-util-gfm-footnote "^1.0.0"
+ mdast-util-gfm-strikethrough "^1.0.0"
+ mdast-util-gfm-table "^1.0.0"
+ mdast-util-gfm-task-list-item "^1.0.0"
+ mdast-util-to-markdown "^1.0.0"
+
+mdast-util-gfm@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz"
+ integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-gfm-autolink-literal "^2.0.0"
+ mdast-util-gfm-footnote "^2.0.0"
+ mdast-util-gfm-strikethrough "^2.0.0"
+ mdast-util-gfm-table "^2.0.0"
+ mdast-util-gfm-task-list-item "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-expression@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz"
+ integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-jsx@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz"
+ integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ parse-entities "^4.0.0"
+ stringify-entities "^4.0.0"
+ unist-util-stringify-position "^4.0.0"
+ vfile-message "^4.0.0"
+
+mdast-util-mdx@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz"
+ integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==
+ dependencies:
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdxjs-esm@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz"
+ integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-phrasing@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz"
+ integrity sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ unist-util-is "^5.0.0"
+
+mdast-util-phrasing@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz"
+ integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ unist-util-is "^6.0.0"
+
+mdast-util-to-hast@^12.1.0:
+ version "12.3.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz"
+ integrity sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/mdast" "^3.0.0"
+ mdast-util-definitions "^5.0.0"
+ micromark-util-sanitize-uri "^1.1.0"
+ trim-lines "^3.0.0"
+ unist-util-generated "^2.0.0"
+ unist-util-position "^4.0.0"
+ unist-util-visit "^4.0.0"
+
+mdast-util-to-hast@^13.0.0:
+ version "13.2.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz"
+ integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ trim-lines "^3.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+mdast-util-to-markdown@^1.0.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz"
+ integrity sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^3.0.0"
+ mdast-util-to-string "^3.0.0"
+ micromark-util-decode-string "^1.0.0"
+ unist-util-visit "^4.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-markdown@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz"
+ integrity sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^3.0.0"
+ mdast-util-to-string "^3.0.0"
+ micromark-util-decode-string "^1.0.0"
+ unist-util-visit "^4.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-markdown@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz"
+ integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^4.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ unist-util-visit "^5.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz"
+ integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+
+mdast-util-to-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz"
+ integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+
+mdn-data@2.0.28:
+ version "2.0.28"
+ resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz"
+ integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==
+
+mdn-data@2.0.30:
+ version "2.0.30"
+ resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz"
+ integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+medium-zoom@^1.0.8:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.1.0.tgz"
+ integrity sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==
+
+memfs@^3.1.2, memfs@^3.4.3:
+ version "3.6.0"
+ resolved "https://registry.npmjs.org/memfs/-/memfs-3.6.0.tgz"
+ integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
+ dependencies:
+ fs-monkey "^1.0.4"
+
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
+merge-descriptors@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz"
+ integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+methods@^1.1.2, methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+micromark-core-commonmark@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz"
+ integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-factory-destination "^1.0.0"
+ micromark-factory-label "^1.0.0"
+ micromark-factory-space "^1.0.0"
+ micromark-factory-title "^1.0.0"
+ micromark-factory-whitespace "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-classify-character "^1.0.0"
+ micromark-util-html-tag-name "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-subtokenize "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.1"
+ uvu "^0.5.0"
+
+micromark-core-commonmark@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz"
+ integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-factory-destination "^1.0.0"
+ micromark-factory-label "^1.0.0"
+ micromark-factory-space "^1.0.0"
+ micromark-factory-title "^1.0.0"
+ micromark-factory-whitespace "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-classify-character "^1.0.0"
+ micromark-util-html-tag-name "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-subtokenize "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.1"
+ uvu "^0.5.0"
+
+micromark-core-commonmark@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz"
+ integrity sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-destination "^2.0.0"
+ micromark-factory-label "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-factory-title "^2.0.0"
+ micromark-factory-whitespace "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-html-tag-name "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-directive@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz"
+ integrity sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-factory-whitespace "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ parse-entities "^4.0.0"
+
+micromark-extension-frontmatter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz"
+ integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==
+ dependencies:
+ fault "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-autolink-literal@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz"
+ integrity sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-extension-gfm-autolink-literal@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz"
+ integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-footnote@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz"
+ integrity sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==
+ dependencies:
+ micromark-core-commonmark "^1.0.0"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-footnote@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz"
+ integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-strikethrough@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz"
+ integrity sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==
+ dependencies:
+ micromark-util-chunked "^1.0.0"
+ micromark-util-classify-character "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-strikethrough@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz"
+ integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-table@^1.0.0:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz"
+ integrity sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-table@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz"
+ integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-tagfilter@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz"
+ integrity sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==
+ dependencies:
+ micromark-util-types "^1.0.0"
+
+micromark-extension-gfm-tagfilter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz"
+ integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm-task-list-item@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz"
+ integrity sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-extension-gfm-task-list-item@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz"
+ integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-gfm@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz"
+ integrity sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==
+ dependencies:
+ micromark-extension-gfm-autolink-literal "^1.0.0"
+ micromark-extension-gfm-footnote "^1.0.0"
+ micromark-extension-gfm-strikethrough "^1.0.0"
+ micromark-extension-gfm-table "^1.0.0"
+ micromark-extension-gfm-tagfilter "^1.0.0"
+ micromark-extension-gfm-task-list-item "^1.0.0"
+ micromark-util-combine-extensions "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-extension-gfm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz"
+ integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==
+ dependencies:
+ micromark-extension-gfm-autolink-literal "^2.0.0"
+ micromark-extension-gfm-footnote "^2.0.0"
+ micromark-extension-gfm-strikethrough "^2.0.0"
+ micromark-extension-gfm-table "^2.0.0"
+ micromark-extension-gfm-tagfilter "^2.0.0"
+ micromark-extension-gfm-task-list-item "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-expression@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz"
+ integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdx-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz"
+ integrity sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==
+ dependencies:
+ "@types/acorn" "^4.0.0"
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ micromark-factory-mdx-expression "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdx-md@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz"
+ integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-extension-mdxjs-esm@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz"
+ integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-extension-mdxjs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz"
+ integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==
+ dependencies:
+ acorn "^8.0.0"
+ acorn-jsx "^5.0.0"
+ micromark-extension-mdx-expression "^3.0.0"
+ micromark-extension-mdx-jsx "^3.0.0"
+ micromark-extension-mdx-md "^2.0.0"
+ micromark-extension-mdxjs-esm "^3.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-destination@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz"
+ integrity sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-destination@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz"
+ integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-label@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz"
+ integrity sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-factory-label@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz"
+ integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-mdx-expression@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz"
+ integrity sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-events-to-acorn "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-position-from-estree "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-factory-space@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz"
+ integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-space@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz"
+ integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-title@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz"
+ integrity sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-title@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz"
+ integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-whitespace@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz"
+ integrity sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==
+ dependencies:
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-factory-whitespace@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz"
+ integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-character@^1.0.0, micromark-util-character@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz"
+ integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-character@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz"
+ integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-chunked@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz"
+ integrity sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-chunked@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz"
+ integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-classify-character@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz"
+ integrity sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-classify-character@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz"
+ integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-combine-extensions@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz"
+ integrity sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==
+ dependencies:
+ micromark-util-chunked "^1.0.0"
+ micromark-util-types "^1.0.0"
+
+micromark-util-combine-extensions@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz"
+ integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==
+ dependencies:
+ micromark-util-chunked "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-decode-numeric-character-reference@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz"
+ integrity sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-decode-numeric-character-reference@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz"
+ integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-decode-string@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz"
+ integrity sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-decode-string@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz"
+ integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-encode@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz"
+ integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==
+
+micromark-util-encode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz"
+ integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
+
+micromark-util-events-to-acorn@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz"
+ integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==
+ dependencies:
+ "@types/acorn" "^4.0.0"
+ "@types/estree" "^1.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ estree-util-visit "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ vfile-message "^4.0.0"
+
+micromark-util-html-tag-name@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz"
+ integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==
+
+micromark-util-html-tag-name@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz"
+ integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==
+
+micromark-util-normalize-identifier@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz"
+ integrity sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==
+ dependencies:
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-normalize-identifier@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz"
+ integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-resolve-all@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz"
+ integrity sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==
+ dependencies:
+ micromark-util-types "^1.0.0"
+
+micromark-util-resolve-all@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz"
+ integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz"
+ integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==
+ dependencies:
+ micromark-util-character "^1.0.0"
+ micromark-util-encode "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+
+micromark-util-sanitize-uri@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz"
+ integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-subtokenize@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz"
+ integrity sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==
+ dependencies:
+ micromark-util-chunked "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.0"
+ uvu "^0.5.0"
+
+micromark-util-subtokenize@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz"
+ integrity sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-symbol@^1.0.0, micromark-util-symbol@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz"
+ integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==
+
+micromark-util-symbol@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz"
+ integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
+
+micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz"
+ integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==
+
+micromark-util-types@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz"
+ integrity sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==
+
+micromark@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz"
+ integrity sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ micromark-core-commonmark "^1.0.1"
+ micromark-factory-space "^1.0.0"
+ micromark-util-character "^1.0.0"
+ micromark-util-chunked "^1.0.0"
+ micromark-util-combine-extensions "^1.0.0"
+ micromark-util-decode-numeric-character-reference "^1.0.0"
+ micromark-util-encode "^1.0.0"
+ micromark-util-normalize-identifier "^1.0.0"
+ micromark-util-resolve-all "^1.0.0"
+ micromark-util-sanitize-uri "^1.0.0"
+ micromark-util-subtokenize "^1.0.0"
+ micromark-util-symbol "^1.0.0"
+ micromark-util-types "^1.0.1"
+ uvu "^0.5.0"
+
+micromark@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz"
+ integrity sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromatch@^4.0.2, micromatch@^4.0.5, micromatch@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
+ integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+miller-rabin@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz"
+ integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+"mime-db@>= 1.43.0 < 2":
+ version "1.53.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz"
+ integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
+
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz"
+ integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-format@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz"
+ integrity sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==
+ dependencies:
+ charset "^1.0.0"
+
+mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@2.1.35:
+ version "2.1.35"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime-types@2.1.18:
+ version "2.1.18"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz"
+ integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+ dependencies:
+ mime-db "~1.33.0"
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mime@2.6.0:
+ version "2.6.0"
+ resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz"
+ integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
+
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+mimic-response@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+mimic-response@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz"
+ integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==
+
+mini-css-extract-plugin@^2.9.1:
+ version "2.9.2"
+ resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz"
+ integrity sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==
+ dependencies:
+ schema-utils "^4.0.0"
+ tapable "^2.2.1"
+
+minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz"
+ integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
+
+minimalistic-crypto-utils@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"
+ integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
+
+minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^5.0.1:
+ version "5.1.6"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
+ integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^8.0.2:
+ version "8.0.4"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz"
+ integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimist@^1.2.0, minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+minipass@^4.2.4:
+ version "4.2.8"
+ resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz"
+ integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==
+
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
+mkdirp@^0.5.1:
+ version "0.5.6"
+ resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
+monaco-editor@^0.31.1, "monaco-editor@>= 0.25.0 < 1":
+ version "0.31.1"
+ resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.31.1.tgz"
+ integrity sha512-FYPwxGZAeP6mRRyrr5XTGHD9gRXVjy7GUzF4IPChnyt3fS5WrNxIkS8DNujWf6EQy0Zlzpxw8oTVE+mWI2/D1Q==
+
+mri@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz"
+ integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
+
+mrmime@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz"
+ integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
+
+ms@^2.1.3, ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+multicast-dns@^7.2.5:
+ version "7.2.5"
+ resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz"
+ integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==
+ dependencies:
+ dns-packet "^5.2.2"
+ thunky "^1.0.2"
+
+mustache@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz"
+ integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
+
+mz@^2.7.0:
+ version "2.7.0"
+ resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz"
+ integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+ dependencies:
+ any-promise "^1.0.0"
+ object-assign "^4.0.1"
+ thenify-all "^1.0.0"
+
+nanoid@^3.3.8:
+ version "3.3.8"
+ resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz"
+ integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
+
+native-promise-only@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz"
+ integrity sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==
+
+negotiator@~0.6.4:
+ version "0.6.4"
+ resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz"
+ integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+neotraverse@0.6.15:
+ version "0.6.15"
+ resolved "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.15.tgz"
+ integrity sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA==
+
+no-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz"
+ integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+ dependencies:
+ lower-case "^2.0.2"
+ tslib "^2.0.3"
+
+node-addon-api@^7.0.0:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
+ integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
+
+node-emoji@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz"
+ integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==
+ dependencies:
+ "@sindresorhus/is" "^4.6.0"
+ char-regex "^1.0.2"
+ emojilib "^2.4.0"
+ skin-tone "^2.0.0"
+
+node-fetch-h2@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz"
+ integrity sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==
+ dependencies:
+ http2-client "^1.2.5"
+
+node-fetch@^2.0.0, node-fetch@^2.6.1, node-fetch@2:
+ version "2.7.0"
+ resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-fetch@2.6.6:
+ version "2.6.6"
+ resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz"
+ integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-forge@^1:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz"
+ integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
+
+node-polyfill-webpack-plugin@^1.1.2:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-1.1.4.tgz"
+ integrity sha512-Z0XTKj1wRWO8o/Vjobsw5iOJCN+Sua3EZEUc2Ziy9CyVvmHKu6o+t4gUH9GOE0czyPR94LI6ZCV/PpcM8b5yow==
+ dependencies:
+ assert "^2.0.0"
+ browserify-zlib "^0.2.0"
+ buffer "^6.0.3"
+ console-browserify "^1.2.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.12.0"
+ domain-browser "^4.19.0"
+ events "^3.3.0"
+ filter-obj "^2.0.2"
+ https-browserify "^1.0.0"
+ os-browserify "^0.3.0"
+ path-browserify "^1.0.1"
+ process "^0.11.10"
+ punycode "^2.1.1"
+ querystring-es3 "^0.2.1"
+ readable-stream "^3.6.0"
+ stream-browserify "^3.0.0"
+ stream-http "^3.2.0"
+ string_decoder "^1.3.0"
+ timers-browserify "^2.0.12"
+ tty-browserify "^0.0.1"
+ url "^0.11.0"
+ util "^0.12.4"
+ vm-browserify "^1.1.2"
+
+node-polyfill-webpack-plugin@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-3.0.0.tgz"
+ integrity sha512-QpG496dDBiaelQZu9wDcVvpLbtk7h9Ctz693RaUMZBgl8DUoFToO90ZTLKq57gP7rwKqYtGbMBXkcEgLSag2jQ==
+ dependencies:
+ assert "^2.1.0"
+ browserify-zlib "^0.2.0"
+ buffer "^6.0.3"
+ console-browserify "^1.2.0"
+ constants-browserify "^1.0.0"
+ crypto-browserify "^3.12.0"
+ domain-browser "^4.22.0"
+ events "^3.3.0"
+ https-browserify "^1.0.0"
+ os-browserify "^0.3.0"
+ path-browserify "^1.0.1"
+ process "^0.11.10"
+ punycode "^2.3.0"
+ querystring-es3 "^0.2.1"
+ readable-stream "^4.4.2"
+ stream-browserify "^3.0.0"
+ stream-http "^3.2.0"
+ string_decoder "^1.3.0"
+ timers-browserify "^2.0.12"
+ tty-browserify "^0.0.1"
+ type-fest "^4.4.0"
+ url "^0.11.3"
+ util "^0.12.5"
+ vm-browserify "^1.1.2"
+
+node-readfiles@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz"
+ integrity sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==
+ dependencies:
+ es6-promise "^3.2.1"
+
+node-releases@^2.0.19:
+ version "2.0.19"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz"
+ integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
+ integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
+
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
+normalize-url@^8.0.0:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz"
+ integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==
+
+notion-client@^4:
+ version "4.19.8"
+ resolved "https://registry.npmjs.org/notion-client/-/notion-client-4.19.8.tgz"
+ integrity sha512-be9hMYILCKH8kQIUhNJKTBj5VWKufRvnMIRcP1Rj2nxdXW6w0vyTeL6akqYrdMoIJq1wxL3eFFwhB7Ji7b0cdQ==
+ dependencies:
+ got "^11.8.1"
+ notion-types "^4.19.8"
+ notion-utils "^4.19.8"
+ p-map "^4.0.0"
+
+notion-to-md@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/notion-to-md/-/notion-to-md-3.1.1.tgz"
+ integrity sha512-Zaa2P1B9Rx99bevFYTGuUMYbbfdHn2G1AZMsytYGDWIJjr6Ie1qp/8CorpwVUh1qrquES/V2PkEREqCuTu1zKA==
+ dependencies:
+ markdown-table "^2.0.0"
+ node-fetch "2"
+
+notion-types@^4.19.8:
+ version "4.19.8"
+ resolved "https://registry.npmjs.org/notion-types/-/notion-types-4.19.8.tgz"
+ integrity sha512-KLszLpe2UtejnX0bCoLe8JFZPeQyUS15F29LW4iRPFwnlNZePWz0h303F0IBhxOXQRiWIwy4mEGzRDu45a+dhA==
+
+notion-utils@^4.19.8:
+ version "4.19.8"
+ resolved "https://registry.npmjs.org/notion-utils/-/notion-utils-4.19.8.tgz"
+ integrity sha512-d85bSrfr58/vReesEALiZcDcczRa/iiBZagmoNFjLX87ooMeJoPs9CLwGuCg0inhDFFqAU8c4PJgAc7/FNOM/A==
+ dependencies:
+ notion-types "^4.19.8"
+ p-queue "6"
+
+npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
+nprogress@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz"
+ integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==
+
+nth-check@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz"
+ integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+ dependencies:
+ boolbase "^1.0.0"
+
+null-loader@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz"
+ integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
+oas-kit-common@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz"
+ integrity sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==
+ dependencies:
+ fast-safe-stringify "^2.0.7"
+
+oas-linter@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz"
+ integrity sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==
+ dependencies:
+ "@exodus/schemasafe" "^1.0.0-rc.2"
+ should "^13.2.1"
+ yaml "^1.10.0"
+
+oas-resolver-browser@2.5.6:
+ version "2.5.6"
+ resolved "https://registry.npmjs.org/oas-resolver-browser/-/oas-resolver-browser-2.5.6.tgz"
+ integrity sha512-Jw5elT/kwUJrnGaVuRWe1D7hmnYWB8rfDDjBnpQ+RYY/dzAewGXeTexXzt4fGEo6PUE4eqKqPWF79MZxxvMppA==
+ dependencies:
+ node-fetch-h2 "^2.3.0"
+ oas-kit-common "^1.0.8"
+ path-browserify "^1.0.1"
+ reftools "^1.1.9"
+ yaml "^1.10.0"
+ yargs "^17.0.1"
+
+oas-resolver@^2.5.6:
+ version "2.5.6"
+ resolved "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz"
+ integrity sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==
+ dependencies:
+ node-fetch-h2 "^2.3.0"
+ oas-kit-common "^1.0.8"
+ reftools "^1.1.9"
+ yaml "^1.10.0"
+ yargs "^17.0.1"
+
+oas-schema-walker@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz"
+ integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==
+
+oas-validator@^5.0.8:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz"
+ integrity sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==
+ dependencies:
+ call-me-maybe "^1.0.1"
+ oas-kit-common "^1.0.8"
+ oas-linter "^3.2.2"
+ oas-resolver "^2.5.6"
+ oas-schema-walker "^1.1.5"
+ reftools "^1.1.9"
+ should "^13.2.1"
+ yaml "^1.10.0"
+
+object-assign@^4.0.1, object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-hash@^3.0.0, object-hash@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz"
+ integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
+
+object-inspect@^1.13.3:
+ version "1.13.4"
+ resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz"
+ integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
+
+object-is@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz"
+ integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0, object.assign@^4.1.4:
+ version "4.1.7"
+ resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz"
+ integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+ has-symbols "^1.1.0"
+ object-keys "^1.1.1"
+
+obuf@^1.0.0, obuf@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz"
+ integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz"
+ integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+onetime@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
+
+open@^8.0.9, open@^8.4.0:
+ version "8.4.2"
+ resolved "https://registry.npmjs.org/open/-/open-8.4.2.tgz"
+ integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
+openapi-to-postmanv2@^4.20.1, openapi-to-postmanv2@^4.21.0:
+ version "4.25.0"
+ resolved "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-4.25.0.tgz"
+ integrity sha512-sIymbkQby0gzxt2Yez8YKB6hoISEel05XwGwNrAhr6+vxJWXNxkmssQc/8UEtVkuJ9ZfUXLkip9PYACIpfPDWg==
+ dependencies:
+ ajv "8.11.0"
+ ajv-draft-04 "1.0.0"
+ ajv-formats "2.1.1"
+ async "3.2.4"
+ commander "2.20.3"
+ graphlib "2.1.8"
+ js-yaml "4.1.0"
+ json-pointer "0.6.2"
+ json-schema-merge-allof "0.8.1"
+ lodash "4.17.21"
+ neotraverse "0.6.15"
+ oas-resolver-browser "2.5.6"
+ object-hash "3.0.0"
+ path-browserify "1.0.1"
+ postman-collection "^4.4.0"
+ swagger2openapi "7.0.8"
+ yaml "1.10.2"
+
+opener@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz"
+ integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+
+os-browserify@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz"
+ integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==
+
+os-homedir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"
+ integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
+
+os-tmpdir@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
+ integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+
+p-cancelable@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz"
+ integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
+
+p-cancelable@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz"
+ integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz"
+ integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
+p-limit@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-limit@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz"
+ integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==
+ dependencies:
+ yocto-queue "^1.0.0"
+
+p-locate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz"
+ integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
+ dependencies:
+ p-limit "^2.0.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+p-locate@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz"
+ integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==
+ dependencies:
+ p-limit "^4.0.0"
+
+p-map@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz"
+ integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+ dependencies:
+ aggregate-error "^3.0.0"
+
+p-queue@6:
+ version "6.6.2"
+ resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz"
+ integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
+ dependencies:
+ eventemitter3 "^4.0.4"
+ p-timeout "^3.2.0"
+
+p-retry@^4.5.0:
+ version "4.6.2"
+ resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz"
+ integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==
+ dependencies:
+ "@types/retry" "0.12.0"
+ retry "^0.13.1"
+
+p-timeout@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz"
+ integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
+ dependencies:
+ p-finally "^1.0.0"
+
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+package-json-from-dist@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
+ integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
+
+package-json@^8.1.0:
+ version "8.1.1"
+ resolved "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz"
+ integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==
+ dependencies:
+ got "^12.1.0"
+ registry-auth-token "^5.0.1"
+ registry-url "^6.0.0"
+ semver "^7.3.7"
+
+pako@~1.0.5:
+ version "1.0.11"
+ resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
+ integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
+param-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz"
+ integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parse-asn1@^5.0.0, parse-asn1@^5.1.7:
+ version "5.1.7"
+ resolved "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz"
+ integrity sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==
+ dependencies:
+ asn1.js "^4.10.1"
+ browserify-aes "^1.2.0"
+ evp_bytestokey "^1.0.3"
+ hash-base "~3.0"
+ pbkdf2 "^3.1.2"
+ safe-buffer "^5.2.1"
+
+parse-entities@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz"
+ integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ character-entities-legacy "^3.0.0"
+ character-reference-invalid "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ is-alphanumerical "^2.0.0"
+ is-decimal "^2.0.0"
+ is-hexadecimal "^2.0.0"
+
+parse-json@^5.0.0, parse-json@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
+ integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ error-ex "^1.3.1"
+ json-parse-even-better-errors "^2.3.0"
+ lines-and-columns "^1.1.6"
+
+parse-numeric-range@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz"
+ integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
+
+parse5-htmlparser2-tree-adapter@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz"
+ integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==
+ dependencies:
+ domhandler "^5.0.3"
+ parse5 "^7.0.0"
+
+parse5-parser-stream@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz"
+ integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==
+ dependencies:
+ parse5 "^7.0.0"
+
+parse5@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz"
+ integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
+parse5@^7.0.0, parse5@^7.1.2:
+ version "7.2.1"
+ resolved "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz"
+ integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==
+ dependencies:
+ entities "^4.5.0"
+
+parseurl@~1.3.2, parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+pascal-case@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz"
+ integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+path-browserify@^1.0.1, path-browserify@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz"
+ integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
+ integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-exists@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz"
+ integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==
+
+path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-is-inside@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz"
+ integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
+
+path-key@^3.0.0, path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-loader@^1.0.10:
+ version "1.0.12"
+ resolved "https://registry.npmjs.org/path-loader/-/path-loader-1.0.12.tgz"
+ integrity sha512-n7oDG8B+k/p818uweWrOixY9/Dsr89o2TkCm6tOTex3fpdo2+BFDgR+KpB37mGKBRsBAlR8CIJMFN0OEy/7hIQ==
+ dependencies:
+ native-promise-only "^0.8.1"
+ superagent "^7.1.6"
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-root-regex@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz"
+ integrity sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==
+
+path-root@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz"
+ integrity sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==
+ dependencies:
+ path-root-regex "^0.1.0"
+
+path-scurry@^1.11.1, path-scurry@^1.6.1:
+ version "1.11.1"
+ resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz"
+ integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+ dependencies:
+ lru-cache "^10.2.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
+path-to-regexp@^1.7.0:
+ version "1.9.0"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz"
+ integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==
+ dependencies:
+ isarray "0.0.1"
+
+path-to-regexp@0.1.12:
+ version "0.1.12"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz"
+ integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
+
+path-to-regexp@3.3.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz"
+ integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+path@^0.12.7, path@0.12.7:
+ version "0.12.7"
+ resolved "https://registry.npmjs.org/path/-/path-0.12.7.tgz"
+ integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
+ dependencies:
+ process "^0.11.1"
+ util "^0.10.3"
+
+pbkdf2@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz"
+ integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==
+ dependencies:
+ create-hash "^1.1.2"
+ create-hmac "^1.1.4"
+ ripemd160 "^2.0.1"
+ safe-buffer "^5.0.1"
+ sha.js "^2.4.8"
+
+peek-readable@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz"
+ integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==
+
+picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
+ integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
+
+pirates@^4.0.1:
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz"
+ integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
+
+pkg-dir@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz"
+ integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==
+ dependencies:
+ find-up "^6.3.0"
+
+pkg-up@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz"
+ integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
+ dependencies:
+ find-up "^3.0.0"
+
+pluralize@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz"
+ integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
+
+possible-typed-array-names@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz"
+ integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
+
+postcss-attribute-case-insensitive@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz"
+ integrity sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-calc@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz"
+ integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==
+ dependencies:
+ postcss-selector-parser "^6.0.11"
+ postcss-value-parser "^4.2.0"
+
+postcss-clamp@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz"
+ integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-color-functional-notation@^7.0.7:
+ version "7.0.7"
+ resolved "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.7.tgz"
+ integrity sha512-EZvAHsvyASX63vXnyXOIynkxhaHRSsdb7z6yiXKIovGXAolW4cMZ3qoh7k3VdTsLBS6VGdksGfIo3r6+waLoOw==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+postcss-color-hex-alpha@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz"
+ integrity sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==
+ dependencies:
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-color-rebeccapurple@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz"
+ integrity sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==
+ dependencies:
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-colormin@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz"
+ integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==
+ dependencies:
+ browserslist "^4.23.0"
+ caniuse-api "^3.0.0"
+ colord "^2.9.3"
+ postcss-value-parser "^4.2.0"
+
+postcss-convert-values@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz"
+ integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==
+ dependencies:
+ browserslist "^4.23.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-custom-media@^11.0.5:
+ version "11.0.5"
+ resolved "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz"
+ integrity sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==
+ dependencies:
+ "@csstools/cascade-layer-name-parser" "^2.0.4"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/media-query-list-parser" "^4.0.2"
+
+postcss-custom-properties@^14.0.4:
+ version "14.0.4"
+ resolved "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz"
+ integrity sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==
+ dependencies:
+ "@csstools/cascade-layer-name-parser" "^2.0.4"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-custom-selectors@^8.0.4:
+ version "8.0.4"
+ resolved "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz"
+ integrity sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==
+ dependencies:
+ "@csstools/cascade-layer-name-parser" "^2.0.4"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ postcss-selector-parser "^7.0.0"
+
+postcss-dir-pseudo-class@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz"
+ integrity sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-discard-comments@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz"
+ integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==
+
+postcss-discard-duplicates@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz"
+ integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==
+
+postcss-discard-empty@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz"
+ integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==
+
+postcss-discard-overridden@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz"
+ integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==
+
+postcss-discard-unused@^6.0.5:
+ version "6.0.5"
+ resolved "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz"
+ integrity sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==
+ dependencies:
+ postcss-selector-parser "^6.0.16"
+
+postcss-double-position-gradients@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz"
+ integrity sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==
+ dependencies:
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-focus-visible@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz"
+ integrity sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-focus-within@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz"
+ integrity sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-font-variant@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz"
+ integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==
+
+postcss-gap-properties@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz"
+ integrity sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==
+
+postcss-image-set-function@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz"
+ integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==
+ dependencies:
+ "@csstools/utilities" "^2.0.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-import@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz"
+ integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
+ dependencies:
+ postcss-value-parser "^4.0.0"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-js@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz"
+ integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
+ dependencies:
+ camelcase-css "^2.0.1"
+
+postcss-lab-function@^7.0.7:
+ version "7.0.7"
+ resolved "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.7.tgz"
+ integrity sha512-+ONj2bpOQfsCKZE2T9VGMyVVdGcGUpr7u3SVfvkJlvhTRmDCfY25k4Jc8fubB9DclAPR4+w8uVtDZmdRgdAHig==
+ dependencies:
+ "@csstools/css-color-parser" "^3.0.7"
+ "@csstools/css-parser-algorithms" "^3.0.4"
+ "@csstools/css-tokenizer" "^3.0.3"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/utilities" "^2.0.0"
+
+postcss-load-config@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz"
+ integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==
+ dependencies:
+ lilconfig "^3.0.0"
+ yaml "^2.3.4"
+
+postcss-loader@^7.3.3:
+ version "7.3.4"
+ resolved "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz"
+ integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==
+ dependencies:
+ cosmiconfig "^8.3.5"
+ jiti "^1.20.0"
+ semver "^7.5.4"
+
+postcss-logical@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz"
+ integrity sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-merge-idents@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz"
+ integrity sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==
+ dependencies:
+ cssnano-utils "^4.0.2"
+ postcss-value-parser "^4.2.0"
+
+postcss-merge-longhand@^6.0.5:
+ version "6.0.5"
+ resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz"
+ integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+ stylehacks "^6.1.1"
+
+postcss-merge-rules@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz"
+ integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==
+ dependencies:
+ browserslist "^4.23.0"
+ caniuse-api "^3.0.0"
+ cssnano-utils "^4.0.2"
+ postcss-selector-parser "^6.0.16"
+
+postcss-minify-font-values@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz"
+ integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-gradients@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz"
+ integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==
+ dependencies:
+ colord "^2.9.3"
+ cssnano-utils "^4.0.2"
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-params@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz"
+ integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==
+ dependencies:
+ browserslist "^4.23.0"
+ cssnano-utils "^4.0.2"
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-selectors@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz"
+ integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==
+ dependencies:
+ postcss-selector-parser "^6.0.16"
+
+postcss-modules-extract-imports@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz"
+ integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==
+
+postcss-modules-local-by-default@^4.0.5:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz"
+ integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==
+ dependencies:
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+ postcss-value-parser "^4.1.0"
+
+postcss-modules-scope@^3.2.0:
+ version "3.2.1"
+ resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz"
+ integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+ dependencies:
+ icss-utils "^5.0.0"
+
+postcss-nested@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz"
+ integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==
+ dependencies:
+ postcss-selector-parser "^6.1.1"
+
+postcss-nesting@^13.0.1:
+ version "13.0.1"
+ resolved "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz"
+ integrity sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==
+ dependencies:
+ "@csstools/selector-resolve-nested" "^3.0.0"
+ "@csstools/selector-specificity" "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+
+postcss-normalize-charset@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz"
+ integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==
+
+postcss-normalize-display-values@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz"
+ integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-positions@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz"
+ integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-repeat-style@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz"
+ integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-string@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz"
+ integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-timing-functions@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz"
+ integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-unicode@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz"
+ integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==
+ dependencies:
+ browserslist "^4.23.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-url@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz"
+ integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-whitespace@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz"
+ integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-opacity-percentage@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz"
+ integrity sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==
+
+postcss-ordered-values@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz"
+ integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==
+ dependencies:
+ cssnano-utils "^4.0.2"
+ postcss-value-parser "^4.2.0"
+
+postcss-overflow-shorthand@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz"
+ integrity sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-page-break@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz"
+ integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==
+
+postcss-place@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz"
+ integrity sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-preset-env@^10.1.0:
+ version "10.1.4"
+ resolved "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.4.tgz"
+ integrity sha512-awWKS3CwyY7I4Eb3YSWOZisbj3qXyuQCrylYiu2vSHxnSZAj3LHStN6jOcpCrc6EjYunLwbeNto3M5/JBtXpzg==
+ dependencies:
+ "@csstools/postcss-cascade-layers" "^5.0.1"
+ "@csstools/postcss-color-function" "^4.0.7"
+ "@csstools/postcss-color-mix-function" "^3.0.7"
+ "@csstools/postcss-content-alt-text" "^2.0.4"
+ "@csstools/postcss-exponential-functions" "^2.0.6"
+ "@csstools/postcss-font-format-keywords" "^4.0.0"
+ "@csstools/postcss-gamut-mapping" "^2.0.7"
+ "@csstools/postcss-gradients-interpolation-method" "^5.0.7"
+ "@csstools/postcss-hwb-function" "^4.0.7"
+ "@csstools/postcss-ic-unit" "^4.0.0"
+ "@csstools/postcss-initial" "^2.0.1"
+ "@csstools/postcss-is-pseudo-class" "^5.0.1"
+ "@csstools/postcss-light-dark-function" "^2.0.7"
+ "@csstools/postcss-logical-float-and-clear" "^3.0.0"
+ "@csstools/postcss-logical-overflow" "^2.0.0"
+ "@csstools/postcss-logical-overscroll-behavior" "^2.0.0"
+ "@csstools/postcss-logical-resize" "^3.0.0"
+ "@csstools/postcss-logical-viewport-units" "^3.0.3"
+ "@csstools/postcss-media-minmax" "^2.0.6"
+ "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.4"
+ "@csstools/postcss-nested-calc" "^4.0.0"
+ "@csstools/postcss-normalize-display-values" "^4.0.0"
+ "@csstools/postcss-oklab-function" "^4.0.7"
+ "@csstools/postcss-progressive-custom-properties" "^4.0.0"
+ "@csstools/postcss-random-function" "^1.0.2"
+ "@csstools/postcss-relative-color-syntax" "^3.0.7"
+ "@csstools/postcss-scope-pseudo-class" "^4.0.1"
+ "@csstools/postcss-sign-functions" "^1.1.1"
+ "@csstools/postcss-stepped-value-functions" "^4.0.6"
+ "@csstools/postcss-text-decoration-shorthand" "^4.0.1"
+ "@csstools/postcss-trigonometric-functions" "^4.0.6"
+ "@csstools/postcss-unset-value" "^4.0.0"
+ autoprefixer "^10.4.19"
+ browserslist "^4.24.4"
+ css-blank-pseudo "^7.0.1"
+ css-has-pseudo "^7.0.2"
+ css-prefers-color-scheme "^10.0.0"
+ cssdb "^8.2.3"
+ postcss-attribute-case-insensitive "^7.0.1"
+ postcss-clamp "^4.1.0"
+ postcss-color-functional-notation "^7.0.7"
+ postcss-color-hex-alpha "^10.0.0"
+ postcss-color-rebeccapurple "^10.0.0"
+ postcss-custom-media "^11.0.5"
+ postcss-custom-properties "^14.0.4"
+ postcss-custom-selectors "^8.0.4"
+ postcss-dir-pseudo-class "^9.0.1"
+ postcss-double-position-gradients "^6.0.0"
+ postcss-focus-visible "^10.0.1"
+ postcss-focus-within "^9.0.1"
+ postcss-font-variant "^5.0.0"
+ postcss-gap-properties "^6.0.0"
+ postcss-image-set-function "^7.0.0"
+ postcss-lab-function "^7.0.7"
+ postcss-logical "^8.0.0"
+ postcss-nesting "^13.0.1"
+ postcss-opacity-percentage "^3.0.0"
+ postcss-overflow-shorthand "^6.0.0"
+ postcss-page-break "^3.0.4"
+ postcss-place "^10.0.0"
+ postcss-pseudo-class-any-link "^10.0.1"
+ postcss-replace-overflow-wrap "^4.0.0"
+ postcss-selector-not "^8.0.1"
+
+postcss-pseudo-class-any-link@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz"
+ integrity sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-reduce-idents@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz"
+ integrity sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-reduce-initial@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz"
+ integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==
+ dependencies:
+ browserslist "^4.23.0"
+ caniuse-api "^3.0.0"
+
+postcss-reduce-transforms@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz"
+ integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-replace-overflow-wrap@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz"
+ integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==
+
+postcss-selector-not@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz"
+ integrity sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==
+ dependencies:
+ postcss-selector-parser "^7.0.0"
+
+postcss-selector-parser@^6.0.11:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-selector-parser@^6.0.16:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-selector-parser@^6.1.1:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-selector-parser@^6.1.2:
+ version "6.1.2"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-selector-parser@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz"
+ integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-sort-media-queries@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz"
+ integrity sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==
+ dependencies:
+ sort-css-media-queries "2.2.0"
+
+postcss-svgo@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz"
+ integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+ svgo "^3.2.0"
+
+postcss-unique-selectors@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz"
+ integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==
+ dependencies:
+ postcss-selector-parser "^6.0.16"
+
+postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss-zindex@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz"
+ integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==
+
+"postcss@^7.0.0 || ^8.0.1", postcss@^8, postcss@^8.0.0, postcss@^8.0.3, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.2.2, postcss@^8.4, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.31, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47, postcss@^8.4.6, postcss@>=8.0.9:
+ version "8.5.2"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz"
+ integrity sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==
+ dependencies:
+ nanoid "^3.3.8"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+posthog-js@^1.45.1:
+ version "1.219.3"
+ resolved "https://registry.npmjs.org/posthog-js/-/posthog-js-1.219.3.tgz"
+ integrity sha512-oKN4no9RRAptZ86R/MvMjsxQnFAe97rwU2emmTzf/q9ng+7V4nU+APM0ItzrESFtRYx1X8kKtxDUlkujNhfMvw==
+ dependencies:
+ core-js "^3.38.1"
+ fflate "^0.4.8"
+ preact "^10.19.3"
+ web-vitals "^4.2.0"
+
+postman-code-generators@^1.0.0, postman-code-generators@^1.10.1:
+ version "1.14.1"
+ resolved "https://registry.npmjs.org/postman-code-generators/-/postman-code-generators-1.14.1.tgz"
+ integrity sha512-IQ/D4VqNNK9cLxQttFGI9Jx4Q6uxCvOf4UKf+hfatmqivguJ1ICeSCcjEfYXhLQTa/RsJ3PFERHztF+CE0M/WQ==
+ dependencies:
+ async "3.2.2"
+ detect-package-manager "3.0.2"
+ lodash "4.17.21"
+ path "0.12.7"
+ postman-collection "^4.4.0"
+ shelljs "0.8.5"
+
+postman-collection@^4.1.0, postman-collection@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.npmjs.org/postman-collection/-/postman-collection-4.5.0.tgz"
+ integrity sha512-152JSW9pdbaoJihwjc7Q8lc3nPg/PC9lPTHdMk7SHnHhu/GBJB7b2yb9zG7Qua578+3PxkQ/HYBuXpDSvsf7GQ==
+ dependencies:
+ "@faker-js/faker" "5.5.3"
+ file-type "3.9.0"
+ http-reasons "0.1.0"
+ iconv-lite "0.6.3"
+ liquid-json "0.3.1"
+ lodash "4.17.21"
+ mime-format "2.0.1"
+ mime-types "2.1.35"
+ postman-url-encoder "3.0.5"
+ semver "7.6.3"
+ uuid "8.3.2"
+
+postman-url-encoder@3.0.5:
+ version "3.0.5"
+ resolved "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz"
+ integrity sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==
+ dependencies:
+ punycode "^2.1.1"
+
+preact@^10.19.3:
+ version "10.26.2"
+ resolved "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz"
+ integrity sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==
+
+pretty-error@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz"
+ integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==
+ dependencies:
+ lodash "^4.17.20"
+ renderkid "^3.0.0"
+
+pretty-time@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz"
+ integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==
+
+prism-react-renderer@^1.3.5:
+ version "1.3.5"
+ resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz"
+ integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==
+
+prism-react-renderer@^2.1.0:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz"
+ integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==
+ dependencies:
+ "@types/prismjs" "^1.26.0"
+ clsx "^2.0.0"
+
+prism-react-renderer@^2.3.0:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz"
+ integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==
+ dependencies:
+ "@types/prismjs" "^1.26.0"
+ clsx "^2.0.0"
+
+prism-react-renderer@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz"
+ integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==
+ dependencies:
+ "@types/prismjs" "^1.26.0"
+ clsx "^2.0.0"
+
+prismjs@^1.29.0:
+ version "1.29.0"
+ resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz"
+ integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
+private@^0.1.6, private@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.npmjs.org/private/-/private-0.1.8.tgz"
+ integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+process@^0.11.1, process@^0.11.10:
+ version "0.11.10"
+ resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
+ integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
+
+prompts@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz"
+ integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
+ dependencies:
+ kleur "^3.0.3"
+ sisteransi "^1.0.5"
+
+prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.7.2:
+ version "15.8.1"
+ resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
+property-information@^6.0.0:
+ version "6.5.0"
+ resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz"
+ integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==
+
+proto-list@~1.2.1:
+ version "1.2.4"
+ resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
+ integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
+
+proxy-addr@~2.0.7:
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
+ integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
+
+public-encrypt@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz"
+ integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
+ dependencies:
+ bn.js "^4.1.0"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ parse-asn1 "^5.0.0"
+ randombytes "^2.0.1"
+ safe-buffer "^5.1.2"
+
+pump@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz"
+ integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz"
+ integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
+
+punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+pupa@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz"
+ integrity sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==
+ dependencies:
+ escape-goat "^4.0.0"
+
+qs@^6.10.3, qs@^6.11.0, qs@^6.12.3:
+ version "6.14.0"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz"
+ integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
+ dependencies:
+ side-channel "^1.1.0"
+
+qs@6.13.0:
+ version "6.13.0"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+ dependencies:
+ side-channel "^1.0.6"
+
+querystring-es3@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz"
+ integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+queue@6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz"
+ integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==
+ dependencies:
+ inherits "~2.0.3"
+
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+randomfill@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz"
+ integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==
+ dependencies:
+ randombytes "^2.0.5"
+ safe-buffer "^5.1.0"
+
+range-parser@^1.2.1, range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+range-parser@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz"
+ integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
+
+raw-body@2.5.2:
+ version "2.5.2"
+ resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz"
+ integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+rc@1.2.8:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+react-dev-utils@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz"
+ integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==
+ dependencies:
+ "@babel/code-frame" "^7.16.0"
+ address "^1.1.2"
+ browserslist "^4.18.1"
+ chalk "^4.1.2"
+ cross-spawn "^7.0.3"
+ detect-port-alt "^1.1.6"
+ escape-string-regexp "^4.0.0"
+ filesize "^8.0.6"
+ find-up "^5.0.0"
+ fork-ts-checker-webpack-plugin "^6.5.0"
+ global-modules "^2.0.0"
+ globby "^11.0.4"
+ gzip-size "^6.0.0"
+ immer "^9.0.7"
+ is-root "^2.1.0"
+ loader-utils "^3.2.0"
+ open "^8.4.0"
+ pkg-up "^3.1.0"
+ prompts "^2.4.2"
+ react-error-overlay "^6.0.11"
+ recursive-readdir "^2.2.2"
+ shell-quote "^1.7.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+react-dom@*, "react-dom@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", "react-dom@^16.14.0 || 17 || ^18", "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^17.x || ^18.x", react-dom@^18.0.0, "react-dom@^18.0.0 || ^19.0.0", react-dom@^18.2.0, "react-dom@>= 16.8.0 < 19.0.0", react-dom@>=16.8.0, react-dom@>=18.0.0:
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
+ integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.2"
+
+react-error-overlay@^6.0.11:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz"
+ integrity sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==
+
+react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz"
+ integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
+
+"react-helmet-async@npm:@slorber/react-helmet-async@*", "react-helmet-async@npm:@slorber/react-helmet-async@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz"
+ integrity sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ invariant "^2.2.4"
+ prop-types "^15.7.2"
+ react-fast-compare "^3.2.0"
+ shallowequal "^1.1.0"
+
+react-hook-form@^7.0.0, react-hook-form@^7.43.8:
+ version "7.54.2"
+ resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz"
+ integrity sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==
+
+react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
+ version "16.13.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-is@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+react-is@^18.0.0:
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+
+react-json-view-lite@^1.2.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz"
+ integrity sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==
+
+react-lifecycles-compat@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz"
+ integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
+react-live@^4.0.0:
+ version "4.1.8"
+ resolved "https://registry.npmjs.org/react-live/-/react-live-4.1.8.tgz"
+ integrity sha512-B2SgNqwPuS2ekqj4lcxi5TibEcjWkdVyYykBEUBshPAPDQ527x2zPEZg560n8egNtAjUpwXFQm7pcXV65aAYmg==
+ dependencies:
+ prism-react-renderer "^2.4.0"
+ sucrase "^3.35.0"
+ use-editable "^2.3.3"
+
+react-loadable-ssr-addon-v5-slorber@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz"
+ integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==
+ dependencies:
+ "@babel/runtime" "^7.10.3"
+
+react-loadable@*, "react-loadable@npm:@docusaurus/react-loadable@6.0.0":
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz"
+ integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==
+ dependencies:
+ "@types/react" "*"
+
+react-magic-dropzone@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/react-magic-dropzone/-/react-magic-dropzone-1.0.1.tgz"
+ integrity sha512-0BIROPARmXHpk4AS3eWBOsewxoM5ndk2psYP/JmbCq8tz3uR2LIV1XiroZ9PKrmDRMctpW+TvsBCtWasuS8vFA==
+
+react-markdown@^8.0.1:
+ version "8.0.7"
+ resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz"
+ integrity sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/prop-types" "^15.0.0"
+ "@types/unist" "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-whitespace "^2.0.0"
+ prop-types "^15.0.0"
+ property-information "^6.0.0"
+ react-is "^18.0.0"
+ remark-parse "^10.0.0"
+ remark-rehype "^10.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-object "^0.4.0"
+ unified "^10.0.0"
+ unist-util-visit "^4.0.0"
+ vfile "^5.0.0"
+
+react-modal@^3.15.1:
+ version "3.16.3"
+ resolved "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz"
+ integrity sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==
+ dependencies:
+ exenv "^1.2.0"
+ prop-types "^15.7.2"
+ react-lifecycles-compat "^3.0.0"
+ warning "^4.0.3"
+
+react-player@^2.10.1:
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz"
+ integrity sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==
+ dependencies:
+ deepmerge "^4.0.0"
+ load-script "^1.0.0"
+ memoize-one "^5.1.1"
+ prop-types "^15.7.2"
+ react-fast-compare "^3.0.1"
+
+react-property@2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz"
+ integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
+
+react-redux@^7.2.0, "react-redux@^7.2.1 || ^8.0.2":
+ version "7.2.9"
+ resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz"
+ integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
+ dependencies:
+ "@babel/runtime" "^7.15.4"
+ "@types/react-redux" "^7.1.20"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
+react-router-config@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz"
+ integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+
+react-router-dom@^5.3.4:
+ version "5.3.4"
+ resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz"
+ integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ loose-envify "^1.3.1"
+ prop-types "^15.6.2"
+ react-router "5.3.4"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
+react-router@^5.3.4, react-router@>=5, react-router@5.3.4:
+ version "5.3.4"
+ resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz"
+ integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ hoist-non-react-statics "^3.1.0"
+ loose-envify "^1.3.1"
+ path-to-regexp "^1.7.0"
+ prop-types "^15.6.2"
+ react-is "^16.6.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
+react@*, "react@^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", "react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.14.0 || ^17 || ^18", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^17.x || ^18.x", react@^18.0.0, "react@^18.0.0 || ^19.0.0", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0", "react@>= 16.8.0 < 19.0.0", react@>=0.14.9, react@>=15, react@>=16, react@>=16.0.0, react@>=16.6.0, react@>=16.8.0, react@>=18.0.0, "react@0.14 || 15 || 16 || 17 || 18":
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
+ integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz"
+ integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
+ dependencies:
+ pify "^2.3.0"
+
+readable-stream@^2.0.1:
+ version "2.3.8"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
+ integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^2.3.8:
+ version "2.3.8"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
+ integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^3.0.6, readable-stream@^3.5.0, readable-stream@^3.6.0:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+readable-stream@^4.4.2:
+ version "4.7.0"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz"
+ integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
+ dependencies:
+ abort-controller "^3.0.0"
+ buffer "^6.0.3"
+ events "^3.3.0"
+ process "^0.11.10"
+ string_decoder "^1.3.0"
+
+readable-stream@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz"
+ integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
+ dependencies:
+ abort-controller "^3.0.0"
+ buffer "^6.0.3"
+ events "^3.3.0"
+ process "^0.11.10"
+ string_decoder "^1.3.0"
+
+readable-web-to-node-stream@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz"
+ integrity sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==
+ dependencies:
+ readable-stream "^4.7.0"
+
+readdirp@^4.0.1:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
+ integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+reading-time@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz"
+ integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==
+
+rechoir@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz"
+ integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
+ dependencies:
+ resolve "^1.1.6"
+
+recma-build-jsx@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz"
+ integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-util-build-jsx "^3.0.0"
+ vfile "^6.0.0"
+
+recma-jsx@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz"
+ integrity sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==
+ dependencies:
+ acorn-jsx "^5.0.0"
+ estree-util-to-js "^2.0.0"
+ recma-parse "^1.0.0"
+ recma-stringify "^1.0.0"
+ unified "^11.0.0"
+
+recma-parse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz"
+ integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ esast-util-from-js "^2.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+recma-stringify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz"
+ integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-util-to-js "^2.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+recursive-readdir@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz"
+ integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==
+ dependencies:
+ minimatch "^3.0.5"
+
+redux-devtools-extension@^2.13.8:
+ version "2.13.9"
+ resolved "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz"
+ integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==
+
+redux-thunk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz"
+ integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
+
+"redux@^3.1.0 || ^4.0.0", redux@^4, redux@^4.0.0, redux@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz"
+ integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
+refractor@^4.8.1:
+ version "4.8.1"
+ resolved "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz"
+ integrity sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/prismjs" "^1.0.0"
+ hastscript "^7.0.0"
+ parse-entities "^4.0.0"
+
+reftools@^1.1.9:
+ version "1.1.9"
+ resolved "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz"
+ integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==
+
+regenerate-unicode-properties@^10.2.0:
+ version "10.2.0"
+ resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz"
+ integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==
+ dependencies:
+ regenerate "^1.4.2"
+
+regenerate@^1.2.1, regenerate@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz"
+ integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
+
+regenerator-runtime@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz"
+ integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
+
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
+regenerator-transform@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz"
+ integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==
+ dependencies:
+ babel-runtime "^6.18.0"
+ babel-types "^6.19.0"
+ private "^0.1.6"
+
+regenerator-transform@^0.15.2:
+ version "0.15.2"
+ resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz"
+ integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
+regexpu-core@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz"
+ integrity sha512-tJ9+S4oKjxY8IZ9jmjnp/mtytu1u3iyIQAfmI51IKWH6bFf7XR1ybtaO6j7INhZKXOTYADk7V5qxaqLkmNxiZQ==
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regexpu-core@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz"
+ integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==
+ dependencies:
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^10.2.0"
+ regjsgen "^0.8.0"
+ regjsparser "^0.12.0"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.1.0"
+
+registry-auth-token@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz"
+ integrity sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==
+ dependencies:
+ "@pnpm/npm-conf" "^2.1.0"
+
+registry-url@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz"
+ integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==
+ dependencies:
+ rc "1.2.8"
+
+regjsgen@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz"
+ integrity sha512-x+Y3yA24uF68m5GA+tBjbGYo64xXVJpbToBaWCoSNSc1hdk6dfctaRWrNFTVJZIIhL5GxW8zwjoixbnifnK59g==
+
+regjsgen@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz"
+ integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==
+
+regjsparser@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz"
+ integrity sha512-jlQ9gYLfk2p3V5Ag5fYhA7fv7OHzd1KUH0PRP46xc3TgwjwgROIW572AfYg/X9kaNq/LJnu6oJcFRXlIrGoTRw==
+ dependencies:
+ jsesc "~0.5.0"
+
+regjsparser@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz"
+ integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==
+ dependencies:
+ jsesc "~3.0.2"
+
+rehype-raw@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz"
+ integrity sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-raw "^7.2.0"
+ unified "^10.0.0"
+
+rehype-raw@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz"
+ integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-raw "^9.0.0"
+ vfile "^6.0.0"
+
+rehype-recma@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz"
+ integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ hast-util-to-estree "^3.0.0"
+
+relateurl@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz"
+ integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
+
+remark-directive@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz"
+ integrity sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-directive "^3.0.0"
+ micromark-extension-directive "^3.0.0"
+ unified "^11.0.0"
+
+remark-emoji@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz"
+ integrity sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==
+ dependencies:
+ "@types/mdast" "^4.0.2"
+ emoticon "^4.0.1"
+ mdast-util-find-and-replace "^3.0.1"
+ node-emoji "^2.1.0"
+ unified "^11.0.4"
+
+remark-frontmatter@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz"
+ integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-frontmatter "^2.0.0"
+ micromark-extension-frontmatter "^2.0.0"
+ unified "^11.0.0"
+
+remark-gfm@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz"
+ integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-gfm "^3.0.0"
+ micromark-extension-gfm "^3.0.0"
+ remark-parse "^11.0.0"
+ remark-stringify "^11.0.0"
+ unified "^11.0.0"
+
+remark-gfm@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz"
+ integrity sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-gfm "^2.0.0"
+ micromark-extension-gfm "^2.0.0"
+ unified "^10.0.0"
+
+remark-mdx@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz"
+ integrity sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==
+ dependencies:
+ mdast-util-mdx "^3.0.0"
+ micromark-extension-mdxjs "^3.0.0"
+
+remark-parse@^10.0.0:
+ version "10.0.2"
+ resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz"
+ integrity sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ mdast-util-from-markdown "^1.0.0"
+ unified "^10.0.0"
+
+remark-parse@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz"
+ integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unified "^11.0.0"
+
+remark-rehype@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz"
+ integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ "@types/mdast" "^3.0.0"
+ mdast-util-to-hast "^12.1.0"
+ unified "^10.0.0"
+
+remark-rehype@^11.0.0:
+ version "11.1.1"
+ resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz"
+ integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-hast "^13.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
+remark-stringify@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz"
+ integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ unified "^11.0.0"
+
+renderkid@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz"
+ integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==
+ dependencies:
+ css-select "^4.1.3"
+ dom-converter "^0.2.0"
+ htmlparser2 "^6.1.0"
+ lodash "^4.17.21"
+ strip-ansi "^6.0.1"
+
+repeat-string@^1.0.0:
+ version "1.6.1"
+ resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz"
+ integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz"
+ integrity sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==
+ dependencies:
+ is-finite "^1.0.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+"require-like@>= 0.1.1":
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz"
+ integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==
+
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
+ integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+reselect@^4.1.8:
+ version "4.1.8"
+ resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz"
+ integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
+
+resolve-alpn@^1.0.0, resolve-alpn@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz"
+ integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve-package-path@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/resolve-package-path/-/resolve-package-path-4.0.3.tgz"
+ integrity sha512-SRpNAPW4kewOaNUt8VPqhJ0UMxawMwzJD8V7m1cJfdSTK9ieZwS6K7Dabsm4bmLFM96Z5Y/UznrpG5kt1im8yA==
+ dependencies:
+ path-root "^0.1.1"
+
+resolve-pathname@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz"
+ integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
+
+resolve@^1.1.6, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.22.8:
+ version "1.22.10"
+ resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz"
+ integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
+ dependencies:
+ is-core-module "^2.16.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+responselike@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz"
+ integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==
+ dependencies:
+ lowercase-keys "^2.0.0"
+
+responselike@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz"
+ integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==
+ dependencies:
+ lowercase-keys "^3.0.0"
+
+retry@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rimraf@^4.1.2:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz"
+ integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==
+ dependencies:
+ glob "^9.2.0"
+
+ripemd160@^2.0.0, ripemd160@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz"
+ integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
+ dependencies:
+ hash-base "^3.0.0"
+ inherits "^2.0.1"
+
+rtlcss@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz"
+ integrity sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+ postcss "^8.4.21"
+ strip-json-comments "^3.1.1"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+sade@^1.7.3:
+ version "1.8.1"
+ resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz"
+ integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
+ dependencies:
+ mri "^1.1.0"
+
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-regex-test@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz"
+ integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ is-regex "^1.2.1"
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+sanitize-filename@^1.6.3:
+ version "1.6.3"
+ resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz"
+ integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==
+ dependencies:
+ truncate-utf8-bytes "^1.0.0"
+
+sass-loader@^16.0.2:
+ version "16.0.5"
+ resolved "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz"
+ integrity sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==
+ dependencies:
+ neo-async "^2.6.2"
+
+sass@^1.3.0, sass@^1.30.0, sass@^1.80.4:
+ version "1.85.0"
+ resolved "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz"
+ integrity sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==
+ dependencies:
+ chokidar "^4.0.0"
+ immutable "^5.0.2"
+ source-map-js ">=0.6.2 <2.0.0"
+ optionalDependencies:
+ "@parcel/watcher" "^2.4.1"
+
+sax@^1.2.4:
+ version "1.4.1"
+ resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz"
+ integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
+
+scheduler@^0.23.2:
+ version "0.23.2"
+ resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
+ integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+schema-utils@^3.0.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz"
+ integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^4.0.0, schema-utils@^4.0.1, schema-utils@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz"
+ integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ ajv "^8.9.0"
+ ajv-formats "^2.1.1"
+ ajv-keywords "^5.1.0"
+
+schema-utils@2.7.0:
+ version "2.7.0"
+ resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz"
+ integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
+ dependencies:
+ "@types/json-schema" "^7.0.4"
+ ajv "^6.12.2"
+ ajv-keywords "^3.4.1"
+
+"search-insights@>= 1 < 3":
+ version "2.17.3"
+ resolved "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz"
+ integrity sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==
+
+section-matter@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz"
+ integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
+ dependencies:
+ extend-shallow "^2.0.1"
+ kind-of "^6.0.0"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"
+ integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
+
+selfsigned@^2.1.1:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz"
+ integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
+ dependencies:
+ "@types/node-forge" "^1.3.0"
+ node-forge "^1"
+
+semver-diff@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz"
+ integrity sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==
+ dependencies:
+ semver "^7.3.5"
+
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4:
+ version "7.7.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz"
+ integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
+
+semver@7.6.3:
+ version "7.6.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+send@0.19.0:
+ version "0.19.0"
+ resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz"
+ integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+ dependencies:
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ mime "1.6.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
+ range-parser "~1.2.1"
+ statuses "2.0.1"
+
+serialize-javascript@^6.0.0, serialize-javascript@^6.0.1, serialize-javascript@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz"
+ integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
+ dependencies:
+ randombytes "^2.1.0"
+
+serve-handler@^6.1.6:
+ version "6.1.6"
+ resolved "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz"
+ integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==
+ dependencies:
+ bytes "3.0.0"
+ content-disposition "0.5.2"
+ mime-types "2.1.18"
+ minimatch "3.1.2"
+ path-is-inside "1.0.2"
+ path-to-regexp "3.3.0"
+ range-parser "1.2.0"
+
+serve-index@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz"
+ integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==
+ dependencies:
+ accepts "~1.3.4"
+ batch "0.6.1"
+ debug "2.6.9"
+ escape-html "~1.0.3"
+ http-errors "~1.6.2"
+ mime-types "~2.1.17"
+ parseurl "~1.3.2"
+
+serve-static@1.16.2:
+ version "1.16.2"
+ resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz"
+ integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
+ dependencies:
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.19.0"
+
+set-function-length@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
+setimmediate@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
+ integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
+
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz"
+ integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+sha.js@^2.4.0, sha.js@^2.4.8:
+ version "2.4.11"
+ resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz"
+ integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
+ dependencies:
+ inherits "^2.0.1"
+ safe-buffer "^5.0.1"
+
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
+shallowequal@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz"
+ integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz"
+ integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
+ integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shell-quote@^1.7.3, shell-quote@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz"
+ integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==
+
+shelljs@^0.8.5, shelljs@0.8.5:
+ version "0.8.5"
+ resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz"
+ integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
+ dependencies:
+ glob "^7.0.0"
+ interpret "^1.0.0"
+ rechoir "^0.6.2"
+
+should-equal@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz"
+ integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==
+ dependencies:
+ should-type "^1.4.0"
+
+should-format@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz"
+ integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==
+ dependencies:
+ should-type "^1.3.0"
+ should-type-adaptors "^1.0.1"
+
+should-type-adaptors@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz"
+ integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==
+ dependencies:
+ should-type "^1.3.0"
+ should-util "^1.0.0"
+
+should-type@^1.3.0, should-type@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz"
+ integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==
+
+should-util@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz"
+ integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==
+
+should@^13.2.1:
+ version "13.2.3"
+ resolved "https://registry.npmjs.org/should/-/should-13.2.3.tgz"
+ integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==
+ dependencies:
+ should-equal "^2.0.0"
+ should-format "^3.0.3"
+ should-type "^1.4.0"
+ should-type-adaptors "^1.0.1"
+ should-util "^1.0.0"
+
+side-channel-list@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz"
+ integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+
+side-channel-map@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz"
+ integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+
+side-channel-weakmap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz"
+ integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.5"
+ object-inspect "^1.13.3"
+ side-channel-map "^1.0.1"
+
+side-channel@^1.0.6, side-channel@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
+ integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
+ dependencies:
+ es-errors "^1.3.0"
+ object-inspect "^1.13.3"
+ side-channel-list "^1.0.0"
+ side-channel-map "^1.0.1"
+ side-channel-weakmap "^1.0.2"
+
+signal-exit@^3.0.2, signal-exit@^3.0.3:
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+signal-exit@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
+sirv@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz"
+ integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==
+ dependencies:
+ "@polka/url" "^1.0.0-next.24"
+ mrmime "^2.0.0"
+ totalist "^3.0.0"
+
+sisteransi@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
+ integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+
+sitemap@^7.1.1:
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz"
+ integrity sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==
+ dependencies:
+ "@types/node" "^17.0.5"
+ "@types/sax" "^1.2.1"
+ arg "^5.0.0"
+ sax "^1.2.4"
+
+skin-tone@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz"
+ integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==
+ dependencies:
+ unicode-emoji-modifier-base "^1.0.0"
+
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz"
+ integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==
+
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+slash@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz"
+ integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+
+slugify@^1.6.5:
+ version "1.6.6"
+ resolved "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz"
+ integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==
+
+snake-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz"
+ integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
+sockjs@^0.3.24:
+ version "0.3.24"
+ resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz"
+ integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==
+ dependencies:
+ faye-websocket "^0.11.3"
+ uuid "^8.3.2"
+ websocket-driver "^0.7.4"
+
+sort-css-media-queries@2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz"
+ integrity sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==
+
+source-map-js@^1.0.1, source-map-js@^1.2.1, "source-map-js@>=0.6.2 <2.0.0":
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+source-map-support@^0.4.15:
+ version "0.4.18"
+ resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz"
+ integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
+ dependencies:
+ source-map "^0.5.6"
+
+source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.5.6, source-map@^0.5.7:
+ version "0.5.7"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
+ integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
+
+source-map@^0.6.0:
+ version "0.6.1"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+source-map@^0.7.0:
+ version "0.7.4"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
+
+source-map@~0.6.0:
+ version "0.6.1"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+space-separated-tokens@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
+ integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
+
+spdy-transport@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz"
+ integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
+ dependencies:
+ debug "^4.1.0"
+ detect-node "^2.0.4"
+ hpack.js "^2.1.6"
+ obuf "^1.1.2"
+ readable-stream "^3.0.6"
+ wbuf "^1.7.3"
+
+spdy@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz"
+ integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==
+ dependencies:
+ debug "^4.1.0"
+ handle-thing "^2.0.0"
+ http-deceiver "^1.2.7"
+ select-hose "^2.0.0"
+ spdy-transport "^3.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+ integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+srcset@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz"
+ integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==
+
+state-local@^1.0.6:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz"
+ integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==
+
+"statuses@>= 1.4.0 < 2":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
+ integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+std-env@^3.7.0:
+ version "3.8.0"
+ resolved "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz"
+ integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
+
+stream-browserify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz"
+ integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==
+ dependencies:
+ inherits "~2.0.4"
+ readable-stream "^3.5.0"
+
+stream-http@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz"
+ integrity sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.4"
+ readable-stream "^3.6.0"
+ xtend "^4.0.2"
+
+string_decoder@^1.1.1, string_decoder@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^5.0.1:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+stringify-entities@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz"
+ integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
+stringify-object@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz"
+ integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
+ dependencies:
+ get-own-enumerable-property-symbols "^3.0.0"
+ is-obj "^1.0.1"
+ is-regexp "^1.0.0"
+
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
+ integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+strip-bom-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz"
+ integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
+
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz"
+ integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+striptags@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz"
+ integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==
+
+strtok3@^6.0.3:
+ version "6.3.0"
+ resolved "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz"
+ integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==
+ dependencies:
+ "@tokenizer/token" "^0.3.0"
+ peek-readable "^4.1.0"
+
+style-to-js@1.1.8:
+ version "1.1.8"
+ resolved "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.8.tgz"
+ integrity sha512-bPSspCXkkhETLXnEgDbaoWRWyv3lF2bj32YIc8IElok2IIMHUlZtQUrxYmAkKUNxpluhH0qnKWrmuoXUyTY12g==
+ dependencies:
+ style-to-object "1.0.3"
+
+style-to-object@^0.4.0:
+ version "0.4.4"
+ resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz"
+ integrity sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==
+ dependencies:
+ inline-style-parser "0.1.1"
+
+style-to-object@^1.0.0:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz"
+ integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==
+ dependencies:
+ inline-style-parser "0.2.4"
+
+style-to-object@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.3.tgz"
+ integrity sha512-xOpx7S53E0V3DpVsvt7ySvoiumRpfXiC99PUXLqGB3wiAnN9ybEIpuzlZ8LAZg+h1sl9JkEUwtSQXxcCgFqbbg==
+ dependencies:
+ inline-style-parser "0.2.2"
+
+stylehacks@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz"
+ integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==
+ dependencies:
+ browserslist "^4.23.0"
+ postcss-selector-parser "^6.0.16"
+
+sucrase@^3.35.0:
+ version "3.35.0"
+ resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz"
+ integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.2"
+ commander "^4.0.0"
+ glob "^10.3.10"
+ lines-and-columns "^1.1.6"
+ mz "^2.7.0"
+ pirates "^4.0.1"
+ ts-interface-checker "^0.1.9"
+
+superagent@^7.1.6:
+ version "7.1.6"
+ resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz"
+ integrity sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==
+ dependencies:
+ component-emitter "^1.3.0"
+ cookiejar "^2.1.3"
+ debug "^4.3.4"
+ fast-safe-stringify "^2.1.1"
+ form-data "^4.0.0"
+ formidable "^2.0.1"
+ methods "^1.1.2"
+ mime "2.6.0"
+ qs "^6.10.3"
+ readable-stream "^3.6.0"
+ semver "^7.3.7"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
+ integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+svg-parser@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz"
+ integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
+
+svgo@^3.0.2, svgo@^3.2.0:
+ version "3.3.2"
+ resolved "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz"
+ integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==
+ dependencies:
+ "@trysound/sax" "0.2.0"
+ commander "^7.2.0"
+ css-select "^5.1.0"
+ css-tree "^2.3.1"
+ css-what "^6.1.0"
+ csso "^5.0.5"
+ picocolors "^1.0.0"
+
+swagger2openapi@^7.0.8, swagger2openapi@7.0.8:
+ version "7.0.8"
+ resolved "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz"
+ integrity sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==
+ dependencies:
+ call-me-maybe "^1.0.1"
+ node-fetch "^2.6.1"
+ node-fetch-h2 "^2.3.0"
+ node-readfiles "^0.2.0"
+ oas-kit-common "^1.0.8"
+ oas-resolver "^2.5.6"
+ oas-schema-walker "^1.1.5"
+ oas-validator "^5.0.8"
+ reftools "^1.1.9"
+ yaml "^1.10.0"
+ yargs "^17.0.1"
+
+tailwindcss@^3.4.4:
+ version "3.4.17"
+ resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz"
+ integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==
+ dependencies:
+ "@alloc/quick-lru" "^5.2.0"
+ arg "^5.0.2"
+ chokidar "^3.6.0"
+ didyoumean "^1.2.2"
+ dlv "^1.1.3"
+ fast-glob "^3.3.2"
+ glob-parent "^6.0.2"
+ is-glob "^4.0.3"
+ jiti "^1.21.6"
+ lilconfig "^3.1.3"
+ micromatch "^4.0.8"
+ normalize-path "^3.0.0"
+ object-hash "^3.0.0"
+ picocolors "^1.1.1"
+ postcss "^8.4.47"
+ postcss-import "^15.1.0"
+ postcss-js "^4.0.1"
+ postcss-load-config "^4.0.2"
+ postcss-nested "^6.2.0"
+ postcss-selector-parser "^6.1.2"
+ resolve "^1.22.8"
+ sucrase "^3.35.0"
+
+tapable@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz"
+ integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
+
+tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+terser-webpack-plugin@^5.3.11:
+ version "5.3.11"
+ resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz"
+ integrity sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jest-worker "^27.4.5"
+ schema-utils "^4.3.0"
+ serialize-javascript "^6.0.2"
+ terser "^5.31.1"
+
+terser-webpack-plugin@^5.3.9:
+ version "5.3.11"
+ resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz"
+ integrity sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jest-worker "^27.4.5"
+ schema-utils "^4.3.0"
+ serialize-javascript "^6.0.2"
+ terser "^5.31.1"
+
+terser@^5.10.0:
+ version "5.39.0"
+ resolved "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz"
+ integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+terser@^5.15.1, terser@^5.31.1:
+ version "5.39.0"
+ resolved "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz"
+ integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+thenify-all@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz"
+ integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
+ dependencies:
+ thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz"
+ integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
+ dependencies:
+ any-promise "^1.0.0"
+
+thunky@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz"
+ integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
+
+timers-browserify@^2.0.12:
+ version "2.0.12"
+ resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz"
+ integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==
+ dependencies:
+ setimmediate "^1.0.4"
+
+tiny-invariant@^1.0.2:
+ version "1.3.3"
+ resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz"
+ integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
+
+tiny-warning@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
+ integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
+to-fast-properties@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz"
+ integrity sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+token-types@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/token-types/-/token-types-2.1.1.tgz"
+ integrity sha512-wnQcqlreS6VjthyHO3Y/kpK/emflxDBNhlNUPfh7wE39KnuDdOituXomIbyI79vBtF0Ninpkh72mcuRHo+RG3Q==
+ dependencies:
+ "@tokenizer/token" "^0.1.1"
+ ieee754 "^1.2.1"
+
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
+trim-right@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz"
+ integrity sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==
+
+trough@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz"
+ integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
+
+truncate-utf8-bytes@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz"
+ integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==
+ dependencies:
+ utf8-byte-length "^1.0.1"
+
+ts-interface-checker@^0.1.9:
+ version "0.1.13"
+ resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
+ integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
+
+ts-node@^10.2.1, ts-node@>=10, ts-node@>=9.0.0:
+ version "10.9.2"
+ resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz"
+ integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
+ dependencies:
+ "@cspotcode/source-map-support" "^0.8.0"
+ "@tsconfig/node10" "^1.0.7"
+ "@tsconfig/node12" "^1.0.7"
+ "@tsconfig/node14" "^1.0.0"
+ "@tsconfig/node16" "^1.0.2"
+ acorn "^8.4.1"
+ acorn-walk "^8.1.1"
+ arg "^4.1.0"
+ create-require "^1.1.0"
+ diff "^4.0.1"
+ make-error "^1.1.1"
+ v8-compile-cache-lib "^3.0.1"
+ yn "3.1.1"
+
+tslib@^2.0.3, tslib@^2.4.0, tslib@^2.6.0:
+ version "2.8.1"
+ resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+tty-browserify@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz"
+ integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==
+
+type-fest@^0.21.3:
+ version "0.21.3"
+ resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
+ integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
+type-fest@^1.0.1:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz"
+ integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
+
+type-fest@^2.13.0, type-fest@^2.5.0:
+ version "2.19.0"
+ resolved "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz"
+ integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
+
+type-fest@^4.4.0:
+ version "4.35.0"
+ resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz"
+ integrity sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+ dependencies:
+ is-typedarray "^1.0.0"
+
+typescript@^5.2.2, "typescript@>= 2.7", typescript@>=2.7, typescript@>=4, typescript@>=4.9.5:
+ version "5.7.3"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
+ integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
+
+undici-types@~6.20.0:
+ version "6.20.0"
+ resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz"
+ integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
+
+undici@^6.19.5:
+ version "6.21.1"
+ resolved "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz"
+ integrity sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==
+
+unicode-canonical-property-names-ecmascript@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz"
+ integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==
+
+unicode-emoji-modifier-base@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz"
+ integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==
+
+unicode-match-property-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz"
+ integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==
+ dependencies:
+ unicode-canonical-property-names-ecmascript "^2.0.0"
+ unicode-property-aliases-ecmascript "^2.0.0"
+
+unicode-match-property-value-ecmascript@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz"
+ integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==
+
+unicode-property-aliases-ecmascript@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz"
+ integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
+
+unified@^10.0.0:
+ version "10.1.2"
+ resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz"
+ integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ bail "^2.0.0"
+ extend "^3.0.0"
+ is-buffer "^2.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^5.0.0"
+
+unified@^11.0.0, unified@^11.0.3, unified@^11.0.4:
+ version "11.0.5"
+ resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz"
+ integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ bail "^2.0.0"
+ devlop "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^6.0.0"
+
+unique-string@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz"
+ integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==
+ dependencies:
+ crypto-random-string "^4.0.0"
+
+unist-util-generated@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz"
+ integrity sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==
+
+unist-util-is@^5.0.0:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz"
+ integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-is@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz"
+ integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position-from-estree@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz"
+ integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz"
+ integrity sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz"
+ integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-stringify-position@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz"
+ integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+
+unist-util-stringify-position@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz"
+ integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-parents@^5.0.0:
+ version "5.1.3"
+ resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz"
+ integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+
+unist-util-visit-parents@^5.1.1:
+ version "5.1.3"
+ resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz"
+ integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+
+unist-util-visit-parents@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz"
+ integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-visit@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz"
+ integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^5.0.0"
+ unist-util-visit-parents "^5.1.1"
+
+unist-util-visit@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz"
+ integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
+universalify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
+unpipe@~1.0.0, unpipe@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+update-browserslist-db@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz"
+ integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
+update-notifier@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz"
+ integrity sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==
+ dependencies:
+ boxen "^7.0.0"
+ chalk "^5.0.1"
+ configstore "^6.0.0"
+ has-yarn "^3.0.0"
+ import-lazy "^4.0.0"
+ is-ci "^3.0.1"
+ is-installed-globally "^0.4.0"
+ is-npm "^6.0.0"
+ is-yarn-global "^0.4.0"
+ latest-version "^7.0.0"
+ pupa "^3.1.0"
+ semver "^7.3.7"
+ semver-diff "^4.0.0"
+ xdg-basedir "^5.1.0"
+
+uri-js-replace@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz"
+ integrity sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+url-loader@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz"
+ integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
+ dependencies:
+ loader-utils "^2.0.0"
+ mime-types "^2.1.27"
+ schema-utils "^3.0.0"
+
+url@^0.11.0, url@^0.11.3:
+ version "0.11.4"
+ resolved "https://registry.npmjs.org/url/-/url-0.11.4.tgz"
+ integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==
+ dependencies:
+ punycode "^1.4.1"
+ qs "^6.12.3"
+
+use-editable@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.npmjs.org/use-editable/-/use-editable-2.3.3.tgz"
+ integrity sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA==
+
+utf8-byte-length@^1.0.1:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz"
+ integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==
+
+util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+util@^0.10.3:
+ version "0.10.4"
+ resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz"
+ integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
+ dependencies:
+ inherits "2.0.3"
+
+util@^0.12.4, util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.npmjs.org/util/-/util-0.12.5.tgz"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
+utila@~0.4:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz"
+ integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==
+
+utility-types@^3.10.0:
+ version "3.11.0"
+ resolved "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz"
+ integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+uuid@^8.3.2, uuid@8.3.2:
+ version "8.3.2"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
+uvu@^0.5.0:
+ version "0.5.6"
+ resolved "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz"
+ integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==
+ dependencies:
+ dequal "^2.0.0"
+ diff "^5.0.0"
+ kleur "^4.0.3"
+ sade "^1.7.3"
+
+v8-compile-cache-lib@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
+ integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
+
+validate-peer-dependencies@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/validate-peer-dependencies/-/validate-peer-dependencies-2.2.0.tgz"
+ integrity sha512-8X1OWlERjiUY6P6tdeU9E0EwO8RA3bahoOVG7ulOZT5MqgNDUO/BQoVjYiHPcNe+v8glsboZRIw9iToMAA2zAA==
+ dependencies:
+ resolve-package-path "^4.0.3"
+ semver "^7.3.8"
+
+validate.io-array@^1.0.3:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz"
+ integrity sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==
+
+validate.io-function@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz"
+ integrity sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==
+
+validate.io-integer-array@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz"
+ integrity sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==
+ dependencies:
+ validate.io-array "^1.0.3"
+ validate.io-integer "^1.0.4"
+
+validate.io-integer@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz"
+ integrity sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==
+ dependencies:
+ validate.io-number "^1.0.3"
+
+validate.io-number@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz"
+ integrity sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==
+
+value-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz"
+ integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+vfile-location@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz"
+ integrity sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ vfile "^5.0.0"
+
+vfile-location@^5.0.0:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz"
+ integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile "^6.0.0"
+
+vfile-message@^3.0.0:
+ version "3.1.4"
+ resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz"
+ integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-stringify-position "^3.0.0"
+
+vfile-message@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz"
+ integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+vfile@^5.0.0:
+ version "5.3.7"
+ resolved "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz"
+ integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ is-buffer "^2.0.0"
+ unist-util-stringify-position "^3.0.0"
+ vfile-message "^3.0.0"
+
+vfile@^6.0.0, vfile@^6.0.1:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz"
+ integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile-message "^4.0.0"
+
+vm-browserify@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz"
+ integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+
+warning@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
+watchpack@^2.4.1:
+ version "2.4.2"
+ resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz"
+ integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz"
+ integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+web-namespaces@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz"
+ integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
+
+web-vitals@^4.2.0:
+ version "4.2.4"
+ resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz"
+ integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+webpack-bundle-analyzer@^4.10.2:
+ version "4.10.2"
+ resolved "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz"
+ integrity sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==
+ dependencies:
+ "@discoveryjs/json-ext" "0.5.7"
+ acorn "^8.0.4"
+ acorn-walk "^8.0.0"
+ commander "^7.2.0"
+ debounce "^1.2.1"
+ escape-string-regexp "^4.0.0"
+ gzip-size "^6.0.0"
+ html-escaper "^2.0.2"
+ opener "^1.5.2"
+ picocolors "^1.0.0"
+ sirv "^2.0.3"
+ ws "^7.3.1"
+
+webpack-dev-middleware@^5.3.4:
+ version "5.3.4"
+ resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz"
+ integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==
+ dependencies:
+ colorette "^2.0.10"
+ memfs "^3.4.3"
+ mime-types "^2.1.31"
+ range-parser "^1.2.1"
+ schema-utils "^4.0.0"
+
+webpack-dev-server@^4.15.2:
+ version "4.15.2"
+ resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz"
+ integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==
+ dependencies:
+ "@types/bonjour" "^3.5.9"
+ "@types/connect-history-api-fallback" "^1.3.5"
+ "@types/express" "^4.17.13"
+ "@types/serve-index" "^1.9.1"
+ "@types/serve-static" "^1.13.10"
+ "@types/sockjs" "^0.3.33"
+ "@types/ws" "^8.5.5"
+ ansi-html-community "^0.0.8"
+ bonjour-service "^1.0.11"
+ chokidar "^3.5.3"
+ colorette "^2.0.10"
+ compression "^1.7.4"
+ connect-history-api-fallback "^2.0.0"
+ default-gateway "^6.0.3"
+ express "^4.17.3"
+ graceful-fs "^4.2.6"
+ html-entities "^2.3.2"
+ http-proxy-middleware "^2.0.3"
+ ipaddr.js "^2.0.1"
+ launch-editor "^2.6.0"
+ open "^8.0.9"
+ p-retry "^4.5.0"
+ rimraf "^3.0.2"
+ schema-utils "^4.0.0"
+ selfsigned "^2.1.1"
+ serve-index "^1.9.1"
+ sockjs "^0.3.24"
+ spdy "^4.0.2"
+ webpack-dev-middleware "^5.3.4"
+ ws "^8.13.0"
+
+webpack-merge@^5.9.0:
+ version "5.10.0"
+ resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz"
+ integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==
+ dependencies:
+ clone-deep "^4.0.1"
+ flat "^5.0.2"
+ wildcard "^2.0.0"
+
+webpack-merge@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz"
+ integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==
+ dependencies:
+ clone-deep "^4.0.1"
+ flat "^5.0.2"
+ wildcard "^2.0.1"
+
+webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.61.0, webpack@^5.88.1, webpack@^5.95.0, "webpack@>= 4", "webpack@>=4.41.1 || 5.x", webpack@>=5, "webpack@3 || 4 || 5":
+ version "5.98.0"
+ resolved "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz"
+ integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==
+ dependencies:
+ "@types/eslint-scope" "^3.7.7"
+ "@types/estree" "^1.0.6"
+ "@webassemblyjs/ast" "^1.14.1"
+ "@webassemblyjs/wasm-edit" "^1.14.1"
+ "@webassemblyjs/wasm-parser" "^1.14.1"
+ acorn "^8.14.0"
+ browserslist "^4.24.0"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.17.1"
+ es-module-lexer "^1.2.1"
+ eslint-scope "5.1.1"
+ events "^3.2.0"
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.2.11"
+ json-parse-even-better-errors "^2.3.1"
+ loader-runner "^4.2.0"
+ mime-types "^2.1.27"
+ neo-async "^2.6.2"
+ schema-utils "^4.3.0"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.3.11"
+ watchpack "^2.4.1"
+ webpack-sources "^3.2.3"
+
+webpackbar@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz"
+ integrity sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==
+ dependencies:
+ ansi-escapes "^4.3.2"
+ chalk "^4.1.2"
+ consola "^3.2.3"
+ figures "^3.2.0"
+ markdown-table "^2.0.0"
+ pretty-time "^1.1.0"
+ std-env "^3.7.0"
+ wrap-ansi "^7.0.0"
+
+websocket-driver@^0.7.4, websocket-driver@>=0.5.1:
+ version "0.7.4"
+ resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
+whatwg-encoding@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz"
+ integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==
+ dependencies:
+ iconv-lite "0.6.3"
+
+whatwg-mimetype@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz"
+ integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+which-typed-array@^1.1.16, which-typed-array@^1.1.2:
+ version "1.1.18"
+ resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz"
+ integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ for-each "^0.3.3"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+
+which@^1.2.9, which@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+widest-line@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz"
+ integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
+ dependencies:
+ string-width "^5.0.1"
+
+wildcard@^2.0.0, wildcard@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz"
+ integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==
+
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+write-file-atomic@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz"
+ integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+ dependencies:
+ imurmurhash "^0.1.4"
+ is-typedarray "^1.0.0"
+ signal-exit "^3.0.2"
+ typedarray-to-buffer "^3.1.5"
+
+ws@^7.3.1:
+ version "7.5.10"
+ resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
+ integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
+
+ws@^8.13.0:
+ version "8.18.0"
+ resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz"
+ integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
+
+xdg-basedir@^5.0.1, xdg-basedir@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz"
+ integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==
+
+xml-formatter@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.npmjs.org/xml-formatter/-/xml-formatter-2.6.1.tgz"
+ integrity sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==
+ dependencies:
+ xml-parser-xo "^3.2.0"
+
+xml-js@^1.6.11:
+ version "1.6.11"
+ resolved "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz"
+ integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
+ dependencies:
+ sax "^1.2.4"
+
+xml-parser-xo@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-3.2.0.tgz"
+ integrity sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==
+
+xtend@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz"
+ integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yaml-ast-parser@0.0.43:
+ version "0.0.43"
+ resolved "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz"
+ integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==
+
+yaml@^1.10.0, yaml@^1.7.2, yaml@1.10.2:
+ version "1.10.2"
+ resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+
+yaml@^2.3.4:
+ version "2.7.0"
+ resolved "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz"
+ integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.0.1:
+ version "17.7.2"
+ resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
+yn@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
+ integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+yocto-queue@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz"
+ integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
+
+zwitch@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
diff --git a/langflow/eslint.config.js b/langflow/eslint.config.js
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/pyproject.toml b/langflow/pyproject.toml
new file mode 100644
index 0000000..0c01510
--- /dev/null
+++ b/langflow/pyproject.toml
@@ -0,0 +1,309 @@
+
+[project]
+name = "langflow"
+version = "1.2.0"
+description = "A Python package with a built-in web application"
+requires-python = ">=3.10,<3.14"
+license = "MIT"
+keywords = ["nlp", "langchain", "openai", "gpt", "gui"]
+readme = "README.md"
+maintainers = [
+ { name = "Carlos Coelho", email = "carlos@langflow.org" },
+ { name = "Cristhian Zanforlin", email = "cristhian.lousa@gmail.com" },
+ { name = "Gabriel Almeida", email = "gabriel@langflow.org" },
+ { name = "Igor Carvalho", email = "igorr.ackerman@gmail.com" },
+ { name = "Lucas Eduoli", email = "lucaseduoli@gmail.com" },
+ { name = "Otávio Anovazzi", email = "otavio2204@gmail.com" },
+ { name = "Rodrigo Nader", email = "rodrigo@langflow.org" },
+ { name = "Italo dos Anjos", email = "italojohnnydosanjos@gmail.com" },
+]
+# Define your main dependencies here
+dependencies = [
+ "langflow-base==0.2.0",
+ "beautifulsoup4==4.12.3",
+ "google-search-results>=2.4.1,<3.0.0",
+ "google-api-python-client==2.154.0",
+ "huggingface-hub[inference]>=0.23.2,<1.0.0",
+ "networkx==3.4.2",
+ "fake-useragent==1.5.1",
+ "pyarrow==19.0.0",
+ "wikipedia==1.4.0",
+ "qdrant-client==1.9.2",
+ "weaviate-client==4.10.2",
+ "faiss-cpu==1.9.0.post1",
+ "types-cachetools==5.5.0.20240820",
+ "pymongo==4.10.1",
+ "supabase==2.6.0",
+ "certifi>=2023.11.17,<2025.0.0",
+ "certifi==2024.8.30",
+ "fastavro==1.9.7",
+ "redis==5.2.1",
+ "metaphor-python==0.1.23",
+ 'pywin32==307; sys_platform == "win32"',
+ "langfuse==2.53.9",
+ "metal_sdk==2.5.1",
+ "MarkupSafe==3.0.2",
+ "boto3==1.34.162",
+ "numexpr==2.10.2",
+ "qianfan==0.3.5",
+ "pgvector==0.3.6",
+ "langchain==0.3.10",
+ "elasticsearch==8.16.0",
+ "pytube==15.0.0",
+ "dspy-ai==2.5.41",
+ "assemblyai==0.35.1",
+ "litellm==1.60.2",
+ "chromadb==0.5.23",
+ "zep-python==2.0.2",
+ "youtube-transcript-api==0.6.3",
+ "Markdown==3.7",
+ "upstash-vector==0.6.0",
+ "GitPython==3.1.43",
+ "kubernetes==31.0.0",
+ "json_repair==0.30.3",
+ "langwatch==0.1.16",
+ "langsmith==0.1.147",
+ "yfinance==0.2.50",
+ "wolframalpha==5.1.3",
+ "astra-assistants[tools]~=2.2.9",
+ "composio-langchain==0.7.1",
+ "composio-core==0.7.1",
+ "spider-client==0.1.24",
+ "nltk==3.9.1",
+ "lark==1.2.2",
+ "jq==1.8.0",
+ "pydantic-settings==2.4.0",
+ "ragstack-ai-knowledge-store==0.2.1",
+ "duckduckgo_search==7.2.1",
+ "opensearch-py==2.8.0",
+ "langchain-google-genai==2.0.6",
+ "langchain-cohere==0.3.3",
+ "langchain-anthropic==0.3.0",
+ "langchain-astradb==0.5.2",
+ "langchain-openai==0.2.12",
+ "langchain-google-vertexai==2.0.7",
+ "langchain-groq==0.2.1",
+ "langchain-pinecone==0.2.2",
+ "langchain-mistralai==0.2.3",
+ "langchain-chroma==0.1.4",
+ "langchain-aws==0.2.7",
+ "langchain-unstructured==0.1.5",
+ "langchain-milvus==0.1.7",
+ "langchain-mongodb==0.2.0",
+ "langchain-nvidia-ai-endpoints==0.3.8",
+ "langchain-google-calendar-tools==0.0.1",
+ "langchain-google-community==2.0.3",
+ "langchain-elasticsearch==0.3.0",
+ "langchain-ollama==0.2.1",
+ "langchain-sambanova==0.1.0",
+ "langchain-community~=0.3.10",
+ "sqlalchemy[aiosqlite]>=2.0.38,<3.0.0",
+ "atlassian-python-api==3.41.16",
+ "mem0ai==0.1.34",
+ "needle-python>=0.4.0",
+ "aiofile>=3.9.0,<4.0.0",
+ "sseclient-py==1.8.0",
+ "arize-phoenix-otel>=0.6.1",
+ "openinference-instrumentation-langchain>=0.1.29",
+ "crewai==0.102.0",
+ "mcp>=0.9.1",
+ "uv>=0.5.7",
+ "ag2>=0.1.0",
+ "scrapegraph-py>=1.12.0",
+ "pydantic-ai>=0.0.19",
+ "smolagents>=1.8.0",
+ "apify-client>=1.8.1",
+ "pylint>=3.3.4",
+ "ruff>=0.9.7",
+]
+
+[dependency-groups]
+dev = [
+ "pytest-instafail>=0.5.0",
+ "types-redis>=4.6.0.5",
+ "ipykernel>=6.29.0",
+ "mypy>=1.11.0",
+ "ruff>=0.9.7,<0.10",
+ "httpx>=0.27.0",
+ "pytest>=8.2.0",
+ "types-requests>=2.32.0",
+ "requests>=2.32.0",
+ "pytest-cov>=5.0.0",
+ "pandas-stubs>=2.1.4.231227",
+ "types-pillow>=10.2.0.20240213",
+ "types-pyyaml>=6.0.12.8",
+ "types-python-jose>=3.3.4.8",
+ "types-passlib>=1.7.7.13",
+ "pytest-mock>=3.14.0",
+ "pytest-xdist>=3.6.0",
+ "types-pywin32>=306.0.0.4",
+ "types-google-cloud-ndb>=2.2.0.0",
+ "pytest-sugar>=1.0.0",
+ "respx>=0.21.1",
+ "pytest-asyncio>=0.23.0",
+ "pytest-profiling>=1.7.0",
+ "pre-commit>=3.7.0",
+ "vulture>=2.11",
+ "dictdiffer>=0.9.0",
+ "pytest-split>=0.9.0",
+ "pytest-flakefinder>=1.1.0",
+ "types-markdown>=3.7.0.20240822",
+ "packaging>=24.1,<25.0",
+ "asgi-lifespan>=2.1.0",
+ "pytest-github-actions-annotate-failures>=0.2.0",
+ "pytest-codspeed>=3.0.0",
+ "blockbuster>=1.5.20,<1.6",
+ "types-aiofiles>=24.1.0.20240626",
+ "codeflash>=0.8.4",
+ "hypothesis>=6.123.17",
+ "locust>=2.32.9",
+ "pytest-rerunfailures>=15.0",
+]
+
+[tool.uv.sources]
+langflow-base = { workspace = true }
+langflow = { workspace = true }
+
+[tool.uv.workspace]
+members = ["src/backend/base", "."]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/backend/langflow"]
+
+
+[project.urls]
+Repository = "https://github.com/langflow-ai/langflow"
+Documentation = "https://docs.langflow.org"
+
+[project.optional-dependencies]
+deploy = [
+ "celery[redis]>=5.3.6",
+ "flower>=2.0.0"
+]
+couchbase = [
+ "couchbase>=4.2.1"
+]
+cassio = [
+ "cassio>=0.1.7"
+]
+local = [
+ "llama-cpp-python~=0.2.0",
+ "sentence-transformers>=2.3.1",
+ "ctransformers>=0.2.10"
+]
+clickhouse-connect = [
+ "clickhouse-connect==0.7.19"
+]
+
+nv-ingest = [
+ # nv-ingest-client 2025.2.7.dev0 does not correctly install its
+ # dependencies, so we need to install some manually.
+ "nv-ingest-client==2025.2.7.dev0",
+ "python-pptx==0.6.23",
+ "pymilvus[bulk_writer,model]==2.5.0",
+ "llama-index-embeddings-nvidia==0.1.5",
+]
+
+postgresql = [
+ "sqlalchemy[postgresql_psycopg2binary]",
+ "sqlalchemy[postgresql_psycopg]",
+
+]
+
+[project.scripts]
+langflow = "langflow.__main__:main"
+
+[tool.codespell]
+skip = '.git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,*.trig.,**/node_modules/**,./stuff/*,*.csv'
+# Ignore latin etc
+ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
+
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+testpaths = ["tests", "integration"]
+console_output_style = "progress"
+filterwarnings = ["ignore::DeprecationWarning", "ignore::ResourceWarning"]
+log_cli = true
+log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
+log_cli_date_format = "%Y-%m-%d %H:%M:%S"
+markers = ["async_test", "api_key_required"]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
+
+[tool.coverage.run]
+command_line = """
+ -m pytest --ignore=tests/integration
+ --cov --cov-report=term --cov-report=html
+ --instafail -ra -n auto -m "not api_key_required"
+"""
+source = ["src/backend/base/langflow/"]
+omit = ["*/alembic/*", "tests/*", "*/__init__.py"]
+
+
+[tool.coverage.report]
+sort = "Stmts"
+skip_empty = true
+show_missing = false
+ignore_errors = true
+
+
+[tool.coverage.html]
+directory = "coverage"
+
+
+[tool.ruff]
+exclude = ["src/backend/base/langflow/alembic/*"]
+line-length = 120
+
+[tool.ruff.lint]
+pydocstyle.convention = "google"
+select = ["ALL"]
+ignore = [
+ "C90", # McCabe complexity
+ "CPY", # Missing copyright
+ "COM812", # Messes with the formatter
+ "ERA", # Eradicate commented-out code
+ "FIX002", # Line contains TODO
+ "ISC001", # Messes with the formatter
+ "PERF203", # Rarely useful
+ "PLR09", # Too many something (arg, statements, etc)
+ "RUF012", # Pydantic models are currently not well detected. See https://github.com/astral-sh/ruff/issues/13630
+ "TD002", # Missing author in TODO
+ "TD003", # Missing issue link in TODO
+ "TRY301", # A bit too harsh (Abstract `raise` to an inner function)
+
+ # Rules that are TODOs
+ "ANN",
+]
+
+# Preview rules that are not yet activated
+external = ["RUF027"]
+
+[tool.ruff.lint.per-file-ignores]
+"scripts/*" = [
+ "D1",
+ "INP",
+ "T201",
+]
+"src/backend/tests/*" = [
+ "D1",
+ "PLR2004",
+ "S101",
+ "SLF001",
+]
+
+[tool.ruff.lint.flake8-builtins]
+builtins-allowed-modules = [ "io", "logging", "socket"]
+
+[tool.mypy]
+plugins = ["pydantic.mypy"]
+follow_imports = "skip"
+disable_error_code = ["type-var"]
+namespace_packages = true
+mypy_path = "langflow"
+ignore_missing_imports = true
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
diff --git a/langflow/render.yaml b/langflow/render.yaml
new file mode 100644
index 0000000..7f0cc79
--- /dev/null
+++ b/langflow/render.yaml
@@ -0,0 +1,24 @@
+services:
+ # A Docker web service
+ - type: web
+ name: langflow
+ runtime: docker
+ dockerfilePath: ./docker/render.Dockerfile
+ repo: https://github.com/langflow-ai/langflow
+ branch: main
+ plan: standard
+ healthCheckPath: /health_check
+ autoDeploy: false
+ envVars:
+ - key: LANGFLOW_DATABASE_URL
+ value: sqlite:////app/data/.cache/langflow/langflow.db
+ - key: LANGFLOW_HOST
+ value: 0.0.0.0
+ - key: LANGFLOW_PORT
+ # default render port https://docs.render.com/web-services#port-binding
+ value: 10000
+ - key: LANGFLOW_LOG_LEVEL
+ value: INFO
+ disk:
+ name: langflow-data
+ mountPath: /app/data
diff --git a/langflow/scripts/__init__.py b/langflow/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/scripts/aws/.env.example b/langflow/scripts/aws/.env.example
new file mode 100644
index 0000000..4778f7c
--- /dev/null
+++ b/langflow/scripts/aws/.env.example
@@ -0,0 +1,11 @@
+# Description: Example of .env file
+# Usage: Copy this file to .env and change the values
+# according to your needs
+# Do not commit .env file to git
+# Do not change .env.example file
+# You can set up a superuser's username and password
+# If there is no need for user management, set LANGFLOW_AUTO_LOGIN=true and delete LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD.
+
+LANGFLOW_AUTO_LOGIN=false
+LANGFLOW_SUPERUSER=admin
+LANGFLOW_SUPERUSER_PASSWORD=123456
\ No newline at end of file
diff --git a/langflow/scripts/aws/.gitignore b/langflow/scripts/aws/.gitignore
new file mode 100644
index 0000000..1a7a152
--- /dev/null
+++ b/langflow/scripts/aws/.gitignore
@@ -0,0 +1,9 @@
+*.js
+!jest.config.js
+*.d.ts
+node_modules
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
+!/lib
diff --git a/langflow/scripts/aws/.npmignore b/langflow/scripts/aws/.npmignore
new file mode 100644
index 0000000..c1d6d45
--- /dev/null
+++ b/langflow/scripts/aws/.npmignore
@@ -0,0 +1,6 @@
+*.ts
+!*.d.ts
+
+# CDK asset staging directory
+.cdk.staging
+cdk.out
diff --git a/langflow/scripts/aws/README.ja.md b/langflow/scripts/aws/README.ja.md
new file mode 100644
index 0000000..86e8699
--- /dev/null
+++ b/langflow/scripts/aws/README.ja.md
@@ -0,0 +1,57 @@
+# Langflow on AWS
+
+**想定時間**: 30 分
+
+## 説明
+
+Langflow on AWS では、 [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK) を用いて Langflow を AWS 上にデプロイする方法を学べます。
+このチュートリアルは、AWS アカウントと AWS に関する基本的な知識を有していることを前提としています。
+
+作成するアプリケーションのアーキテクチャです。
+
+AWS CDK によって Langflow のアプリケーションをデプロイします。アプリケーションは [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls) を介して配信されます。CloudFront は 2 つのオリジンを有しています。1 つ目は静的な Web サイトを配信するための [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3)、2 つ目は バックエンドと通信するための [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) です。ALB の背後には FastAPI が動作する [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a) 、データベースの [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a) が作成されます。
+Fargate は [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR) に保存された Docker イメージを使用します。
+Aurora のシークレットは [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a) によって管理されます。
+
+# 環境構築とデプロイ方法
+
+1. [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1)を開きます。
+
+1. 以下のコマンドを実行します。
+
+ ```shell
+ git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
+ cd cloud9-setup-for-prototyping
+ ./bin/bootstrap
+ ```
+
+1. `Done!` と表示されたら [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/) から `cloud9-for-prototyping` を開きます。
+ 
+
+1. 以下のコマンドを実行します。
+ ```shell
+ git clone https://github.com/langflow-ai/langflow.git
+ cd langflow/scripts/aws
+ cp .env.example .env # 環境設定を変える場合はこのファイル(.env)を編集してください。
+ npm ci
+ cdk bootstrap
+ cdk deploy
+ ```
+1. 表示される URL にアクセスします。
+ ```shell
+ Outputs:
+ LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
+ ```
+1. サインイン画面でユーザー名とパスワードを入力します。`.env`ファイルでユーザー名とパスワードを設定していない場合、ユーザー名は`admin`、パスワードは`123456`で設定されます。
+ 
+
+# 環境の削除
+
+1. `Cloud9` で以下のコマンドを実行します。
+
+ ```shell
+ bash delete-resources.sh
+ ```
+
+1. [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started)を開き、`aws-cloud9-cloud9-for-prototyping-XXXX` を選択して削除します。
+ 
diff --git a/langflow/scripts/aws/README.md b/langflow/scripts/aws/README.md
new file mode 100644
index 0000000..da56174
--- /dev/null
+++ b/langflow/scripts/aws/README.md
@@ -0,0 +1,53 @@
+# Deploy Langflow on AWS
+
+**Duration**: 30 minutes
+
+## Introduction
+
+In this tutorial, you will learn how to deploy langflow on AWS using [AWS Cloud Development Kit](https://aws.amazon.com/cdk/?nc2=type_a) (CDK).
+This tutorial assumes you have an AWS account and basic knowledge of AWS.
+
+The architecture of the application to be created:
+
+Langflow is deployed using AWS CDK. The application is distributed via [Amazon CloudFront](https://aws.amazon.com/cloudfront/?nc1=h_ls), which has two origins: the first is [Amazon Simple Storage Service](https://aws.amazon.com/s3/?nc1=h_ls) (S3) for serving a static website, and the second is an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/?nc1=h_ls) (ALB) for communicating with the backend. [AWS Fargate](https://aws.amazon.com/fargate/?nc2=type_a), where FastAPI runs and [Amazon Aurora](https://aws.amazon.com/rds/aurora/?nc2=type_a), the database, are created behind the ALB.
+Fargate uses a Docker image stored in [Amazon Elastic Container Registry](https://aws.amazon.com/ecr/?nc1=h_ls) (ECR).
+Aurora's secret is managed by [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/?nc2=type_a).
+
+# How to set up your environment and deploy langflow
+
+1. Open [AWS CloudShell](https://us-east-1.console.aws.amazon.com/cloudshell/home?region=us-east-1).
+1. Run the following commands in Cloudshell:
+ ```shell
+ git clone https://github.com/aws-samples/cloud9-setup-for-prototyping
+ cd cloud9-setup-for-prototyping
+ cat params.json | jq '.name |= "c9-for-langflow"'
+ ./bin/bootstrap
+ ```
+1. When you see `Done!` in Cloudshell, open `c9-for-langflow` from [AWS Cloud9](https://us-east-1.console.aws.amazon.com/cloud9control/home?region=us-east-1#/).
+ 
+1. Run the following command in the Cloud9 terminal.
+ ```shell
+ git clone https://github.com/langflow-ai/langflow.git
+ cd langflow/scripts/aws
+ cp .env.example .env # Edit this file if you need environment settings
+ npm ci
+ cdk bootstrap
+ cdk deploy
+ ```
+1. Access the URL displayed.
+ ```shell
+ Outputs:
+ LangflowAppStack.frontendURLXXXXXX = https://XXXXXXXXXXX.cloudfront.net
+ ```
+1. Enter your user name and password to sign in. If you have not set a user name and password in your `.env` file, the user name will be set to `admin` and the password to `123456`.
+ 
+
+# Cleanup
+
+1. Run the following command in the Cloud9 terminal.
+ ```shell
+ bash delete-resources.sh
+ ```
+1. Open [AWS CloudFormation](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/getting-started), select `aws-cloud9-c9-for-langflow-XXXX` and delete it.
+ 
+ s
diff --git a/langflow/scripts/aws/bin/cdk.ts b/langflow/scripts/aws/bin/cdk.ts
new file mode 100644
index 0000000..82b96f6
--- /dev/null
+++ b/langflow/scripts/aws/bin/cdk.ts
@@ -0,0 +1,22 @@
+#!/usr/bin/env node
+import 'source-map-support/register';
+import * as cdk from 'aws-cdk-lib';
+import { LangflowAppStack } from '../lib/cdk-stack';
+
+const app = new cdk.App();
+
+new LangflowAppStack(app, 'LangflowAppStack', {
+ /* If you don't specify 'env', this stack will be environment-agnostic.
+ * Account/Region-dependent features and context lookups will not work,
+ * but a single synthesized template can be deployed anywhere. */
+
+ /* Uncomment the next line to specialize this stack for the AWS Account
+ * and Region that are implied by the current CLI configuration. */
+ // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
+
+ /* Uncomment the next line if you know exactly what Account and Region you
+ * want to deploy the stack to. */
+ // env: { account: '123456789012', region: 'us-east-1' },
+
+ /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
+});
\ No newline at end of file
diff --git a/langflow/scripts/aws/cdk.json b/langflow/scripts/aws/cdk.json
new file mode 100644
index 0000000..6527707
--- /dev/null
+++ b/langflow/scripts/aws/cdk.json
@@ -0,0 +1,57 @@
+{
+ "app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
+ "watch": {
+ "include": [
+ "**"
+ ],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "**/*.d.ts",
+ "**/*.js",
+ "tsconfig.json",
+ "package*.json",
+ "yarn.lock",
+ "node_modules",
+ "test"
+ ]
+ },
+ "context": {
+ "ragEnabled": false,
+ "kendraIndexArn": null,
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
+ "@aws-cdk/core:checkSecretUsage": true,
+ "@aws-cdk/core:target-partitions": [
+ "aws",
+ "aws-cn"
+ ],
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
+ "@aws-cdk/aws-iam:minimizePolicies": true,
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
+ "@aws-cdk/core:enablePartitionLiterals": true,
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
+ "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
+ "@aws-cdk/aws-redshift:columnId": true,
+ "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
+ "@aws-cdk/aws-kms:aliasNameRef": true,
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true
+ }
+}
diff --git a/langflow/scripts/aws/delete-docker-images.sh b/langflow/scripts/aws/delete-docker-images.sh
new file mode 100644
index 0000000..1e15950
--- /dev/null
+++ b/langflow/scripts/aws/delete-docker-images.sh
@@ -0,0 +1,3 @@
+docker stop $(docker ps -aq)
+docker rm $(docker ps -aq)
+docker rmi -f $(docker images -aq)
\ No newline at end of file
diff --git a/langflow/scripts/aws/delete-resources.sh b/langflow/scripts/aws/delete-resources.sh
new file mode 100644
index 0000000..bbe1a62
--- /dev/null
+++ b/langflow/scripts/aws/delete-resources.sh
@@ -0,0 +1,4 @@
+# aws cloudformation delete-stack --stack-name LangflowAppStack
+aws ecr delete-repository --repository-name langflow-backend-repository --force
+# aws ecr delete-repository --repository-name langflow-frontend-repository --force
+# aws ecr describe-repositories --output json | jq -re ".repositories[].repositoryName"
\ No newline at end of file
diff --git a/langflow/scripts/aws/img/langflow-archi.png b/langflow/scripts/aws/img/langflow-archi.png
new file mode 100644
index 0000000..9067833
Binary files /dev/null and b/langflow/scripts/aws/img/langflow-archi.png differ
diff --git a/langflow/scripts/aws/img/langflow-cfn.png b/langflow/scripts/aws/img/langflow-cfn.png
new file mode 100644
index 0000000..fecdeb7
Binary files /dev/null and b/langflow/scripts/aws/img/langflow-cfn.png differ
diff --git a/langflow/scripts/aws/img/langflow-cloud9-en.png b/langflow/scripts/aws/img/langflow-cloud9-en.png
new file mode 100644
index 0000000..a0679de
Binary files /dev/null and b/langflow/scripts/aws/img/langflow-cloud9-en.png differ
diff --git a/langflow/scripts/aws/img/langflow-signin.png b/langflow/scripts/aws/img/langflow-signin.png
new file mode 100644
index 0000000..3a8691a
Binary files /dev/null and b/langflow/scripts/aws/img/langflow-signin.png differ
diff --git a/langflow/scripts/aws/jest.config.js b/langflow/scripts/aws/jest.config.js
new file mode 100644
index 0000000..08263b8
--- /dev/null
+++ b/langflow/scripts/aws/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ testEnvironment: 'node',
+ roots: ['/test'],
+ testMatch: ['**/*.test.ts'],
+ transform: {
+ '^.+\\.tsx?$': 'ts-jest'
+ }
+};
diff --git a/langflow/scripts/aws/lib/cdk-stack.ts b/langflow/scripts/aws/lib/cdk-stack.ts
new file mode 100644
index 0000000..9706231
--- /dev/null
+++ b/langflow/scripts/aws/lib/cdk-stack.ts
@@ -0,0 +1,70 @@
+import * as cdk from 'aws-cdk-lib';
+import { Construct } from 'constructs';
+import * as ecs from 'aws-cdk-lib/aws-ecs'
+
+import { Network, EcrRepository, Web, BackEndCluster, Rds, EcsIAM, Rag} from './construct';
+// import * as sqs from 'aws-cdk-lib/aws-sqs';
+
+const errorMessageForBooleanContext = (key: string) => {
+ return `There was an error setting $ {key}. Possible causes are as follows.
+ - Trying to set it with the -c option instead of changing cdk.json
+ - cdk.json is set to a value that is not a boolean (e.g. “true” double quotes are not required)
+ - no items in cdk.json (unset) `;
+};
+
+
+export class LangflowAppStack extends cdk.Stack {
+ constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+ super(scope, id, props);
+ // Kendra Enable
+ const ragEnabled: boolean = this.node.tryGetContext('ragEnabled')!;
+ if (typeof ragEnabled !== 'boolean') {
+ throw new Error(errorMessageForBooleanContext('ragEnabled'));
+ }
+ if (ragEnabled) {
+ new Rag(this, 'Rag', {
+ });
+ }
+
+ // Arch
+ const arch = ecs.CpuArchitecture.X86_64
+
+ // VPC
+ const { vpc, cluster, ecsBackSG, dbSG, backendLogGroup, alb, albTG, albSG} = new Network(this, 'Network')
+
+ // ECR
+ const { ecrBackEndRepository } = new EcrRepository(this, 'Ecr', {
+ arch:arch
+ })
+
+ // RDS
+ // VPCとSGのリソース情報をPropsとして引き渡す
+ const { rdsCluster } = new Rds(this, 'Rds', { vpc, dbSG })
+
+ // IAM
+ const { backendTaskRole, backendTaskExecutionRole } = new EcsIAM(this, 'EcsIAM',{
+ rdsCluster:rdsCluster
+ })
+
+ const backendService = new BackEndCluster(this, 'backend', {
+ cluster:cluster,
+ ecsBackSG:ecsBackSG,
+ ecrBackEndRepository:ecrBackEndRepository,
+ backendTaskRole:backendTaskRole,
+ backendTaskExecutionRole:backendTaskExecutionRole,
+ backendLogGroup:backendLogGroup,
+ rdsCluster:rdsCluster,
+ arch:arch,
+ albTG:albTG
+ })
+ backendService.node.addDependency(rdsCluster);
+
+ const frontendService = new Web(this, 'frontend',{
+ cluster:cluster,
+ alb:alb,
+ albSG:albSG
+ })
+ frontendService.node.addDependency(backendService);
+
+ }
+}
diff --git a/langflow/scripts/aws/lib/construct/backend.ts b/langflow/scripts/aws/lib/construct/backend.ts
new file mode 100644
index 0000000..cba31f9
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/backend.ts
@@ -0,0 +1,90 @@
+import { Duration } from 'aws-cdk-lib'
+import { Construct } from 'constructs';
+import {
+ aws_ec2 as ec2,
+ aws_ecs as ecs,
+ aws_ecr as ecr,
+ aws_rds as rds,
+ aws_servicediscovery as servicediscovery,
+ aws_iam as iam,
+ aws_logs as logs,
+ aws_elasticloadbalancingv2 as elb,
+} from 'aws-cdk-lib';
+import * as dotenv from 'dotenv';
+const path = require('path');
+dotenv.config({path: path.join(__dirname, "../../.env")});
+
+interface BackEndProps {
+ cluster: ecs.Cluster
+ ecsBackSG:ec2.SecurityGroup
+ ecrBackEndRepository:ecr.Repository
+ backendTaskRole: iam.Role;
+ backendTaskExecutionRole: iam.Role;
+ backendLogGroup: logs.LogGroup;
+ rdsCluster:rds.DatabaseCluster
+ arch:ecs.CpuArchitecture
+ albTG: elb.ApplicationTargetGroup;
+}
+
+export class BackEndCluster extends Construct {
+
+ constructor(scope: Construct, id: string, props:BackEndProps) {
+ super(scope, id)
+ const backendServiceName = 'backend'
+ const backendServicePort = 7860
+ // Secrets ManagerからDB認証情報を取ってくる
+ const secretsDB = props.rdsCluster.secret!;
+
+ // Create Backend Fargate Service
+ const backendTaskDefinition = new ecs.FargateTaskDefinition(
+ this,
+ 'BackEndTaskDef',
+ {
+ memoryLimitMiB: 3072,
+ cpu: 1024,
+ executionRole: props.backendTaskExecutionRole,
+ runtimePlatform:{
+ operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
+ cpuArchitecture: props.arch,
+ },
+ taskRole: props.backendTaskRole,
+ }
+ );
+ backendTaskDefinition.addContainer('backendContainer', {
+ image: ecs.ContainerImage.fromEcrRepository(props.ecrBackEndRepository, "latest"),
+ containerName:'langflow-back-container',
+ logging: ecs.LogDriver.awsLogs({
+ streamPrefix: 'my-stream',
+ logGroup: props.backendLogGroup,
+ }),
+ environment:{
+ "LANGFLOW_AUTO_LOGIN" : process.env.LANGFLOW_AUTO_LOGIN ?? 'false',
+ "LANGFLOW_SUPERUSER" : process.env.LANGFLOW_SUPERUSER ?? "admin",
+ "LANGFLOW_SUPERUSER_PASSWORD" : process.env.LANGFLOW_SUPERUSER_PASSWORD ?? "123456"
+ },
+ portMappings: [
+ {
+ containerPort: backendServicePort,
+ protocol: ecs.Protocol.TCP,
+ },
+ ],
+ // Secretの設定
+ secrets: {
+ "dbname": ecs.Secret.fromSecretsManager(secretsDB, 'dbname'),
+ "username": ecs.Secret.fromSecretsManager(secretsDB, 'username'),
+ "host": ecs.Secret.fromSecretsManager(secretsDB, 'host'),
+ "password": ecs.Secret.fromSecretsManager(secretsDB, 'password'),
+ },
+ });
+
+ const backendService = new ecs.FargateService(this, 'BackEndService', {
+ cluster: props.cluster,
+ serviceName: backendServiceName,
+ taskDefinition: backendTaskDefinition,
+ enableExecuteCommand: true,
+ securityGroups: [props.ecsBackSG],
+ vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
+ });
+ props.albTG.addTarget(backendService);
+ }
+}
\ No newline at end of file
diff --git a/langflow/scripts/aws/lib/construct/db.ts b/langflow/scripts/aws/lib/construct/db.ts
new file mode 100644
index 0000000..255ec66
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/db.ts
@@ -0,0 +1,85 @@
+import { Construct } from "constructs";
+import * as ec2 from "aws-cdk-lib/aws-ec2";
+import * as rds from "aws-cdk-lib/aws-rds";
+import * as cdk from "aws-cdk-lib";
+
+interface RdsProps {
+ vpc: ec2.Vpc;
+ dbSG: ec2.SecurityGroup;
+}
+
+export class Rds extends Construct {
+ readonly rdsCluster: rds.DatabaseCluster;
+
+ constructor(scope: Construct, id: string, props: RdsProps) {
+ super(scope, id);
+
+ const { vpc, dbSG } = props;
+ const instanceType = ec2.InstanceType.of(
+ ec2.InstanceClass.BURSTABLE4_GRAVITON,
+ ec2.InstanceSize.MEDIUM,
+ );
+
+ // RDSのパスワードを自動生成してSecrets Managerに格納
+ const rdsCredentials = rds.Credentials.fromGeneratedSecret("db_user", {
+ secretName: "langflow-DbSecret",
+ });
+
+ // DB クラスターのパラメータグループ作成
+ const clusterParameterGroup = new rds.ParameterGroup(
+ scope,
+ "ClusterParameterGroup",
+ {
+ engine: rds.DatabaseClusterEngine.auroraMysql({
+ version: rds.AuroraMysqlEngineVersion.of(
+ "8.0.mysql_aurora.3.05.2",
+ "8.0",
+ ),
+ }),
+ description: "for-langflow",
+ },
+ );
+ clusterParameterGroup.bindToCluster({});
+
+ // DB インスタンスのパラメタグループ作成
+ const instanceParameterGroup = new rds.ParameterGroup(
+ scope,
+ "InstanceParameterGroup",
+ {
+ engine: rds.DatabaseClusterEngine.auroraMysql({
+ version: rds.AuroraMysqlEngineVersion.of(
+ "8.0.mysql_aurora.3.05.2",
+ "8.0",
+ ),
+ }),
+ description: "for-langflow",
+ },
+ );
+ instanceParameterGroup.bindToInstance({});
+
+ this.rdsCluster = new rds.DatabaseCluster(scope, "LangflowDbCluster", {
+ engine: rds.DatabaseClusterEngine.auroraMysql({
+ version: rds.AuroraMysqlEngineVersion.of(
+ "8.0.mysql_aurora.3.05.2",
+ "8.0",
+ ),
+ }),
+ storageEncrypted: true,
+ credentials: rdsCredentials,
+ instanceIdentifierBase: "langflow-instance",
+ vpc: vpc,
+ vpcSubnets: vpc.selectSubnets({
+ subnetGroupName: "langflow-Isolated",
+ }),
+ securityGroups: [dbSG],
+ writer: rds.ClusterInstance.provisioned("WriterInstance", {
+ instanceType: instanceType,
+ enablePerformanceInsights: true,
+ parameterGroup: instanceParameterGroup,
+ }),
+ // 2台目以降はreaders:で設定
+ parameterGroup: clusterParameterGroup,
+ defaultDatabaseName: "langflow",
+ });
+ }
+}
diff --git a/langflow/scripts/aws/lib/construct/ecr.ts b/langflow/scripts/aws/lib/construct/ecr.ts
new file mode 100644
index 0000000..903099f
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/ecr.ts
@@ -0,0 +1,54 @@
+import { RemovalPolicy } from 'aws-cdk-lib'
+import * as ecr from 'aws-cdk-lib/aws-ecr'
+import * as ecrdeploy from 'cdk-ecr-deployment'
+import * as ecs from 'aws-cdk-lib/aws-ecs'
+import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery'
+import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets'
+import * as path from "path";
+import { Construct } from 'constructs'
+
+
+interface ECRProps {
+ arch:ecs.CpuArchitecture;
+}
+
+export class EcrRepository extends Construct {
+ readonly ecrBackEndRepository: ecr.Repository
+
+ constructor(scope: Construct, id: string, props: ECRProps) {
+ super(scope, id)
+
+ const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
+ const backendPath = path.join(__dirname, "../../../../", "docker")
+ const excludeDir = ['node_modules','.git', 'cdk.out']
+ const LifecycleRule = {
+ tagStatus: ecr.TagStatus.ANY,
+ description: 'Delete more than 30 image',
+ maxImageCount: 30,
+ }
+
+ // Backend ECR リポジトリ作成
+ this.ecrBackEndRepository = new ecr.Repository(scope, 'LangflowBackEndRepository', {
+ repositoryName: 'langflow-backend-repository',
+ removalPolicy: RemovalPolicy.RETAIN,
+ imageScanOnPush: true,
+ })
+ // LifecycleRule作成
+ this.ecrBackEndRepository.addLifecycleRule(LifecycleRule)
+
+ // Create Docker Image Asset
+ const dockerBackEndImageAsset = new DockerImageAsset(this, "DockerBackEndImageAsset", {
+ directory: backendPath,
+ file:"cdk.Dockerfile",
+ exclude: excludeDir,
+ platform: imagePlatform,
+ });
+
+ // Deploy Docker Image to ECR Repository
+ new ecrdeploy.ECRDeployment(this, "DeployBackEndImage", {
+ src: new ecrdeploy.DockerImageName(dockerBackEndImageAsset.imageUri),
+ dest: new ecrdeploy.DockerImageName(this.ecrBackEndRepository.repositoryUri)
+ });
+
+ }
+}
diff --git a/langflow/scripts/aws/lib/construct/frontend.ts b/langflow/scripts/aws/lib/construct/frontend.ts
new file mode 100644
index 0000000..85eec2c
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/frontend.ts
@@ -0,0 +1,142 @@
+import { Stack, Duration, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
+import { Construct } from 'constructs';
+import {
+ aws_ec2 as ec2,
+ aws_ecs as ecs,
+ aws_s3 as s3,
+ aws_iam as iam,
+ aws_logs as logs,
+ aws_elasticloadbalancingv2 as elb,
+ aws_cloudfront as cloudfront,
+ aws_cloudfront_origins as origins,
+ aws_s3_deployment as s3_deployment
+} from 'aws-cdk-lib';
+import { CloudFrontToS3 } from '@aws-solutions-constructs/aws-cloudfront-s3';
+import { CfnDistribution, Distribution } from 'aws-cdk-lib/aws-cloudfront';
+import { NodejsBuild } from 'deploy-time-build';
+
+interface WebProps {
+ cluster:ecs.Cluster
+ alb:elb.IApplicationLoadBalancer;
+ albSG:ec2.SecurityGroup;
+}
+
+export class Web extends Construct {
+ readonly distribution;
+ constructor(scope: Construct, id: string, props:WebProps) {
+ super(scope, id)
+
+ const commonBucketProps: s3.BucketProps = {
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
+ encryption: s3.BucketEncryption.S3_MANAGED,
+ autoDeleteObjects: true,
+ removalPolicy: RemovalPolicy.DESTROY,
+ objectOwnership: s3.ObjectOwnership.OBJECT_WRITER,
+ enforceSSL: true,
+ };
+
+ // CDKにて 静的WebサイトをホストするためのAmazon S3バケットを作成
+ const websiteBucket = new s3.Bucket(this, 'LangflowWebsiteBucket', commonBucketProps);
+
+ const originAccessIdentity = new cloudfront.OriginAccessIdentity(
+ this,
+ 'OriginAccessIdentity',
+ {
+ comment: 'langflow-distribution-originAccessIdentity',
+ }
+ );
+
+ const webSiteBucketPolicyStatement = new iam.PolicyStatement({
+ actions: ['s3:GetObject'],
+ effect: iam.Effect.ALLOW,
+ principals: [
+ new iam.CanonicalUserPrincipal(
+ originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
+ ),
+ ],
+ resources: [`${websiteBucket.bucketArn}/*`],
+ });
+
+ websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);
+ websiteBucket.grantRead(originAccessIdentity);
+
+ const s3SpaOrigin = new origins.S3Origin(websiteBucket);
+ const ApiSpaOrigin = new origins.LoadBalancerV2Origin(props.alb,{
+ protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY
+ });
+
+ const albBehaviorOptions = {
+ origin: ApiSpaOrigin,
+ allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
+
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL,
+ cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
+ originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
+ }
+
+ const cloudFrontWebDistribution = new cloudfront.Distribution(this, 'distribution', {
+ comment: 'langflow-distribution',
+ defaultRootObject: 'index.html',
+ errorResponses: [
+ {
+ httpStatus: 403,
+ responseHttpStatus: 200,
+ responsePagePath: '/index.html',
+ },
+ {
+ httpStatus: 404,
+ responseHttpStatus: 200,
+ responsePagePath: '/index.html',
+ },
+ ],
+ defaultBehavior: { origin: s3SpaOrigin },
+ additionalBehaviors: {
+ '/api/v1/*': albBehaviorOptions,
+ '/api/v2/*': albBehaviorOptions,
+ '/health' : albBehaviorOptions,
+ },
+ enableLogging: true, // ログ出力設定
+ logBucket: new s3.Bucket(this, 'LogBucket',commonBucketProps),
+ logFilePrefix: 'distribution-access-logs/',
+ logIncludesCookies: true,
+ });
+ this.distribution = cloudFrontWebDistribution;
+
+
+ new NodejsBuild(this, 'BuildFrontEnd', {
+ assets: [
+ {
+ path: '../../src/frontend',
+ exclude: [
+ '.git',
+ '.github',
+ '.gitignore',
+ '.prettierignore',
+ 'build',
+ 'node_modules'
+ ],
+ },
+ ],
+ nodejsVersion:20,
+ destinationBucket: websiteBucket,
+ distribution: cloudFrontWebDistribution,
+ outputSourceDirectory: 'build',
+ buildCommands: ['npm install', 'npm run build'],
+ buildEnvironment: {
+ // VITE_AXIOS_BASE_URL: `https://${this.distribution.domainName}`
+ },
+ });
+
+ // distribution から backendへのinbound 許可
+ const alb_listen_port=80
+ props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port))
+ const alb_listen_port_443=443
+ props.albSG.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(alb_listen_port_443))
+
+
+ new CfnOutput(this, 'URL', {
+ value: `https://${this.distribution.domainName}`,
+ });
+}
+
+}
\ No newline at end of file
diff --git a/langflow/scripts/aws/lib/construct/iam.ts b/langflow/scripts/aws/lib/construct/iam.ts
new file mode 100644
index 0000000..13949bd
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/iam.ts
@@ -0,0 +1,82 @@
+import { RemovalPolicy, Duration } from 'aws-cdk-lib'
+import { Construct } from 'constructs'
+import {
+ aws_rds as rds,
+ aws_iam as iam,
+} from 'aws-cdk-lib';
+
+interface IAMProps {
+ rdsCluster:rds.DatabaseCluster
+}
+
+export class EcsIAM extends Construct {
+ readonly backendTaskRole: iam.Role;
+ readonly backendTaskExecutionRole: iam.Role;
+
+ constructor(scope: Construct, id: string, props:IAMProps) {
+ super(scope, id)
+
+ // Policy Statements
+ // ECS Policy State
+ const ECSExecPolicyStatement = new iam.PolicyStatement({
+ sid: 'allowECSExec',
+ resources: ['*'],
+ actions: [
+ 'ecr:GetAuthorizationToken',
+ 'ecr:BatchCheckLayerAvailability',
+ 'ecr:GetDownloadUrlForLayer',
+ 'ecr:BatchGetImage',
+ ],
+ });
+ // Bedrock Policy State
+ const BedrockPolicyStatement = new iam.PolicyStatement({
+ sid: 'allowBedrockAccess',
+ resources: ['*'],
+ actions: [
+ 'bedrock:*',
+ ],
+ });
+ // Kendra Policy State
+ const KendraPolicyStatement = new iam.PolicyStatement({
+ sid: 'allowKendraAccess',
+ resources: ['*'],
+ actions: [
+ 'kendra:*'
+ ],
+ });
+ // Create Rag Policy
+ const RagAccessPolicy = new iam.Policy(this, 'RAGFullAccess', {
+ statements: [KendraPolicyStatement,BedrockPolicyStatement],
+ })
+ // Secrets ManagerからDB認証情報を取ってくるためのPolicy
+ const SecretsManagerPolicy = new iam.Policy(this, 'SMGetPolicy', {
+ statements: [new iam.PolicyStatement({
+ actions: ['secretsmanager:GetSecretValue'],
+ resources: [props.rdsCluster.secret!.secretArn],
+ })],
+ })
+
+ // BackEnd Task Role
+ this.backendTaskRole = new iam.Role(this, 'BackendTaskRole', {
+ assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
+ });
+ // ECS Exec Policyの付与
+ this.backendTaskRole.addToPolicy(ECSExecPolicyStatement);
+ // KendraとBedrockのアクセス権付与
+ this.backendTaskRole.attachInlinePolicy(RagAccessPolicy);
+
+ // BackEnd Task ExecutionRole
+ this.backendTaskExecutionRole = new iam.Role(this, 'backendTaskExecutionRole', {
+ assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
+ managedPolicies: [
+ {
+ managedPolicyArn:
+ 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
+ },
+ ],
+ });
+
+ this.backendTaskExecutionRole.attachInlinePolicy(SecretsManagerPolicy);
+ this.backendTaskExecutionRole.attachInlinePolicy(RagAccessPolicy);
+ }
+}
\ No newline at end of file
diff --git a/langflow/scripts/aws/lib/construct/index.ts b/langflow/scripts/aws/lib/construct/index.ts
new file mode 100644
index 0000000..91e2d2c
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/index.ts
@@ -0,0 +1,7 @@
+export * from './db';
+export * from './ecr';
+export * from './iam';
+export * from './frontend';
+export * from './backend';
+export * from './network';
+export * from './kendra';
\ No newline at end of file
diff --git a/langflow/scripts/aws/lib/construct/kendra.ts b/langflow/scripts/aws/lib/construct/kendra.ts
new file mode 100644
index 0000000..80f60eb
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/kendra.ts
@@ -0,0 +1,141 @@
+import * as kendra from 'aws-cdk-lib/aws-kendra';
+import * as iam from 'aws-cdk-lib/aws-iam';
+import { Construct } from 'constructs';
+import { Duration, Token, Arn } from 'aws-cdk-lib';
+import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
+import { Runtime } from 'aws-cdk-lib/aws-lambda';
+
+export interface RagProps {
+}
+
+/**
+ * RAG を実行するためのリソースを作成する
+ */
+export class Rag extends Construct {
+ constructor(scope: Construct, id: string, props: RagProps) {
+ super(scope, id);
+
+ const kendraIndexArnInCdkContext =
+ this.node.tryGetContext('kendraIndexArn');
+
+ let kendraIndexArn: string;
+ let kendraIndexId: string;
+
+ if (kendraIndexArnInCdkContext) {
+ // 既存の Kendra Index を利用する場合
+ kendraIndexArn = kendraIndexArnInCdkContext!;
+ kendraIndexId = Arn.extractResourceName(
+ kendraIndexArnInCdkContext,
+ 'index'
+ );
+ } else {
+ // 新規に Kendra Index を作成する場合
+ const indexRole = new iam.Role(this, 'KendraIndexRole', {
+ assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
+ });
+
+ indexRole.addToPolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ resources: ['*'],
+ actions: ['s3:GetObject'],
+ })
+ );
+
+ indexRole.addManagedPolicy(
+ iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess')
+ );
+
+ const index = new kendra.CfnIndex(this, 'KendraIndex', {
+ name: 'langflow-index',
+ edition: 'DEVELOPER_EDITION',
+ roleArn: indexRole.roleArn,
+ });
+
+ kendraIndexArn = Token.asString(index.getAtt('Arn'));
+ kendraIndexId = index.ref;
+
+ // WebCrawler を作成
+ const webCrawlerRole = new iam.Role(this, 'KendraWebCrawlerRole', {
+ assumedBy: new iam.ServicePrincipal('kendra.amazonaws.com'),
+ });
+ webCrawlerRole.addToPolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ resources: [kendraIndexArn],
+ actions: ['kendra:BatchPutDocument', 'kendra:BatchDeleteDocument'],
+ })
+ );
+
+ new kendra.CfnDataSource(this, 'WebCrawler', {
+ indexId: kendraIndexId,
+ name: 'WebCrawler',
+ type: 'WEBCRAWLER',
+ roleArn: webCrawlerRole.roleArn,
+ languageCode: 'ja',
+ dataSourceConfiguration: {
+ webCrawlerConfiguration: {
+ urls: {
+ seedUrlConfiguration: {
+ webCrawlerMode: 'HOST_ONLY',
+ // デモ用に AWS の GenAI 関連のページを取り込む
+ seedUrls: [
+ 'https://aws.amazon.com/jp/what-is/generative-ai/',
+ 'https://aws.amazon.com/jp/generative-ai/',
+ 'https://aws.amazon.com/jp/generative-ai/use-cases/',
+ 'https://aws.amazon.com/jp/bedrock/',
+ 'https://aws.amazon.com/jp/bedrock/features/',
+ 'https://aws.amazon.com/jp/bedrock/testimonials/',
+ ],
+ },
+ },
+ crawlDepth: 1,
+ urlInclusionPatterns: ['https://aws.amazon.com/jp/.*'],
+ },
+ },
+ });
+ }
+
+ // RAG 関連の API を追加する
+ // Lambda
+ const queryFunction = new NodejsFunction(this, 'Query', {
+ runtime: Runtime.NODEJS_18_X,
+ entry: './lambda/queryKendra.ts',
+ timeout: Duration.minutes(15),
+ bundling: {
+ // 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
+ externalModules: [],
+ },
+ environment: {
+ INDEX_ID: kendraIndexId,
+ },
+ });
+ queryFunction.role?.addToPrincipalPolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ resources: [kendraIndexArn],
+ actions: ['kendra:Query'],
+ })
+ );
+
+ const retrieveFunction = new NodejsFunction(this, 'Retrieve', {
+ runtime: Runtime.NODEJS_18_X,
+ entry: './lambda/retrieveKendra.ts',
+ timeout: Duration.minutes(15),
+ bundling: {
+ // 新しい Kendra の機能を使うため、AWS SDK を明示的にバンドルする
+ externalModules: [],
+ },
+ environment: {
+ INDEX_ID: kendraIndexId,
+ },
+ });
+ retrieveFunction.role?.addToPrincipalPolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ resources: [kendraIndexArn],
+ actions: ['kendra:Retrieve'],
+ })
+ );
+ }
+}
\ No newline at end of file
diff --git a/langflow/scripts/aws/lib/construct/network.ts b/langflow/scripts/aws/lib/construct/network.ts
new file mode 100644
index 0000000..1abd78d
--- /dev/null
+++ b/langflow/scripts/aws/lib/construct/network.ts
@@ -0,0 +1,113 @@
+import { RemovalPolicy, Duration, CfnOutput } from 'aws-cdk-lib'
+import { Construct } from 'constructs'
+import {
+ aws_ec2 as ec2,
+ aws_ecs as ecs,
+ aws_logs as logs,
+ aws_servicediscovery as servicediscovery,
+ aws_elasticloadbalancingv2 as elb,
+} from 'aws-cdk-lib';
+
+export class Network extends Construct {
+ readonly vpc: ec2.Vpc;
+ readonly cluster: ecs.Cluster;
+ readonly ecsBackSG: ec2.SecurityGroup;
+ readonly dbSG: ec2.SecurityGroup;
+ readonly backendLogGroup: logs.LogGroup;
+ readonly alb: elb.IApplicationLoadBalancer;
+ readonly albTG: elb.ApplicationTargetGroup;
+ readonly albSG: ec2.SecurityGroup;
+
+ constructor(scope: Construct, id: string) {
+ super(scope, id)
+ const alb_listen_port=80
+ const back_service_port=7860
+
+ // VPC等リソースの作成
+ this.vpc = new ec2.Vpc(scope, 'VPC', {
+ vpcName: 'langflow-vpc',
+ ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
+ maxAzs: 3,
+ subnetConfiguration: [
+ {
+ cidrMask: 24,
+ name: 'langflow-Isolated',
+ subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
+ },
+ {
+ cidrMask: 24,
+ name: 'langflow-Public',
+ subnetType: ec2.SubnetType.PUBLIC,
+ },
+ {
+ cidrMask: 24,
+ name: 'langflow-Private',
+ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
+ },
+ ],
+ natGateways: 1,
+ })
+
+ // ALBに設定するセキュリティグループ
+ this.albSG = new ec2.SecurityGroup(scope, 'ALBSecurityGroup', {
+ securityGroupName: 'alb-sg',
+ description: 'for alb',
+ vpc: this.vpc,
+ })
+
+ this.alb = new elb.ApplicationLoadBalancer(this,'langflow-alb',{
+ internetFacing: true, //インターネットからのアクセスを許可するかどうか指定
+ loadBalancerName: 'langflow-alb',
+ securityGroup: this.albSG, //作成したセキュリティグループを割り当てる
+ vpc:this.vpc,
+ })
+
+ const listener = this.alb.addListener('Listener', { port: alb_listen_port });
+
+ this.albTG = listener.addTargets('targetGroup', {
+ port: back_service_port,
+ protocol: elb.ApplicationProtocol.HTTP,
+ healthCheck: {
+ enabled: true,
+ path: '/health',
+ healthyThresholdCount: 2,
+ unhealthyThresholdCount: 4,
+ interval: Duration.seconds(100),
+ timeout: Duration.seconds(30),
+ healthyHttpCodes: '200',
+ },
+ });
+
+ // Cluster
+ this.cluster = new ecs.Cluster(this, 'EcsCluster', {
+ clusterName: 'langflow-cluster',
+ vpc: this.vpc,
+ enableFargateCapacityProviders: true,
+ });
+
+ // ECS BackEndに設定するセキュリティグループ
+ this.ecsBackSG = new ec2.SecurityGroup(scope, 'ECSBackEndSecurityGroup', {
+ securityGroupName: 'langflow-ecs-back-sg',
+ description: 'for langflow-back-ecs',
+ vpc: this.vpc,
+ })
+ this.ecsBackSG.addIngressRule(this.albSG,ec2.Port.tcp(back_service_port))
+
+ // RDSに設定するセキュリティグループ
+ this.dbSG = new ec2.SecurityGroup(scope, 'DBSecurityGroup', {
+ allowAllOutbound: true,
+ securityGroupName: 'langflow-db',
+ description: 'for langflow-db',
+ vpc: this.vpc,
+ })
+ // langflow-ecs-back-sg からのポート3306:mysql(5432:postgres)のインバウンドを許可
+ this.dbSG.addIngressRule(this.ecsBackSG, ec2.Port.tcp(3306))
+
+ // Create CloudWatch Log Group
+ this.backendLogGroup = new logs.LogGroup(this, 'backendLogGroup', {
+ logGroupName: 'langflow-backend-logs',
+ removalPolicy: RemovalPolicy.DESTROY,
+ });
+
+ }
+}
\ No newline at end of file
diff --git a/langflow/scripts/aws/package-lock.json b/langflow/scripts/aws/package-lock.json
new file mode 100644
index 0000000..07b1144
--- /dev/null
+++ b/langflow/scripts/aws/package-lock.json
@@ -0,0 +1,4754 @@
+{
+ "name": "cdk",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "cdk",
+ "version": "0.1.0",
+ "dependencies": {
+ "@aws-solutions-constructs/aws-cloudfront-s3": "^2.57.0",
+ "aws-cdk-lib": "^2.141.0",
+ "cdk-ecr-deployment": "^3.0.55",
+ "constructs": "^10.3.0",
+ "deploy-time-build": "^0.3.21",
+ "dotenv": "^16.4.5",
+ "source-map-support": "^0.5.21"
+ },
+ "bin": {
+ "cdk": "bin/cdk.js"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.12.12",
+ "aws-cdk": "^2.1000.2",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.4.5"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@aws-cdk/asset-awscli-v1": {
+ "version": "2.2.224",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.224.tgz",
+ "integrity": "sha512-4CQP+y0rLq4IWzOlTqBhe8IxBU3Tul9KcmHxiAqztQRWLIl5HAVGCOWdLzHMLgbpFWNNMlIJxB8GwBEV0pWtfQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz",
+ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema": {
+ "version": "39.2.20",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-39.2.20.tgz",
+ "integrity": "sha512-RI7S8jphGA8mak154ElnEJQPNTTV4PZmA7jgqnBBHQGyOPJIXxtACubNQ5m4YgjpkK3UJHsWT+/cOAfM/Au/Wg==",
+ "bundleDependencies": [
+ "jsonschema",
+ "semver"
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jsonschema": "~1.4.1",
+ "semver": "^7.7.1"
+ }
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": {
+ "version": "1.4.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
+ "version": "7.7.1",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@aws-cdk/integ-tests-alpha": {
+ "version": "2.138.0-alpha.0",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/integ-tests-alpha/-/integ-tests-alpha-2.138.0-alpha.0.tgz",
+ "integrity": "sha512-TqLcJbC9VX05X2Py2GpFZ8quO7Mj3i1NuerekzqM8RV8AiU3aPMcZFGRK+MdXHNIiGzYx9ZGfMPERVT4Qqwn6A==",
+ "engines": {
+ "node": ">= 14.15.0"
+ },
+ "peerDependencies": {
+ "aws-cdk-lib": "^2.138.0",
+ "constructs": "^10.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/aws-cloudfront-s3": {
+ "version": "2.57.0",
+ "resolved": "https://registry.npmjs.org/@aws-solutions-constructs/aws-cloudfront-s3/-/aws-cloudfront-s3-2.57.0.tgz",
+ "integrity": "sha512-ReL7pk7UE2Xgg0mw6v8TmMC6Jhkmv1R4s3UZqM9w4RMXzp3LceS78YZLJHBT6m8AAT6wxY4kAFqJNoUZJ7fFKg==",
+ "dependencies": {
+ "@aws-cdk/integ-tests-alpha": "2.138.0-alpha.0",
+ "@aws-solutions-constructs/core": "2.57.0",
+ "@aws-solutions-constructs/resources": "2.57.0",
+ "constructs": "^10.0.0"
+ },
+ "peerDependencies": {
+ "@aws-solutions-constructs/core": "2.57.0",
+ "@aws-solutions-constructs/resources": "2.57.0",
+ "aws-cdk-lib": "^2.138.0",
+ "constructs": "^10.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core": {
+ "version": "2.57.0",
+ "resolved": "https://registry.npmjs.org/@aws-solutions-constructs/core/-/core-2.57.0.tgz",
+ "integrity": "sha512-AGDqfup4vfo1ueum44M70lcnUPVFM1jZhK9XVksxEQdQJA1T6O1OdcGGJO3aw3vQVC+9YS9upTYCfHUyC5j3qw==",
+ "bundleDependencies": [
+ "deepmerge",
+ "npmlog",
+ "deep-diff"
+ ],
+ "dependencies": {
+ "@aws-cdk/integ-tests-alpha": "2.138.0-alpha.0",
+ "constructs": "^10.0.0",
+ "deep-diff": "^1.0.2",
+ "deepmerge": "^4.0.0",
+ "npmlog": "^7.0.0"
+ },
+ "peerDependencies": {
+ "aws-cdk-lib": "^2.138.0",
+ "constructs": "^10.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/aproba": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/are-we-there-yet": {
+ "version": "4.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/color-support": {
+ "version": "1.1.3",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/deep-diff": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/deepmerge": {
+ "version": "4.3.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/gauge": {
+ "version": "5.0.2",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^4.0.1",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/has-unicode": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/npmlog": {
+ "version": "7.0.1",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "are-we-there-yet": "^4.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^5.0.0",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/set-blocking": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/string-width": {
+ "version": "4.2.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/core/node_modules/wide-align": {
+ "version": "1.1.5",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/@aws-solutions-constructs/resources": {
+ "version": "2.57.0",
+ "resolved": "https://registry.npmjs.org/@aws-solutions-constructs/resources/-/resources-2.57.0.tgz",
+ "integrity": "sha512-2LYLlTF8GPvKY1KIFcF7MJNO7vnND7g93+FZY3sg95cSB6U9wKqhVdEn1LRGZLGljmsJJBHxUSedy8TOVIjOsQ==",
+ "bundleDependencies": [
+ "@aws-sdk/client-kms",
+ "@aws-sdk/client-s3",
+ "aws-sdk-client-mock"
+ ],
+ "dependencies": {
+ "@aws-cdk/integ-tests-alpha": "2.138.0-alpha.0",
+ "@aws-sdk/client-kms": "^3.478.0",
+ "@aws-sdk/client-s3": "^3.478.0",
+ "@aws-solutions-constructs/core": "2.57.0",
+ "aws-sdk-client-mock": "^3.0.0",
+ "constructs": "^10.0.0"
+ },
+ "peerDependencies": {
+ "@aws-solutions-constructs/core": "2.57.0",
+ "aws-cdk-lib": "^2.138.0",
+ "constructs": "^10.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.24.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
+ "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.24.2",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.24.4",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz",
+ "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
+ "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.24.2",
+ "@babel/generator": "^7.24.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-module-transforms": "^7.24.5",
+ "@babel/helpers": "^7.24.5",
+ "@babel/parser": "^7.24.5",
+ "@babel/template": "^7.24.0",
+ "@babel/traverse": "^7.24.5",
+ "@babel/types": "^7.24.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
+ "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.23.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
+ "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.23.5",
+ "@babel/helper-validator-option": "^7.23.5",
+ "browserslist": "^4.22.2",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.23.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+ "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.22.15",
+ "@babel/types": "^7.23.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.24.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
+ "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
+ "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-module-imports": "^7.24.3",
+ "@babel/helper-simple-access": "^7.24.5",
+ "@babel/helper-split-export-declaration": "^7.24.5",
+ "@babel/helper-validator-identifier": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
+ "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
+ "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+ "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
+ "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
+ "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+ "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
+ "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.24.0",
+ "@babel/traverse": "^7.24.5",
+ "@babel/types": "^7.24.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
+ "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.5",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
+ "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
+ "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz",
+ "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
+ "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.23.5",
+ "@babel/parser": "^7.24.0",
+ "@babel/types": "^7.24.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
+ "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.24.2",
+ "@babel/generator": "^7.24.5",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.24.5",
+ "@babel/parser": "^7.24.5",
+ "@babel/types": "^7.24.5",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
+ "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.24.1",
+ "@babel/helper-validator-identifier": "^7.24.5",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz",
+ "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "29.5.12",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz",
+ "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.0.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.12.12",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
+ "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.32",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+ "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+ "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/aws-cdk": {
+ "version": "2.1000.2",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1000.2.tgz",
+ "integrity": "sha512-QsXqJhGWjHNqP7etgE3sHOTiDBXItmSKdFKgsm1qPMBabCMyFfmWZnEeUxfZ4sMaIoxvLpr3sqoWSNeLuUk4sg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "cdk": "bin/cdk"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/aws-cdk-lib": {
+ "version": "2.179.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.179.0.tgz",
+ "integrity": "sha512-fwkoEzvh3TXYj+GPkyq/GSQt63JJV1dBgDKwr3xRdb8lQaPntSclmisa+ARO8tjPfkru1NqxAFQTtiqtAE83cA==",
+ "bundleDependencies": [
+ "@balena/dockerignore",
+ "case",
+ "fs-extra",
+ "ignore",
+ "jsonschema",
+ "minimatch",
+ "punycode",
+ "semver",
+ "table",
+ "yaml",
+ "mime-types"
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-cdk/asset-awscli-v1": "^2.2.208",
+ "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0",
+ "@aws-cdk/cloud-assembly-schema": "^39.2.0",
+ "@balena/dockerignore": "^1.0.2",
+ "case": "1.6.3",
+ "fs-extra": "^11.2.0",
+ "ignore": "^5.3.2",
+ "jsonschema": "^1.4.1",
+ "mime-types": "^2.1.35",
+ "minimatch": "^3.1.2",
+ "punycode": "^2.3.1",
+ "semver": "^7.6.3",
+ "table": "^6.8.2",
+ "yaml": "1.10.2"
+ },
+ "engines": {
+ "node": ">= 14.15.0"
+ },
+ "peerDependencies": {
+ "constructs": "^10.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/aws-cdk-lib/node_modules/ajv": {
+ "version": "8.17.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/astral-regex": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/case": {
+ "version": "1.6.3",
+ "inBundle": true,
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/color-convert": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/color-name": {
+ "version": "1.1.4",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/concat-map": {
+ "version": "0.0.1",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fast-uri": {
+ "version": "3.0.6",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "inBundle": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fs-extra": {
+ "version": "11.3.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/aws-cdk-lib/node_modules/ignore": {
+ "version": "5.3.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/jsonschema": {
+ "version": "1.5.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/mime-db": {
+ "version": "1.52.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/mime-types": {
+ "version": "2.1.35",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/minimatch": {
+ "version": "3.1.2",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/punycode": {
+ "version": "2.3.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/require-from-string": {
+ "version": "2.0.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/semver": {
+ "version": "7.6.3",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/string-width": {
+ "version": "4.2.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/table": {
+ "version": "6.9.0",
+ "inBundle": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/universalify": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/yaml": {
+ "version": "1.10.2",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
+ "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.8.3",
+ "@babel/plugin-syntax-import-meta": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.8.3",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-top-level-await": "^7.8.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+ "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001587",
+ "electron-to-chromium": "^1.4.668",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.0.13"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001618",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz",
+ "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/cdk-ecr-deployment": {
+ "version": "3.0.55",
+ "resolved": "https://registry.npmjs.org/cdk-ecr-deployment/-/cdk-ecr-deployment-3.0.55.tgz",
+ "integrity": "sha512-z10vrHqdFX08uNT3KPFU6TeBJIP+j0xst0atwW7XL1TK3TPe8CSUG+LAs2Uh0dkX3fsVesAWEIbw4QmiMiIsoQ==",
+ "bundleDependencies": [
+ "got",
+ "hpagent"
+ ],
+ "dependencies": {
+ "aws-cdk-lib": "^2.0.0",
+ "constructs": "^10.0.5",
+ "got": "^11.8.6",
+ "hpagent": "^0.1.2"
+ },
+ "peerDependencies": {
+ "aws-cdk-lib": "^2.0.0",
+ "constructs": "^10.0.5"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/cacheable-request/node_modules/@types/node": {
+ "version": "20.12.11",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/keyv/node_modules/@types/node": {
+ "version": "20.12.11",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/@types/responselike/node_modules/@types/node": {
+ "version": "20.12.11",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/clone-response": {
+ "version": "1.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/clone-response/node_modules/mimic-response": {
+ "version": "1.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/decompress-response": {
+ "version": "6.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/get-stream": {
+ "version": "5.2.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/got": {
+ "version": "11.8.6",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/hpagent": {
+ "version": "0.1.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "inBundle": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/json-buffer": {
+ "version": "3.0.1",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/keyv": {
+ "version": "4.5.4",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/normalize-url": {
+ "version": "6.1.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/once": {
+ "version": "1.4.0",
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/pump": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/quick-lru": {
+ "version": "5.1.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/responselike": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/undici-types": {
+ "version": "5.26.5",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/cdk-ecr-deployment/node_modules/wrappy": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
+ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
+ "dev": true
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/constructs": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz",
+ "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==",
+ "engines": {
+ "node": ">= 16.14.0"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
+ "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "dev": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deploy-time-build": {
+ "version": "0.3.21",
+ "resolved": "https://registry.npmjs.org/deploy-time-build/-/deploy-time-build-0.3.21.tgz",
+ "integrity": "sha512-lbc5TS08md+5e+cU4sxpDzOiUNWOEfuvElhapPHGWMrzJjINdiAv6hw9vc5ExnTZmYoOCcE1oNdaVbc8FCqiRA==",
+ "peerDependencies": {
+ "aws-cdk-lib": "^2.38.0",
+ "constructs": "^10.0.5"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.5",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.767",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.767.tgz",
+ "integrity": "sha512-nzzHfmQqBss7CE3apQHkHjXW77+8w3ubGCIoEijKCJebPufREaFETgGXWTkh32t259F3Kcq+R8MZdFdOJROgYw==",
+ "dev": true
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
+ "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
+ "dev": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz",
+ "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner/node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ]
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve.exports": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
+ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.1.2",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz",
+ "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==",
+ "dev": true,
+ "dependencies": {
+ "bs-logger": "0.x",
+ "fast-json-stable-stringify": "2.x",
+ "jest-util": "^29.0.0",
+ "json5": "^2.2.3",
+ "lodash.memoize": "4.x",
+ "make-error": "1.x",
+ "semver": "^7.5.3",
+ "yargs-parser": "^21.0.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/types": "^29.0.0",
+ "babel-jest": "^29.0.0",
+ "jest": "^29.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.4.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
+ "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
+ "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
+ "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/langflow/scripts/aws/package.json b/langflow/scripts/aws/package.json
new file mode 100644
index 0000000..dadba65
--- /dev/null
+++ b/langflow/scripts/aws/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "cdk",
+ "version": "0.1.0",
+ "bin": {
+ "cdk": "bin/cdk.js"
+ },
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w",
+ "test": "jest",
+ "cdk": "cdk"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.12.12",
+ "aws-cdk": "^2.1000.2",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.4.5"
+ },
+ "dependencies": {
+ "@aws-solutions-constructs/aws-cloudfront-s3": "^2.57.0",
+ "aws-cdk-lib": "^2.141.0",
+ "cdk-ecr-deployment": "^3.0.55",
+ "constructs": "^10.3.0",
+ "deploy-time-build": "^0.3.21",
+ "dotenv": "^16.4.5",
+ "source-map-support": "^0.5.21"
+ }
+}
diff --git a/langflow/scripts/aws/test/cdk.test.ts b/langflow/scripts/aws/test/cdk.test.ts
new file mode 100644
index 0000000..1e6b29c
--- /dev/null
+++ b/langflow/scripts/aws/test/cdk.test.ts
@@ -0,0 +1,17 @@
+// import * as cdk from 'aws-cdk-lib';
+// import { Template } from 'aws-cdk-lib/assertions';
+// import * as Cdk from '../lib/cdk-stack';
+
+// example test. To run these tests, uncomment this file along with the
+// example resource in lib/cdk-stack.ts
+test('SQS Queue Created', () => {
+// const app = new cdk.App();
+// // WHEN
+// const stack = new Cdk.CdkStack(app, 'MyTestStack');
+// // THEN
+// const template = Template.fromStack(stack);
+
+// template.hasResourceProperties('AWS::SQS::Queue', {
+// VisibilityTimeout: 300
+// });
+});
diff --git a/langflow/scripts/ci/__init__.py b/langflow/scripts/ci/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/scripts/ci/pypi_nightly_tag.py b/langflow/scripts/ci/pypi_nightly_tag.py
new file mode 100644
index 0000000..d15dffc
--- /dev/null
+++ b/langflow/scripts/ci/pypi_nightly_tag.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+"""Idea from https://github.com/streamlit/streamlit/blob/4841cf91f1c820a392441092390c4c04907f9944/scripts/pypi_nightly_create_tag.py."""
+
+import sys
+
+import packaging.version
+from packaging.version import Version
+
+PYPI_LANGFLOW_URL = "https://pypi.org/pypi/langflow/json"
+PYPI_LANGFLOW_NIGHTLY_URL = "https://pypi.org/pypi/langflow-nightly/json"
+
+PYPI_LANGFLOW_BASE_URL = "https://pypi.org/pypi/langflow-base/json"
+PYPI_LANGFLOW_BASE_NIGHTLY_URL = "https://pypi.org/pypi/langflow-base-nightly/json"
+
+ARGUMENT_NUMBER = 2
+
+
+def get_latest_published_version(build_type: str, *, is_nightly: bool) -> Version:
+ import requests
+
+ url = ""
+ if build_type == "base":
+ url = PYPI_LANGFLOW_BASE_NIGHTLY_URL if is_nightly else PYPI_LANGFLOW_BASE_URL
+ elif build_type == "main":
+ url = PYPI_LANGFLOW_NIGHTLY_URL if is_nightly else PYPI_LANGFLOW_URL
+ else:
+ msg = f"Invalid build type: {build_type}"
+ raise ValueError(msg)
+
+ res = requests.get(url, timeout=10)
+ try:
+ version_str = res.json()["info"]["version"]
+ except Exception as e:
+ msg = "Got unexpected response from PyPI"
+ raise RuntimeError(msg) from e
+ return Version(version_str)
+
+
+def create_tag(build_type: str):
+ current_version = get_latest_published_version(build_type, is_nightly=False)
+ current_nightly_version = get_latest_published_version(build_type, is_nightly=True)
+
+ build_number = "0"
+ latest_base_version = current_version.base_version
+ nightly_base_version = current_nightly_version.base_version
+
+ if latest_base_version == nightly_base_version:
+ # If the latest version is the same as the nightly version, increment the build number
+ build_number = str(current_nightly_version.dev + 1)
+
+ new_nightly_version = latest_base_version + ".dev" + build_number
+
+ # Prepend "v" to the version, if DNE.
+ # This is an update to the nightly version format.
+ if not new_nightly_version.startswith("v"):
+ new_nightly_version = "v" + new_nightly_version
+
+ # X.Y.Z.dev.YYYYMMDD
+ # This takes the base version of the current version and appends the
+ # current date. If the last release was on the same day, we exit, as
+ # pypi does not allow for overwriting the same version.
+ #
+ # We could use a different versioning scheme, such as just incrementing
+ # an integer.
+ # version_with_date = (
+ # ".".join([str(x) for x in current_version.release])
+ # + ".dev"
+ # + "0"
+ # + datetime.now(pytz.timezone("UTC")).strftime("%Y%m%d")
+ # )
+
+ # Verify if version is PEP440 compliant.
+ packaging.version.Version(new_nightly_version)
+
+ return new_nightly_version
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != ARGUMENT_NUMBER:
+ msg = "Specify base or main"
+ raise ValueError(msg)
+
+ build_type = sys.argv[1]
+ tag = create_tag(build_type)
+ print(tag)
diff --git a/langflow/scripts/ci/update_lf_base_dependency.py b/langflow/scripts/ci/update_lf_base_dependency.py
new file mode 100644
index 0000000..e3e5d1a
--- /dev/null
+++ b/langflow/scripts/ci/update_lf_base_dependency.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+import re
+import sys
+from pathlib import Path
+
+import packaging.version
+
+BASE_DIR = Path(__file__).parent.parent.parent
+ARGUMENT_NUMBER = 2
+
+
+def update_base_dep(pyproject_path: str, new_version: str) -> None:
+ """Update the langflow-base dependency in pyproject.toml."""
+ filepath = BASE_DIR / pyproject_path
+ content = filepath.read_text(encoding="utf-8")
+
+ replacement = f'langflow-base-nightly = "{new_version}"'
+
+ # Updates the pattern for poetry
+ pattern = re.compile(r'langflow-base = \{ path = "\./src/backend/base", develop = true \}')
+ if not pattern.search(content):
+ msg = f'langflow-base poetry dependency not found in "{filepath}"'
+ raise ValueError(msg)
+ content = pattern.sub(replacement, content)
+ filepath.write_text(content, encoding="utf-8")
+
+
+def verify_pep440(version):
+ """Verify if version is PEP440 compliant.
+
+ https://github.com/pypa/packaging/blob/16.7/packaging/version.py#L191
+ """
+ return packaging.version.Version(version)
+
+
+def main() -> None:
+ if len(sys.argv) != ARGUMENT_NUMBER:
+ msg = "New version not specified"
+ raise ValueError(msg)
+ base_version = sys.argv[1]
+
+ # Strip "v" prefix from version if present
+ base_version = base_version.removeprefix("v")
+
+ verify_pep440(base_version)
+ update_base_dep("pyproject.toml", base_version)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/langflow/scripts/ci/update_pyproject_combined.py b/langflow/scripts/ci/update_pyproject_combined.py
new file mode 100644
index 0000000..20c6e3d
--- /dev/null
+++ b/langflow/scripts/ci/update_pyproject_combined.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# scripts/ci/update_pyproject_combined.py
+import sys
+from pathlib import Path
+
+from update_pyproject_name import update_pyproject_name
+from update_pyproject_name import update_uv_dep as update_name_uv_dep
+from update_pyproject_version import update_pyproject_version
+from update_uv_dependency import update_uv_dep as update_version_uv_dep
+
+# Add the current directory to the path so we can import the other scripts
+current_dir = Path(__file__).resolve().parent
+sys.path.append(str(current_dir))
+
+
+def main():
+ """Universal update script that handles both base and main updates in a single run.
+
+ Usage:
+ update_pyproject_combined.py main
+ """
+ arg_count = 4
+ if len(sys.argv) != arg_count:
+ print("Usage:")
+ print(" update_pyproject_combined.py main ")
+ sys.exit(1)
+
+ mode = sys.argv[1]
+ if mode != "main":
+ print("Only 'main' mode is supported")
+ print("Usage: update_pyproject_combined.py main ")
+ sys.exit(1)
+
+ main_tag = sys.argv[2]
+ base_tag = sys.argv[3]
+
+ # First handle base package updates
+ update_pyproject_name("src/backend/base/pyproject.toml", "langflow-base-nightly")
+ update_name_uv_dep("pyproject.toml", "langflow-base-nightly")
+ update_pyproject_version("src/backend/base/pyproject.toml", base_tag)
+
+ # Then handle main package updates
+ update_pyproject_name("pyproject.toml", "langflow-nightly")
+ update_name_uv_dep("pyproject.toml", "langflow-nightly")
+ update_pyproject_version("pyproject.toml", main_tag)
+ # Update dependency version (strip 'v' prefix if present)
+ base_version = base_tag.lstrip("v")
+ update_version_uv_dep(base_version)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/langflow/scripts/ci/update_pyproject_name.py b/langflow/scripts/ci/update_pyproject_name.py
new file mode 100644
index 0000000..38511bf
--- /dev/null
+++ b/langflow/scripts/ci/update_pyproject_name.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+
+import re
+import sys
+from pathlib import Path
+
+BASE_DIR = Path(__file__).parent.parent.parent
+ARGUMENT_NUMBER = 3
+
+
+def update_pyproject_name(pyproject_path: str, new_project_name: str) -> None:
+ """Update the project name in pyproject.toml."""
+ filepath = BASE_DIR / pyproject_path
+ content = filepath.read_text(encoding="utf-8")
+
+ # Regex to match the version line under [tool.poetry]
+ pattern = re.compile(r'(?<=^name = ")[^"]+(?=")', re.MULTILINE)
+
+ if not pattern.search(content):
+ msg = f'Project name not found in "{filepath}"'
+ raise ValueError(msg)
+ content = pattern.sub(new_project_name, content)
+
+ filepath.write_text(content, encoding="utf-8")
+
+
+def update_uv_dep(pyproject_path: str, new_project_name: str) -> None:
+ """Update the langflow-base dependency in pyproject.toml."""
+ filepath = BASE_DIR / pyproject_path
+ content = filepath.read_text(encoding="utf-8")
+
+ if new_project_name == "langflow-nightly":
+ pattern = re.compile(r"langflow = \{ workspace = true \}")
+ replacement = "langflow-nightly = { workspace = true }"
+ elif new_project_name == "langflow-base-nightly":
+ pattern = re.compile(r"langflow-base = \{ workspace = true \}")
+ replacement = "langflow-base-nightly = { workspace = true }"
+ else:
+ msg = f"Invalid project name: {new_project_name}"
+ raise ValueError(msg)
+
+ # Updates the dependency name for uv
+ if not pattern.search(content):
+ msg = f"{replacement} uv dependency not found in {filepath}"
+ raise ValueError(msg)
+ content = pattern.sub(replacement, content)
+ filepath.write_text(content, encoding="utf-8")
+
+
+def main() -> None:
+ if len(sys.argv) != ARGUMENT_NUMBER:
+ msg = "Must specify project name and build type, e.g. langflow-nightly base"
+ raise ValueError(msg)
+ new_project_name = sys.argv[1]
+ build_type = sys.argv[2]
+
+ if build_type == "base":
+ update_pyproject_name("src/backend/base/pyproject.toml", new_project_name)
+ update_uv_dep("pyproject.toml", new_project_name)
+ elif build_type == "main":
+ update_pyproject_name("pyproject.toml", new_project_name)
+ update_uv_dep("pyproject.toml", new_project_name)
+ else:
+ msg = f"Invalid build type: {build_type}"
+ raise ValueError(msg)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/langflow/scripts/ci/update_pyproject_version.py b/langflow/scripts/ci/update_pyproject_version.py
new file mode 100644
index 0000000..79cbbdc
--- /dev/null
+++ b/langflow/scripts/ci/update_pyproject_version.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+import re
+import sys
+from pathlib import Path
+
+import packaging.version
+
+BASE_DIR = Path(__file__).parent.parent.parent
+ARGUMENT_NUMBER = 3
+
+
+def update_pyproject_version(pyproject_path: str, new_version: str) -> None:
+ """Update the version in pyproject.toml."""
+ filepath = BASE_DIR / pyproject_path
+ content = filepath.read_text(encoding="utf-8")
+
+ # Regex to match the version line under [tool.poetry]
+ pattern = re.compile(r'(?<=^version = ")[^"]+(?=")', re.MULTILINE)
+
+ if not pattern.search(content):
+ msg = f'Project version not found in "{filepath}"'
+ raise ValueError(msg)
+
+ content = pattern.sub(new_version, content)
+
+ filepath.write_text(content, encoding="utf-8")
+
+
+def verify_pep440(version):
+ """Verify if version is PEP440 compliant.
+
+ https://github.com/pypa/packaging/blob/16.7/packaging/version.py#L191
+ """
+ return packaging.version.Version(version)
+
+
+def main() -> None:
+ if len(sys.argv) != ARGUMENT_NUMBER:
+ msg = "New version not specified"
+ raise ValueError(msg)
+ new_version = sys.argv[1]
+
+ # Strip "v" prefix from version if present
+ new_version = new_version.removeprefix("v")
+
+ build_type = sys.argv[2]
+
+ verify_pep440(new_version)
+
+ if build_type == "base":
+ update_pyproject_version("src/backend/base/pyproject.toml", new_version)
+ elif build_type == "main":
+ update_pyproject_version("pyproject.toml", new_version)
+ else:
+ msg = f"Invalid build type: {build_type}"
+ raise ValueError(msg)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/langflow/scripts/ci/update_starter_projects.py b/langflow/scripts/ci/update_starter_projects.py
new file mode 100644
index 0000000..b001ebf
--- /dev/null
+++ b/langflow/scripts/ci/update_starter_projects.py
@@ -0,0 +1,43 @@
+"""Script to update Langflow starter projects with the latest component versions."""
+
+import asyncio
+import os
+
+import langflow.main # noqa: F401
+from langflow.initial_setup.setup import (
+ get_project_data,
+ load_starter_projects,
+ update_edges_with_latest_component_versions,
+ update_project_file,
+ update_projects_components_with_latest_component_versions,
+)
+from langflow.interface.components import get_and_cache_all_types_dict
+from langflow.services.deps import get_settings_service
+from langflow.services.utils import initialize_services
+
+
+async def main():
+ """Updates the starter projects with the latest component versions.
+
+ Copies the code from langflow/initial_setup/setup.py. Doesn't use the
+ create_or_update_starter_projects function directly to avoid sql interactions.
+ """
+ await initialize_services(fix_migration=False)
+ all_types_dict = await get_and_cache_all_types_dict(get_settings_service())
+
+ starter_projects = await load_starter_projects()
+ for project_path, project in starter_projects:
+ _, _, _, _, project_data, _, _, _, _ = get_project_data(project)
+ do_update_starter_projects = os.environ.get("LANGFLOW_UPDATE_STARTER_PROJECTS", "true").lower() == "true"
+ if do_update_starter_projects:
+ updated_project_data = update_projects_components_with_latest_component_versions(
+ project_data.copy(), all_types_dict
+ )
+ updated_project_data = update_edges_with_latest_component_versions(updated_project_data)
+ if updated_project_data != project_data:
+ project_data = updated_project_data
+ await update_project_file(project_path, project, updated_project_data)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/langflow/scripts/ci/update_uv_dependency.py b/langflow/scripts/ci/update_uv_dependency.py
new file mode 100644
index 0000000..c4ac2e8
--- /dev/null
+++ b/langflow/scripts/ci/update_uv_dependency.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import re
+import sys
+from pathlib import Path
+
+BASE_DIR = Path(__file__).parent.parent.parent
+ARGUMENT_NUMBER = 2
+
+
+def update_uv_dep(base_version: str) -> None:
+ """Update the langflow-base dependency in pyproject.toml."""
+ pyproject_path = BASE_DIR / "pyproject.toml"
+
+ # Read the pyproject.toml file content
+ content = pyproject_path.read_text(encoding="utf-8")
+
+ # For the main project, update the langflow-base dependency in the UV section
+ pattern = re.compile(r'(dependencies\s*=\s*\[\s*\n\s*)("langflow-base==[\d.]+")')
+ replacement = rf'\1"langflow-base-nightly=={base_version}"'
+
+ # Check if the pattern is found
+ if not pattern.search(content):
+ msg = f"{pattern} UV dependency not found in {pyproject_path}"
+ raise ValueError(msg)
+
+ # Replace the matched pattern with the new one
+ content = pattern.sub(replacement, content)
+
+ # Write the updated content back to the file
+ pyproject_path.write_text(content, encoding="utf-8")
+
+
+def main() -> None:
+ if len(sys.argv) != ARGUMENT_NUMBER:
+ msg = "specify base version"
+ raise ValueError(msg)
+ base_version = sys.argv[1]
+ base_version = base_version.lstrip("v")
+ update_uv_dep(base_version)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/langflow/scripts/factory_restart_space.py b/langflow/scripts/factory_restart_space.py
new file mode 100644
index 0000000..b7bdfbd
--- /dev/null
+++ b/langflow/scripts/factory_restart_space.py
@@ -0,0 +1,51 @@
+# /// script
+# requires-python = ">=3.10"
+# dependencies = [
+# "huggingface-hub", # Library for interacting with the Hugging Face Hub API.
+# "rich", # Library for colourful and formatted console output.
+# ]
+# ///
+
+import argparse
+import sys
+
+from huggingface_hub import HfApi, list_models # Import required functions and classes from huggingface-hub.
+from rich import print # noqa: A004
+
+# Fetch and list all models available on the Hugging Face Hub.
+# This part is unrelated to restarting a space but demonstrates the usage of list_models.
+models = list_models()
+
+# Initialize an argument parser to handle command-line inputs.
+args = argparse.ArgumentParser(description="Restart a space in the Hugging Face Hub.")
+args.add_argument("--space", type=str, help="The space to restart.") # Argument for specifying the space name.
+args.add_argument("--token", type=str, help="The Hugging Face API token.") # Argument for providing the API token.
+
+# Parse the command-line arguments.
+parsed_args = args.parse_args()
+
+# Extract the space name from the parsed arguments.
+space = parsed_args.space
+
+# Check if the space name is provided; exit with an error message if not.
+if not space:
+ print("Please provide a space to restart.")
+ sys.exit()
+
+# Check if the API token is provided; exit with an error message if not.
+if not parsed_args.token:
+ print("Please provide an API token.")
+ sys.exit()
+
+# Create an instance of the HfApi class to interact with the Hugging Face Hub.
+hf_api = HfApi(
+ endpoint="https://huggingface.co", # Base endpoint URL for Hugging Face Hub.
+ token=parsed_args.token, # API token used for authentication.
+)
+
+# Restart the specified space with a factory reboot.
+# The `factory_reboot=True` option resets the space to its original state.
+space_runtime = hf_api.restart_space(space, factory_reboot=True)
+
+# Print the runtime status of the restarted space.
+print(space_runtime)
diff --git a/langflow/scripts/gcp/GCP_DEPLOYMENT.md b/langflow/scripts/gcp/GCP_DEPLOYMENT.md
new file mode 100644
index 0000000..a848d3d
--- /dev/null
+++ b/langflow/scripts/gcp/GCP_DEPLOYMENT.md
@@ -0,0 +1,29 @@
+# Run Langflow from a New Google Cloud Project
+
+This guide will help you set up a Langflow development VM in a Google Cloud Platform project using Google Cloud Shell.
+
+> **Note**: When Cloud Shell opens, be sure to select **Trust repo**. Some `gcloud` commands might not run in an ephemeral Cloud Shell environment.
+
+## Standard VM
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial.md)
+
+This script sets up a Debian-based VM with the Langflow package, Nginx, and the necessary configurations to run the Langflow Dev environment.
+
+
+
+## Spot/Preemptible Instance
+
+[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/langflow-ai/langflow&working_dir=scripts/gcp&shellonly=true&tutorial=walkthroughtutorial_spot.md)
+
+When running as a [spot (preemptible) instance](https://cloud.google.com/compute/docs/instances/preemptible), the code and VM will behave the same way as in a regular instance, executing the startup script to configure the environment, install necessary dependencies, and run the Langflow application. However, **due to the nature of spot instances, the VM may be terminated at any time if Google Cloud needs to reclaim the resources**. This makes spot instances suitable for fault-tolerant, stateless, or interruptible workloads that can handle unexpected terminations and restarts.
+
+## Pricing (approximate)
+
+> For a more accurate breakdown of costs, please use the [**GCP Pricing Calculator**](https://cloud.google.com/products/calculator) >
+
+| Component | Regular Cost (Hourly) | Regular Cost (Monthly) | Spot/Preemptible Cost (Hourly) | Spot/Preemptible Cost (Monthly) | Notes |
+| ------------------ | --------------------- | ---------------------- | ------------------------------ | ------------------------------- | -------------------------------------------------------------------------- |
+| 100 GB Disk | - | $10/month | - | $10/month | Disk cost remains the same for both regular and Spot/Preemptible VMs |
+| VM (n1-standard-4) | $0.15/hr | ~$108/month | ~$0.04/hr | ~$29/month | The VM cost can be significantly reduced using a Spot/Preemptible instance |
+| **Total** | **$0.15/hr** | **~$118/month** | **~$0.04/hr** | **~$39/month** | Total costs for running the VM and disk 24/7 for an entire month |
diff --git a/langflow/scripts/gcp/deploy_langflow_gcp.sh b/langflow/scripts/gcp/deploy_langflow_gcp.sh
new file mode 100644
index 0000000..cdb8e20
--- /dev/null
+++ b/langflow/scripts/gcp/deploy_langflow_gcp.sh
@@ -0,0 +1,74 @@
+# Set the VM, image, and networking configuration
+VM_NAME="langflow-dev"
+IMAGE_FAMILY="debian-11"
+IMAGE_PROJECT="debian-cloud"
+BOOT_DISK_SIZE="100GB"
+ZONE="us-central1-a"
+REGION="us-central1"
+VPC_NAME="default"
+SUBNET_NAME="default"
+SUBNET_RANGE="10.128.0.0/20"
+NAT_GATEWAY_NAME="nat-gateway"
+CLOUD_ROUTER_NAME="nat-client"
+
+# Set the GCP project's compute region
+gcloud config set compute/region $REGION
+
+# Check if the VPC exists, and create it if not
+vpc_exists=$(gcloud compute networks list --filter="name=$VPC_NAME" --format="value(name)")
+if [[ -z "$vpc_exists" ]]; then
+ gcloud compute networks create $VPC_NAME --subnet-mode=custom
+fi
+
+# Check if the subnet exists, and create it if not
+subnet_exists=$(gcloud compute networks subnets list --filter="name=$SUBNET_NAME AND region=$REGION" --format="value(name)")
+if [[ -z "$subnet_exists" ]]; then
+ gcloud compute networks subnets create $SUBNET_NAME --network=$VPC_NAME --region=$REGION --range=$SUBNET_RANGE
+fi
+
+# Create a firewall rule to allow TCP port 7860 for all instances in the VPC
+firewall_7860_exists=$(gcloud compute firewall-rules list --filter="name=allow-tcp-7860" --format="value(name)")
+if [[ -z "$firewall_7860_exists" ]]; then
+ gcloud compute firewall-rules create allow-tcp-7860 --network $VPC_NAME --allow tcp:7860 --source-ranges 0.0.0.0/0 --direction INGRESS
+fi
+
+# Create a firewall rule to allow IAP traffic
+firewall_iap_exists=$(gcloud compute firewall-rules list --filter="name=allow-iap" --format="value(name)")
+if [[ -z "$firewall_iap_exists" ]]; then
+ gcloud compute firewall-rules create allow-iap --network $VPC_NAME --allow tcp:80,tcp:443,tcp:22,tcp:3389 --source-ranges 35.235.240.0/20 --direction INGRESS
+fi
+
+# Define the startup script as a multiline Bash here-doc
+STARTUP_SCRIPT=$(cat <<'EOF'
+#!/bin/bash
+
+# Update and upgrade the system
+apt -y update
+apt -y upgrade
+
+# Install Python 3 pip, Langflow, and Nginx
+apt -y install python3-pip
+pip3 install pip -U
+apt -y update
+pip3 install langflow
+langflow run --host 0.0.0.0 --port 7860
+EOF
+)
+
+# Create a temporary file to store the startup script
+tempfile=$(mktemp)
+echo "$STARTUP_SCRIPT" > $tempfile
+
+# Create the VM instance with the specified configuration and startup script
+gcloud compute instances create $VM_NAME \
+ --image-family $IMAGE_FAMILY \
+ --image-project $IMAGE_PROJECT \
+ --boot-disk-size $BOOT_DISK_SIZE \
+ --machine-type=n1-standard-4 \
+ --metadata-from-file startup-script=$tempfile \
+ --zone $ZONE \
+ --network $VPC_NAME \
+ --subnet $SUBNET_NAME
+
+# Remove the temporary file after the VM is created
+rm $tempfile
diff --git a/langflow/scripts/gcp/deploy_langflow_gcp_spot.sh b/langflow/scripts/gcp/deploy_langflow_gcp_spot.sh
new file mode 100644
index 0000000..6a25899
--- /dev/null
+++ b/langflow/scripts/gcp/deploy_langflow_gcp_spot.sh
@@ -0,0 +1,73 @@
+# Set the VM, image, and networking configuration
+VM_NAME="langflow-dev"
+IMAGE_FAMILY="debian-11"
+IMAGE_PROJECT="debian-cloud"
+BOOT_DISK_SIZE="100GB"
+ZONE="us-central1-a"
+REGION="us-central1"
+VPC_NAME="default"
+SUBNET_NAME="default"
+SUBNET_RANGE="10.128.0.0/20"
+NAT_GATEWAY_NAME="nat-gateway"
+CLOUD_ROUTER_NAME="nat-client"
+
+# Set the GCP project's compute region
+gcloud config set compute/region $REGION
+
+# Check if the VPC exists, and create it if not
+vpc_exists=$(gcloud compute networks list --filter="name=$VPC_NAME" --format="value(name)")
+if [[ -z "$vpc_exists" ]]; then
+ gcloud compute networks create $VPC_NAME --subnet-mode=custom
+fi
+
+# Check if the subnet exists, and create it if not
+subnet_exists=$(gcloud compute networks subnets list --filter="name=$SUBNET_NAME AND region=$REGION" --format="value(name)")
+if [[ -z "$subnet_exists" ]]; then
+ gcloud compute networks subnets create $SUBNET_NAME --network=$VPC_NAME --region=$REGION --range=$SUBNET_RANGE
+fi
+
+# Create a firewall rule to allow TCP port 7860 for all instances in the VPC
+firewall_7860_exists=$(gcloud compute firewall-rules list --filter="name=allow-tcp-7860" --format="value(name)")
+if [[ -z "$firewall_7860_exists" ]]; then
+ gcloud compute firewall-rules create allow-tcp-7860 --network $VPC_NAME --allow tcp:7860 --source-ranges 0.0.0.0/0 --direction INGRESS
+fi
+
+# Create a firewall rule to allow IAP traffic
+firewall_iap_exists=$(gcloud compute firewall-rules list --filter="name=allow-iap" --format="value(name)")
+if [[ -z "$firewall_iap_exists" ]]; then
+ gcloud compute firewall-rules create allow-iap --network $VPC_NAME --allow tcp:80,tcp:443,tcp:22,tcp:3389 --source-ranges 35.235.240.0/20 --direction INGRESS
+fi
+
+# Define the startup script as a multiline Bash here-doc
+STARTUP_SCRIPT=$(cat <<'EOF'
+#!/bin/bash
+
+# Update and upgrade the system
+apt -y update
+apt -y upgrade
+
+# Install Python 3 pip, Langflow, and Nginx
+apt -y install python3-pip
+pip install langflow
+langflow --host 0.0.0.0 --port 7860
+EOF
+)
+
+# Create a temporary file to store the startup script
+tempfile=$(mktemp)
+echo "$STARTUP_SCRIPT" > $tempfile
+
+# Create the VM instance with the specified configuration and startup script
+gcloud compute instances create $VM_NAME \
+ --image-family $IMAGE_FAMILY \
+ --image-project $IMAGE_PROJECT \
+ --boot-disk-size $BOOT_DISK_SIZE \
+ --machine-type=n1-standard-4 \
+ --metadata-from-file startup-script=$tempfile \
+ --zone $ZONE \
+ --network $VPC_NAME \
+ --subnet $SUBNET_NAME \
+ --preemptible
+
+# Remove the temporary file after the VM is created
+rm $tempfile
diff --git a/langflow/scripts/gcp/walkthroughtutorial.md b/langflow/scripts/gcp/walkthroughtutorial.md
new file mode 100644
index 0000000..83ea308
--- /dev/null
+++ b/langflow/scripts/gcp/walkthroughtutorial.md
@@ -0,0 +1,86 @@
+# Deploy Langflow on Google Cloud Platform
+
+**Duration**: 45 minutes
+**Author**: [Robert Wilkins III](https://www.linkedin.com/in/robertwilkinsiii)
+
+## Introduction
+
+In this tutorial, you will learn how to deploy Langflow on [Google Cloud Platform](https://cloud.google.com/) (GCP) using Google Cloud Shell.
+
+This tutorial assumes you have a GCP account and basic knowledge of Google Cloud Shell. If you're not familiar with Cloud Shell, you can review the [Cloud Shell documentation](https://cloud.google.com/shell/docs).
+
+## Set up your environment
+
+Before you start, make sure you have the following prerequisites:
+
+- A GCP account with the necessary permissions to create resources
+- A project on GCP where you want to deploy Langflow
+
+[**Select your GCP project**]
+
+
+
+In the next step, you'll configure the GCP environment and deploy Langflow.
+
+## Configure the GCP environment and deploy Langflow
+Run the deploy_langflow_gcp.sh script to configure the GCP environment and deploy Langflow:
+
+```sh
+gcloud config set project
+bash ./deploy_langflow_gcp.sh
+```
+
+The script will:
+
+1. Check if the required resources (VPC, subnet, firewall rules, and Cloud Router) exist and create them if needed
+2. Create a startup script to install Python, Langflow, and Nginx
+3. Create a Compute Engine VM instance with the specified configuration and startup script
+4. Run Langflow to serve content on TCP port 7860
+
+
+> The process may take approximately 30 minutes to complete. Rest assured that progress is being made, and you'll be able to proceed once the process is finished.
+
+In the next step, you'll learn how to connect to the Langflow VM.
+
+## Connect to the Langflow VM
+To connect to your new Langflow VM, follow these steps:
+
+1. Navigate to the [VM instances](https://console.cloud.google.com/compute/instances) page and click on the external IP for your VM. Make sure to use HTTP and set the port to 7860
+ **or**
+3. Run the following command to display the URL for your Langflow environment:
+```bash
+export LANGFLOW_IP=$(gcloud compute instances list --filter="NAME=langflow-dev" --format="value(EXTERNAL_IP)")
+
+echo http://$LANGFLOW_IP:7860
+```
+
+4. Click on the Langflow URL in cloudshell to be greeted by the Langflow Dev environment
+
+Congratulations! You have successfully deployed Langflow on Google Cloud Platform.
+
+
+
+## Cleanup
+If you want to remove the resources created during this tutorial, you can use the following commands:
+
+```sql
+gcloud compute instances delete langflow-dev --zone us-central1-a --quiet
+```
+The following network settings and services are used during this walkthrough. If you plan to continue using the project after the walkthrough, you may keep these configurations in place.
+
+However, if you decide to remove them after completing the walkthrough, you can use the following gcloud commands:
+
+
+> These commands will delete the firewall rules and network configurations created during the walkthrough. Make sure to run them only if you no longer need these settings.
+
+```
+gcloud compute firewall-rules delete allow-tcp-7860 --quiet
+
+gcloud compute firewall-rules delete allow-iap --quiet
+
+gcloud compute networks subnets delete default --region us-central1 --quiet
+
+gcloud compute networks delete default --quiet
+```
diff --git a/langflow/scripts/gcp/walkthroughtutorial_spot.md b/langflow/scripts/gcp/walkthroughtutorial_spot.md
new file mode 100644
index 0000000..3792bc1
--- /dev/null
+++ b/langflow/scripts/gcp/walkthroughtutorial_spot.md
@@ -0,0 +1,83 @@
+# Deploy Langflow on Google Cloud Platform
+
+**Duration**: 45 minutes
+**Author**: [Robert Wilkins III](https://www.linkedin.com/in/robertwilkinsiii)
+
+## Introduction
+
+In this tutorial, you will learn how to deploy Langflow on [Google Cloud Platform](https://cloud.google.com/) (GCP) using Google Cloud Shell.
+
+This tutorial assumes you have a GCP account and basic knowledge of Google Cloud Shell. If you're not familiar with Cloud Shell, you can review the [Cloud Shell documentation](https://cloud.google.com/shell/docs).
+
+## Set up your environment
+
+Before you start, make sure you have the following prerequisites:
+
+- A GCP account with the necessary permissions to create resources
+- A project on GCP where you want to deploy Langflow
+
+[**Select your GCP project**]
+
+
+
+In the next step, you'll configure the GCP environment and deploy Langflow.
+
+## Configure the GCP environment and deploy Langflow
+Run the deploy_langflow_gcp_spot.sh script to configure the GCP environment and deploy Langflow:
+
+```sh
+gcloud config set project
+bash ./deploy_langflow_gcp_spot.sh
+```
+
+The script will:
+
+1. Check if the required resources (VPC, subnet, firewall rules, and Cloud Router) exist and create them if needed
+2. Create a startup script to install Python, Langflow, and Nginx
+3. Create a Compute Engine VM instance with the specified configuration and startup script
+4. Run Langflow to serve content on TCP port 7860
+
+> The process may take approximately 30 minutes to complete. Rest assured that progress is being made, and you'll be able to proceed once the process is finished.
+
+In the next step, you'll learn how to connect to the Langflow VM.
+
+## Connect to the Langflow VM
+To connect to your new Langflow VM, follow these steps:
+
+1. Navigate to the [VM instances](https://console.cloud.google.com/compute/instances) page and click on the external IP for your VM. Make sure to use HTTP and set the port to 7860
+ **or**
+3. Run the following command to display the URL for your Langflow environment:
+```bash
+export LANGFLOW_IP=$(gcloud compute instances list --filter="NAME=langflow-dev" --format="value(EXTERNAL_IP)")
+
+echo http://$LANGFLOW_IP:7860
+```
+
+4. Click on the Langflow URL in cloudshell to be greeted by the Langflow Dev environment
+
+Congratulations! You have successfully deployed Langflow on Google Cloud Platform.
+
+
+
+## Cleanup
+If you want to remove the resources created during this tutorial, you can use the following commands:
+
+```sql
+gcloud compute instances delete langflow-dev --zone us-central1-a --quiet
+```
+The following network settings and services are used during this walkthrough. If you plan to continue using the project after the walkthrough, you may keep these configurations in place.
+
+However, if you decide to remove them after completing the walkthrough, you can use the following gcloud commands:
+> These commands will delete the firewall rules and network configurations created during the walkthrough. Make sure to run them only if you no longer need these settings.
+
+```
+gcloud compute firewall-rules delete allow-tcp-7860 --quiet
+
+gcloud compute firewall-rules delete allow-iap --quiet
+
+gcloud compute networks subnets delete default --region us-central1 --quiet
+
+gcloud compute networks delete default --quiet
+```
diff --git a/langflow/scripts/setup/check_env.sh b/langflow/scripts/setup/check_env.sh
new file mode 100644
index 0000000..af0191a
--- /dev/null
+++ b/langflow/scripts/setup/check_env.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Detect and use appropriate Python interpreter from virtual environments
+if [ -n "$VIRTUAL_ENV" ]; then
+ PYTHON_EXEC=python
+elif [ -n "$CONDA_DEFAULT_ENV" ]; then
+ PYTHON_EXEC=conda run -n "$CONDA_DEFAULT_ENV" python
+elif [ -f "Pipfile" ]; then
+ PYTHON_EXEC=pipenv run python
+elif [ -d ".pyenv" ]; then
+ PYTHON_EXEC=pyenv exec python
+else
+ PYTHON_EXEC=python
+fi
+
+# Check if Python version is compatible
+REQUIRED_VERSION=$1
+PYTHON_INSTALLED=$($PYTHON_EXEC -c "import sys; print(sys.version.split()[0])")
+
+echo "Detected Python version: $PYTHON_INSTALLED"
+
+$PYTHON_EXEC -c "
+import sys
+from distutils.version import LooseVersion
+
+required_version = '$REQUIRED_VERSION'
+python_installed = '$PYTHON_INSTALLED'
+
+min_version, max_version = required_version.replace('>=', '').replace('<', '').split(',')
+if not (LooseVersion(min_version) <= LooseVersion(python_installed) < LooseVersion(max_version)):
+ sys.exit(f'Error: Python version {python_installed} is not compatible with required version {required_version}.')
+" || exit 1
+
diff --git a/langflow/scripts/setup/setup_env.sh b/langflow/scripts/setup/setup_env.sh
new file mode 100644
index 0000000..a594fc1
--- /dev/null
+++ b/langflow/scripts/setup/setup_env.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Create a .env if it doesn't exist, log all cases
+if [ ! -f .env ]; then
+ echo "Creating .env file"
+ touch .env
+else
+ # do nothing and do not log
+ true
+fi
diff --git a/langflow/src/backend/.gitignore b/langflow/src/backend/.gitignore
new file mode 100644
index 0000000..ac0cc6c
--- /dev/null
+++ b/langflow/src/backend/.gitignore
@@ -0,0 +1,134 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+notebooks
+
+# frontend
+src/frontend
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+*.db
\ No newline at end of file
diff --git a/langflow/src/backend/Dockerfile b/langflow/src/backend/Dockerfile
new file mode 100644
index 0000000..ba1f3c9
--- /dev/null
+++ b/langflow/src/backend/Dockerfile
@@ -0,0 +1,14 @@
+FROM langflowai/backend_build as backend_build
+
+FROM python:3.10-slim
+WORKDIR /app
+
+RUN apt-get update && apt-get install git -y
+
+COPY --from=backend_build /app/dist/*.whl /app/
+RUN pip install langflow-*.whl
+RUN rm *.whl
+
+EXPOSE 80
+
+CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "7860", "--factory", "langflow.main:create_app" ]
diff --git a/langflow/src/backend/base/README.md b/langflow/src/backend/base/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/__init__.py b/langflow/src/backend/base/langflow/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/__main__.py b/langflow/src/backend/base/langflow/__main__.py
new file mode 100644
index 0000000..cbfe65d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/__main__.py
@@ -0,0 +1,646 @@
+import asyncio
+import inspect
+import os
+import platform
+import signal
+import socket
+import sys
+import time
+import warnings
+from contextlib import suppress
+from pathlib import Path
+
+import click
+import httpx
+import typer
+from dotenv import load_dotenv
+from httpx import HTTPError
+from multiprocess import cpu_count
+from multiprocess.context import Process
+from packaging import version as pkg_version
+from rich import box
+from rich import print as rprint
+from rich.console import Console
+from rich.panel import Panel
+from rich.table import Table
+from sqlmodel import select
+
+from langflow.initial_setup.setup import get_or_create_default_folder
+from langflow.logging.logger import configure, logger
+from langflow.main import setup_app
+from langflow.services.database.utils import session_getter
+from langflow.services.deps import get_db_service, get_settings_service, session_scope
+from langflow.services.settings.constants import DEFAULT_SUPERUSER
+from langflow.services.utils import initialize_services
+from langflow.utils.version import fetch_latest_version, get_version_info
+from langflow.utils.version import is_pre_release as langflow_is_pre_release
+
+console = Console()
+
+app = typer.Typer(no_args_is_help=True)
+
+
+def get_number_of_workers(workers=None):
+ if workers == -1 or workers is None:
+ workers = (cpu_count() * 2) + 1
+ logger.debug(f"Number of workers: {workers}")
+ return workers
+
+
+def display_results(results) -> None:
+ """Display the results of the migration."""
+ for table_results in results:
+ table = Table(title=f"Migration {table_results.table_name}")
+ table.add_column("Name")
+ table.add_column("Type")
+ table.add_column("Status")
+
+ for result in table_results.results:
+ status = "Success" if result.success else "Failure"
+ color = "green" if result.success else "red"
+ table.add_row(result.name, result.type, f"[{color}]{status}[/{color}]")
+
+ console.print(table)
+ console.print() # Print a new line
+
+
+def set_var_for_macos_issue() -> None:
+ # OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
+ # we need to set this var is we are running on MacOS
+ # otherwise we get an error when running gunicorn
+
+ if platform.system() == "Darwin":
+ import os
+
+ os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
+ # https://stackoverflow.com/questions/75747888/uwsgi-segmentation-fault-with-flask-python-app-behind-nginx-after-running-for-2 # noqa: E501
+ os.environ["no_proxy"] = "*" # to avoid error with gunicorn
+ logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
+
+
+def handle_sigterm(signum, frame): # noqa: ARG001
+ """Handle SIGTERM signal gracefully."""
+ logger.info("Received SIGTERM signal. Performing graceful shutdown...")
+ # Raise SystemExit to trigger graceful shutdown
+ sys.exit(0)
+
+
+@app.command()
+def run(
+ *,
+ host: str | None = typer.Option(None, help="Host to bind the server to.", show_default=False),
+ workers: int | None = typer.Option(None, help="Number of worker processes.", show_default=False),
+ worker_timeout: int | None = typer.Option(None, help="Worker timeout in seconds.", show_default=False),
+ port: int | None = typer.Option(None, help="Port to listen on.", show_default=False),
+ components_path: Path | None = typer.Option(
+ Path(__file__).parent / "components",
+ help="Path to the directory containing custom components.",
+ show_default=False,
+ ),
+ # .env file param
+ env_file: Path | None = typer.Option(
+ None,
+ help="Path to the .env file containing environment variables.",
+ show_default=False,
+ ),
+ log_level: str | None = typer.Option(None, help="Logging level.", show_default=False),
+ log_file: Path | None = typer.Option(None, help="Path to the log file.", show_default=False),
+ cache: str | None = typer.Option( # noqa: ARG001
+ None,
+ help="Type of cache to use. (InMemoryCache, SQLiteCache)",
+ show_default=False,
+ ),
+ dev: bool | None = typer.Option(None, help="Run in development mode (may contain bugs)", show_default=False), # noqa: ARG001
+ frontend_path: str | None = typer.Option(
+ None,
+ help="Path to the frontend directory containing build files. This is for development purposes only.",
+ show_default=False,
+ ),
+ open_browser: bool | None = typer.Option(
+ None,
+ help="Open the browser after starting the server.",
+ show_default=False,
+ ),
+ remove_api_keys: bool | None = typer.Option( # noqa: ARG001
+ None,
+ help="Remove API keys from the projects saved in the database.",
+ show_default=False,
+ ),
+ backend_only: bool | None = typer.Option(
+ None,
+ help="Run only the backend server without the frontend.",
+ show_default=False,
+ ),
+ store: bool | None = typer.Option( # noqa: ARG001
+ None,
+ help="Enables the store features.",
+ show_default=False,
+ ),
+ auto_saving: bool | None = typer.Option( # noqa: ARG001
+ None,
+ help="Defines if the auto save is enabled.",
+ show_default=False,
+ ),
+ auto_saving_interval: int | None = typer.Option( # noqa: ARG001
+ None,
+ help="Defines the debounce time for the auto save.",
+ show_default=False,
+ ),
+ health_check_max_retries: bool | None = typer.Option( # noqa: ARG001
+ None,
+ help="Defines the number of retries for the health check.",
+ show_default=False,
+ ),
+ max_file_size_upload: int | None = typer.Option( # noqa: ARG001
+ None,
+ help="Defines the maximum file size for the upload in MB.",
+ show_default=False,
+ ),
+) -> None:
+ """Run Langflow."""
+ # Register SIGTERM handler
+ signal.signal(signal.SIGTERM, handle_sigterm)
+
+ if env_file:
+ load_dotenv(env_file, override=True)
+
+ configure(log_level=log_level, log_file=log_file)
+ logger.debug(f"Loading config from file: '{env_file}'" if env_file else "No env_file provided.")
+ set_var_for_macos_issue()
+ settings_service = get_settings_service()
+
+ for key, value in os.environ.items():
+ new_key = key.replace("LANGFLOW_", "")
+ if hasattr(settings_service.auth_settings, new_key):
+ setattr(settings_service.auth_settings, new_key, value)
+
+ frame = inspect.currentframe()
+ valid_args: list = []
+ values: dict = {}
+ if frame is not None:
+ arguments, _, _, values = inspect.getargvalues(frame)
+ valid_args = [arg for arg in arguments if values[arg] is not None]
+
+ for arg in valid_args:
+ if arg == "components_path":
+ settings_service.settings.update_settings(components_path=components_path)
+ elif hasattr(settings_service.settings, arg):
+ settings_service.set(arg, values[arg])
+ elif hasattr(settings_service.auth_settings, arg):
+ settings_service.auth_settings.set(arg, values[arg])
+ logger.debug(f"Loading config from cli parameter '{arg}': '{values[arg]}'")
+
+ host = settings_service.settings.host
+ port = settings_service.settings.port
+ workers = settings_service.settings.workers
+ worker_timeout = settings_service.settings.worker_timeout
+ log_level = settings_service.settings.log_level
+ frontend_path = settings_service.settings.frontend_path
+ backend_only = settings_service.settings.backend_only
+
+ # create path object if frontend_path is provided
+ static_files_dir: Path | None = Path(frontend_path) if frontend_path else None
+
+ app = setup_app(static_files_dir=static_files_dir, backend_only=backend_only)
+ # check if port is being used
+ if is_port_in_use(port, host):
+ port = get_free_port(port)
+
+ options = {
+ "bind": f"{host}:{port}",
+ "workers": get_number_of_workers(workers),
+ "timeout": worker_timeout,
+ }
+
+ # Define an env variable to know if we are just testing the server
+ if "pytest" in sys.modules:
+ return
+ process: Process | None = None
+ try:
+ if platform.system() == "Windows":
+ # Run using uvicorn on MacOS and Windows
+ # Windows doesn't support gunicorn
+ # MacOS requires an env variable to be set to use gunicorn
+ run_on_windows(host, port, log_level, options, app)
+ else:
+ # Run using gunicorn on Linux
+ process = run_on_mac_or_linux(host, port, log_level, options, app)
+ if open_browser and not backend_only:
+ click.launch(f"http://{host}:{port}")
+ if process:
+ process.join()
+ except (KeyboardInterrupt, SystemExit) as e:
+ logger.info("Shutting down server...")
+ if process is not None:
+ process.terminate()
+ process.join(timeout=15) # Wait up to 15 seconds for process to terminate
+ if process.is_alive():
+ logger.warning("Process did not terminate gracefully, forcing...")
+ process.kill()
+ raise typer.Exit(0) from e
+ except Exception as e:
+ logger.exception(e)
+ if process is not None:
+ process.terminate()
+ raise typer.Exit(1) from e
+
+
+def wait_for_server_ready(host, port) -> None:
+ """Wait for the server to become ready by polling the health endpoint."""
+ status_code = 0
+ while status_code != httpx.codes.OK:
+ try:
+ status_code = httpx.get(f"http://{host}:{port}/health").status_code
+ except HTTPError:
+ time.sleep(1)
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).debug("Error while waiting for the server to become ready.")
+ time.sleep(1)
+
+
+def run_on_mac_or_linux(host, port, log_level, options, app):
+ webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
+ webapp_process.start()
+ wait_for_server_ready(host, port)
+
+ print_banner(host, port)
+ return webapp_process
+
+
+def run_on_windows(host, port, log_level, options, app) -> None:
+ """Run the Langflow server on Windows."""
+ print_banner(host, port)
+ run_langflow(host, port, log_level, options, app)
+
+
+def is_port_in_use(port, host="localhost"):
+ """Check if a port is in use.
+
+ Args:
+ port (int): The port number to check.
+ host (str): The host to check the port on. Defaults to 'localhost'.
+
+ Returns:
+ bool: True if the port is in use, False otherwise.
+ """
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ return s.connect_ex((host, port)) == 0
+
+
+def get_free_port(port):
+ """Given a used port, find a free port.
+
+ Args:
+ port (int): The port number to check.
+
+ Returns:
+ int: A free port number.
+ """
+ while is_port_in_use(port):
+ port += 1
+ return port
+
+
+def get_letter_from_version(version: str) -> str | None:
+ """Get the letter from a pre-release version."""
+ if "a" in version:
+ return "a"
+ if "b" in version:
+ return "b"
+ if "rc" in version:
+ return "rc"
+ return None
+
+
+def build_version_notice(current_version: str, package_name: str) -> str:
+ """Build a version notice message if a newer version is available.
+
+ This function checks if there is a newer version of the package available on PyPI
+ and returns an appropriate notice message.
+
+ Args:
+ current_version (str): The currently installed version of the package
+ package_name (str): The name of the package to check
+
+ Returns:
+ str: A notice message if a newer version is available, empty string otherwise.
+ The message will indicate if the newer version is a pre-release.
+
+ Example:
+ >>> build_version_notice("1.0.0", "langflow")
+ 'A new version of langflow is available: 1.1.0'
+ """
+ with suppress(httpx.ConnectError):
+ latest_version = fetch_latest_version(package_name, include_prerelease=langflow_is_pre_release(current_version))
+ if latest_version and pkg_version.parse(current_version) < pkg_version.parse(latest_version):
+ release_type = "pre-release" if langflow_is_pre_release(latest_version) else "version"
+ return f"A new {release_type} of {package_name} is available: {latest_version}"
+ return ""
+
+
+def generate_pip_command(package_names, is_pre_release) -> str:
+ """Generate the pip install command based on the packages and whether it's a pre-release."""
+ base_command = "pip install"
+ if is_pre_release:
+ return f"{base_command} {' '.join(package_names)} -U --pre"
+ return f"{base_command} {' '.join(package_names)} -U"
+
+
+def stylize_text(text: str, to_style: str, *, is_prerelease: bool) -> str:
+ color = "#42a7f5" if is_prerelease else "#6e42f5"
+ # return "".join(f"[{color}]{char}[/]" for char in text)
+ styled_text = f"[{color}]{to_style}[/]"
+ return text.replace(to_style, styled_text)
+
+
+def print_banner(host: str, port: int) -> None:
+ notices = []
+ package_names = [] # Track package names for pip install instructions
+ is_pre_release = False # Track if any package is a pre-release
+ package_name = ""
+
+ # Use langflow.utils.version to get the version info
+ version_info = get_version_info()
+ langflow_version = version_info["version"]
+ package_name = version_info["package"]
+ is_pre_release |= langflow_is_pre_release(langflow_version) # Update pre-release status
+
+ notice = build_version_notice(langflow_version, package_name)
+
+ notice = stylize_text(notice, package_name, is_prerelease=is_pre_release)
+ if notice:
+ notices.append(notice)
+ package_names.append(package_name)
+
+ # Generate pip command based on the collected data
+ pip_command = generate_pip_command(package_names, is_pre_release)
+
+ # Add pip install command to notices if any package needs an update
+ if notices:
+ notices.append(f"Run '{pip_command}' to update.")
+
+ styled_notices = [f"[bold]{notice}[/bold]" for notice in notices if notice]
+ styled_package_name = stylize_text(
+ package_name, package_name, is_prerelease=any("pre-release" in notice for notice in notices)
+ )
+
+ title = f"[bold]Welcome to :chains: {styled_package_name}[/bold]\n"
+ info_text = (
+ "Collaborate, and contribute at our "
+ "[bold][link=https://github.com/langflow-ai/langflow]GitHub Repo[/link][/bold] :star2:"
+ )
+ telemetry_text = (
+ "We collect anonymous usage data to improve Langflow.\n"
+ "You can opt-out by setting [bold]DO_NOT_TRACK=true[/bold] in your environment."
+ )
+ access_link = f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]"
+
+ panel_content = "\n\n".join([title, *styled_notices, info_text, telemetry_text, access_link])
+ panel = Panel(panel_content, box=box.ROUNDED, border_style="blue", expand=False)
+ rprint(panel)
+
+
+def run_langflow(host, port, log_level, options, app) -> None:
+ """Run Langflow server on localhost."""
+ if platform.system() == "Windows":
+ import uvicorn
+
+ uvicorn.run(
+ app,
+ host=host,
+ port=port,
+ log_level=log_level.lower(),
+ loop="asyncio",
+ )
+ else:
+ from langflow.server import LangflowApplication
+
+ server = LangflowApplication(app, options)
+
+ def graceful_shutdown(signum, frame): # noqa: ARG001
+ """Gracefully shutdown the server when receiving SIGTERM."""
+ # Suppress click exceptions during shutdown
+ import click
+
+ click.echo = lambda *args, **kwargs: None # noqa: ARG005
+
+ logger.info("Gracefully shutting down server...")
+ # For Gunicorn workers, we raise SystemExit to trigger graceful shutdown
+ raise SystemExit(0)
+
+ # Register signal handlers
+ signal.signal(signal.SIGTERM, graceful_shutdown)
+ signal.signal(signal.SIGINT, graceful_shutdown)
+
+ try:
+ server.run()
+ except (KeyboardInterrupt, SystemExit):
+ # Suppress the exception output
+ sys.exit(0)
+
+
+@app.command()
+def superuser(
+ username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
+ password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
+ log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
+) -> None:
+ """Create a superuser."""
+ configure(log_level=log_level)
+ db_service = get_db_service()
+
+ async def _create_superuser():
+ await initialize_services()
+ async with session_getter(db_service) as session:
+ from langflow.services.auth.utils import create_super_user
+
+ if await create_super_user(db=session, username=username, password=password):
+ # Verify that the superuser was created
+ from langflow.services.database.models.user.model import User
+
+ stmt = select(User).where(User.username == username)
+ user: User = (await session.exec(stmt)).first()
+ if user is None or not user.is_superuser:
+ typer.echo("Superuser creation failed.")
+ return
+ # Now create the first folder for the user
+ result = await get_or_create_default_folder(session, user.id)
+ if result:
+ typer.echo("Default folder created successfully.")
+ else:
+ msg = "Could not create default folder."
+ raise RuntimeError(msg)
+ typer.echo("Superuser created successfully.")
+
+ else:
+ typer.echo("Superuser creation failed.")
+
+ asyncio.run(_create_superuser())
+
+
+# command to copy the langflow database from the cache to the current directory
+# because now the database is stored per installation
+@app.command()
+def copy_db() -> None:
+ """Copy the database files to the current directory.
+
+ This function copies the 'langflow.db' and 'langflow-pre.db' files from the cache directory to the current
+ directory.
+ If the files exist in the cache directory, they will be copied to the same directory as this script (__main__.py).
+
+ Returns:
+ None
+ """
+ import shutil
+
+ from platformdirs import user_cache_dir
+
+ cache_dir = Path(user_cache_dir("langflow"))
+ db_path = cache_dir / "langflow.db"
+ pre_db_path = cache_dir / "langflow-pre.db"
+ # It should be copied to the current directory
+ # this file is __main__.py and it should be in the same directory as the database
+ destination_folder = Path(__file__).parent
+ if db_path.exists():
+ shutil.copy(db_path, destination_folder)
+ typer.echo(f"Database copied to {destination_folder}")
+ else:
+ typer.echo("Database not found in the cache directory.")
+ if pre_db_path.exists():
+ shutil.copy(pre_db_path, destination_folder)
+ typer.echo(f"Pre-release database copied to {destination_folder}")
+ else:
+ typer.echo("Pre-release database not found in the cache directory.")
+
+
+async def _migration(*, test: bool, fix: bool) -> None:
+ await initialize_services(fix_migration=fix)
+ db_service = get_db_service()
+ if not test:
+ await db_service.run_migrations()
+ results = await db_service.run_migrations_test()
+ display_results(results)
+
+
+@app.command()
+def migration(
+ test: bool = typer.Option(default=True, help="Run migrations in test mode."), # noqa: FBT001
+ fix: bool = typer.Option( # noqa: FBT001
+ default=False,
+ help="Fix migrations. This is a destructive operation, and should only be used if you know what you are doing.",
+ ),
+) -> None:
+ """Run or test migrations."""
+ if fix and not typer.confirm(
+ "This will delete all data necessary to fix migrations. Are you sure you want to continue?"
+ ):
+ raise typer.Abort
+
+ asyncio.run(_migration(test=test, fix=fix))
+
+
+@app.command()
+def api_key(
+ log_level: str = typer.Option("error", help="Logging level."),
+) -> None:
+ """Creates an API key for the default superuser if AUTO_LOGIN is enabled.
+
+ Args:
+ log_level (str, optional): Logging level. Defaults to "error".
+
+ Returns:
+ None
+ """
+ configure(log_level=log_level)
+
+ async def aapi_key():
+ await initialize_services()
+ settings_service = get_settings_service()
+ auth_settings = settings_service.auth_settings
+ if not auth_settings.AUTO_LOGIN:
+ typer.echo("Auto login is disabled. API keys cannot be created through the CLI.")
+ return None
+
+ async with session_scope() as session:
+ from langflow.services.database.models.user.model import User
+
+ stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
+ superuser = (await session.exec(stmt)).first()
+ if not superuser:
+ typer.echo(
+ "Default superuser not found. This command requires a superuser and AUTO_LOGIN to be enabled."
+ )
+ return None
+ from langflow.services.database.models.api_key import ApiKey, ApiKeyCreate
+ from langflow.services.database.models.api_key.crud import create_api_key, delete_api_key
+
+ stmt = select(ApiKey).where(ApiKey.user_id == superuser.id)
+ api_key = (await session.exec(stmt)).first()
+ if api_key:
+ await delete_api_key(session, api_key.id)
+
+ api_key_create = ApiKeyCreate(name="CLI")
+ unmasked_api_key = await create_api_key(session, api_key_create, user_id=superuser.id)
+ await session.commit()
+ return unmasked_api_key
+
+ unmasked_api_key = asyncio.run(aapi_key())
+ # Create a banner to display the API key and tell the user it won't be shown again
+ api_key_banner(unmasked_api_key)
+
+
+def show_version(*, value: bool):
+ if value:
+ default = "DEV"
+ raw_info = get_version_info()
+ version = raw_info.get("version", default) if raw_info else default
+ typer.echo(f"langflow {version}")
+ raise typer.Exit
+
+
+@app.callback()
+def version_option(
+ *,
+ version: bool = typer.Option(
+ None,
+ "--version",
+ "-v",
+ callback=show_version,
+ is_eager=True,
+ help="Show the version and exit.",
+ ),
+):
+ pass
+
+
+def api_key_banner(unmasked_api_key) -> None:
+ is_mac = platform.system() == "Darwin"
+ import pyperclip
+
+ pyperclip.copy(unmasked_api_key.api_key)
+ panel = Panel(
+ f"[bold]API Key Created Successfully:[/bold]\n\n"
+ f"[bold blue]{unmasked_api_key.api_key}[/bold blue]\n\n"
+ "This is the only time the API key will be displayed. \n"
+ "Make sure to store it in a secure location. \n\n"
+ f"The API key has been copied to your clipboard. [bold]{['Ctrl', 'Cmd'][is_mac]} + V[/bold] to paste it.",
+ box=box.ROUNDED,
+ border_style="blue",
+ expand=False,
+ )
+ console = Console()
+ console.print(panel)
+
+
+def main() -> None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ app()
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except Exception as e:
+ logger.exception(e)
+ raise typer.Exit(1) from e
diff --git a/langflow/src/backend/base/langflow/alembic.ini b/langflow/src/backend/base/langflow/alembic.ini
new file mode 100644
index 0000000..6de6e6e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic.ini
@@ -0,0 +1,113 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = alembic
+
+# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
+# Uncomment the line below if you want the files to be prepended with date and time
+# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
+# for all available tokens
+# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
+
+# sys.path path, will be prepended to sys.path if present.
+# defaults to the current working directory.
+prepend_sys_path = .
+
+# timezone to use when rendering the date within the migration file
+# as well as the filename.
+# If specified, requires the python-dateutil library that can be
+# installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the
+# "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; This defaults
+# to alembic/versions. When using multiple version
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator" below.
+# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
+# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
+# Valid values for version_path_separator are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
+
+# set to 'true' to search source files recursively
+# in each "version_locations" directory
+# new in Alembic version 1.10
+# recursive_version_locations = false
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+# This is the path to the db in the root of the project.
+# When the user runs Langflow the database url will
+# be set dynamically.
+sqlalchemy.url = sqlite+aiosqlite:///./langflow.db
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts. See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks = black
+# black.type = console_scripts
+# black.entrypoint = black
+# black.options = -l 79 REVISION_SCRIPT_FILENAME
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = DEBUG
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/langflow/src/backend/base/langflow/alembic/README b/langflow/src/backend/base/langflow/alembic/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/alembic/env.py b/langflow/src/backend/base/langflow/alembic/env.py
new file mode 100644
index 0000000..affada9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/env.py
@@ -0,0 +1,120 @@
+# noqa: INP001
+import asyncio
+from logging.config import fileConfig
+
+from alembic import context
+from sqlalchemy import pool, text
+from sqlalchemy.event import listen
+from sqlalchemy.ext.asyncio import async_engine_from_config
+
+from langflow.services.database.service import SQLModel
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+if config.config_file_name is not None:
+ fileConfig(config.config_file_name)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = SQLModel.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline() -> None:
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(
+ url=url,
+ target_metadata=target_metadata,
+ literal_binds=True,
+ dialect_opts={"paramstyle": "named"},
+ render_as_batch=True,
+ prepare_threshold=None
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def _sqlite_do_connect(
+ dbapi_connection,
+ connection_record, # noqa: ARG001
+):
+ # disable pysqlite's emitting of the BEGIN statement entirely.
+ # also stops it from emitting COMMIT before any DDL.
+ dbapi_connection.isolation_level = None
+
+
+def _sqlite_do_begin(conn):
+ # emit our own BEGIN
+ conn.exec_driver_sql("PRAGMA busy_timeout = 60000")
+ conn.exec_driver_sql("BEGIN EXCLUSIVE")
+
+
+def _do_run_migrations(connection):
+ context.configure(
+ connection=connection,
+ target_metadata=target_metadata,
+ render_as_batch=True,
+ prepare_threshold=None
+ )
+
+ with context.begin_transaction():
+ if connection.dialect.name == "postgresql":
+ connection.execute(text("SET LOCAL lock_timeout = '60s';"))
+ connection.execute(text("SELECT pg_advisory_xact_lock(112233);"))
+ context.run_migrations()
+
+
+async def _run_async_migrations() -> None:
+ connectable = async_engine_from_config(
+ config.get_section(config.config_ini_section, {}),
+ prefix="sqlalchemy.",
+ poolclass=pool.NullPool,
+ )
+
+ if connectable.dialect.name == "sqlite":
+ # See https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl
+ listen(connectable.sync_engine, "connect", _sqlite_do_connect)
+ listen(connectable.sync_engine, "begin", _sqlite_do_begin)
+
+ async with connectable.connect() as connection:
+ await connection.run_sync(_do_run_migrations)
+
+ await connectable.dispose()
+
+
+def run_migrations_online() -> None:
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+ asyncio.run(_run_async_migrations())
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/langflow/src/backend/base/langflow/alembic/script.py.mako b/langflow/src/backend/base/langflow/alembic/script.py.mako
new file mode 100644
index 0000000..6086a86
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/script.py.mako
@@ -0,0 +1,31 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+from sqlalchemy.engine.reflection import Inspector
+from langflow.utils import migration
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision: str = ${repr(up_revision)}
+down_revision: Union[str, None] = ${repr(down_revision)}
+branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
+depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ ${downgrades if downgrades else "pass"}
diff --git a/langflow/src/backend/base/langflow/alembic/versions/006b3990db50_add_unique_constraints.py b/langflow/src/backend/base/langflow/alembic/versions/006b3990db50_add_unique_constraints.py
new file mode 100644
index 0000000..efb4c53
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/006b3990db50_add_unique_constraints.py
@@ -0,0 +1,66 @@
+"""Add unique constraints
+
+Revision ID: 006b3990db50
+Revises: 1ef9c4f3765d
+Create Date: 2023-12-13 18:55:52.587360
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "006b3990db50"
+down_revision: Union[str, None] = "1ef9c4f3765d"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ api_key_constraints = inspector.get_unique_constraints("apikey")
+ flow_constraints = inspector.get_unique_constraints("flow")
+ user_constraints = inspector.get_unique_constraints("user")
+ try:
+ if not any(constraint["column_names"] == ["id"] for constraint in api_key_constraints):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.create_unique_constraint("uq_apikey_id", ["id"])
+ if not any(constraint["column_names"] == ["id"] for constraint in flow_constraints):
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.create_unique_constraint("uq_flow_id", ["id"])
+ if not any(constraint["column_names"] == ["id"] for constraint in user_constraints):
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.create_unique_constraint("uq_user_id", ["id"])
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ api_key_constraints = inspector.get_unique_constraints("apikey")
+ flow_constraints = inspector.get_unique_constraints("flow")
+ user_constraints = inspector.get_unique_constraints("user")
+ try:
+ if any(constraint["name"] == "uq_apikey_id" for constraint in api_key_constraints):
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.drop_constraint("uq_user_id", type_="unique")
+ if any(constraint["name"] == "uq_flow_id" for constraint in flow_constraints):
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.drop_constraint("uq_flow_id", type_="unique")
+ if any(constraint["name"] == "uq_user_id" for constraint in user_constraints):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.drop_constraint("uq_apikey_id", type_="unique")
+ except Exception as e:
+ print(e)
+ pass
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/012fb73ac359_add_folder_table.py b/langflow/src/backend/base/langflow/alembic/versions/012fb73ac359_add_folder_table.py
new file mode 100644
index 0000000..8000ce2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/012fb73ac359_add_folder_table.py
@@ -0,0 +1,82 @@
+"""Add Folder table
+
+Revision ID: 012fb73ac359
+Revises: c153816fd85f
+Create Date: 2024-05-07 12:52:16.954691
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "012fb73ac359"
+down_revision: Union[str, None] = "c153816fd85f"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "folder" not in table_names:
+ op.create_table(
+ "folder",
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("parent_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True),
+ sa.ForeignKeyConstraint(
+ ["parent_id"],
+ ["folder.id"],
+ ),
+ sa.ForeignKeyConstraint(
+ ["user_id"],
+ ["user.id"],
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ indexes = inspector.get_indexes("folder")
+ if "ix_folder_name" not in [index["name"] for index in indexes]:
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ batch_op.create_index(batch_op.f("ix_folder_name"), ["name"], unique=False)
+
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "folder_id" not in column_names:
+ batch_op.add_column(sa.Column("folder_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True))
+ batch_op.create_foreign_key("flow_folder_id_fkey", "folder", ["folder_id"], ["id"])
+ if "folder" in column_names:
+ batch_op.drop_column("folder")
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "folder" not in column_names:
+ batch_op.add_column(sa.Column("folder", sa.VARCHAR(), nullable=True))
+ if "folder_id" in column_names:
+ batch_op.drop_column("folder_id")
+ batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
+
+ indexes = inspector.get_indexes("folder")
+ if "ix_folder_name" in [index["name"] for index in indexes]:
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f("ix_folder_name"))
+
+ if "folder" in table_names:
+ op.drop_table("folder")
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/0ae3a2674f32_update_the_columns_that_need_to_change_.py b/langflow/src/backend/base/langflow/alembic/versions/0ae3a2674f32_update_the_columns_that_need_to_change_.py
new file mode 100644
index 0000000..ea8edda
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/0ae3a2674f32_update_the_columns_that_need_to_change_.py
@@ -0,0 +1,71 @@
+"""Update the columns that need to change their type to text
+
+Revision ID: 0ae3a2674f32
+Revises: d2d475a1f7c0
+Create Date: 2024-10-04 17:30:12.924809
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.dialects import sqlite
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = '0ae3a2674f32'
+down_revision: Union[str, None] = 'd2d475a1f7c0'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+
+ with op.batch_alter_table("vertex_build", schema=None) as batch_op:
+ if migration.column_exists(table_name="vertex_build", column_name="params", conn=conn):
+ columns = inspector.get_columns("vertex_build")
+ params_column = next((column for column in columns if column["name"] == "params"), None)
+ if params_column is not None and isinstance(params_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "params", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ if migration.column_exists(table_name="message", column_name="text", conn=conn):
+ columns = inspector.get_columns("message")
+ text_column = next((column for column in columns if column["name"] == "text"), None)
+ if text_column is not None and isinstance(text_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "text", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ if migration.column_exists(table_name="message", column_name="text", conn=conn):
+ columns = inspector.get_columns("message")
+ text_column = next((column for column in columns if column["name"] == "text"), None)
+ if text_column is not None and isinstance(text_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "text", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ with op.batch_alter_table("vertex_build", schema=None) as batch_op:
+ if migration.column_exists(table_name="vertex_build", column_name="params", conn=conn):
+ columns = inspector.get_columns("vertex_build")
+ params_column = next((column for column in columns if column["name"] == "params"), None)
+ if params_column is not None and isinstance(params_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "params", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/0b8757876a7c_.py b/langflow/src/backend/base/langflow/alembic/versions/0b8757876a7c_.py
new file mode 100644
index 0000000..e53b61c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/0b8757876a7c_.py
@@ -0,0 +1,32 @@
+"""empty message
+
+Revision ID: 0b8757876a7c
+Revises: 006b3990db50
+Create Date: 2024-01-17 10:32:56.686287
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "0b8757876a7c"
+down_revision: Union[str, None] = "006b3990db50"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ pass
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+
+ pass
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/0d60fcbd4e8e_create_vertex_builds_table.py b/langflow/src/backend/base/langflow/alembic/versions/0d60fcbd4e8e_create_vertex_builds_table.py
new file mode 100644
index 0000000..db13294
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/0d60fcbd4e8e_create_vertex_builds_table.py
@@ -0,0 +1,51 @@
+"""create vertex_builds table
+
+Revision ID: 0d60fcbd4e8e
+Revises: 90be8e2ed91e
+Create Date: 2024-07-26 11:41:31.274271
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "0d60fcbd4e8e"
+down_revision: Union[str, None] = "90be8e2ed91e"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ if not migration.table_exists("vertex_build", conn):
+ op.create_table(
+ "vertex_build",
+ sa.Column("timestamp", sa.DateTime(), nullable=False),
+ sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("data", sa.JSON(), nullable=True),
+ sa.Column("artifacts", sa.JSON(), nullable=True),
+ sa.Column("params", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("build_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("flow_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("valid", sa.BOOLEAN(), nullable=False),
+ sa.ForeignKeyConstraint(
+ ["flow_id"],
+ ["flow.id"],
+ "fk_vertex_build_flow_id",
+ ),
+ sa.PrimaryKeyConstraint("build_id"),
+ )
+ pass
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ if migration.table_exists("vertex_build", conn):
+ op.drop_table("vertex_build")
+ pass
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py b/langflow/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py
new file mode 100644
index 0000000..3443d2b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py
@@ -0,0 +1,66 @@
+"""Replace Credential table with Variable
+
+Revision ID: 1a110b568907
+Revises: 63b9c451fd30
+Create Date: 2024-03-25 09:40:02.743453
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "1a110b568907"
+down_revision: Union[str, None] = "63b9c451fd30"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "variable" not in table_names:
+ op.create_table(
+ "variable",
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("value", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("type", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("created_at", sa.DateTime(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_variable_user_id"),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ if "credential" in table_names:
+ op.drop_table("credential")
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "credential" not in table_names:
+ op.create_table(
+ "credential",
+ sa.Column("name", sa.VARCHAR(), nullable=True),
+ sa.Column("value", sa.VARCHAR(), nullable=True),
+ sa.Column("provider", sa.VARCHAR(), nullable=True),
+ sa.Column("user_id", sa.CHAR(length=32), nullable=False),
+ sa.Column("id", sa.CHAR(length=32), nullable=False),
+ sa.Column("created_at", sa.DATETIME(), nullable=False),
+ sa.Column("updated_at", sa.DATETIME(), nullable=True),
+ sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_credential_user_id"),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ if "variable" in table_names:
+ op.drop_table("variable")
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1c79524817ed_add_unique_constraints_per_user_in_.py b/langflow/src/backend/base/langflow/alembic/versions/1c79524817ed_add_unique_constraints_per_user_in_.py
new file mode 100644
index 0000000..c1ddf82
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1c79524817ed_add_unique_constraints_per_user_in_.py
@@ -0,0 +1,43 @@
+"""Add unique constraints per user in folder table
+
+Revision ID: 1c79524817ed
+Revises: 3bb0ddf32dfb
+Create Date: 2024-05-29 23:12:09.146880
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "1c79524817ed"
+down_revision: Union[str, None] = "3bb0ddf32dfb"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("folder")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ if "unique_folder_name" not in constraints_names:
+ batch_op.create_unique_constraint("unique_folder_name", ["user_id", "name"])
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("folder")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ if "unique_folder_name" in constraints_names:
+ batch_op.drop_constraint("unique_folder_name", type_="unique")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1d90f8a0efe1_update_description_columns_type.py b/langflow/src/backend/base/langflow/alembic/versions/1d90f8a0efe1_update_description_columns_type.py
new file mode 100644
index 0000000..dc32c97
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1d90f8a0efe1_update_description_columns_type.py
@@ -0,0 +1,71 @@
+"""Update description columns type
+
+Revision ID: 4522eb831f5c
+Revises: 0d60fcbd4e8e
+Create Date: 2024-08-20 11:46:56.266061
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "4522eb831f5c"
+down_revision: Union[str, None] = "0d60fcbd4e8e"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if migration.column_exists(table_name="flow", column_name="description", conn=conn):
+ columns = inspector.get_columns("flow")
+ description_column = next((column for column in columns if column["name"] == "description"), None)
+ if description_column is not None and isinstance(description_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "description", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ if migration.column_exists(table_name="folder", column_name="description", conn=conn):
+ columns = inspector.get_columns("folder")
+ description_column = next((column for column in columns if column["name"] == "description"), None)
+ if description_column is not None and isinstance(description_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "description", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+ with op.batch_alter_table("folder", schema=None) as batch_op:
+ if migration.column_exists(table_name="folder", column_name="description", conn=conn):
+ columns = inspector.get_columns("folder")
+ description_column = next((column for column in columns if column["name"] == "description"), None)
+ if description_column is not None and isinstance(description_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "description", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if migration.column_exists(table_name="flow", column_name="description", conn=conn):
+ columns = inspector.get_columns("flow")
+ description_column = next((column for column in columns if column["name"] == "description"), None)
+ if description_column is not None and isinstance(description_column["type"], sa.VARCHAR):
+ batch_op.alter_column(
+ "description", existing_type=sa.VARCHAR(), type_=sa.Text(), existing_nullable=True
+ )
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1eab2c3eb45e_event_error.py b/langflow/src/backend/base/langflow/alembic/versions/1eab2c3eb45e_event_error.py
new file mode 100644
index 0000000..6a71155
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1eab2c3eb45e_event_error.py
@@ -0,0 +1,53 @@
+"""event_error
+
+Revision ID: 1eab2c3eb45e
+Revises: eb5e72293a8e
+Create Date: 2024-10-24 12:03:24.118937
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects import sqlite
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = '1eab2c3eb45e'
+down_revision: Union[str, None] = 'eb5e72293a8e'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("message")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('message', schema=None) as batch_op:
+ if "properties" not in column_names:
+ batch_op.add_column(sa.Column('properties', sa.JSON(), nullable=True))
+ if "category" not in column_names:
+ batch_op.add_column(sa.Column('category', sa.Text(), nullable=True))
+ if "content_blocks" not in column_names:
+ batch_op.add_column(sa.Column('content_blocks', sa.JSON(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("message")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('message', schema=None) as batch_op:
+ if "content_blocks" in column_names:
+ batch_op.drop_column('content_blocks')
+ if "category" in column_names:
+ batch_op.drop_column('category')
+ if "properties" in column_names:
+ batch_op.drop_column('properties')
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1ef9c4f3765d_.py b/langflow/src/backend/base/langflow/alembic/versions/1ef9c4f3765d_.py
new file mode 100644
index 0000000..5607df8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1ef9c4f3765d_.py
@@ -0,0 +1,54 @@
+"""
+
+
+Revision ID: 1ef9c4f3765d
+Revises: fd531f8868b1
+Create Date: 2023-12-04 15:00:27.968998
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "1ef9c4f3765d"
+down_revision: Union[str, None] = "fd531f8868b1"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ if migration.column_exists(table_name="apikey", column_name="name", conn=conn):
+ api_key_columns = inspector.get_columns("apikey")
+ name_column = next((column for column in api_key_columns if column["name"] == "name"), None)
+ if name_column is not None and isinstance(name_column["type"], sa.VARCHAR) and not name_column["nullable"]:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ if migration.column_exists(table_name="apikey", column_name="name", conn=conn):
+ api_key_columns = inspector.get_columns("apikey")
+ name_column = next((column for column in api_key_columns if column["name"] == "name"), None)
+ if name_column is not None and isinstance(name_column["type"], sa.VARCHAR) and name_column["nullable"]:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=False)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py b/langflow/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py
new file mode 100644
index 0000000..f261746
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py
@@ -0,0 +1,43 @@
+"""Add default_fields column
+
+Revision ID: 1f4d6df60295
+Revises: 6e7b581b5648
+Create Date: 2024-04-29 09:49:46.864145
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "1f4d6df60295"
+down_revision: Union[str, None] = "6e7b581b5648"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("variable")]
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if "default_fields" not in column_names:
+ batch_op.add_column(sa.Column("default_fields", sa.JSON(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("variable")]
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if "default_fields" in column_names:
+ batch_op.drop_column("default_fields")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/260dbcc8b680_adds_tables.py b/langflow/src/backend/base/langflow/alembic/versions/260dbcc8b680_adds_tables.py
new file mode 100644
index 0000000..9a3275d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/260dbcc8b680_adds_tables.py
@@ -0,0 +1,151 @@
+"""Adds tables
+
+Revision ID: 260dbcc8b680
+Revises:
+Create Date: 2023-08-27 19:49:02.681355
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "260dbcc8b680"
+down_revision: Union[str, None] = None
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # List existing tables
+ existing_tables = inspector.get_table_names()
+ # Drop 'flowstyle' table if it exists
+ # and other related indices
+ if "flowstyle" in existing_tables:
+ op.drop_table("flowstyle")
+ if "ix_flowstyle_flow_id" in [index["name"] for index in inspector.get_indexes("flowstyle")]:
+ op.drop_index("ix_flowstyle_flow_id", table_name="flowstyle", if_exists=True)
+
+ existing_indices_flow = []
+ existing_fks_flow = []
+ if "flow" in existing_tables:
+ existing_indices_flow = [index["name"] for index in inspector.get_indexes("flow")]
+ # Existing foreign keys for the 'flow' table, if it exists
+ existing_fks_flow = [
+ fk["referred_table"] + "." + fk["referred_columns"][0] for fk in inspector.get_foreign_keys("flow")
+ ]
+ # Now check if the columns user_id exists in the 'flow' table
+ # If it does not exist, we need to create the foreign key
+
+ if "user" not in existing_tables:
+ op.create_table(
+ "user",
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("password", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("is_active", sa.Boolean(), nullable=False),
+ sa.Column("is_superuser", sa.Boolean(), nullable=False),
+ sa.Column("create_at", sa.DateTime(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
+ sa.Column("last_login_at", sa.DateTime(), nullable=True),
+ sa.PrimaryKeyConstraint("id", name="pk_user"),
+ sa.UniqueConstraint("id", name="uq_user_id"),
+ )
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.create_index(batch_op.f("ix_user_username"), ["username"], unique=True)
+
+ if "apikey" not in existing_tables:
+ op.create_table(
+ "apikey",
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("created_at", sa.DateTime(), nullable=False),
+ sa.Column("last_used_at", sa.DateTime(), nullable=True),
+ sa.Column("total_uses", sa.Integer(), nullable=False, default=0),
+ sa.Column("is_active", sa.Boolean(), nullable=False, default=True),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_apikey_user_id_user"),
+ sa.PrimaryKeyConstraint("id", name="pk_apikey"),
+ sa.UniqueConstraint("id", name="uq_apikey_id"),
+ )
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.create_index(batch_op.f("ix_apikey_api_key"), ["api_key"], unique=True)
+ batch_op.create_index(batch_op.f("ix_apikey_name"), ["name"], unique=False)
+ batch_op.create_index(batch_op.f("ix_apikey_user_id"), ["user_id"], unique=False)
+ if "flow" not in existing_tables:
+ op.create_table(
+ "flow",
+ sa.Column("data", sa.JSON(), nullable=True),
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_flow_user_id_user"),
+ sa.PrimaryKeyConstraint("id", name="pk_flow"),
+ sa.UniqueConstraint("id", name="uq_flow_id"),
+ )
+ # Conditionally create indices for 'flow' table
+ # if _alembic_tmp_flow exists, then we need to drop it first
+ # This is to deal with SQLite not being able to ROLLBACK
+ # for some unknown reason
+ if "_alembic_tmp_flow" in existing_tables:
+ op.drop_table("_alembic_tmp_flow")
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ flow_columns = [col["name"] for col in inspector.get_columns("flow")]
+ if "user_id" not in flow_columns:
+ batch_op.add_column(
+ sa.Column(
+ "user_id",
+ sqlmodel.sql.sqltypes.types.Uuid(),
+ nullable=True, # This should be False, but we need to allow NULL values for now
+ )
+ )
+ if "user.id" not in existing_fks_flow:
+ batch_op.create_foreign_key("fk_flow_user_id", "user", ["user_id"], ["id"])
+ if "ix_flow_description" not in existing_indices_flow:
+ batch_op.create_index(batch_op.f("ix_flow_description"), ["description"], unique=False)
+ if "ix_flow_name" not in existing_indices_flow:
+ batch_op.create_index(batch_op.f("ix_flow_name"), ["name"], unique=False)
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "ix_flow_user_id" not in existing_indices_flow:
+ batch_op.create_index(batch_op.f("ix_flow_user_id"), ["user_id"], unique=False)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # List existing tables
+ existing_tables = inspector.get_table_names()
+ if "flow" in existing_tables:
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f("ix_flow_user_id"), if_exists=True)
+ batch_op.drop_index(batch_op.f("ix_flow_name"), if_exists=True)
+ batch_op.drop_index(batch_op.f("ix_flow_description"), if_exists=True)
+
+ op.drop_table("flow")
+ if "apikey" in existing_tables:
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f("ix_apikey_user_id"), if_exists=True)
+ batch_op.drop_index(batch_op.f("ix_apikey_name"), if_exists=True)
+ batch_op.drop_index(batch_op.f("ix_apikey_api_key"), if_exists=True)
+
+ op.drop_table("apikey")
+ if "user" in existing_tables:
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f("ix_user_username"), if_exists=True)
+
+ op.drop_table("user")
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/29fe8f1f806b_add_missing_index.py b/langflow/src/backend/base/langflow/alembic/versions/29fe8f1f806b_add_missing_index.py
new file mode 100644
index 0000000..5d10247
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/29fe8f1f806b_add_missing_index.py
@@ -0,0 +1,44 @@
+"""Add missing index
+
+Revision ID: 29fe8f1f806b
+Revises: 012fb73ac359
+Create Date: 2024-05-21 09:23:48.772367
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+revision: str = "29fe8f1f806b"
+down_revision: Union[str, None] = "012fb73ac359"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ indexes = inspector.get_indexes("flow")
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ indexes_names = [index["name"] for index in indexes]
+ if "ix_flow_folder_id" not in indexes_names:
+ batch_op.create_index(batch_op.f("ix_flow_folder_id"), ["folder_id"], unique=False)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ indexes = inspector.get_indexes("flow")
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ indexes_names = [index["name"] for index in indexes]
+ if "ix_flow_folder_id" in indexes_names:
+ batch_op.drop_index(batch_op.f("ix_flow_folder_id"))
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/2ac71eb9c3ae_adds_credential_table.py b/langflow/src/backend/base/langflow/alembic/versions/2ac71eb9c3ae_adds_credential_table.py
new file mode 100644
index 0000000..baf7922
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/2ac71eb9c3ae_adds_credential_table.py
@@ -0,0 +1,55 @@
+"""Adds Credential table
+
+Revision ID: c1c8e217a069
+Revises: 7d2162acc8b2
+Create Date: 2023-11-24 10:45:38.465302
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "2ac71eb9c3ae"
+down_revision: Union[str, None] = "7d2162acc8b2"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ tables = inspector.get_table_names()
+ try:
+ if "credential" not in tables:
+ op.create_table(
+ "credential",
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("value", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("provider", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("created_at", sa.DateTime(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), nullable=True),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ except Exception as e:
+ print(e)
+
+ pass
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ op.drop_table("credential")
+ except Exception as e:
+ print(e)
+ pass
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/3bb0ddf32dfb_add_unique_constraints_per_user_in_flow_.py b/langflow/src/backend/base/langflow/alembic/versions/3bb0ddf32dfb_add_unique_constraints_per_user_in_flow_.py
new file mode 100644
index 0000000..944f27c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/3bb0ddf32dfb_add_unique_constraints_per_user_in_flow_.py
@@ -0,0 +1,55 @@
+"""Add unique constraints per user in flow table
+
+Revision ID: 3bb0ddf32dfb
+Revises: a72f5cf9c2f9
+Create Date: 2024-05-29 23:08:43.935040
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "3bb0ddf32dfb"
+down_revision: Union[str, None] = "a72f5cf9c2f9"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ indexes_names = [index["name"] for index in inspector.get_indexes("flow")]
+ constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "ix_flow_endpoint_name" in indexes_names:
+ batch_op.drop_index("ix_flow_endpoint_name")
+ batch_op.create_index(batch_op.f("ix_flow_endpoint_name"), ["endpoint_name"], unique=False)
+ if "unique_flow_endpoint_name" not in constraints_names:
+ batch_op.create_unique_constraint("unique_flow_endpoint_name", ["user_id", "endpoint_name"])
+ if "unique_flow_name" not in constraints_names:
+ batch_op.create_unique_constraint("unique_flow_name", ["user_id", "name"])
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ indexes_names = [index["name"] for index in inspector.get_indexes("flow")]
+ constraints_names = [constraint["name"] for constraint in inspector.get_unique_constraints("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "unique_flow_name" in constraints_names:
+ batch_op.drop_constraint("unique_flow_name", type_="unique")
+ if "unique_flow_endpoint_name" in constraints_names:
+ batch_op.drop_constraint("unique_flow_endpoint_name", type_="unique")
+ if "ix_flow_endpoint_name" in indexes_names:
+ batch_op.drop_index(batch_op.f("ix_flow_endpoint_name"))
+ batch_op.create_index("ix_flow_endpoint_name", ["endpoint_name"], unique=1)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/4e5980a44eaa_fix_date_times_again.py b/langflow/src/backend/base/langflow/alembic/versions/4e5980a44eaa_fix_date_times_again.py
new file mode 100644
index 0000000..089949e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/4e5980a44eaa_fix_date_times_again.py
@@ -0,0 +1,130 @@
+"""Fix date times again
+
+Revision ID: 4e5980a44eaa
+Revises: 79e675cb6752
+Create Date: 2024-04-12 18:11:06.454037
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from loguru import logger
+from sqlalchemy.dialects import postgresql
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "4e5980a44eaa"
+down_revision: Union[str, None] = "79e675cb6752"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "apikey" in table_names:
+ columns = inspector.get_columns("apikey")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ if created_at_column is not None and isinstance(created_at_column["type"], postgresql.TIMESTAMP):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=False,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'apikey'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'apikey'")
+ if "variable" in table_names:
+ columns = inspector.get_columns("variable")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if created_at_column is not None and isinstance(created_at_column["type"], postgresql.TIMESTAMP):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=True,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'variable'")
+ if updated_at_column is not None and isinstance(updated_at_column["type"], postgresql.TIMESTAMP):
+ batch_op.alter_column(
+ "updated_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=True,
+ )
+ else:
+ if updated_at_column is None:
+ logger.warning("Column 'updated_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'updated_at' has type {updated_at_column['type']} in table 'variable'")
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "variable" in table_names:
+ columns = inspector.get_columns("variable")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if updated_at_column is not None and isinstance(updated_at_column["type"], sa.DateTime):
+ batch_op.alter_column(
+ "updated_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=True,
+ )
+ else:
+ if updated_at_column is None:
+ logger.warning("Column 'updated_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'updated_at' has type {updated_at_column['type']} in table 'variable'")
+ if created_at_column is not None and isinstance(created_at_column["type"], sa.DateTime):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=True,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'variable'")
+
+ if "apikey" in table_names:
+ columns = inspector.get_columns("apikey")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ if created_at_column is not None and isinstance(created_at_column["type"], sa.DateTime):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=False,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'apikey'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'apikey'")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/58b28437a398_modify_nullable.py b/langflow/src/backend/base/langflow/alembic/versions/58b28437a398_modify_nullable.py
new file mode 100644
index 0000000..564f778
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/58b28437a398_modify_nullable.py
@@ -0,0 +1,66 @@
+"""Modify nullable
+
+Revision ID: 58b28437a398
+Revises: 4e5980a44eaa
+Create Date: 2024-04-13 10:57:23.061709
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from loguru import logger
+from sqlalchemy.engine.reflection import Inspector
+
+down_revision: Union[str, None] = "4e5980a44eaa"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+# Revision identifiers, used by Alembic.
+revision = "58b28437a398"
+down_revision = "4e5980a44eaa"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ conn = op.get_bind()
+ inspector = sa.inspect(conn)
+ tables = ["apikey", "variable"] # List of tables to modify
+
+ for table_name in tables:
+ modify_nullable(conn, inspector, table_name, upgrade=True)
+
+
+def downgrade():
+ conn = op.get_bind()
+ inspector = sa.inspect(conn)
+ tables = ["apikey", "variable"] # List of tables to revert
+
+ for table_name in tables:
+ modify_nullable(conn, inspector, table_name, upgrade=False)
+
+
+def modify_nullable(conn, inspector, table_name, upgrade=True):
+ columns = inspector.get_columns(table_name)
+ nullable_changes = {"apikey": {"created_at": False}, "variable": {"created_at": True, "updated_at": True}}
+
+ if table_name in columns:
+ with op.batch_alter_table(table_name, schema=None) as batch_op:
+ for column_name, nullable_setting in nullable_changes.get(table_name, {}).items():
+ column_info = next((col for col in columns if col["name"] == column_name), None)
+ if column_info:
+ current_nullable = column_info["nullable"]
+ target_nullable = nullable_setting if upgrade else not nullable_setting
+
+ if current_nullable != target_nullable:
+ batch_op.alter_column(
+ column_name, existing_type=sa.DateTime(timezone=True), nullable=target_nullable
+ )
+ else:
+ logger.info(
+ f"Column '{column_name}' in table '{table_name}' already has nullable={target_nullable}"
+ )
+ else:
+ logger.warning(f"Column '{column_name}' not found in table '{table_name}'")
diff --git a/langflow/src/backend/base/langflow/alembic/versions/5ace73a7f223_new_remove_table_upgrade_op.py b/langflow/src/backend/base/langflow/alembic/versions/5ace73a7f223_new_remove_table_upgrade_op.py
new file mode 100644
index 0000000..f2b8f9b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/5ace73a7f223_new_remove_table_upgrade_op.py
@@ -0,0 +1,39 @@
+"""new remove table upgrade op
+
+Revision ID: 5ace73a7f223
+Revises: 0ae3a2674f32
+Create Date: 2024-10-08 10:59:12.980671
+
+"""
+
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+from sqlalchemy.engine.reflection import Inspector
+from langflow.utils import migration
+from sqlalchemy.dialects import sqlite
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "5ace73a7f223"
+down_revision: Union[str, None] = "0ae3a2674f32"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ batch_op.alter_column("text", existing_type=sa.TEXT(), nullable=True)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ batch_op.alter_column("text", existing_type=sa.TEXT(), nullable=False)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/631faacf5da2_add_webhook_columns.py b/langflow/src/backend/base/langflow/alembic/versions/631faacf5da2_add_webhook_columns.py
new file mode 100644
index 0000000..8f90648
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/631faacf5da2_add_webhook_columns.py
@@ -0,0 +1,45 @@
+"""Add webhook columns
+
+Revision ID: 631faacf5da2
+Revises: 1c79524817ed
+Create Date: 2024-04-22 15:14:43.454784
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "631faacf5da2"
+down_revision: Union[str, None] = "1c79524817ed"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "flow" in table_names and "webhook" not in column_names:
+ batch_op.add_column(sa.Column("webhook", sa.Boolean(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "flow" in table_names and "webhook" in column_names:
+ batch_op.drop_column("webhook")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/63b9c451fd30_add_icon_and_icon_bg_color_to_flow.py b/langflow/src/backend/base/langflow/alembic/versions/63b9c451fd30_add_icon_and_icon_bg_color_to_flow.py
new file mode 100644
index 0000000..3b4822d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/63b9c451fd30_add_icon_and_icon_bg_color_to_flow.py
@@ -0,0 +1,50 @@
+"""Add icon and icon_bg_color to Flow
+
+Revision ID: 63b9c451fd30
+Revises: bc2f01c40e4a
+Create Date: 2024-03-06 10:53:47.148658
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "63b9c451fd30"
+down_revision: Union[str, None] = "bc2f01c40e4a"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "icon" not in column_names:
+ batch_op.add_column(sa.Column("icon", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+ if "icon_bg_color" not in column_names:
+ batch_op.add_column(sa.Column("icon_bg_color", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "icon" in column_names:
+ batch_op.drop_column("icon")
+ if "icon_bg_color" in column_names:
+ batch_op.drop_column("icon_bg_color")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/67cc006d50bf_add_profile_image_column.py b/langflow/src/backend/base/langflow/alembic/versions/67cc006d50bf_add_profile_image_column.py
new file mode 100644
index 0000000..e7ae54f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/67cc006d50bf_add_profile_image_column.py
@@ -0,0 +1,57 @@
+"""Add profile-image column
+
+Revision ID: 67cc006d50bf
+Revises: 260dbcc8b680
+Create Date: 2023-09-08 07:36:13.387318
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "67cc006d50bf"
+down_revision: Union[str, None] = "260dbcc8b680"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ if "user" in inspector.get_table_names() and "profile_image" not in [
+ column["name"] for column in inspector.get_columns("user")
+ ]:
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ "profile_image",
+ sqlmodel.sql.sqltypes.AutoString(),
+ nullable=True,
+ )
+ )
+ except Exception as e:
+ print(e)
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ if "user" in inspector.get_table_names() and "profile_image" in [
+ column["name"] for column in inspector.get_columns("user")
+ ]:
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.drop_column("profile_image")
+ except Exception as e:
+ print(e)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/6e7b581b5648_fix_nullable.py b/langflow/src/backend/base/langflow/alembic/versions/6e7b581b5648_fix_nullable.py
new file mode 100644
index 0000000..a60ddf7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/6e7b581b5648_fix_nullable.py
@@ -0,0 +1,59 @@
+"""Fix nullable
+
+Revision ID: 6e7b581b5648
+Revises: 58b28437a398
+Create Date: 2024-04-30 09:17:45.024688
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "6e7b581b5648"
+down_revision: Union[str, None] = "58b28437a398"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ columns = inspector.get_columns("apikey")
+ column_names = {column["name"]: column for column in columns}
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ created_at_column = [column for column in columns if column["name"] == "created_at"][0]
+ if "created_at" in column_names and created_at_column.get("nullable"):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DATETIME(),
+ nullable=False,
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"), # type: ignore
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # table_names = inspector.get_table_names()
+ columns = inspector.get_columns("apikey")
+ column_names = {column["name"]: column for column in columns}
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ created_at_column = [column for column in columns if column["name"] == "created_at"][0]
+ if "created_at" in column_names and not created_at_column.get("nullable"):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DATETIME(),
+ nullable=True,
+ existing_server_default=sa.text("(CURRENT_TIMESTAMP)"), # type: ignore
+ )
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/7843803a87b5_store_updates.py b/langflow/src/backend/base/langflow/alembic/versions/7843803a87b5_store_updates.py
new file mode 100644
index 0000000..d58ceef
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/7843803a87b5_store_updates.py
@@ -0,0 +1,55 @@
+"""Store updates
+
+Revision ID: 7843803a87b5
+Revises: eb5866d51fd2
+Create Date: 2023-10-18 23:08:57.744906
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "7843803a87b5"
+down_revision: Union[str, None] = "eb5866d51fd2"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ flow_columns = [column["name"] for column in inspector.get_columns("flow")]
+ user_columns = [column["name"] for column in inspector.get_columns("user")]
+ try:
+ if "is_component" not in flow_columns:
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
+ except Exception:
+ pass
+ try:
+ if "store_api_key" not in user_columns:
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("store_api_key", sqlmodel.AutoString(), nullable=True))
+ except Exception:
+ pass
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ with op.batch_alter_table("user", schema=None) as batch_op:
+ batch_op.drop_column("store_api_key")
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.drop_column("is_component")
+ except Exception as e:
+ print(e)
+ pass
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/79e675cb6752_change_datetime_type.py b/langflow/src/backend/base/langflow/alembic/versions/79e675cb6752_change_datetime_type.py
new file mode 100644
index 0000000..b71706c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/79e675cb6752_change_datetime_type.py
@@ -0,0 +1,130 @@
+"""Change datetime type
+
+Revision ID: 79e675cb6752
+Revises: e3bc869fa272
+Create Date: 2024-04-11 19:23:10.697335
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from loguru import logger
+from sqlalchemy.dialects import postgresql
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "79e675cb6752"
+down_revision: Union[str, None] = "e3bc869fa272"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "apikey" in table_names:
+ columns = inspector.get_columns("apikey")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ if created_at_column is not None and isinstance(created_at_column["type"], postgresql.TIMESTAMP):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=False,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'apikey'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'apikey'")
+ if "variable" in table_names:
+ columns = inspector.get_columns("variable")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if created_at_column is not None and isinstance(created_at_column["type"], postgresql.TIMESTAMP):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=True,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'variable'")
+ if updated_at_column is not None and isinstance(updated_at_column["type"], postgresql.TIMESTAMP):
+ batch_op.alter_column(
+ "updated_at",
+ existing_type=postgresql.TIMESTAMP(),
+ type_=sa.DateTime(timezone=True),
+ existing_nullable=True,
+ )
+ else:
+ if updated_at_column is None:
+ logger.warning("Column 'updated_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'updated_at' has type {updated_at_column['type']} in table 'variable'")
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "variable" in table_names:
+ columns = inspector.get_columns("variable")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ updated_at_column = next((column for column in columns if column["name"] == "updated_at"), None)
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if updated_at_column is not None and isinstance(updated_at_column["type"], sa.DateTime):
+ batch_op.alter_column(
+ "updated_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=True,
+ )
+ else:
+ if updated_at_column is None:
+ logger.warning("Column 'updated_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'updated_at' has type {updated_at_column['type']} in table 'variable'")
+ if created_at_column is not None and isinstance(created_at_column["type"], sa.DateTime):
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=True,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'variable'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'variable'")
+
+ if "apikey" in table_names:
+ columns = inspector.get_columns("apikey")
+ created_at_column = next((column for column in columns if column["name"] == "created_at"), None)
+ if created_at_column is not None and isinstance(created_at_column["type"], sa.DateTime):
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.DateTime(timezone=True),
+ type_=postgresql.TIMESTAMP(),
+ existing_nullable=False,
+ )
+ else:
+ if created_at_column is None:
+ logger.warning("Column 'created_at' not found in table 'apikey'")
+ else:
+ logger.warning(f"Column 'created_at' has type {created_at_column['type']} in table 'apikey'")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/7d2162acc8b2_adds_updated_at_and_folder_cols.py b/langflow/src/backend/base/langflow/alembic/versions/7d2162acc8b2_adds_updated_at_and_folder_cols.py
new file mode 100644
index 0000000..743d6a2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/7d2162acc8b2_adds_updated_at_and_folder_cols.py
@@ -0,0 +1,74 @@
+"""Adds updated_at and folder cols
+
+Revision ID: 7d2162acc8b2
+Revises: f5ee9749d1a6
+Create Date: 2023-11-21 20:56:53.998781
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "7d2162acc8b2"
+down_revision: Union[str, None] = "f5ee9749d1a6"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ api_key_columns = [column["name"] for column in inspector.get_columns("apikey")]
+ flow_columns = [column["name"] for column in inspector.get_columns("flow")]
+
+ try:
+ if "name" in api_key_columns:
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=False)
+ except Exception as e:
+ print(e)
+
+ pass
+ try:
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "updated_at" not in flow_columns:
+ batch_op.add_column(sa.Column("updated_at", sa.DateTime(), nullable=True))
+ if "folder" not in flow_columns:
+ batch_op.add_column(sa.Column("folder", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+ except Exception as e:
+ print(e)
+
+ pass
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "folder" in column_names:
+ batch_op.drop_column("folder")
+ if "updated_at" in column_names:
+ batch_op.drop_column("updated_at")
+ except Exception as e:
+ print(e)
+ pass
+
+ try:
+ with op.batch_alter_table("apikey", schema=None) as batch_op:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True)
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/90be8e2ed91e_create_transactions_table.py b/langflow/src/backend/base/langflow/alembic/versions/90be8e2ed91e_create_transactions_table.py
new file mode 100644
index 0000000..1c3edd8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/90be8e2ed91e_create_transactions_table.py
@@ -0,0 +1,51 @@
+"""create transactions table
+
+Revision ID: 90be8e2ed91e
+Revises: 325180f0c4e1
+Create Date: 2024-07-24 11:37:48.532933
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "90be8e2ed91e"
+down_revision: Union[str, None] = "325180f0c4e1"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ if not migration.table_exists("transaction", conn):
+ op.create_table(
+ "transaction",
+ sa.Column("timestamp", sa.DateTime(), nullable=False),
+ sa.Column("vertex_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("target_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("inputs", sa.JSON(), nullable=True),
+ sa.Column("outputs", sa.JSON(), nullable=True),
+ sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("flow_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("error", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.ForeignKeyConstraint(
+ ["flow_id"],
+ ["flow.id"],
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ pass
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ if migration.table_exists("transaction", conn):
+ op.drop_table("transaction")
+ pass
diff --git a/langflow/src/backend/base/langflow/alembic/versions/a72f5cf9c2f9_add_endpoint_name_col.py b/langflow/src/backend/base/langflow/alembic/versions/a72f5cf9c2f9_add_endpoint_name_col.py
new file mode 100644
index 0000000..ee34c07
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/a72f5cf9c2f9_add_endpoint_name_col.py
@@ -0,0 +1,52 @@
+"""Add endpoint name col
+
+Revision ID: a72f5cf9c2f9
+Revises: 29fe8f1f806b
+Create Date: 2024-05-29 21:44:04.240816
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "a72f5cf9c2f9"
+down_revision: Union[str, None] = "29fe8f1f806b"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ indexes = inspector.get_indexes("flow")
+ index_names = [index["name"] for index in indexes]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "endpoint_name" not in column_names:
+ batch_op.add_column(sa.Column("endpoint_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+ if "ix_flow_endpoint_name" not in index_names:
+ batch_op.create_index(batch_op.f("ix_flow_endpoint_name"), ["endpoint_name"], unique=True)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ indexes = inspector.get_indexes("flow")
+ index_names = [index["name"] for index in indexes]
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "ix_flow_endpoint_name" in index_names:
+ batch_op.drop_index(batch_op.f("ix_flow_endpoint_name"))
+ if "endpoint_name" in column_names:
+ batch_op.drop_column("endpoint_name")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/b2fa308044b5_add_unique_constraints.py b/langflow/src/backend/base/langflow/alembic/versions/b2fa308044b5_add_unique_constraints.py
new file mode 100644
index 0000000..8aae1ac
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/b2fa308044b5_add_unique_constraints.py
@@ -0,0 +1,102 @@
+"""Add unique constraints
+
+Revision ID: b2fa308044b5
+Revises: 0b8757876a7c
+Create Date: 2024-01-26 13:31:14.797548
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from loguru import logger # noqa
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "b2fa308044b5"
+down_revision: Union[str, None] = "0b8757876a7c"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ tables = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ if "flowstyle" in tables:
+ op.drop_table("flowstyle")
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ flow_columns = [column["name"] for column in inspector.get_columns("flow")]
+ if "is_component" not in flow_columns:
+ batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
+ if "updated_at" not in flow_columns:
+ batch_op.add_column(sa.Column("updated_at", sa.DateTime(), nullable=True))
+ if "folder" not in flow_columns:
+ batch_op.add_column(sa.Column("folder", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+ if "user_id" not in flow_columns:
+ batch_op.add_column(sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True))
+
+ indices = inspector.get_indexes("flow")
+ indices_names = [index["name"] for index in indices]
+ if "ix_flow_user_id" not in indices_names:
+ batch_op.create_index(batch_op.f("ix_flow_user_id"), ["user_id"], unique=False)
+
+ # Check for existing foreign key constraints
+ constraints = inspector.get_foreign_keys("flow")
+ constraint_names = [constraint["name"] for constraint in constraints]
+
+ if "fk_flow_user_id_user" not in constraint_names:
+ batch_op.create_foreign_key("fk_flow_user_id_user", "user", ["user_id"], ["id"])
+
+ except Exception as e:
+ logger.exception(f"Error during upgrade: {e}")
+ pass
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ try:
+ # Re-create the dropped table 'flowstyle' if it was previously dropped in upgrade
+ if "flowstyle" not in inspector.get_table_names():
+ op.create_table(
+ "flowstyle",
+ sa.Column("color", sa.String(), nullable=False),
+ sa.Column("emoji", sa.String(), nullable=False),
+ sa.Column("flow_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.ForeignKeyConstraint(["flow_id"], ["flow.id"]),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
+ )
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ # Check and remove newly added columns and constraints in upgrade
+ flow_columns = [column["name"] for column in inspector.get_columns("flow")]
+ if "user_id" in flow_columns:
+ batch_op.drop_column("user_id")
+ if "folder" in flow_columns:
+ batch_op.drop_column("folder")
+ if "updated_at" in flow_columns:
+ batch_op.drop_column("updated_at")
+ if "is_component" in flow_columns:
+ batch_op.drop_column("is_component")
+
+ indices = inspector.get_indexes("flow")
+ indices_names = [index["name"] for index in indices]
+ if "ix_flow_user_id" in indices_names:
+ batch_op.drop_index("ix_flow_user_id")
+ # Assuming fk_flow_user_id_user is a foreign key constraint's name, not an index
+ constraints = inspector.get_foreign_keys("flow")
+ constraint_names = [constraint["name"] for constraint in constraints]
+ if "fk_flow_user_id_user" in constraint_names:
+ batch_op.drop_constraint("fk_flow_user_id_user", type_="foreignkey")
+
+ except Exception as e:
+ # It's generally a good idea to log the exception or handle it in a way other than a bare pass
+ print(f"Error during downgrade: {e}")
diff --git a/langflow/src/backend/base/langflow/alembic/versions/bc2f01c40e4a_new_fixes.py b/langflow/src/backend/base/langflow/alembic/versions/bc2f01c40e4a_new_fixes.py
new file mode 100644
index 0000000..872497b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/bc2f01c40e4a_new_fixes.py
@@ -0,0 +1,73 @@
+"""New fixes
+
+Revision ID: bc2f01c40e4a
+Revises: b2fa308044b5
+Create Date: 2024-01-26 13:34:14.496769
+
+"""
+
+from typing import Sequence, Union
+import warnings
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "bc2f01c40e4a"
+down_revision: Union[str, None] = "b2fa308044b5"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ flow_columns = {column["name"] for column in inspector.get_columns("flow")}
+ flow_indexes = {index["name"] for index in inspector.get_indexes("flow")}
+
+ # Suppress the SQLite foreign key warning
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", message=".*SQL-parsed foreign key constraint.*")
+ flow_fks = {fk["name"] for fk in inspector.get_foreign_keys("flow")}
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "is_component" not in flow_columns:
+ batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
+ if "updated_at" not in flow_columns:
+ batch_op.add_column(sa.Column("updated_at", sa.DateTime(), nullable=True))
+ if "folder" not in flow_columns:
+ batch_op.add_column(sa.Column("folder", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+ if "user_id" not in flow_columns:
+ batch_op.add_column(sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True))
+ if "ix_flow_user_id" not in flow_indexes:
+ batch_op.create_index(batch_op.f("ix_flow_user_id"), ["user_id"], unique=False)
+ if "flow_user_id_fkey" not in flow_fks:
+ batch_op.create_foreign_key("flow_user_id_fkey", "user", ["user_id"], ["id"])
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ flow_columns = {column["name"] for column in inspector.get_columns("flow")}
+ flow_indexes = {index["name"] for index in inspector.get_indexes("flow")}
+
+ # Suppress the SQLite foreign key warning
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", message=".*SQL-parsed foreign key constraint.*")
+ flow_fks = {fk["name"] for fk in inspector.get_foreign_keys("flow")}
+
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ if "flow_user_id_fkey" in flow_fks:
+ batch_op.drop_constraint("flow_user_id_fkey", type_="foreignkey")
+ if "ix_flow_user_id" in flow_indexes:
+ batch_op.drop_index(batch_op.f("ix_flow_user_id"))
+ if "user_id" in flow_columns:
+ batch_op.drop_column("user_id")
+ if "folder" in flow_columns:
+ batch_op.drop_column("folder")
+ if "updated_at" in flow_columns:
+ batch_op.drop_column("updated_at")
+ if "is_component" in flow_columns:
+ batch_op.drop_column("is_component")
diff --git a/langflow/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py b/langflow/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py
new file mode 100644
index 0000000..d41e494
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py
@@ -0,0 +1,52 @@
+"""Set name and value to not nullable
+
+Revision ID: c153816fd85f
+Revises: 1f4d6df60295
+Create Date: 2024-04-30 14:31:23.898995
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "c153816fd85f"
+down_revision: Union[str, None] = "1f4d6df60295"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ # ### commands auto generated by Alembic - please adjust! ###
+ columns = inspector.get_columns("variable")
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ name_column = [column for column in columns if column["name"] == "name"][0]
+ if name_column and name_column["nullable"]:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=False)
+ value_column = [column for column in columns if column["name"] == "value"][0]
+ if value_column and value_column["nullable"]:
+ batch_op.alter_column("value", existing_type=sa.VARCHAR(), nullable=False)
+
+
+# ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ columns = inspector.get_columns("variable")
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ name_column = [column for column in columns if column["name"] == "name"][0]
+ if name_column and not name_column["nullable"]:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True)
+ value_column = [column for column in columns if column["name"] == "value"][0]
+ if value_column and not value_column["nullable"]:
+ batch_op.alter_column("name", existing_type=sa.VARCHAR(), nullable=True)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/d066bfd22890_add_message_table.py b/langflow/src/backend/base/langflow/alembic/versions/d066bfd22890_add_message_table.py
new file mode 100644
index 0000000..e985a7b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/d066bfd22890_add_message_table.py
@@ -0,0 +1,52 @@
+"""Add message table
+
+Revision ID: 325180f0c4e1
+Revises: 631faacf5da2
+Create Date: 2024-06-23 21:29:28.220100
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "325180f0c4e1"
+down_revision: Union[str, None] = "631faacf5da2"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if not migration.table_exists("message", conn):
+ op.create_table(
+ "message",
+ sa.Column("timestamp", sa.DateTime(), nullable=False),
+ sa.Column("sender", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("sender_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("text", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("flow_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=True),
+ sa.Column("files", sa.JSON(), nullable=True),
+ sa.ForeignKeyConstraint(
+ ["flow_id"],
+ ["flow.id"],
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if migration.table_exists("message", conn):
+ op.drop_table("message")
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/d2d475a1f7c0_add_tags_column_to_flow.py b/langflow/src/backend/base/langflow/alembic/versions/d2d475a1f7c0_add_tags_column_to_flow.py
new file mode 100644
index 0000000..d314930
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/d2d475a1f7c0_add_tags_column_to_flow.py
@@ -0,0 +1,41 @@
+"""add tags column to flow
+
+Revision ID: d2d475a1f7c0
+Revises: d3dbf656a499
+Create Date: 2024-10-03 13:33:59.517261
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = 'd2d475a1f7c0'
+down_revision: Union[str, None] = 'd3dbf656a499'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if not migration.column_exists(table_name='flow', column_name='tags', conn=conn):
+ batch_op.add_column(sa.Column('tags', sa.JSON(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if migration.column_exists(table_name='flow', column_name='tags', conn=conn):
+ batch_op.drop_column('tags')
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/d3dbf656a499_add_gradient_column_in_flow.py b/langflow/src/backend/base/langflow/alembic/versions/d3dbf656a499_add_gradient_column_in_flow.py
new file mode 100644
index 0000000..b40c63d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/d3dbf656a499_add_gradient_column_in_flow.py
@@ -0,0 +1,41 @@
+"""add gradient column in Flow
+
+Revision ID: d3dbf656a499
+Revises: e5a65ecff2cd
+Create Date: 2024-09-27 09:35:19.424089
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = 'd3dbf656a499'
+down_revision: Union[str, None] = 'e5a65ecff2cd'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if not migration.column_exists(table_name='flow', column_name='gradient', conn=conn):
+ batch_op.add_column(sa.Column('gradient', sqlmodel.sql.sqltypes.AutoString(), nullable=True))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if migration.column_exists(table_name='flow', column_name='gradient', conn=conn):
+ batch_op.drop_column('gradient')
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/dd9e0804ebd1_add_v2_file_table.py b/langflow/src/backend/base/langflow/alembic/versions/dd9e0804ebd1_add_v2_file_table.py
new file mode 100644
index 0000000..2f9575b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/dd9e0804ebd1_add_v2_file_table.py
@@ -0,0 +1,49 @@
+"""Add V2 File Table
+
+Revision ID: dd9e0804ebd1
+Revises: e3162c1804e6
+Create Date: 2025-02-03 11:47:16.101523
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+from langflow.utils import migration
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'dd9e0804ebd1'
+down_revision: Union[str, None] = 'e3162c1804e6'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ if not migration.table_exists("file", conn):
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table(
+ "file",
+ sa.Column("id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("user_id", sqlmodel.sql.sqltypes.types.Uuid(), nullable=False),
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False, unique=True),
+ sa.Column("path", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
+ sa.Column("size", sa.Integer(), nullable=False),
+ sa.Column("provider", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
+ sa.Column("created_at", sa.DateTime(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_file_user_id_user"),
+ sa.UniqueConstraint("name"),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if migration.table_exists("file", conn):
+ op.drop_table("file")
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/e3162c1804e6_add_persistent_locked_state.py b/langflow/src/backend/base/langflow/alembic/versions/e3162c1804e6_add_persistent_locked_state.py
new file mode 100644
index 0000000..9bfa34e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/e3162c1804e6_add_persistent_locked_state.py
@@ -0,0 +1,43 @@
+"""add persistent locked state
+
+Revision ID: e3162c1804e6
+Revises: 1eab2c3eb45e
+Create Date: 2024-11-07 14:50:35.201760
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+import sqlmodel
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = 'e3162c1804e6'
+down_revision: Union[str, None] = '1eab2c3eb45e'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = Inspector.from_engine(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if "locked" not in column_names:
+ batch_op.add_column(sa.Column('locked', sa.Boolean(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = Inspector.from_engine(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("flow")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if "locked" in column_names:
+ batch_op.drop_column('locked')
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/e3bc869fa272_fix_nullable.py b/langflow/src/backend/base/langflow/alembic/versions/e3bc869fa272_fix_nullable.py
new file mode 100644
index 0000000..2d806ac
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/e3bc869fa272_fix_nullable.py
@@ -0,0 +1,68 @@
+"""Fix nullable
+
+Revision ID: e3bc869fa272
+Revises: 1a110b568907
+Create Date: 2024-04-10 19:17:22.820455
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "e3bc869fa272"
+down_revision: Union[str, None] = "1a110b568907"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "variable" not in table_names:
+ return
+ columns = [column for column in inspector.get_columns("variable")]
+ column_names = [column["name"] for column in columns]
+
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if "created_at" in column_names:
+ created_at_colunmn = next(column for column in columns if column["name"] == "created_at")
+ if created_at_colunmn["nullable"] is False:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.TIMESTAMP(timezone=True),
+ nullable=True,
+ # existing_server_default expects str | bool | Identity | Computed | None
+ # sa.text("now()") is not a valid value for existing_server_default
+ existing_server_default=False,
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names()
+ # ### commands auto generated by Alembic - please adjust! ###
+ if "variable" not in table_names:
+ return
+ columns = [column for column in inspector.get_columns("variable")]
+ column_names = [column["name"] for column in columns]
+ with op.batch_alter_table("variable", schema=None) as batch_op:
+ if "created_at" in column_names:
+ created_at_colunmn = next(column for column in columns if column["name"] == "created_at")
+ if created_at_colunmn["nullable"] is True:
+ batch_op.alter_column(
+ "created_at",
+ existing_type=sa.TIMESTAMP(timezone=True),
+ nullable=False,
+ existing_server_default=False,
+ )
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/e5a65ecff2cd_nullable_in_vertex_build.py b/langflow/src/backend/base/langflow/alembic/versions/e5a65ecff2cd_nullable_in_vertex_build.py
new file mode 100644
index 0000000..b22ee1c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/e5a65ecff2cd_nullable_in_vertex_build.py
@@ -0,0 +1,49 @@
+"""nullable in vertex build
+
+Revision ID: e5a65ecff2cd
+Revises: 4522eb831f5c
+Create Date: 2024-09-02 14:55:19.707355
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+from langflow.utils import migration
+
+# revision identifiers, used by Alembic.
+revision: str = "e5a65ecff2cd"
+down_revision: Union[str, None] = "4522eb831f5c"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+ with op.batch_alter_table("vertex_build", schema=None) as batch_op:
+ if migration.column_exists(table_name="vertex_build", column_name="id", conn=conn):
+ columns = inspector.get_columns("vertex_build")
+ id_column = next((column for column in columns if column["name"] == "id"), None)
+ if id_column is not None and id_column["nullable"]:
+ batch_op.alter_column("id", existing_type=sa.VARCHAR(), nullable=False)
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ # ### commands auto generated by Alembic - please adjust! ###
+ inspector = sa.inspect(conn) # type: ignore
+ with op.batch_alter_table("vertex_build", schema=None) as batch_op:
+ if migration.column_exists(table_name="vertex_build", column_name="id", conn=conn):
+ columns = inspector.get_columns("vertex_build")
+ id_column = next((column for column in columns if column["name"] == "id"), None)
+ if id_column is not None and not id_column["nullable"]:
+ batch_op.alter_column("id", existing_type=sa.VARCHAR(), nullable=True)
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/eb5866d51fd2_change_columns_to_be_nullable.py b/langflow/src/backend/base/langflow/alembic/versions/eb5866d51fd2_change_columns_to_be_nullable.py
new file mode 100644
index 0000000..acb09cd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/eb5866d51fd2_change_columns_to_be_nullable.py
@@ -0,0 +1,31 @@
+"""Change columns to be nullable
+
+Revision ID: eb5866d51fd2
+Revises: 67cc006d50bf
+Create Date: 2023-10-04 10:18:25.640458
+
+"""
+
+from typing import Sequence, Union
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "eb5866d51fd2"
+down_revision: Union[str, None] = "67cc006d50bf"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ connection = op.get_bind() # noqa
+
+ pass
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ pass
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/eb5e72293a8e_add_error_and_edit_flags_to_message.py b/langflow/src/backend/base/langflow/alembic/versions/eb5e72293a8e_add_error_and_edit_flags_to_message.py
new file mode 100644
index 0000000..496ab2a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/eb5e72293a8e_add_error_and_edit_flags_to_message.py
@@ -0,0 +1,49 @@
+"""Add error and edit flags to message
+
+Revision ID: eb5e72293a8e
+Revises: 5ace73a7f223
+Create Date: 2024-09-19 16:18:50.828648
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "eb5e72293a8e"
+down_revision: Union[str, None] = "5ace73a7f223"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("message")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ if "error" not in column_names:
+ batch_op.add_column(sa.Column("error", sa.Boolean(), nullable=False, server_default=sa.false()))
+ if "edit" not in column_names:
+ batch_op.add_column(sa.Column("edit", sa.Boolean(), nullable=False, server_default=sa.false()))
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ table_names = inspector.get_table_names() # noqa
+ column_names = [column["name"] for column in inspector.get_columns("message")]
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table("message", schema=None) as batch_op:
+ if "edit" in column_names:
+ batch_op.drop_column("edit")
+ if "error" in column_names:
+ batch_op.drop_column("error")
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py b/langflow/src/backend/base/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py
new file mode 100644
index 0000000..842c558
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py
@@ -0,0 +1,42 @@
+"""User id can be null in Flow
+
+Revision ID: f5ee9749d1a6
+Revises: 7843803a87b5
+Create Date: 2023-10-18 23:12:27.297016
+
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "f5ee9749d1a6"
+down_revision: Union[str, None] = "7843803a87b5"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.alter_column("user_id", existing_type=sa.CHAR(length=32), nullable=True)
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ try:
+ with op.batch_alter_table("flow", schema=None) as batch_op:
+ batch_op.alter_column("user_id", existing_type=sa.CHAR(length=32), nullable=False)
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/alembic/versions/fd531f8868b1_fix_credential_table.py b/langflow/src/backend/base/langflow/alembic/versions/fd531f8868b1_fix_credential_table.py
new file mode 100644
index 0000000..e180b71
--- /dev/null
+++ b/langflow/src/backend/base/langflow/alembic/versions/fd531f8868b1_fix_credential_table.py
@@ -0,0 +1,60 @@
+"""Fix Credential table
+
+Revision ID: fd531f8868b1
+Revises: 2ac71eb9c3ae
+Create Date: 2023-11-24 15:07:37.566516
+
+"""
+
+from typing import Optional, Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.engine.reflection import Inspector
+
+# revision identifiers, used by Alembic.
+revision: str = "fd531f8868b1"
+down_revision: Union[str, None] = "2ac71eb9c3ae"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ tables = inspector.get_table_names()
+ foreign_keys_names = []
+ if "credential" in tables:
+ foreign_keys = inspector.get_foreign_keys("credential")
+ foreign_keys_names = [fk["name"] for fk in foreign_keys]
+
+ try:
+ if "credential" in tables and "fk_credential_user_id" not in foreign_keys_names:
+ with op.batch_alter_table("credential", schema=None) as batch_op:
+ batch_op.create_foreign_key("fk_credential_user_id", "user", ["user_id"], ["id"])
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ conn = op.get_bind()
+ inspector = sa.inspect(conn) # type: ignore
+ tables = inspector.get_table_names()
+ foreign_keys_names: list[Optional[str]] = []
+ if "credential" in tables:
+ foreign_keys = inspector.get_foreign_keys("credential")
+ foreign_keys_names = [fk["name"] for fk in foreign_keys]
+ try:
+ if "credential" in tables and "fk_credential_user_id" in foreign_keys_names:
+ with op.batch_alter_table("credential", schema=None) as batch_op:
+ batch_op.drop_constraint("fk_credential_user_id", type_="foreignkey")
+ except Exception as e:
+ print(e)
+ pass
+
+ # ### end Alembic commands ###
diff --git a/langflow/src/backend/base/langflow/api/__init__.py b/langflow/src/backend/base/langflow/api/__init__.py
new file mode 100644
index 0000000..0efbd0b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/__init__.py
@@ -0,0 +1,5 @@
+from langflow.api.health_check_router import health_check_router
+from langflow.api.log_router import log_router
+from langflow.api.router import router, router_v2
+
+__all__ = ["health_check_router", "log_router", "router", "router_v2"]
diff --git a/langflow/src/backend/base/langflow/api/build.py b/langflow/src/backend/base/langflow/api/build.py
new file mode 100644
index 0000000..ef05da8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/build.py
@@ -0,0 +1,484 @@
+import asyncio
+import json
+import time
+import traceback
+import uuid
+from collections.abc import AsyncIterator
+
+from fastapi import BackgroundTasks, HTTPException
+from fastapi.responses import JSONResponse
+from loguru import logger
+from sqlmodel import select
+
+from langflow.api.disconnect import DisconnectHandlerStreamingResponse
+from langflow.api.utils import (
+ CurrentActiveUser,
+ build_graph_from_data,
+ build_graph_from_db,
+ format_elapsed_time,
+ format_exception_message,
+ get_top_level_vertices,
+ parse_exception,
+)
+from langflow.api.v1.schemas import (
+ FlowDataRequest,
+ InputValueRequest,
+ ResultDataResponse,
+ VertexBuildResponse,
+)
+from langflow.events.event_manager import EventManager
+from langflow.exceptions.component import ComponentBuildError
+from langflow.graph.graph.base import Graph
+from langflow.graph.utils import log_vertex_build
+from langflow.schema.message import ErrorMessage
+from langflow.schema.schema import OutputValue
+from langflow.services.database.models.flow import Flow
+from langflow.services.deps import get_chat_service, get_telemetry_service, session_scope
+from langflow.services.job_queue.service import JobQueueService
+from langflow.services.telemetry.schema import ComponentPayload, PlaygroundPayload
+
+
+async def start_flow_build(
+ *,
+ flow_id: uuid.UUID,
+ background_tasks: BackgroundTasks,
+ inputs: InputValueRequest | None,
+ data: FlowDataRequest | None,
+ files: list[str] | None,
+ stop_component_id: str | None,
+ start_component_id: str | None,
+ log_builds: bool,
+ current_user: CurrentActiveUser,
+ queue_service: JobQueueService,
+) -> str:
+ """Start the flow build process by setting up the queue and starting the build task.
+
+ Returns:
+ the job_id.
+ """
+ job_id = str(uuid.uuid4())
+ try:
+ _, event_manager = queue_service.create_queue(job_id)
+ task_coro = generate_flow_events(
+ flow_id=flow_id,
+ background_tasks=background_tasks,
+ event_manager=event_manager,
+ inputs=inputs,
+ data=data,
+ files=files,
+ stop_component_id=stop_component_id,
+ start_component_id=start_component_id,
+ log_builds=log_builds,
+ current_user=current_user,
+ )
+ queue_service.start_job(job_id, task_coro)
+ except Exception as e:
+ logger.exception("Failed to create queue and start task")
+ raise HTTPException(status_code=500, detail=str(e)) from e
+ return job_id
+
+
+async def get_flow_events_response(
+ *,
+ job_id: str,
+ queue_service: JobQueueService,
+ stream: bool = True,
+):
+ """Get events for a specific build job, either as a stream or single event."""
+ try:
+ main_queue, event_manager, event_task = queue_service.get_queue_data(job_id)
+ if stream:
+ if event_task is None:
+ raise HTTPException(status_code=404, detail="No event task found for job")
+ return await create_flow_response(
+ queue=main_queue,
+ event_manager=event_manager,
+ event_task=event_task,
+ )
+
+ # Polling mode - get exactly one event
+ _, value, _ = await main_queue.get()
+ if value is None:
+ # End of stream, trigger end event
+ if event_task is not None:
+ event_task.cancel()
+ event_manager.on_end(data={})
+
+ return JSONResponse({"event": value.decode("utf-8") if value else None})
+
+ except ValueError as exc:
+ raise HTTPException(status_code=404, detail=str(exc)) from exc
+
+
+async def create_flow_response(
+ queue: asyncio.Queue,
+ event_manager: EventManager,
+ event_task: asyncio.Task,
+) -> DisconnectHandlerStreamingResponse:
+ """Create a streaming response for the flow build process."""
+
+ async def consume_and_yield() -> AsyncIterator[str]:
+ while True:
+ try:
+ event_id, value, put_time = await queue.get()
+ if value is None:
+ break
+ get_time = time.time()
+ yield value.decode("utf-8")
+ logger.debug(f"Event {event_id} consumed in {get_time - put_time:.4f}s")
+ except Exception as exc: # noqa: BLE001
+ logger.exception(f"Error consuming event: {exc}")
+ break
+
+ def on_disconnect() -> None:
+ logger.debug("Client disconnected, closing tasks")
+ event_task.cancel()
+ event_manager.on_end(data={})
+
+ return DisconnectHandlerStreamingResponse(
+ consume_and_yield(),
+ media_type="application/x-ndjson",
+ on_disconnect=on_disconnect,
+ )
+
+
+async def generate_flow_events(
+ *,
+ flow_id: uuid.UUID,
+ background_tasks: BackgroundTasks,
+ event_manager: EventManager,
+ inputs: InputValueRequest | None,
+ data: FlowDataRequest | None,
+ files: list[str] | None,
+ stop_component_id: str | None,
+ start_component_id: str | None,
+ log_builds: bool,
+ current_user: CurrentActiveUser,
+) -> None:
+ """Generate events for flow building process.
+
+ This function handles the core flow building logic and generates appropriate events:
+ - Building and validating the graph
+ - Processing vertices
+ - Handling errors and cleanup
+ """
+ chat_service = get_chat_service()
+ telemetry_service = get_telemetry_service()
+ if not inputs:
+ inputs = InputValueRequest(session=str(flow_id))
+
+ async def build_graph_and_get_order() -> tuple[list[str], list[str], Graph]:
+ start_time = time.perf_counter()
+ components_count = 0
+ graph = None
+ try:
+ flow_id_str = str(flow_id)
+ # Create a fresh session for database operations
+ async with session_scope() as fresh_session:
+ graph = await create_graph(fresh_session, flow_id_str)
+
+ graph.validate_stream()
+ first_layer = sort_vertices(graph)
+
+ if inputs is not None and getattr(inputs, "session", None) is not None:
+ graph.session_id = inputs.session
+
+ for vertex_id in first_layer:
+ graph.run_manager.add_to_vertices_being_run(vertex_id)
+
+ # Now vertices is a list of lists
+ # We need to get the id of each vertex
+ # and return the same structure but only with the ids
+ components_count = len(graph.vertices)
+ vertices_to_run = list(graph.vertices_to_run.union(get_top_level_vertices(graph, graph.vertices_to_run)))
+
+ await chat_service.set_cache(flow_id_str, graph)
+ await log_telemetry(start_time, components_count, success=True)
+
+ except Exception as exc:
+ await log_telemetry(start_time, components_count, success=False, error_message=str(exc))
+
+ if "stream or streaming set to True" in str(exc):
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+ logger.exception("Error checking build status")
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+ return first_layer, vertices_to_run, graph
+
+ async def log_telemetry(
+ start_time: float, components_count: int, *, success: bool, error_message: str | None = None
+ ):
+ background_tasks.add_task(
+ telemetry_service.log_package_playground,
+ PlaygroundPayload(
+ playground_seconds=int(time.perf_counter() - start_time),
+ playground_component_count=components_count,
+ playground_success=success,
+ playground_error_message=str(error_message) if error_message else "",
+ ),
+ )
+
+ async def create_graph(fresh_session, flow_id_str: str) -> Graph:
+ if not data:
+ return await build_graph_from_db(flow_id=flow_id, session=fresh_session, chat_service=chat_service)
+
+ result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
+ flow_name = result.first()
+
+ return await build_graph_from_data(
+ flow_id=flow_id_str,
+ payload=data.model_dump(),
+ user_id=str(current_user.id),
+ flow_name=flow_name,
+ )
+
+ def sort_vertices(graph: Graph) -> list[str]:
+ try:
+ return graph.sort_vertices(stop_component_id, start_component_id)
+ except Exception: # noqa: BLE001
+ logger.exception("Error sorting vertices")
+ return graph.sort_vertices()
+
+ async def _build_vertex(vertex_id: str, graph: Graph, event_manager: EventManager) -> VertexBuildResponse:
+ flow_id_str = str(flow_id)
+ next_runnable_vertices = []
+ top_level_vertices = []
+ start_time = time.perf_counter()
+ error_message = None
+ try:
+ vertex = graph.get_vertex(vertex_id)
+ try:
+ lock = chat_service.async_cache_locks[flow_id_str]
+ vertex_build_result = await graph.build_vertex(
+ vertex_id=vertex_id,
+ user_id=str(current_user.id),
+ inputs_dict=inputs.model_dump() if inputs else {},
+ files=files,
+ get_cache=chat_service.get_cache,
+ set_cache=chat_service.set_cache,
+ event_manager=event_manager,
+ )
+ result_dict = vertex_build_result.result_dict
+ params = vertex_build_result.params
+ valid = vertex_build_result.valid
+ artifacts = vertex_build_result.artifacts
+ next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False)
+ top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices)
+
+ result_data_response = ResultDataResponse.model_validate(result_dict, from_attributes=True)
+ except Exception as exc: # noqa: BLE001
+ if isinstance(exc, ComponentBuildError):
+ params = exc.message
+ tb = exc.formatted_traceback
+ else:
+ tb = traceback.format_exc()
+ logger.exception("Error building Component")
+ params = format_exception_message(exc)
+ message = {"errorMessage": params, "stackTrace": tb}
+ valid = False
+ error_message = params
+ output_label = vertex.outputs[0]["name"] if vertex.outputs else "output"
+ outputs = {output_label: OutputValue(message=message, type="error")}
+ result_data_response = ResultDataResponse(results={}, outputs=outputs)
+ artifacts = {}
+ background_tasks.add_task(graph.end_all_traces, error=exc)
+
+ result_data_response.message = artifacts
+
+ # Log the vertex build
+ if not vertex.will_stream and log_builds:
+ background_tasks.add_task(
+ log_vertex_build,
+ flow_id=flow_id_str,
+ vertex_id=vertex_id,
+ valid=valid,
+ params=params,
+ data=result_data_response,
+ artifacts=artifacts,
+ )
+ else:
+ await chat_service.set_cache(flow_id_str, graph)
+
+ timedelta = time.perf_counter() - start_time
+ duration = format_elapsed_time(timedelta)
+ result_data_response.duration = duration
+ result_data_response.timedelta = timedelta
+ vertex.add_build_time(timedelta)
+ inactivated_vertices = list(graph.inactivated_vertices)
+ graph.reset_inactivated_vertices()
+ graph.reset_activated_vertices()
+ # graph.stop_vertex tells us if the user asked
+ # to stop the build of the graph at a certain vertex
+ # if it is in next_vertices_ids, we need to remove other
+ # vertices from next_vertices_ids
+ if graph.stop_vertex and graph.stop_vertex in next_runnable_vertices:
+ next_runnable_vertices = [graph.stop_vertex]
+
+ if not graph.run_manager.vertices_being_run and not next_runnable_vertices:
+ background_tasks.add_task(graph.end_all_traces)
+
+ build_response = VertexBuildResponse(
+ inactivated_vertices=list(set(inactivated_vertices)),
+ next_vertices_ids=list(set(next_runnable_vertices)),
+ top_level_vertices=list(set(top_level_vertices)),
+ valid=valid,
+ params=params,
+ id=vertex.id,
+ data=result_data_response,
+ )
+ background_tasks.add_task(
+ telemetry_service.log_package_component,
+ ComponentPayload(
+ component_name=vertex_id.split("-")[0],
+ component_seconds=int(time.perf_counter() - start_time),
+ component_success=valid,
+ component_error_message=error_message,
+ ),
+ )
+ except Exception as exc:
+ background_tasks.add_task(
+ telemetry_service.log_package_component,
+ ComponentPayload(
+ component_name=vertex_id.split("-")[0],
+ component_seconds=int(time.perf_counter() - start_time),
+ component_success=False,
+ component_error_message=str(exc),
+ ),
+ )
+ logger.exception("Error building Component")
+ message = parse_exception(exc)
+ raise HTTPException(status_code=500, detail=message) from exc
+
+ return build_response
+
+ async def build_vertices(
+ vertex_id: str,
+ graph: Graph,
+ event_manager: EventManager,
+ ) -> None:
+ """Build vertices and handle their events.
+
+ Args:
+ vertex_id: The ID of the vertex to build
+ graph: The graph instance
+ event_manager: Manager for handling events
+ """
+ try:
+ vertex_build_response: VertexBuildResponse = await _build_vertex(vertex_id, graph, event_manager)
+ except asyncio.CancelledError as exc:
+ logger.error(f"Build cancelled: {exc}")
+ raise
+
+ # send built event or error event
+ try:
+ vertex_build_response_json = vertex_build_response.model_dump_json()
+ build_data = json.loads(vertex_build_response_json)
+ except Exception as exc:
+ msg = f"Error serializing vertex build response: {exc}"
+ raise ValueError(msg) from exc
+
+ event_manager.on_end_vertex(data={"build_data": build_data})
+
+ if vertex_build_response.valid and vertex_build_response.next_vertices_ids:
+ tasks = []
+ for next_vertex_id in vertex_build_response.next_vertices_ids:
+ task = asyncio.create_task(
+ build_vertices(
+ next_vertex_id,
+ graph,
+ event_manager,
+ )
+ )
+ tasks.append(task)
+ await asyncio.gather(*tasks)
+
+ try:
+ ids, vertices_to_run, graph = await build_graph_and_get_order()
+ except Exception as e:
+ error_message = ErrorMessage(
+ flow_id=flow_id,
+ exception=e,
+ )
+ event_manager.on_error(data=error_message.data)
+ raise
+
+ event_manager.on_vertices_sorted(data={"ids": ids, "to_run": vertices_to_run})
+
+ tasks = []
+ for vertex_id in ids:
+ task = asyncio.create_task(build_vertices(vertex_id, graph, event_manager))
+ tasks.append(task)
+ try:
+ await asyncio.gather(*tasks)
+ except asyncio.CancelledError:
+ background_tasks.add_task(graph.end_all_traces)
+ raise
+ except Exception as e:
+ logger.error(f"Error building vertices: {e}")
+ custom_component = graph.get_vertex(vertex_id).custom_component
+ trace_name = getattr(custom_component, "trace_name", None)
+ error_message = ErrorMessage(
+ flow_id=flow_id,
+ exception=e,
+ session_id=graph.session_id,
+ trace_name=trace_name,
+ )
+ event_manager.on_error(data=error_message.data)
+ raise
+ event_manager.on_end(data={})
+ await event_manager.queue.put((None, None, time.time()))
+
+
+async def cancel_flow_build(
+ *,
+ job_id: str,
+ queue_service: JobQueueService,
+) -> bool:
+ """Cancel an ongoing flow build job.
+
+ Args:
+ job_id: The unique identifier of the job to cancel
+ queue_service: The service managing job queues
+
+ Returns:
+ True if the job was successfully canceled or doesn't need cancellation
+ False if the cancellation failed
+
+ Raises:
+ ValueError: If the job doesn't exist
+ asyncio.CancelledError: If the task cancellation failed
+ """
+ # Get the event task and event manager for the job
+ _, _, event_task = queue_service.get_queue_data(job_id)
+
+ if event_task is None:
+ logger.warning(f"No event task found for job_id {job_id}")
+ return True # Nothing to cancel is still a success
+
+ if event_task.done():
+ logger.info(f"Task for job_id {job_id} is already completed")
+ return True # Nothing to cancel is still a success
+
+ # Store the task reference to check status after cleanup
+ task_before_cleanup = event_task
+
+ try:
+ # Perform cleanup using the queue service
+ await queue_service.cleanup_job(job_id)
+ except asyncio.CancelledError:
+ # Check if the task was actually cancelled
+ if task_before_cleanup.cancelled():
+ logger.info(f"Successfully cancelled flow build for job_id {job_id} (CancelledError caught)")
+ return True
+ # If the task wasn't cancelled, re-raise the exception
+ logger.error(f"CancelledError caught but task for job_id {job_id} was not cancelled")
+ raise
+
+ # If no exception was raised, verify that the task was actually cancelled
+ # The task should be done (cancelled) after cleanup
+ if task_before_cleanup.cancelled():
+ logger.info(f"Successfully cancelled flow build for job_id {job_id}")
+ return True
+
+ # If we get here, the task wasn't cancelled properly
+ logger.error(f"Failed to cancel flow build for job_id {job_id}, task is still running")
+ return False
diff --git a/langflow/src/backend/base/langflow/api/disconnect.py b/langflow/src/backend/base/langflow/api/disconnect.py
new file mode 100644
index 0000000..a11454c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/disconnect.py
@@ -0,0 +1,31 @@
+import asyncio
+import typing
+
+from fastapi.responses import StreamingResponse
+from starlette.background import BackgroundTask
+from starlette.responses import ContentStream
+from starlette.types import Receive
+
+
+class DisconnectHandlerStreamingResponse(StreamingResponse):
+ def __init__(
+ self,
+ content: ContentStream,
+ status_code: int = 200,
+ headers: typing.Mapping[str, str] | None = None,
+ media_type: str | None = None,
+ background: BackgroundTask | None = None,
+ on_disconnect: typing.Callable | None = None,
+ ):
+ super().__init__(content, status_code, headers, media_type, background)
+ self.on_disconnect = on_disconnect
+
+ async def listen_for_disconnect(self, receive: Receive) -> None:
+ while True:
+ message = await receive()
+ if message["type"] == "http.disconnect":
+ if self.on_disconnect:
+ coro = self.on_disconnect()
+ if asyncio.iscoroutine(coro):
+ await coro
+ break
diff --git a/langflow/src/backend/base/langflow/api/health_check_router.py b/langflow/src/backend/base/langflow/api/health_check_router.py
new file mode 100644
index 0000000..3edeb08
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/health_check_router.py
@@ -0,0 +1,65 @@
+import uuid
+
+from fastapi import APIRouter, HTTPException, status
+from loguru import logger
+from pydantic import BaseModel
+from sqlmodel import select
+
+from langflow.api.utils import DbSession
+from langflow.services.database.models.flow import Flow
+from langflow.services.deps import get_chat_service
+
+health_check_router = APIRouter(tags=["Health Check"])
+
+
+class HealthResponse(BaseModel):
+ status: str = "nok"
+ chat: str = "error check the server logs"
+ db: str = "error check the server logs"
+ """
+ Do not send exceptions and detailed error messages to the client because it might contain credentials and other
+ sensitive server information.
+ """
+
+ def has_error(self) -> bool:
+ return any(v.startswith("error") for v in self.model_dump().values())
+
+
+# /health is also supported by uvicorn
+# it means uvicorn's /health serves first before the langflow instance is up
+# therefore it's not a reliable health check for a langflow instance
+# we keep this for backward compatibility
+@health_check_router.get("/health")
+async def health():
+ return {"status": "ok"}
+
+
+# /health_check evaluates key services
+# It's a reliable health check for a langflow instance
+@health_check_router.get("/health_check")
+async def health_check(
+ session: DbSession,
+) -> HealthResponse:
+ response = HealthResponse()
+ # use a fixed valid UUId that UUID collision is very unlikely
+ user_id = "da93c2bd-c857-4b10-8c8c-60988103320f"
+ try:
+ # Check database to query a bogus flow
+ stmt = select(Flow).where(Flow.id == uuid.uuid4())
+ (await session.exec(stmt)).first()
+ response.db = "ok"
+ except Exception: # noqa: BLE001
+ logger.exception("Error checking database")
+
+ try:
+ chat = get_chat_service()
+ await chat.set_cache("health_check", str(user_id))
+ await chat.get_cache("health_check")
+ response.chat = "ok"
+ except Exception: # noqa: BLE001
+ logger.exception("Error checking chat service")
+
+ if response.has_error():
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=response.model_dump())
+ response.status = "ok"
+ return response
diff --git a/langflow/src/backend/base/langflow/api/limited_background_tasks.py b/langflow/src/backend/base/langflow/api/limited_background_tasks.py
new file mode 100644
index 0000000..b09bc31
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/limited_background_tasks.py
@@ -0,0 +1,29 @@
+from fastapi import BackgroundTasks
+
+from langflow.graph.utils import log_vertex_build
+from langflow.services.deps import get_settings_service
+
+
+class LimitVertexBuildBackgroundTasks(BackgroundTasks):
+ """A subclass of FastAPI BackgroundTasks that limits the number of tasks added per vertex_id.
+
+ If more than max_vertex_builds_per_vertex tasks are added for a given vertex_id,
+ the oldest task is removed so that only the most recent remain.
+ This only applies to log_vertex_build tasks.
+ """
+
+ def add_task(self, func, *args, **kwargs):
+ # Only apply limiting logic to log_vertex_build tasks
+ if func == log_vertex_build:
+ vertex_id = kwargs.get("vertex_id")
+ if vertex_id is not None:
+ # Filter tasks that are log_vertex_build calls with the same vertex_id
+ relevant_tasks = [
+ t for t in self.tasks if t.func == log_vertex_build and t.kwargs.get("vertex_id") == vertex_id
+ ]
+ if len(relevant_tasks) >= get_settings_service().settings.max_vertex_builds_per_vertex:
+ # Remove the oldest task for this vertex_id
+ oldest_task = relevant_tasks[0]
+ self.tasks.remove(oldest_task)
+
+ super().add_task(func, *args, **kwargs)
diff --git a/langflow/src/backend/base/langflow/api/log_router.py b/langflow/src/backend/base/langflow/api/log_router.py
new file mode 100644
index 0000000..3b3af73
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/log_router.py
@@ -0,0 +1,103 @@
+import asyncio
+import json
+from http import HTTPStatus
+from typing import Annotated, Any
+
+from fastapi import APIRouter, HTTPException, Query, Request
+from fastapi.responses import JSONResponse, StreamingResponse
+
+from langflow.logging.logger import log_buffer
+
+log_router = APIRouter(tags=["Log"])
+
+
+NUMBER_OF_NOT_SENT_BEFORE_KEEPALIVE = 5
+
+
+async def event_generator(request: Request):
+ global log_buffer # noqa: PLW0602
+ last_read_item = None
+ current_not_sent = 0
+ while not await request.is_disconnected():
+ to_write: list[Any] = []
+ with log_buffer.get_write_lock():
+ if last_read_item is None:
+ last_read_item = log_buffer.buffer[len(log_buffer.buffer) - 1]
+ else:
+ found_last = False
+ for item in log_buffer.buffer:
+ if found_last:
+ to_write.append(item)
+ last_read_item = item
+ continue
+ if item is last_read_item:
+ found_last = True
+ continue
+
+ # in case the last item is nomore in the buffer
+ if not found_last:
+ for item in log_buffer.buffer:
+ to_write.append(item)
+ last_read_item = item
+ if to_write:
+ for ts, msg in to_write:
+ yield f"{json.dumps({ts: msg})}\n\n"
+ else:
+ current_not_sent += 1
+ if current_not_sent == NUMBER_OF_NOT_SENT_BEFORE_KEEPALIVE:
+ current_not_sent = 0
+ yield "keepalive\n\n"
+
+ await asyncio.sleep(1)
+
+
+@log_router.get("/logs-stream")
+async def stream_logs(
+ request: Request,
+):
+ """HTTP/2 Server-Sent-Event (SSE) endpoint for streaming logs.
+
+ It establishes a long-lived connection to the server and receives log messages in real-time.
+ The client should use the header "Accept: text/event-stream".
+ """
+ global log_buffer # noqa: PLW0602
+ if log_buffer.enabled() is False:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_IMPLEMENTED,
+ detail="Log retrieval is disabled",
+ )
+
+ return StreamingResponse(event_generator(request), media_type="text/event-stream")
+
+
+@log_router.get("/logs")
+async def logs(
+ lines_before: Annotated[int, Query(description="The number of logs before the timestamp or the last log")] = 0,
+ lines_after: Annotated[int, Query(description="The number of logs after the timestamp")] = 0,
+ timestamp: Annotated[int, Query(description="The timestamp to start getting logs from")] = 0,
+):
+ global log_buffer # noqa: PLW0602
+ if log_buffer.enabled() is False:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_IMPLEMENTED,
+ detail="Log retrieval is disabled",
+ )
+ if lines_after > 0 and lines_before > 0:
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="Cannot request logs before and after the timestamp",
+ )
+ if timestamp <= 0:
+ if lines_after > 0:
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="Timestamp is required when requesting logs after the timestamp",
+ )
+ content = log_buffer.get_last_n(10) if lines_before <= 0 else log_buffer.get_last_n(lines_before)
+ elif lines_before > 0:
+ content = log_buffer.get_before_timestamp(timestamp=timestamp, lines=lines_before)
+ elif lines_after > 0:
+ content = log_buffer.get_after_timestamp(timestamp=timestamp, lines=lines_after)
+ else:
+ content = log_buffer.get_before_timestamp(timestamp=timestamp, lines=10)
+ return JSONResponse(content=content)
diff --git a/langflow/src/backend/base/langflow/api/router.py b/langflow/src/backend/base/langflow/api/router.py
new file mode 100644
index 0000000..94d290e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/router.py
@@ -0,0 +1,43 @@
+# Router for base api
+from fastapi import APIRouter
+
+from langflow.api.v1 import (
+ api_key_router,
+ chat_router,
+ endpoints_router,
+ files_router,
+ flows_router,
+ folders_router,
+ login_router,
+ monitor_router,
+ starter_projects_router,
+ store_router,
+ users_router,
+ validate_router,
+ variables_router,
+)
+from langflow.api.v2 import files_router as files_router_v2
+
+router = APIRouter(
+ prefix="/api/v1",
+)
+
+router_v2 = APIRouter(
+ prefix="/api/v2",
+)
+
+router.include_router(chat_router)
+router.include_router(endpoints_router)
+router.include_router(validate_router)
+router.include_router(store_router)
+router.include_router(flows_router)
+router.include_router(users_router)
+router.include_router(api_key_router)
+router.include_router(login_router)
+router.include_router(variables_router)
+router.include_router(files_router)
+router.include_router(monitor_router)
+router.include_router(folders_router)
+router.include_router(starter_projects_router)
+
+router_v2.include_router(files_router_v2)
diff --git a/langflow/src/backend/base/langflow/api/schemas.py b/langflow/src/backend/base/langflow/api/schemas.py
new file mode 100644
index 0000000..99fec0e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/schemas.py
@@ -0,0 +1,14 @@
+from pathlib import Path
+from uuid import UUID
+
+from pydantic import BaseModel
+
+
+class UploadFileResponse(BaseModel):
+ """File upload response schema."""
+
+ id: UUID
+ name: str
+ path: Path
+ size: int
+ provider: str | None = None
diff --git a/langflow/src/backend/base/langflow/api/utils.py b/langflow/src/backend/base/langflow/api/utils.py
new file mode 100644
index 0000000..b65bfd3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/utils.py
@@ -0,0 +1,304 @@
+from __future__ import annotations
+
+import uuid
+from datetime import timedelta
+from typing import TYPE_CHECKING, Annotated, Any
+
+from fastapi import Depends, HTTPException, Query
+from fastapi_pagination import Params
+from loguru import logger
+from sqlalchemy import delete
+from sqlmodel.ext.asyncio.session import AsyncSession
+
+from langflow.graph.graph.base import Graph
+from langflow.services.auth.utils import get_current_active_user
+from langflow.services.database.models import User
+from langflow.services.database.models.flow import Flow
+from langflow.services.database.models.message import MessageTable
+from langflow.services.database.models.transactions.model import TransactionTable
+from langflow.services.database.models.vertex_builds.model import VertexBuildTable
+from langflow.services.deps import get_session, session_scope
+from langflow.services.store.utils import get_lf_version_from_pypi
+
+if TYPE_CHECKING:
+ from langflow.services.chat.service import ChatService
+ from langflow.services.store.schema import StoreComponentCreate
+
+
+API_WORDS = ["api", "key", "token"]
+
+MAX_PAGE_SIZE = 50
+MIN_PAGE_SIZE = 1
+
+CurrentActiveUser = Annotated[User, Depends(get_current_active_user)]
+DbSession = Annotated[AsyncSession, Depends(get_session)]
+
+
+def has_api_terms(word: str):
+ return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
+
+
+def remove_api_keys(flow: dict):
+ """Remove api keys from flow data."""
+ if flow.get("data") and flow["data"].get("nodes"):
+ for node in flow["data"]["nodes"]:
+ node_data = node.get("data").get("node")
+ template = node_data.get("template")
+ for value in template.values():
+ if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
+ value["value"] = None
+
+ return flow
+
+
+def build_input_keys_response(langchain_object, artifacts):
+ """Build the input keys response."""
+ input_keys_response = {
+ "input_keys": dict.fromkeys(langchain_object.input_keys, ""),
+ "memory_keys": [],
+ "handle_keys": artifacts.get("handle_keys", []),
+ }
+
+ # Set the input keys values from artifacts
+ for key, value in artifacts.items():
+ if key in input_keys_response["input_keys"]:
+ input_keys_response["input_keys"][key] = value
+ # If the object has memory, that memory will have a memory_variables attribute
+ # memory variables should be removed from the input keys
+ if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
+ # Remove memory variables from input keys
+ input_keys_response["input_keys"] = {
+ key: value
+ for key, value in input_keys_response["input_keys"].items()
+ if key not in langchain_object.memory.memory_variables
+ }
+ # Add memory variables to memory_keys
+ input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
+
+ if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
+ input_keys_response["template"] = langchain_object.prompt.template
+
+ return input_keys_response
+
+
+def validate_is_component(flows: list[Flow]):
+ for flow in flows:
+ if not flow.data or flow.is_component is not None:
+ continue
+
+ is_component = get_is_component_from_data(flow.data)
+ if is_component is not None:
+ flow.is_component = is_component
+ else:
+ flow.is_component = len(flow.data.get("nodes", [])) == 1
+ return flows
+
+
+def get_is_component_from_data(data: dict):
+ """Returns True if the data is a component."""
+ return data.get("is_component")
+
+
+async def check_langflow_version(component: StoreComponentCreate) -> None:
+ from langflow.utils.version import get_version_info
+
+ __version__ = get_version_info()["version"]
+
+ if not component.last_tested_version:
+ component.last_tested_version = __version__
+
+ langflow_version = await get_lf_version_from_pypi()
+ if langflow_version is None:
+ raise HTTPException(status_code=500, detail="Unable to verify the latest version of Langflow")
+ if langflow_version != component.last_tested_version:
+ logger.warning(
+ f"Your version of Langflow ({component.last_tested_version}) is outdated. "
+ f"Please update to the latest version ({langflow_version}) and try again."
+ )
+
+
+def format_elapsed_time(elapsed_time: float) -> str:
+ """Format elapsed time to a human-readable format coming from perf_counter().
+
+ - Less than 1 second: returns milliseconds
+ - Less than 1 minute: returns seconds rounded to 2 decimals
+ - 1 minute or more: returns minutes and seconds
+ """
+ delta = timedelta(seconds=elapsed_time)
+ if delta < timedelta(seconds=1):
+ milliseconds = round(delta / timedelta(milliseconds=1))
+ return f"{milliseconds} ms"
+
+ if delta < timedelta(minutes=1):
+ seconds = round(elapsed_time, 2)
+ unit = "second" if seconds == 1 else "seconds"
+ return f"{seconds} {unit}"
+
+ minutes = delta // timedelta(minutes=1)
+ seconds = round((delta - timedelta(minutes=minutes)).total_seconds(), 2)
+ minutes_unit = "minute" if minutes == 1 else "minutes"
+ seconds_unit = "second" if seconds == 1 else "seconds"
+ return f"{minutes} {minutes_unit}, {seconds} {seconds_unit}"
+
+
+async def _get_flow_name(flow_id: uuid.UUID) -> str:
+ async with session_scope() as session:
+ flow = await session.get(Flow, flow_id)
+ if flow is None:
+ msg = f"Flow {flow_id} not found"
+ raise ValueError(msg)
+ return flow.name
+
+
+async def build_graph_from_data(flow_id: uuid.UUID | str, payload: dict, **kwargs):
+ """Build and cache the graph."""
+ # Get flow name
+ if "flow_name" not in kwargs:
+ flow_name = await _get_flow_name(flow_id if isinstance(flow_id, uuid.UUID) else uuid.UUID(flow_id))
+ kwargs["flow_name"] = flow_name
+ str_flow_id = str(flow_id)
+ graph = Graph.from_payload(payload, str_flow_id, **kwargs)
+ for vertex_id in graph.has_session_id_vertices:
+ vertex = graph.get_vertex(vertex_id)
+ if vertex is None:
+ msg = f"Vertex {vertex_id} not found"
+ raise ValueError(msg)
+ if not vertex.raw_params.get("session_id"):
+ vertex.update_raw_params({"session_id": str_flow_id}, overwrite=True)
+
+ run_id = uuid.uuid4()
+ graph.set_run_id(run_id)
+ graph.set_run_name()
+ await graph.initialize_run()
+ return graph
+
+
+async def build_graph_from_db_no_cache(flow_id: uuid.UUID, session: AsyncSession):
+ """Build and cache the graph."""
+ flow: Flow | None = await session.get(Flow, flow_id)
+ if not flow or not flow.data:
+ msg = "Invalid flow ID"
+ raise ValueError(msg)
+ return await build_graph_from_data(flow_id, flow.data, flow_name=flow.name, user_id=str(flow.user_id))
+
+
+async def build_graph_from_db(flow_id: uuid.UUID, session: AsyncSession, chat_service: ChatService):
+ graph = await build_graph_from_db_no_cache(flow_id=flow_id, session=session)
+ await chat_service.set_cache(str(flow_id), graph)
+ return graph
+
+
+async def build_and_cache_graph_from_data(
+ flow_id: uuid.UUID | str,
+ chat_service: ChatService,
+ graph_data: dict,
+): # -> Graph | Any:
+ """Build and cache the graph."""
+ # Convert flow_id to str if it's UUID
+ str_flow_id = str(flow_id) if isinstance(flow_id, uuid.UUID) else flow_id
+ graph = Graph.from_payload(graph_data, str_flow_id)
+ await chat_service.set_cache(str_flow_id, graph)
+ return graph
+
+
+def format_syntax_error_message(exc: SyntaxError) -> str:
+ """Format a SyntaxError message for returning to the frontend."""
+ if exc.text is None:
+ return f"Syntax error in code. Error on line {exc.lineno}"
+ return f"Syntax error in code. Error on line {exc.lineno}: {exc.text.strip()}"
+
+
+def get_causing_exception(exc: BaseException) -> BaseException:
+ """Get the causing exception from an exception."""
+ if hasattr(exc, "__cause__") and exc.__cause__:
+ return get_causing_exception(exc.__cause__)
+ return exc
+
+
+def format_exception_message(exc: Exception) -> str:
+ """Format an exception message for returning to the frontend."""
+ # We need to check if the __cause__ is a SyntaxError
+ # If it is, we need to return the message of the SyntaxError
+ causing_exception = get_causing_exception(exc)
+ if isinstance(causing_exception, SyntaxError):
+ return format_syntax_error_message(causing_exception)
+ return str(exc)
+
+
+def get_top_level_vertices(graph, vertices_ids):
+ """Retrieves the top-level vertices from the given graph based on the provided vertex IDs.
+
+ Args:
+ graph (Graph): The graph object containing the vertices.
+ vertices_ids (list): A list of vertex IDs.
+
+ Returns:
+ list: A list of top-level vertex IDs.
+
+ """
+ top_level_vertices = []
+ for vertex_id in vertices_ids:
+ vertex = graph.get_vertex(vertex_id)
+ if vertex.parent_is_top_level:
+ top_level_vertices.append(vertex.parent_node_id)
+ else:
+ top_level_vertices.append(vertex_id)
+ return top_level_vertices
+
+
+def parse_exception(exc):
+ """Parse the exception message."""
+ if hasattr(exc, "body"):
+ return exc.body["message"]
+ return str(exc)
+
+
+def get_suggestion_message(outdated_components: list[str]) -> str:
+ """Get the suggestion message for the outdated components."""
+ count = len(outdated_components)
+ if count == 0:
+ return "The flow contains no outdated components."
+ if count == 1:
+ return (
+ "The flow contains 1 outdated component. "
+ f"We recommend updating the following component: {outdated_components[0]}."
+ )
+ components = ", ".join(outdated_components)
+ return (
+ f"The flow contains {count} outdated components. We recommend updating the following components: {components}."
+ )
+
+
+def parse_value(value: Any, input_type: str) -> Any:
+ """Helper function to parse the value based on input type."""
+ if value == "":
+ return value
+ if input_type == "IntInput":
+ return int(value) if value is not None else None
+ if input_type == "FloatInput":
+ return float(value) if value is not None else None
+ return value
+
+
+async def cascade_delete_flow(session: AsyncSession, flow_id: uuid.UUID) -> None:
+ try:
+ # TODO: Verify if deleting messages is safe in terms of session id relevance
+ # If we delete messages directly, rather than setting flow_id to null,
+ # it might cause unexpected behaviors because the session id could still be
+ # used elsewhere to search for these messages.
+ await session.exec(delete(MessageTable).where(MessageTable.flow_id == flow_id))
+ await session.exec(delete(TransactionTable).where(TransactionTable.flow_id == flow_id))
+ await session.exec(delete(VertexBuildTable).where(VertexBuildTable.flow_id == flow_id))
+ await session.exec(delete(Flow).where(Flow.id == flow_id))
+ except Exception as e:
+ msg = f"Unable to cascade delete flow: {flow_id}"
+ raise RuntimeError(msg, e) from e
+
+
+def custom_params(
+ page: int | None = Query(None),
+ size: int | None = Query(None),
+):
+ if page is None and size is None:
+ return None
+ return Params(page=page or MIN_PAGE_SIZE, size=size or MAX_PAGE_SIZE)
diff --git a/langflow/src/backend/base/langflow/api/v1/__init__.py b/langflow/src/backend/base/langflow/api/v1/__init__.py
new file mode 100644
index 0000000..567415a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/__init__.py
@@ -0,0 +1,31 @@
+from langflow.api.v1.api_key import router as api_key_router
+from langflow.api.v1.chat import router as chat_router
+from langflow.api.v1.endpoints import router as endpoints_router
+from langflow.api.v1.files import router as files_router
+from langflow.api.v1.flows import router as flows_router
+from langflow.api.v1.folders import router as folders_router
+from langflow.api.v1.login import router as login_router
+from langflow.api.v1.mcp import router as mcp_router
+from langflow.api.v1.monitor import router as monitor_router
+from langflow.api.v1.starter_projects import router as starter_projects_router
+from langflow.api.v1.store import router as store_router
+from langflow.api.v1.users import router as users_router
+from langflow.api.v1.validate import router as validate_router
+from langflow.api.v1.variable import router as variables_router
+
+__all__ = [
+ "api_key_router",
+ "chat_router",
+ "endpoints_router",
+ "files_router",
+ "flows_router",
+ "folders_router",
+ "login_router",
+ "mcp_router",
+ "monitor_router",
+ "starter_projects_router",
+ "store_router",
+ "users_router",
+ "validate_router",
+ "variables_router",
+]
diff --git a/langflow/src/backend/base/langflow/api/v1/api_key.py b/langflow/src/backend/base/langflow/api/v1/api_key.py
new file mode 100644
index 0000000..135d5ad
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/api_key.py
@@ -0,0 +1,88 @@
+from uuid import UUID
+
+from fastapi import APIRouter, Depends, HTTPException, Response
+
+from langflow.api.utils import CurrentActiveUser, DbSession
+from langflow.api.v1.schemas import ApiKeyCreateRequest, ApiKeysResponse
+from langflow.services.auth import utils as auth_utils
+
+# Assuming you have these methods in your service layer
+from langflow.services.database.models.api_key.crud import create_api_key, delete_api_key, get_api_keys
+from langflow.services.database.models.api_key.model import ApiKeyCreate, UnmaskedApiKeyRead
+from langflow.services.deps import get_settings_service
+
+router = APIRouter(tags=["APIKey"], prefix="/api_key")
+
+
+@router.get("/")
+async def get_api_keys_route(
+ db: DbSession,
+ current_user: CurrentActiveUser,
+) -> ApiKeysResponse:
+ try:
+ user_id = current_user.id
+ keys = await get_api_keys(db, user_id)
+
+ return ApiKeysResponse(total_count=len(keys), user_id=user_id, api_keys=keys)
+ except Exception as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+
+
+@router.post("/")
+async def create_api_key_route(
+ req: ApiKeyCreate,
+ current_user: CurrentActiveUser,
+ db: DbSession,
+) -> UnmaskedApiKeyRead:
+ try:
+ user_id = current_user.id
+ return await create_api_key(db, req, user_id=user_id)
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+
+@router.delete("/{api_key_id}", dependencies=[Depends(auth_utils.get_current_active_user)])
+async def delete_api_key_route(
+ api_key_id: UUID,
+ db: DbSession,
+):
+ try:
+ await delete_api_key(db, api_key_id)
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ return {"detail": "API Key deleted"}
+
+
+@router.post("/store")
+async def save_store_api_key(
+ api_key_request: ApiKeyCreateRequest,
+ response: Response,
+ current_user: CurrentActiveUser,
+ db: DbSession,
+):
+ settings_service = get_settings_service()
+ auth_settings = settings_service.auth_settings
+
+ try:
+ api_key = api_key_request.api_key
+
+ # Encrypt the API key
+ encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service)
+ current_user.store_api_key = encrypted
+ db.add(current_user)
+ await db.commit()
+
+ response.set_cookie(
+ "apikey_tkn_lflw",
+ encrypted,
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=None, # Set to None to make it a session cookie
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+ return {"detail": "API Key saved"}
diff --git a/langflow/src/backend/base/langflow/api/v1/base.py b/langflow/src/backend/base/langflow/api/v1/base.py
new file mode 100644
index 0000000..879637b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/base.py
@@ -0,0 +1,50 @@
+from pydantic import BaseModel, field_validator, model_serializer
+
+from langflow.template.frontend_node.base import FrontendNode
+
+
+class CacheResponse(BaseModel):
+ data: dict
+
+
+class Code(BaseModel):
+ code: str
+
+
+class FrontendNodeRequest(FrontendNode):
+ template: dict # type: ignore[assignment]
+
+ @model_serializer(mode="wrap")
+ def serialize_model(self, handler):
+ # Override the default serialization method in FrontendNode
+ # because we don't need the name in the response (i.e. {name: {}})
+ return handler(self)
+
+
+class ValidatePromptRequest(BaseModel):
+ name: str
+ template: str
+ custom_fields: dict | None = None
+ frontend_node: FrontendNodeRequest | None = None
+
+
+# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
+class CodeValidationResponse(BaseModel):
+ imports: dict
+ function: dict
+
+ @field_validator("imports")
+ @classmethod
+ def validate_imports(cls, v):
+ return v or {"errors": []}
+
+ @field_validator("function")
+ @classmethod
+ def validate_function(cls, v):
+ return v or {"errors": []}
+
+
+class PromptValidationResponse(BaseModel):
+ input_variables: list
+ # object return for tweak call
+ frontend_node: FrontendNodeRequest | None = None
diff --git a/langflow/src/backend/base/langflow/api/v1/callback.py b/langflow/src/backend/base/langflow/api/v1/callback.py
new file mode 100644
index 0000000..527241a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/callback.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+from uuid import UUID
+
+from langchain_core.agents import AgentAction, AgentFinish
+from langchain_core.callbacks.base import AsyncCallbackHandler
+from loguru import logger
+from typing_extensions import override
+
+from langflow.api.v1.schemas import ChatResponse, PromptResponse
+from langflow.services.deps import get_chat_service, get_socket_service
+from langflow.utils.util import remove_ansi_escape_codes
+
+if TYPE_CHECKING:
+ from langflow.services.socket.service import SocketIOService
+
+
+# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
+class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
+ """Callback handler for streaming LLM responses."""
+
+ @property
+ def ignore_chain(self) -> bool:
+ """Whether to ignore chain callbacks."""
+ return False
+
+ def __init__(self, session_id: str):
+ self.chat_service = get_chat_service()
+ self.client_id = session_id
+ self.socketio_service: SocketIOService = get_socket_service()
+ self.sid = session_id
+ # self.socketio_service = self.chat_service.active_connections[self.client_id]
+
+ @override
+ async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: # type: ignore[misc]
+ resp = ChatResponse(message=token, type="stream", intermediate_steps="")
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
+
+ @override
+ async def on_tool_start(self, serialized: dict[str, Any], input_str: str, **kwargs: Any) -> Any: # type: ignore[misc]
+ """Run when tool starts running."""
+ resp = ChatResponse(
+ message="",
+ type="stream",
+ intermediate_steps=f"Tool input: {input_str}",
+ )
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
+
+ async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
+ """Run when tool ends running."""
+ observation_prefix = kwargs.get("observation_prefix", "Tool output: ")
+ split_output = output.split()
+ first_word = split_output[0]
+ rest_of_output = split_output[1:]
+ # Create a formatted message.
+ intermediate_steps = f"{observation_prefix}{first_word}"
+
+ # Create a ChatResponse instance.
+ resp = ChatResponse(
+ message="",
+ type="stream",
+ intermediate_steps=intermediate_steps,
+ )
+ rest_of_resps = [
+ ChatResponse(
+ message="",
+ type="stream",
+ intermediate_steps=f"{word}",
+ )
+ for word in rest_of_output
+ ]
+ resps = [resp, *rest_of_resps]
+ # Try to send the response, handle potential errors.
+
+ try:
+ # This is to emulate the stream of tokens
+ for resp in resps:
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
+ except Exception: # noqa: BLE001
+ logger.exception("Error sending response")
+
+ async def on_tool_error(
+ self,
+ error: BaseException,
+ *,
+ run_id: UUID,
+ parent_run_id: UUID | None = None,
+ tags: list[str] | None = None,
+ **kwargs: Any,
+ ) -> None:
+ """Run when tool errors."""
+
+ @override
+ async def on_text( # type: ignore[misc]
+ self, text: str, **kwargs: Any
+ ) -> Any:
+ """Run on arbitrary text."""
+ # This runs when first sending the prompt
+ # to the LLM, adding it will send the final prompt
+ # to the frontend
+ if "Prompt after formatting" in text:
+ text = text.replace("Prompt after formatting:\n", "")
+ text = remove_ansi_escape_codes(text)
+ resp = PromptResponse(
+ prompt=text,
+ )
+ await self.socketio_service.emit_message(to=self.sid, data=resp.model_dump())
+
+ @override
+ async def on_agent_action( # type: ignore[misc]
+ self, action: AgentAction, **kwargs: Any
+ ) -> None:
+ log = f"Thought: {action.log}"
+ # if there are line breaks, split them and send them
+ # as separate messages
+ if "\n" in log:
+ logs = log.split("\n")
+ for log in logs:
+ resp = ChatResponse(message="", type="stream", intermediate_steps=log)
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
+ else:
+ resp = ChatResponse(message="", type="stream", intermediate_steps=log)
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
+
+ @override
+ async def on_agent_finish( # type: ignore[misc]
+ self, finish: AgentFinish, **kwargs: Any
+ ) -> Any:
+ """Run on agent end."""
+ resp = ChatResponse(
+ message="",
+ type="stream",
+ intermediate_steps=finish.log,
+ )
+ await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
diff --git a/langflow/src/backend/base/langflow/api/v1/chat.py b/langflow/src/backend/base/langflow/api/v1/chat.py
new file mode 100644
index 0000000..db4bd7c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/chat.py
@@ -0,0 +1,488 @@
+from __future__ import annotations
+
+import asyncio
+import time
+import traceback
+import uuid
+from typing import TYPE_CHECKING, Annotated
+
+from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, status
+from fastapi.responses import StreamingResponse
+from loguru import logger
+
+from langflow.api.build import (
+ cancel_flow_build,
+ get_flow_events_response,
+ start_flow_build,
+)
+from langflow.api.limited_background_tasks import LimitVertexBuildBackgroundTasks
+from langflow.api.utils import (
+ CurrentActiveUser,
+ DbSession,
+ build_and_cache_graph_from_data,
+ build_graph_from_db,
+ format_elapsed_time,
+ format_exception_message,
+ get_top_level_vertices,
+ parse_exception,
+)
+from langflow.api.v1.schemas import (
+ CancelFlowResponse,
+ FlowDataRequest,
+ InputValueRequest,
+ ResultDataResponse,
+ StreamData,
+ VertexBuildResponse,
+ VerticesOrderResponse,
+)
+from langflow.exceptions.component import ComponentBuildError
+from langflow.graph.graph.base import Graph
+from langflow.graph.utils import log_vertex_build
+from langflow.schema.schema import OutputValue
+from langflow.services.cache.utils import CacheMiss
+from langflow.services.chat.service import ChatService
+from langflow.services.database.models.flow.model import Flow
+from langflow.services.deps import (
+ get_chat_service,
+ get_queue_service,
+ get_session,
+ get_telemetry_service,
+ session_scope,
+)
+from langflow.services.job_queue.service import JobQueueService
+from langflow.services.telemetry.schema import ComponentPayload, PlaygroundPayload
+
+if TYPE_CHECKING:
+ from langflow.graph.vertex.vertex_types import InterfaceVertex
+
+router = APIRouter(tags=["Chat"])
+
+
+@router.post("/build/{flow_id}/vertices", deprecated=True)
+async def retrieve_vertices_order(
+ *,
+ flow_id: uuid.UUID,
+ background_tasks: BackgroundTasks,
+ data: Annotated[FlowDataRequest | None, Body(embed=True)] | None = None,
+ stop_component_id: str | None = None,
+ start_component_id: str | None = None,
+ session: DbSession,
+) -> VerticesOrderResponse:
+ """Retrieve the vertices order for a given flow.
+
+ Args:
+ flow_id (str): The ID of the flow.
+ background_tasks (BackgroundTasks): The background tasks.
+ data (Optional[FlowDataRequest], optional): The flow data. Defaults to None.
+ stop_component_id (str, optional): The ID of the stop component. Defaults to None.
+ start_component_id (str, optional): The ID of the start component. Defaults to None.
+ session (AsyncSession, optional): The session dependency.
+
+ Returns:
+ VerticesOrderResponse: The response containing the ordered vertex IDs and the run ID.
+
+ Raises:
+ HTTPException: If there is an error checking the build status.
+ """
+ chat_service = get_chat_service()
+ telemetry_service = get_telemetry_service()
+ start_time = time.perf_counter()
+ components_count = None
+ try:
+ # First, we need to check if the flow_id is in the cache
+ if not data:
+ graph = await build_graph_from_db(flow_id=flow_id, session=session, chat_service=chat_service)
+ else:
+ graph = await build_and_cache_graph_from_data(
+ flow_id=flow_id, graph_data=data.model_dump(), chat_service=chat_service
+ )
+ graph = graph.prepare(stop_component_id, start_component_id)
+
+ # Now vertices is a list of lists
+ # We need to get the id of each vertex
+ # and return the same structure but only with the ids
+ components_count = len(graph.vertices)
+ vertices_to_run = list(graph.vertices_to_run.union(get_top_level_vertices(graph, graph.vertices_to_run)))
+ await chat_service.set_cache(str(flow_id), graph)
+ background_tasks.add_task(
+ telemetry_service.log_package_playground,
+ PlaygroundPayload(
+ playground_seconds=int(time.perf_counter() - start_time),
+ playground_component_count=components_count,
+ playground_success=True,
+ ),
+ )
+ return VerticesOrderResponse(ids=graph.first_layer, run_id=graph.run_id, vertices_to_run=vertices_to_run)
+ except Exception as exc:
+ background_tasks.add_task(
+ telemetry_service.log_package_playground,
+ PlaygroundPayload(
+ playground_seconds=int(time.perf_counter() - start_time),
+ playground_component_count=components_count,
+ playground_success=False,
+ playground_error_message=str(exc),
+ ),
+ )
+ if "stream or streaming set to True" in str(exc):
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+ logger.exception("Error checking build status")
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+@router.post("/build/{flow_id}/flow")
+async def build_flow(
+ *,
+ flow_id: uuid.UUID,
+ background_tasks: LimitVertexBuildBackgroundTasks,
+ inputs: Annotated[InputValueRequest | None, Body(embed=True)] = None,
+ data: Annotated[FlowDataRequest | None, Body(embed=True)] = None,
+ files: list[str] | None = None,
+ stop_component_id: str | None = None,
+ start_component_id: str | None = None,
+ log_builds: bool = True,
+ current_user: CurrentActiveUser,
+ queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
+):
+ """Build and process a flow, returning a job ID for event polling."""
+ # First verify the flow exists
+ async with session_scope() as session:
+ flow = await session.get(Flow, flow_id)
+ if not flow:
+ raise HTTPException(status_code=404, detail=f"Flow with id {flow_id} not found")
+
+ job_id = await start_flow_build(
+ flow_id=flow_id,
+ background_tasks=background_tasks,
+ inputs=inputs,
+ data=data,
+ files=files,
+ stop_component_id=stop_component_id,
+ start_component_id=start_component_id,
+ log_builds=log_builds,
+ current_user=current_user,
+ queue_service=queue_service,
+ )
+ return {"job_id": job_id}
+
+
+@router.get("/build/{job_id}/events")
+async def get_build_events(
+ job_id: str,
+ queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
+ *,
+ stream: bool = True,
+):
+ """Get events for a specific build job."""
+ return await get_flow_events_response(
+ job_id=job_id,
+ queue_service=queue_service,
+ stream=stream,
+ )
+
+
+@router.post("/build/{job_id}/cancel", response_model=CancelFlowResponse)
+async def cancel_build(
+ job_id: str,
+ queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
+):
+ """Cancel a specific build job."""
+ try:
+ # Cancel the flow build and check if it was successful
+ cancellation_success = await cancel_flow_build(job_id=job_id, queue_service=queue_service)
+
+ if cancellation_success:
+ # Cancellation succeeded or wasn't needed
+ return CancelFlowResponse(success=True, message="Flow build cancelled successfully")
+ # Cancellation was attempted but failed
+ return CancelFlowResponse(success=False, message="Failed to cancel flow build")
+ except asyncio.CancelledError:
+ # If CancelledError reaches here, it means the task was not successfully cancelled
+ logger.error(f"Failed to cancel flow build for job_id {job_id} (CancelledError caught)")
+ return CancelFlowResponse(success=False, message="Failed to cancel flow build")
+ except ValueError as exc:
+ # Job not found
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
+ except Exception as exc:
+ # Any other unexpected error
+ logger.exception(f"Error cancelling flow build for job_id {job_id}: {exc}")
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+
+
+@router.post("/build/{flow_id}/vertices/{vertex_id}", deprecated=True)
+async def build_vertex(
+ *,
+ flow_id: uuid.UUID,
+ vertex_id: str,
+ background_tasks: BackgroundTasks,
+ inputs: Annotated[InputValueRequest | None, Body(embed=True)] = None,
+ files: list[str] | None = None,
+ current_user: CurrentActiveUser,
+) -> VertexBuildResponse:
+ """Build a vertex instead of the entire graph.
+
+ Args:
+ flow_id (str): The ID of the flow.
+ vertex_id (str): The ID of the vertex to build.
+ background_tasks (BackgroundTasks): The background tasks dependency.
+ inputs (Optional[InputValueRequest], optional): The input values for the vertex. Defaults to None.
+ files (List[str], optional): The files to use. Defaults to None.
+ current_user (Any, optional): The current user dependency. Defaults to Depends(get_current_active_user).
+
+ Returns:
+ VertexBuildResponse: The response containing the built vertex information.
+
+ Raises:
+ HTTPException: If there is an error building the vertex.
+
+ """
+ chat_service = get_chat_service()
+ telemetry_service = get_telemetry_service()
+ flow_id_str = str(flow_id)
+
+ next_runnable_vertices = []
+ top_level_vertices = []
+ start_time = time.perf_counter()
+ error_message = None
+ try:
+ graph: Graph = await chat_service.get_cache(flow_id_str)
+ except KeyError as exc:
+ raise HTTPException(status_code=404, detail="Graph not found") from exc
+
+ try:
+ cache = await chat_service.get_cache(flow_id_str)
+ if isinstance(cache, CacheMiss):
+ # If there's no cache
+ logger.warning(f"No cache found for {flow_id_str}. Building graph starting at {vertex_id}")
+ graph = await build_graph_from_db(
+ flow_id=flow_id, session=await anext(get_session()), chat_service=chat_service
+ )
+ else:
+ graph = cache.get("result")
+ await graph.initialize_run()
+ vertex = graph.get_vertex(vertex_id)
+
+ try:
+ lock = chat_service.async_cache_locks[flow_id_str]
+ vertex_build_result = await graph.build_vertex(
+ vertex_id=vertex_id,
+ user_id=str(current_user.id),
+ inputs_dict=inputs.model_dump() if inputs else {},
+ files=files,
+ get_cache=chat_service.get_cache,
+ set_cache=chat_service.set_cache,
+ )
+ result_dict = vertex_build_result.result_dict
+ params = vertex_build_result.params
+ valid = vertex_build_result.valid
+ artifacts = vertex_build_result.artifacts
+ next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False)
+ top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices)
+ result_data_response = ResultDataResponse.model_validate(result_dict, from_attributes=True)
+ except Exception as exc: # noqa: BLE001
+ if isinstance(exc, ComponentBuildError):
+ params = exc.message
+ tb = exc.formatted_traceback
+ else:
+ tb = traceback.format_exc()
+ logger.exception("Error building Component")
+ params = format_exception_message(exc)
+ message = {"errorMessage": params, "stackTrace": tb}
+ valid = False
+ error_message = params
+ output_label = vertex.outputs[0]["name"] if vertex.outputs else "output"
+ outputs = {output_label: OutputValue(message=message, type="error")}
+ result_data_response = ResultDataResponse(results={}, outputs=outputs)
+ artifacts = {}
+ background_tasks.add_task(graph.end_all_traces, error=exc)
+ # If there's an error building the vertex
+ # we need to clear the cache
+ await chat_service.clear_cache(flow_id_str)
+
+ result_data_response.message = artifacts
+
+ # Log the vertex build
+ if not vertex.will_stream:
+ background_tasks.add_task(
+ log_vertex_build,
+ flow_id=flow_id_str,
+ vertex_id=vertex_id,
+ valid=valid,
+ params=params,
+ data=result_data_response,
+ artifacts=artifacts,
+ )
+
+ timedelta = time.perf_counter() - start_time
+ duration = format_elapsed_time(timedelta)
+ result_data_response.duration = duration
+ result_data_response.timedelta = timedelta
+ vertex.add_build_time(timedelta)
+ inactivated_vertices = list(graph.inactivated_vertices)
+ graph.reset_inactivated_vertices()
+ graph.reset_activated_vertices()
+
+ await chat_service.set_cache(flow_id_str, graph)
+
+ # graph.stop_vertex tells us if the user asked
+ # to stop the build of the graph at a certain vertex
+ # if it is in next_vertices_ids, we need to remove other
+ # vertices from next_vertices_ids
+ if graph.stop_vertex and graph.stop_vertex in next_runnable_vertices:
+ next_runnable_vertices = [graph.stop_vertex]
+
+ if not graph.run_manager.vertices_being_run and not next_runnable_vertices:
+ background_tasks.add_task(graph.end_all_traces)
+
+ build_response = VertexBuildResponse(
+ inactivated_vertices=list(set(inactivated_vertices)),
+ next_vertices_ids=list(set(next_runnable_vertices)),
+ top_level_vertices=list(set(top_level_vertices)),
+ valid=valid,
+ params=params,
+ id=vertex.id,
+ data=result_data_response,
+ )
+ background_tasks.add_task(
+ telemetry_service.log_package_component,
+ ComponentPayload(
+ component_name=vertex_id.split("-")[0],
+ component_seconds=int(time.perf_counter() - start_time),
+ component_success=valid,
+ component_error_message=error_message,
+ ),
+ )
+ except Exception as exc:
+ background_tasks.add_task(
+ telemetry_service.log_package_component,
+ ComponentPayload(
+ component_name=vertex_id.split("-")[0],
+ component_seconds=int(time.perf_counter() - start_time),
+ component_success=False,
+ component_error_message=str(exc),
+ ),
+ )
+ logger.exception("Error building Component")
+ message = parse_exception(exc)
+ raise HTTPException(status_code=500, detail=message) from exc
+
+ return build_response
+
+
+async def _stream_vertex(flow_id: str, vertex_id: str, chat_service: ChatService):
+ graph = None
+ try:
+ try:
+ cache = await chat_service.get_cache(flow_id)
+ except Exception as exc: # noqa: BLE001
+ logger.exception("Error building Component")
+ yield str(StreamData(event="error", data={"error": str(exc)}))
+ return
+
+ if isinstance(cache, CacheMiss):
+ # If there's no cache
+ msg = f"No cache found for {flow_id}."
+ logger.error(msg)
+ yield str(StreamData(event="error", data={"error": msg}))
+ return
+ else:
+ graph = cache.get("result")
+
+ try:
+ vertex: InterfaceVertex = graph.get_vertex(vertex_id)
+ except Exception as exc: # noqa: BLE001
+ logger.exception("Error building Component")
+ yield str(StreamData(event="error", data={"error": str(exc)}))
+ return
+
+ if not hasattr(vertex, "stream"):
+ msg = f"Vertex {vertex_id} does not support streaming"
+ logger.error(msg)
+ yield str(StreamData(event="error", data={"error": msg}))
+ return
+
+ if isinstance(vertex.built_result, str) and vertex.built_result:
+ stream_data = StreamData(
+ event="message",
+ data={"message": f"Streaming vertex {vertex_id}"},
+ )
+ yield str(stream_data)
+ stream_data = StreamData(
+ event="message",
+ data={"chunk": vertex.built_result},
+ )
+ yield str(stream_data)
+
+ elif not vertex.frozen or not vertex.built:
+ logger.debug(f"Streaming vertex {vertex_id}")
+ stream_data = StreamData(
+ event="message",
+ data={"message": f"Streaming vertex {vertex_id}"},
+ )
+ yield str(stream_data)
+ try:
+ async for chunk in vertex.stream():
+ stream_data = StreamData(
+ event="message",
+ data={"chunk": chunk},
+ )
+ yield str(stream_data)
+ except Exception as exc: # noqa: BLE001
+ logger.exception("Error building Component")
+ exc_message = parse_exception(exc)
+ if exc_message == "The message must be an iterator or an async iterator.":
+ exc_message = "This stream has already been closed."
+ yield str(StreamData(event="error", data={"error": exc_message}))
+ elif vertex.result is not None:
+ stream_data = StreamData(
+ event="message",
+ data={"chunk": vertex.built_result},
+ )
+ yield str(stream_data)
+ else:
+ msg = f"No result found for vertex {vertex_id}"
+ logger.error(msg)
+ yield str(StreamData(event="error", data={"error": msg}))
+ return
+ finally:
+ logger.debug("Closing stream")
+ if graph:
+ await chat_service.set_cache(flow_id, graph)
+ yield str(StreamData(event="close", data={"message": "Stream closed"}))
+
+
+@router.get("/build/{flow_id}/{vertex_id}/stream", response_class=StreamingResponse, deprecated=True)
+async def build_vertex_stream(
+ flow_id: uuid.UUID,
+ vertex_id: str,
+):
+ """Build a vertex instead of the entire graph.
+
+ This function is responsible for building a single vertex instead of the entire graph.
+ It takes the `flow_id` and `vertex_id` as required parameters, and an optional `session_id`.
+ It also depends on the `ChatService` and `SessionService` services.
+
+ If `session_id` is not provided, it retrieves the graph from the cache using the `chat_service`.
+ If `session_id` is provided, it loads the session data using the `session_service`.
+
+ Once the graph is obtained, it retrieves the specified vertex using the `vertex_id`.
+ If the vertex does not support streaming, an error is raised.
+ If the vertex has a built result, it sends the result as a chunk.
+ If the vertex is not frozen or not built, it streams the vertex data.
+ If the vertex has a result, it sends the result as a chunk.
+ If none of the above conditions are met, an error is raised.
+
+ If any exception occurs during the process, an error message is sent.
+ Finally, the stream is closed.
+
+ Returns:
+ A `StreamingResponse` object with the streamed vertex data in text/event-stream format.
+
+ Raises:
+ HTTPException: If an error occurs while building the vertex.
+ """
+ try:
+ return StreamingResponse(
+ _stream_vertex(str(flow_id), vertex_id, get_chat_service()), media_type="text/event-stream"
+ )
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail="Error building Component") from exc
diff --git a/langflow/src/backend/base/langflow/api/v1/endpoints.py b/langflow/src/backend/base/langflow/api/v1/endpoints.py
new file mode 100644
index 0000000..7edab27
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/endpoints.py
@@ -0,0 +1,753 @@
+from __future__ import annotations
+
+import asyncio
+import time
+from collections.abc import AsyncGenerator
+from http import HTTPStatus
+from typing import TYPE_CHECKING, Annotated
+from uuid import UUID
+
+import sqlalchemy as sa
+from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Request, UploadFile, status
+from fastapi.encoders import jsonable_encoder
+from fastapi.responses import StreamingResponse
+from loguru import logger
+from sqlmodel import select
+
+from langflow.api.utils import CurrentActiveUser, DbSession, parse_value
+from langflow.api.v1.schemas import (
+ ConfigResponse,
+ CustomComponentRequest,
+ CustomComponentResponse,
+ InputValueRequest,
+ RunResponse,
+ SimplifiedAPIRequest,
+ TaskStatusResponse,
+ UpdateCustomComponentRequest,
+ UploadFileResponse,
+)
+from langflow.custom.custom_component.component import Component
+from langflow.custom.utils import build_custom_component_template, get_instance_name, update_component_build_config
+from langflow.events.event_manager import create_stream_tokens_event_manager
+from langflow.exceptions.api import APIException, InvalidChatInputError
+from langflow.exceptions.serialization import SerializationError
+from langflow.graph.graph.base import Graph
+from langflow.graph.schema import RunOutputs
+from langflow.helpers.flow import get_flow_by_id_or_endpoint_name
+from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
+from langflow.interface.initialize.loading import update_params_with_load_from_db_fields
+from langflow.processing.process import process_tweaks, run_graph_internal
+from langflow.schema.graph import Tweaks
+from langflow.services.auth.utils import api_key_security, get_current_active_user
+from langflow.services.cache.utils import save_uploaded_file
+from langflow.services.database.models.flow import Flow
+from langflow.services.database.models.flow.model import FlowRead
+from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow
+from langflow.services.database.models.user.model import User, UserRead
+from langflow.services.deps import get_session_service, get_settings_service, get_telemetry_service
+from langflow.services.settings.feature_flags import FEATURE_FLAGS
+from langflow.services.telemetry.schema import RunPayload
+from langflow.utils.version import get_version_info
+
+if TYPE_CHECKING:
+ from langflow.services.event_manager import EventManager
+ from langflow.services.settings.service import SettingsService
+
+router = APIRouter(tags=["Base"])
+
+
+@router.get("/all", dependencies=[Depends(get_current_active_user)])
+async def get_all():
+ from langflow.interface.components import get_and_cache_all_types_dict
+
+ try:
+ return await get_and_cache_all_types_dict(settings_service=get_settings_service())
+
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+def validate_input_and_tweaks(input_request: SimplifiedAPIRequest) -> None:
+ # If the input_value is not None and the input_type is "chat"
+ # then we need to check the tweaks if the ChatInput component is present
+ # and if its input_value is not None
+ # if so, we raise an error
+ if not input_request.tweaks:
+ return
+
+ for key, value in input_request.tweaks.items():
+ if not isinstance(value, dict):
+ continue
+
+ input_value = value.get("input_value")
+ if input_value is None:
+ continue
+
+ request_has_input = input_request.input_value is not None
+
+ if any(chat_key in key for chat_key in ("ChatInput", "Chat Input")):
+ if request_has_input and input_request.input_type == "chat":
+ msg = "If you pass an input_value to the chat input, you cannot pass a tweak with the same name."
+ raise InvalidChatInputError(msg)
+
+ elif (
+ any(text_key in key for text_key in ("TextInput", "Text Input"))
+ and request_has_input
+ and input_request.input_type == "text"
+ ):
+ msg = "If you pass an input_value to the text input, you cannot pass a tweak with the same name."
+ raise InvalidChatInputError(msg)
+
+
+async def simple_run_flow(
+ flow: Flow,
+ input_request: SimplifiedAPIRequest,
+ *,
+ stream: bool = False,
+ api_key_user: User | None = None,
+ event_manager: EventManager | None = None,
+):
+ validate_input_and_tweaks(input_request)
+ try:
+ task_result: list[RunOutputs] = []
+ user_id = api_key_user.id if api_key_user else None
+ flow_id_str = str(flow.id)
+ if flow.data is None:
+ msg = f"Flow {flow_id_str} has no data"
+ raise ValueError(msg)
+ graph_data = flow.data.copy()
+ graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream)
+ graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(user_id), flow_name=flow.name)
+ inputs = None
+ if input_request.input_value is not None:
+ inputs = [
+ InputValueRequest(
+ components=[],
+ input_value=input_request.input_value,
+ type=input_request.input_type,
+ )
+ ]
+ if input_request.output_component:
+ outputs = [input_request.output_component]
+ else:
+ outputs = [
+ vertex.id
+ for vertex in graph.vertices
+ if input_request.output_type == "debug"
+ or (
+ vertex.is_output
+ and (input_request.output_type == "any" or input_request.output_type in vertex.id.lower()) # type: ignore[operator]
+ )
+ ]
+ task_result, session_id = await run_graph_internal(
+ graph=graph,
+ flow_id=flow_id_str,
+ session_id=input_request.session_id,
+ inputs=inputs,
+ outputs=outputs,
+ stream=stream,
+ event_manager=event_manager,
+ )
+
+ return RunResponse(outputs=task_result, session_id=session_id)
+
+ except sa.exc.StatementError as exc:
+ raise ValueError(str(exc)) from exc
+
+
+async def simple_run_flow_task(
+ flow: Flow,
+ input_request: SimplifiedAPIRequest,
+ *,
+ stream: bool = False,
+ api_key_user: User | None = None,
+ event_manager: EventManager | None = None,
+):
+ """Run a flow task as a BackgroundTask, therefore it should not throw exceptions."""
+ try:
+ return await simple_run_flow(
+ flow=flow,
+ input_request=input_request,
+ stream=stream,
+ api_key_user=api_key_user,
+ event_manager=event_manager,
+ )
+
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error running flow {flow.id} task")
+
+
+async def consume_and_yield(queue: asyncio.Queue, client_consumed_queue: asyncio.Queue) -> AsyncGenerator:
+ """Consumes events from a queue and yields them to the client while tracking timing metrics.
+
+ This coroutine continuously pulls events from the input queue and yields them to the client.
+ It tracks timing metrics for how long events spend in the queue and how long the client takes
+ to process them.
+
+ Args:
+ queue (asyncio.Queue): The queue containing events to be consumed and yielded
+ client_consumed_queue (asyncio.Queue): A queue for tracking when the client has consumed events
+
+ Yields:
+ The value from each event in the queue
+
+ Notes:
+ - Events are tuples of (event_id, value, put_time)
+ - Breaks the loop when receiving a None value, signaling completion
+ - Tracks and logs timing metrics for queue time and client processing time
+ - Notifies client consumption via client_consumed_queue
+ """
+ while True:
+ event_id, value, put_time = await queue.get()
+ if value is None:
+ break
+ get_time = time.time()
+ yield value
+ get_time_yield = time.time()
+ client_consumed_queue.put_nowait(event_id)
+ logger.debug(
+ f"consumed event {event_id} "
+ f"(time in queue, {get_time - put_time:.4f}, "
+ f"client {get_time_yield - get_time:.4f})"
+ )
+
+
+async def run_flow_generator(
+ flow: Flow,
+ input_request: SimplifiedAPIRequest,
+ api_key_user: User | None,
+ event_manager: EventManager,
+ client_consumed_queue: asyncio.Queue,
+) -> None:
+ """Executes a flow asynchronously and manages event streaming to the client.
+
+ This coroutine runs a flow with streaming enabled and handles the event lifecycle,
+ including success completion and error scenarios.
+
+ Args:
+ flow (Flow): The flow to execute
+ input_request (SimplifiedAPIRequest): The input parameters for the flow
+ api_key_user (User | None): Optional authenticated user running the flow
+ event_manager (EventManager): Manages the streaming of events to the client
+ client_consumed_queue (asyncio.Queue): Tracks client consumption of events
+
+ Events Generated:
+ - "add_message": Sent when new messages are added during flow execution
+ - "token": Sent for each token generated during streaming
+ - "end": Sent when flow execution completes, includes final result
+ - "error": Sent if an error occurs during execution
+
+ Notes:
+ - Runs the flow with streaming enabled via simple_run_flow()
+ - On success, sends the final result via event_manager.on_end()
+ - On error, logs the error and sends it via event_manager.on_error()
+ - Always sends a final None event to signal completion
+ """
+ try:
+ result = await simple_run_flow(
+ flow=flow,
+ input_request=input_request,
+ stream=True,
+ api_key_user=api_key_user,
+ event_manager=event_manager,
+ )
+ event_manager.on_end(data={"result": result.model_dump()})
+ await client_consumed_queue.get()
+ except (ValueError, InvalidChatInputError, SerializationError) as e:
+ logger.error(f"Error running flow: {e}")
+ event_manager.on_error(data={"error": str(e)})
+ finally:
+ await event_manager.queue.put((None, None, time.time))
+
+
+@router.post("/run/{flow_id_or_name}", response_model_exclude_none=True) # noqa: RUF100, FAST003
+async def simplified_run_flow(
+ *,
+ background_tasks: BackgroundTasks,
+ flow: Annotated[FlowRead | None, Depends(get_flow_by_id_or_endpoint_name)],
+ input_request: SimplifiedAPIRequest | None = None,
+ stream: bool = False,
+ api_key_user: Annotated[UserRead, Depends(api_key_security)],
+):
+ """Executes a specified flow by ID with support for streaming and telemetry.
+
+ This endpoint executes a flow identified by ID or name, with options for streaming the response
+ and tracking execution metrics. It handles both streaming and non-streaming execution modes.
+
+ Args:
+ background_tasks (BackgroundTasks): FastAPI background task manager
+ flow (FlowRead | None): The flow to execute, loaded via dependency
+ input_request (SimplifiedAPIRequest | None): Input parameters for the flow
+ stream (bool): Whether to stream the response
+ api_key_user (UserRead): Authenticated user from API key
+ request (Request): The incoming HTTP request
+
+ Returns:
+ Union[StreamingResponse, RunResponse]: Either a streaming response for real-time results
+ or a RunResponse with the complete execution results
+
+ Raises:
+ HTTPException: For flow not found (404) or invalid input (400)
+ APIException: For internal execution errors (500)
+
+ Notes:
+ - Supports both streaming and non-streaming execution modes
+ - Tracks execution time and success/failure via telemetry
+ - Handles graceful client disconnection in streaming mode
+ - Provides detailed error handling with appropriate HTTP status codes
+ - In streaming mode, uses EventManager to handle events:
+ - "add_message": New messages during execution
+ - "token": Individual tokens during streaming
+ - "end": Final execution result
+ """
+ telemetry_service = get_telemetry_service()
+ input_request = input_request if input_request is not None else SimplifiedAPIRequest()
+ if flow is None:
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Flow not found")
+ start_time = time.perf_counter()
+
+ if stream:
+ asyncio_queue: asyncio.Queue = asyncio.Queue()
+ asyncio_queue_client_consumed: asyncio.Queue = asyncio.Queue()
+ event_manager = create_stream_tokens_event_manager(queue=asyncio_queue)
+ main_task = asyncio.create_task(
+ run_flow_generator(
+ flow=flow,
+ input_request=input_request,
+ api_key_user=api_key_user,
+ event_manager=event_manager,
+ client_consumed_queue=asyncio_queue_client_consumed,
+ )
+ )
+
+ async def on_disconnect() -> None:
+ logger.debug("Client disconnected, closing tasks")
+ main_task.cancel()
+
+ return StreamingResponse(
+ consume_and_yield(asyncio_queue, asyncio_queue_client_consumed),
+ background=on_disconnect,
+ media_type="text/event-stream",
+ )
+
+ try:
+ result = await simple_run_flow(
+ flow=flow,
+ input_request=input_request,
+ stream=stream,
+ api_key_user=api_key_user,
+ )
+ end_time = time.perf_counter()
+ background_tasks.add_task(
+ telemetry_service.log_package_run,
+ RunPayload(
+ run_is_webhook=False,
+ run_seconds=int(end_time - start_time),
+ run_success=True,
+ run_error_message="",
+ ),
+ )
+
+ except ValueError as exc:
+ background_tasks.add_task(
+ telemetry_service.log_package_run,
+ RunPayload(
+ run_is_webhook=False,
+ run_seconds=int(time.perf_counter() - start_time),
+ run_success=False,
+ run_error_message=str(exc),
+ ),
+ )
+ if "badly formed hexadecimal UUID string" in str(exc):
+ # This means the Flow ID is not a valid UUID which means it can't find the flow
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
+ if "not found" in str(exc):
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
+ raise APIException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=exc, flow=flow) from exc
+ except InvalidChatInputError as exc:
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
+ except Exception as exc:
+ background_tasks.add_task(
+ telemetry_service.log_package_run,
+ RunPayload(
+ run_is_webhook=False,
+ run_seconds=int(time.perf_counter() - start_time),
+ run_success=False,
+ run_error_message=str(exc),
+ ),
+ )
+ raise APIException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=exc, flow=flow) from exc
+
+ return result
+
+
+@router.post("/webhook/{flow_id_or_name}", response_model=dict, status_code=HTTPStatus.ACCEPTED) # noqa: RUF100, FAST003
+async def webhook_run_flow(
+ flow: Annotated[Flow, Depends(get_flow_by_id_or_endpoint_name)],
+ user: Annotated[User, Depends(get_user_by_flow_id_or_endpoint_name)],
+ request: Request,
+ background_tasks: BackgroundTasks,
+):
+ """Run a flow using a webhook request.
+
+ Args:
+ flow (Flow, optional): The flow to be executed. Defaults to Depends(get_flow_by_id).
+ user (User): The flow user.
+ request (Request): The incoming HTTP request.
+ background_tasks (BackgroundTasks): The background tasks manager.
+
+ Returns:
+ dict: A dictionary containing the status of the task.
+
+ Raises:
+ HTTPException: If the flow is not found or if there is an error processing the request.
+ """
+ telemetry_service = get_telemetry_service()
+ start_time = time.perf_counter()
+ logger.debug("Received webhook request")
+ error_msg = ""
+ try:
+ try:
+ data = await request.body()
+ except Exception as exc:
+ error_msg = str(exc)
+ raise HTTPException(status_code=500, detail=error_msg) from exc
+
+ if not data:
+ error_msg = "Request body is empty. You should provide a JSON payload containing the flow ID."
+ raise HTTPException(status_code=400, detail=error_msg)
+
+ try:
+ # get all webhook components in the flow
+ webhook_components = get_all_webhook_components_in_flow(flow.data)
+ tweaks = {}
+
+ for component in webhook_components:
+ tweaks[component["id"]] = {"data": data.decode() if isinstance(data, bytes) else data}
+ input_request = SimplifiedAPIRequest(
+ input_value="",
+ input_type="chat",
+ output_type="chat",
+ tweaks=tweaks,
+ session_id=None,
+ )
+
+ logger.debug("Starting background task")
+ background_tasks.add_task(
+ simple_run_flow_task,
+ flow=flow,
+ input_request=input_request,
+ api_key_user=user,
+ )
+ except Exception as exc:
+ error_msg = str(exc)
+ raise HTTPException(status_code=500, detail=error_msg) from exc
+ finally:
+ background_tasks.add_task(
+ telemetry_service.log_package_run,
+ RunPayload(
+ run_is_webhook=True,
+ run_seconds=int(time.perf_counter() - start_time),
+ run_success=not error_msg,
+ run_error_message=error_msg,
+ ),
+ )
+
+ return {"message": "Task started in the background", "status": "in progress"}
+
+
+@router.post(
+ "/run/advanced/{flow_id}",
+ response_model=RunResponse,
+ response_model_exclude_none=True,
+)
+async def experimental_run_flow(
+ *,
+ session: DbSession,
+ flow_id: UUID,
+ inputs: list[InputValueRequest] | None = None,
+ outputs: list[str] | None = None,
+ tweaks: Annotated[Tweaks | None, Body(embed=True)] = None,
+ stream: Annotated[bool, Body(embed=True)] = False,
+ session_id: Annotated[None | str, Body(embed=True)] = None,
+ api_key_user: Annotated[UserRead, Depends(api_key_security)],
+) -> RunResponse:
+ """Executes a specified flow by ID with optional input values, output selection, tweaks, and streaming capability.
+
+ This endpoint supports running flows with caching to enhance performance and efficiency.
+
+ ### Parameters:
+ - `flow_id` (str): The unique identifier of the flow to be executed.
+ - `inputs` (List[InputValueRequest], optional): A list of inputs specifying the input values and components
+ for the flow. Each input can target specific components and provide custom values.
+ - `outputs` (List[str], optional): A list of output names to retrieve from the executed flow.
+ If not provided, all outputs are returned.
+ - `tweaks` (Optional[Tweaks], optional): A dictionary of tweaks to customize the flow execution.
+ The tweaks can be used to modify the flow's parameters and components.
+ Tweaks can be overridden by the input values.
+ - `stream` (bool, optional): Specifies whether the results should be streamed. Defaults to False.
+ - `session_id` (Union[None, str], optional): An optional session ID to utilize existing session data for the flow
+ execution.
+ - `api_key_user` (User): The user associated with the current API key. Automatically resolved from the API key.
+
+ ### Returns:
+ A `RunResponse` object containing the selected outputs (or all if not specified) of the executed flow
+ and the session ID.
+ The structure of the response accommodates multiple inputs, providing a nested list of outputs for each input.
+
+ ### Raises:
+ HTTPException: Indicates issues with finding the specified flow, invalid input formats, or internal errors during
+ flow execution.
+
+ ### Example usage:
+ ```json
+ POST /run/{flow_id}
+ x-api-key: YOUR_API_KEY
+ Payload:
+ {
+ "inputs": [
+ {"components": ["component1"], "input_value": "value1"},
+ {"components": ["component3"], "input_value": "value2"}
+ ],
+ "outputs": ["Component Name", "component_id"],
+ "tweaks": {"parameter_name": "value", "Component Name": {"parameter_name": "value"}, "component_id": {"parameter_name": "value"}}
+ "stream": false
+ }
+ ```
+
+ This endpoint facilitates complex flow executions with customized inputs, outputs, and configurations,
+ catering to diverse application requirements.
+ """ # noqa: E501
+ session_service = get_session_service()
+ flow_id_str = str(flow_id)
+ if outputs is None:
+ outputs = []
+ if inputs is None:
+ inputs = [InputValueRequest(components=[], input_value="")]
+
+ if session_id:
+ try:
+ session_data = await session_service.load_session(session_id, flow_id=flow_id_str)
+ except Exception as exc:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+ graph, _artifacts = session_data or (None, None)
+ if graph is None:
+ msg = f"Session {session_id} not found"
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
+ else:
+ try:
+ # Get the flow that matches the flow_id and belongs to the user
+ # flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
+ stmt = select(Flow).where(Flow.id == flow_id_str).where(Flow.user_id == api_key_user.id)
+ flow = (await session.exec(stmt)).first()
+ except sa.exc.StatementError as exc:
+ # StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
+ if "badly formed hexadecimal UUID string" in str(exc):
+ logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
+ # This means the Flow ID is not a valid UUID which means it can't find the flow
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+
+ if flow is None:
+ msg = f"Flow {flow_id_str} not found"
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
+
+ if flow.data is None:
+ msg = f"Flow {flow_id_str} has no data"
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg)
+ try:
+ graph_data = flow.data
+ graph_data = process_tweaks(graph_data, tweaks or {})
+ graph = Graph.from_payload(graph_data, flow_id=flow_id_str)
+ except Exception as exc:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+
+ try:
+ task_result, session_id = await run_graph_internal(
+ graph=graph,
+ flow_id=flow_id_str,
+ session_id=session_id,
+ inputs=inputs,
+ outputs=outputs,
+ stream=stream,
+ )
+ except Exception as exc:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
+
+ return RunResponse(outputs=task_result, session_id=session_id)
+
+
+@router.post(
+ "/predict/{_flow_id}",
+ dependencies=[Depends(api_key_security)],
+)
+@router.post(
+ "/process/{_flow_id}",
+ dependencies=[Depends(api_key_security)],
+)
+async def process(_flow_id) -> None:
+ """Endpoint to process an input with a given flow_id."""
+ # Raise a depreciation warning
+ logger.warning(
+ "The /process endpoint is deprecated and will be removed in a future version. Please use /run instead."
+ )
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="The /process endpoint is deprecated and will be removed in a future version. Please use /run instead.",
+ )
+
+
+@router.get("/task/{_task_id}", deprecated=True)
+async def get_task_status(_task_id: str) -> TaskStatusResponse:
+ """Get the status of a task by ID (Deprecated).
+
+ This endpoint is deprecated and will be removed in a future version.
+ """
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="The /task endpoint is deprecated and will be removed in a future version. Please use /run instead.",
+ )
+
+
+@router.post(
+ "/upload/{flow_id}",
+ status_code=HTTPStatus.CREATED,
+ deprecated=True,
+)
+async def create_upload_file(
+ file: UploadFile,
+ flow_id: UUID,
+) -> UploadFileResponse:
+ """Upload a file for a specific flow (Deprecated).
+
+ This endpoint is deprecated and will be removed in a future version.
+ """
+ try:
+ flow_id_str = str(flow_id)
+ file_path = await asyncio.to_thread(save_uploaded_file, file, folder_name=flow_id_str)
+
+ return UploadFileResponse(
+ flow_id=flow_id_str,
+ file_path=file_path,
+ )
+ except Exception as exc:
+ logger.exception("Error saving file")
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+# get endpoint to return version of langflow
+@router.get("/version")
+async def get_version():
+ return get_version_info()
+
+
+@router.post("/custom_component", status_code=HTTPStatus.OK)
+async def custom_component(
+ raw_code: CustomComponentRequest,
+ user: CurrentActiveUser,
+) -> CustomComponentResponse:
+ component = Component(_code=raw_code.code)
+
+ built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)
+ if raw_code.frontend_node is not None:
+ built_frontend_node = await component_instance.update_frontend_node(built_frontend_node, raw_code.frontend_node)
+
+ tool_mode: bool = built_frontend_node.get("tool_mode", False)
+ if isinstance(component_instance, Component):
+ await component_instance.run_and_validate_update_outputs(
+ frontend_node=built_frontend_node,
+ field_name="tool_mode",
+ field_value=tool_mode,
+ )
+ type_ = get_instance_name(component_instance)
+ return CustomComponentResponse(data=built_frontend_node, type=type_)
+
+
+@router.post("/custom_component/update", status_code=HTTPStatus.OK)
+async def custom_component_update(
+ code_request: UpdateCustomComponentRequest,
+ user: CurrentActiveUser,
+):
+ """Update a custom component with the provided code request.
+
+ This endpoint generates the CustomComponentFrontendNode normally but then runs the `update_build_config` method
+ on the latest version of the template.
+ This ensures that every time it runs, it has the latest version of the template.
+
+ Args:
+ code_request (CustomComponentRequest): The code request containing the updated code for the custom component.
+ user (User, optional): The user making the request. Defaults to the current active user.
+
+ Returns:
+ dict: The updated custom component node.
+
+ Raises:
+ HTTPException: If there's an error building or updating the component
+ SerializationError: If there's an error serializing the component to JSON
+ """
+ try:
+ component = Component(_code=code_request.code)
+ component_node, cc_instance = build_custom_component_template(
+ component,
+ user_id=user.id,
+ )
+
+ component_node["tool_mode"] = code_request.tool_mode
+
+ if hasattr(cc_instance, "set_attributes"):
+ template = code_request.get_template()
+ params = {}
+
+ for key, value_dict in template.items():
+ if isinstance(value_dict, dict):
+ value = value_dict.get("value")
+ input_type = str(value_dict.get("_input_type"))
+ params[key] = parse_value(value, input_type)
+
+ load_from_db_fields = [
+ field_name
+ for field_name, field_dict in template.items()
+ if isinstance(field_dict, dict) and field_dict.get("load_from_db") and field_dict.get("value")
+ ]
+ params = await update_params_with_load_from_db_fields(cc_instance, params, load_from_db_fields)
+ cc_instance.set_attributes(params)
+ updated_build_config = code_request.get_template()
+ await update_component_build_config(
+ cc_instance,
+ build_config=updated_build_config,
+ field_value=code_request.field_value,
+ field_name=code_request.field,
+ )
+ component_node["template"] = updated_build_config
+
+ if isinstance(cc_instance, Component):
+ await cc_instance.run_and_validate_update_outputs(
+ frontend_node=component_node,
+ field_name=code_request.field,
+ field_value=code_request.field_value,
+ )
+
+ except Exception as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+
+ try:
+ return jsonable_encoder(component_node)
+ except Exception as exc:
+ raise SerializationError.from_exception(exc, data=component_node) from exc
+
+
+@router.get("/config", response_model=ConfigResponse)
+async def get_config():
+ try:
+ from langflow.services.deps import get_settings_service
+
+ settings_service: SettingsService = get_settings_service()
+
+ return {
+ "feature_flags": FEATURE_FLAGS,
+ **settings_service.settings.model_dump(),
+ }
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
diff --git a/langflow/src/backend/base/langflow/api/v1/files.py b/langflow/src/backend/base/langflow/api/v1/files.py
new file mode 100644
index 0000000..0413294
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/files.py
@@ -0,0 +1,193 @@
+import hashlib
+from datetime import datetime, timezone
+from http import HTTPStatus
+from io import BytesIO
+from pathlib import Path
+from typing import Annotated
+from uuid import UUID
+
+from fastapi import APIRouter, Depends, HTTPException, UploadFile
+from fastapi.responses import StreamingResponse
+
+from langflow.api.utils import CurrentActiveUser, DbSession
+from langflow.api.v1.schemas import UploadFileResponse
+from langflow.services.database.models.flow import Flow
+from langflow.services.deps import get_settings_service, get_storage_service
+from langflow.services.settings.service import SettingsService
+from langflow.services.storage.service import StorageService
+from langflow.services.storage.utils import build_content_type_from_extension
+
+router = APIRouter(tags=["Files"], prefix="/files")
+
+
+# Create dep that gets the flow_id from the request
+# then finds it in the database and returns it while
+# using the current user as the owner
+async def get_flow(
+ flow_id: UUID,
+ current_user: CurrentActiveUser,
+ session: DbSession,
+):
+ # AttributeError: 'SelectOfScalar' object has no attribute 'first'
+ flow = await session.get(Flow, flow_id)
+ if not flow:
+ raise HTTPException(status_code=404, detail="Flow not found")
+ if flow.user_id != current_user.id:
+ raise HTTPException(status_code=403, detail="You don't have access to this flow")
+ return flow
+
+
+@router.post("/upload/{flow_id}", status_code=HTTPStatus.CREATED)
+async def upload_file(
+ *,
+ file: UploadFile,
+ flow: Annotated[Flow, Depends(get_flow)],
+ current_user: CurrentActiveUser,
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+ settings_service: Annotated[SettingsService, Depends(get_settings_service)],
+) -> UploadFileResponse:
+ try:
+ max_file_size_upload = settings_service.settings.max_file_size_upload
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if file.size > max_file_size_upload * 1024 * 1024:
+ raise HTTPException(
+ status_code=413, detail=f"File size is larger than the maximum file size {max_file_size_upload}MB."
+ )
+
+ if flow.user_id != current_user.id:
+ raise HTTPException(status_code=403, detail="You don't have access to this flow")
+
+ try:
+ file_content = await file.read()
+ timestamp = datetime.now(tz=timezone.utc).astimezone().strftime("%Y-%m-%d_%H-%M-%S")
+ file_name = file.filename or hashlib.sha256(file_content).hexdigest()
+ full_file_name = f"{timestamp}_{file_name}"
+ folder = str(flow.id)
+ await storage_service.save_file(flow_id=folder, file_name=full_file_name, data=file_content)
+ return UploadFileResponse(flow_id=str(flow.id), file_path=f"{folder}/{full_file_name}")
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/download/{flow_id}/{file_name}")
+async def download_file(
+ file_name: str, flow_id: UUID, storage_service: Annotated[StorageService, Depends(get_storage_service)]
+):
+ flow_id_str = str(flow_id)
+ extension = file_name.split(".")[-1]
+
+ if not extension:
+ raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}")
+ try:
+ content_type = build_content_type_from_extension(extension)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not content_type:
+ raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}")
+
+ try:
+ file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
+ headers = {
+ "Content-Disposition": f"attachment; filename={file_name} filename*=UTF-8''{file_name}",
+ "Content-Type": "application/octet-stream",
+ "Content-Length": str(len(file_content)),
+ }
+ return StreamingResponse(BytesIO(file_content), media_type=content_type, headers=headers)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/images/{flow_id}/{file_name}")
+async def download_image(file_name: str, flow_id: UUID):
+ storage_service = get_storage_service()
+ extension = file_name.split(".")[-1]
+ flow_id_str = str(flow_id)
+
+ if not extension:
+ raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}")
+ try:
+ content_type = build_content_type_from_extension(extension)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not content_type:
+ raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}")
+ if not content_type.startswith("image"):
+ raise HTTPException(status_code=500, detail=f"Content type {content_type} is not an image")
+
+ try:
+ file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
+ return StreamingResponse(BytesIO(file_content), media_type=content_type)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/profile_pictures/{folder_name}/{file_name}")
+async def download_profile_picture(
+ folder_name: str,
+ file_name: str,
+):
+ try:
+ storage_service = get_storage_service()
+ extension = file_name.split(".")[-1]
+ config_dir = storage_service.settings_service.settings.config_dir
+ config_path = Path(config_dir) # type: ignore[arg-type]
+ folder_path = config_path / "profile_pictures" / folder_name
+ content_type = build_content_type_from_extension(extension)
+ file_content = await storage_service.get_file(flow_id=folder_path, file_name=file_name) # type: ignore[arg-type]
+ return StreamingResponse(BytesIO(file_content), media_type=content_type)
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/profile_pictures/list")
+async def list_profile_pictures():
+ try:
+ storage_service = get_storage_service()
+ config_dir = storage_service.settings_service.settings.config_dir
+ config_path = Path(config_dir) # type: ignore[arg-type]
+
+ people_path = config_path / "profile_pictures/People"
+ space_path = config_path / "profile_pictures/Space"
+
+ people = await storage_service.list_files(flow_id=people_path) # type: ignore[arg-type]
+ space = await storage_service.list_files(flow_id=space_path) # type: ignore[arg-type]
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ files = [f"People/{i}" for i in people]
+ files += [f"Space/{i}" for i in space]
+
+ return {"files": files}
+
+
+@router.get("/list/{flow_id}")
+async def list_files(
+ flow: Annotated[Flow, Depends(get_flow)],
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+):
+ try:
+ files = await storage_service.list_files(flow_id=str(flow.id))
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return {"files": files}
+
+
+@router.delete("/delete/{flow_id}/{file_name}")
+async def delete_file(
+ file_name: str,
+ flow: Annotated[Flow, Depends(get_flow)],
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+):
+ try:
+ await storage_service.delete_file(flow_id=str(flow.id), file_name=file_name)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return {"message": f"File {file_name} deleted successfully"}
diff --git a/langflow/src/backend/base/langflow/api/v1/flows.py b/langflow/src/backend/base/langflow/api/v1/flows.py
new file mode 100644
index 0000000..b4a7150
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/flows.py
@@ -0,0 +1,499 @@
+from __future__ import annotations
+
+import io
+import json
+import re
+import zipfile
+from datetime import datetime, timezone
+from typing import Annotated
+from uuid import UUID
+
+import orjson
+from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
+from fastapi.encoders import jsonable_encoder
+from fastapi.responses import StreamingResponse
+from fastapi_pagination import Page, Params
+from fastapi_pagination.ext.sqlalchemy import paginate
+from sqlmodel import and_, col, select
+from sqlmodel.ext.asyncio.session import AsyncSession
+
+from langflow.api.utils import CurrentActiveUser, DbSession, cascade_delete_flow, remove_api_keys, validate_is_component
+from langflow.api.v1.schemas import FlowListCreate
+from langflow.initial_setup.constants import STARTER_FOLDER_NAME
+from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
+from langflow.services.database.models.flow.model import FlowHeader
+from langflow.services.database.models.flow.utils import get_webhook_component_in_flow
+from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
+from langflow.services.database.models.folder.model import Folder
+from langflow.services.deps import get_settings_service
+from langflow.services.settings.service import SettingsService
+
+# build router
+router = APIRouter(prefix="/flows", tags=["Flows"])
+
+
+async def _new_flow(
+ *,
+ session: AsyncSession,
+ flow: FlowCreate,
+ user_id: UUID,
+):
+ try:
+ """Create a new flow."""
+ if flow.user_id is None:
+ flow.user_id = user_id
+
+ # First check if the flow.name is unique
+ # there might be flows with name like: "MyFlow", "MyFlow (1)", "MyFlow (2)"
+ # so we need to check if the name is unique with `like` operator
+ # if we find a flow with the same name, we add a number to the end of the name
+ # based on the highest number found
+ if (await session.exec(select(Flow).where(Flow.name == flow.name).where(Flow.user_id == user_id))).first():
+ flows = (
+ await session.exec(
+ select(Flow).where(Flow.name.like(f"{flow.name} (%")).where(Flow.user_id == user_id) # type: ignore[attr-defined]
+ )
+ ).all()
+ if flows:
+ extract_number = re.compile(r"\((\d+)\)$")
+ numbers = []
+ for _flow in flows:
+ result = extract_number.search(_flow.name)
+ if result:
+ numbers.append(int(result.groups(1)[0]))
+ if numbers:
+ flow.name = f"{flow.name} ({max(numbers) + 1})"
+ else:
+ flow.name = f"{flow.name} (1)"
+ # Now check if the endpoint is unique
+ if (
+ flow.endpoint_name
+ and (
+ await session.exec(
+ select(Flow).where(Flow.endpoint_name == flow.endpoint_name).where(Flow.user_id == user_id)
+ )
+ ).first()
+ ):
+ flows = (
+ await session.exec(
+ select(Flow)
+ .where(Flow.endpoint_name.like(f"{flow.endpoint_name}-%")) # type: ignore[union-attr]
+ .where(Flow.user_id == user_id)
+ )
+ ).all()
+ if flows:
+ # The endpoint name is like "my-endpoint","my-endpoint-1", "my-endpoint-2"
+ # so we need to get the highest number and add 1
+ # we need to get the last part of the endpoint name
+ numbers = [int(flow.endpoint_name.split("-")[-1]) for flow in flows]
+ flow.endpoint_name = f"{flow.endpoint_name}-{max(numbers) + 1}"
+ else:
+ flow.endpoint_name = f"{flow.endpoint_name}-1"
+
+ db_flow = Flow.model_validate(flow, from_attributes=True)
+ db_flow.updated_at = datetime.now(timezone.utc)
+
+ if db_flow.folder_id is None:
+ # Make sure flows always have a folder
+ default_folder = (
+ await session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME, Folder.user_id == user_id))
+ ).first()
+ if default_folder:
+ db_flow.folder_id = default_folder.id
+
+ session.add(db_flow)
+ except Exception as e:
+ # If it is a validation error, return the error message
+ if hasattr(e, "errors"):
+ raise HTTPException(status_code=400, detail=str(e)) from e
+ if isinstance(e, HTTPException):
+ raise
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return db_flow
+
+
+@router.post("/", response_model=FlowRead, status_code=201)
+async def create_flow(
+ *,
+ session: DbSession,
+ flow: FlowCreate,
+ current_user: CurrentActiveUser,
+):
+ try:
+ db_flow = await _new_flow(session=session, flow=flow, user_id=current_user.id)
+ await session.commit()
+ await session.refresh(db_flow)
+ except Exception as e:
+ if "UNIQUE constraint failed" in str(e):
+ # Get the name of the column that failed
+ columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
+ # UNIQUE constraint failed: flow.user_id, flow.name
+ # or UNIQUE constraint failed: flow.name
+ # if the column has id in it, we want the other column
+ column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
+
+ raise HTTPException(
+ status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
+ ) from e
+ if isinstance(e, HTTPException):
+ raise
+ raise HTTPException(status_code=500, detail=str(e)) from e
+ return db_flow
+
+
+@router.get("/", response_model=list[FlowRead] | Page[FlowRead] | list[FlowHeader], status_code=200)
+async def read_flows(
+ *,
+ current_user: CurrentActiveUser,
+ session: DbSession,
+ remove_example_flows: bool = False,
+ components_only: bool = False,
+ get_all: bool = True,
+ folder_id: UUID | None = None,
+ params: Annotated[Params, Depends()],
+ header_flows: bool = False,
+):
+ """Retrieve a list of flows with pagination support.
+
+ Args:
+ current_user (User): The current authenticated user.
+ session (Session): The database session.
+ settings_service (SettingsService): The settings service.
+ components_only (bool, optional): Whether to return only components. Defaults to False.
+
+ get_all (bool, optional): Whether to return all flows without pagination. Defaults to True.
+ **This field must be True because of backward compatibility with the frontend - Release: 1.0.20**
+
+ folder_id (UUID, optional): The folder ID. Defaults to None.
+ params (Params): Pagination parameters.
+ remove_example_flows (bool, optional): Whether to remove example flows. Defaults to False.
+ header_flows (bool, optional): Whether to return only specific headers of the flows. Defaults to False.
+
+ Returns:
+ list[FlowRead] | Page[FlowRead] | list[FlowHeader]
+ A list of flows or a paginated response containing the list of flows or a list of flow headers.
+ """
+ try:
+ auth_settings = get_settings_service().auth_settings
+
+ default_folder = (await session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME))).first()
+ default_folder_id = default_folder.id if default_folder else None
+
+ starter_folder = (await session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME))).first()
+ starter_folder_id = starter_folder.id if starter_folder else None
+
+ if not starter_folder and not default_folder:
+ raise HTTPException(
+ status_code=404,
+ detail="Starter folder and default folder not found. Please create a folder and add flows to it.",
+ )
+
+ if not folder_id:
+ folder_id = default_folder_id
+
+ if auth_settings.AUTO_LOGIN:
+ stmt = select(Flow).where(
+ (Flow.user_id == None) | (Flow.user_id == current_user.id) # noqa: E711
+ )
+ else:
+ stmt = select(Flow).where(Flow.user_id == current_user.id)
+
+ if remove_example_flows:
+ stmt = stmt.where(Flow.folder_id != starter_folder_id)
+
+ if components_only:
+ stmt = stmt.where(Flow.is_component == True) # noqa: E712
+
+ if get_all:
+ flows = (await session.exec(stmt)).all()
+ flows = validate_is_component(flows)
+ if components_only:
+ flows = [flow for flow in flows if flow.is_component]
+ if remove_example_flows and starter_folder_id:
+ flows = [flow for flow in flows if flow.folder_id != starter_folder_id]
+ if header_flows:
+ return [FlowHeader.model_validate(flow, from_attributes=True) for flow in flows]
+ return flows
+
+ stmt = stmt.where(Flow.folder_id == folder_id)
+ return await paginate(session, stmt, params=params)
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+async def _read_flow(
+ session: AsyncSession,
+ flow_id: UUID,
+ user_id: UUID,
+ settings_service: SettingsService,
+):
+ """Read a flow."""
+ auth_settings = settings_service.auth_settings
+ stmt = select(Flow).where(Flow.id == flow_id)
+ if auth_settings.AUTO_LOGIN:
+ # If auto login is enable user_id can be current_user.id or None
+ # so write an OR
+ stmt = stmt.where(
+ (Flow.user_id == user_id) | (Flow.user_id == None) # noqa: E711
+ )
+ return (await session.exec(stmt)).first()
+
+
+@router.get("/{flow_id}", response_model=FlowRead, status_code=200)
+async def read_flow(
+ *,
+ session: DbSession,
+ flow_id: UUID,
+ current_user: CurrentActiveUser,
+):
+ """Read a flow."""
+ if user_flow := await _read_flow(session, flow_id, current_user.id, get_settings_service()):
+ return user_flow
+ raise HTTPException(status_code=404, detail="Flow not found")
+
+
+@router.patch("/{flow_id}", response_model=FlowRead, status_code=200)
+async def update_flow(
+ *,
+ session: DbSession,
+ flow_id: UUID,
+ flow: FlowUpdate,
+ current_user: CurrentActiveUser,
+):
+ """Update a flow."""
+ settings_service = get_settings_service()
+ try:
+ db_flow = await _read_flow(
+ session=session,
+ flow_id=flow_id,
+ user_id=current_user.id,
+ settings_service=settings_service,
+ )
+
+ if not db_flow:
+ raise HTTPException(status_code=404, detail="Flow not found")
+
+ update_data = flow.model_dump(exclude_unset=True, exclude_none=True)
+
+ if settings_service.settings.remove_api_keys:
+ update_data = remove_api_keys(update_data)
+
+ for key, value in update_data.items():
+ setattr(db_flow, key, value)
+
+ webhook_component = get_webhook_component_in_flow(db_flow.data)
+ db_flow.webhook = webhook_component is not None
+ db_flow.updated_at = datetime.now(timezone.utc)
+
+ if db_flow.folder_id is None:
+ default_folder = (await session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME))).first()
+ if default_folder:
+ db_flow.folder_id = default_folder.id
+
+ session.add(db_flow)
+ await session.commit()
+ await session.refresh(db_flow)
+
+ except Exception as e:
+ if "UNIQUE constraint failed" in str(e):
+ # Get the name of the column that failed
+ columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
+ # UNIQUE constraint failed: flow.user_id, flow.name
+ # or UNIQUE constraint failed: flow.name
+ # if the column has id in it, we want the other column
+ column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
+ raise HTTPException(
+ status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
+ ) from e
+
+ if hasattr(e, "status_code"):
+ raise HTTPException(status_code=e.status_code, detail=str(e)) from e
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return db_flow
+
+
+@router.delete("/{flow_id}", status_code=200)
+async def delete_flow(
+ *,
+ session: DbSession,
+ flow_id: UUID,
+ current_user: CurrentActiveUser,
+):
+ """Delete a flow."""
+ flow = await _read_flow(
+ session=session,
+ flow_id=flow_id,
+ user_id=current_user.id,
+ settings_service=get_settings_service(),
+ )
+ if not flow:
+ raise HTTPException(status_code=404, detail="Flow not found")
+ await cascade_delete_flow(session, flow.id)
+ await session.commit()
+ return {"message": "Flow deleted successfully"}
+
+
+@router.post("/batch/", response_model=list[FlowRead], status_code=201)
+async def create_flows(
+ *,
+ session: DbSession,
+ flow_list: FlowListCreate,
+ current_user: CurrentActiveUser,
+):
+ """Create multiple new flows."""
+ db_flows = []
+ for flow in flow_list.flows:
+ flow.user_id = current_user.id
+ db_flow = Flow.model_validate(flow, from_attributes=True)
+ session.add(db_flow)
+ db_flows.append(db_flow)
+ await session.commit()
+ for db_flow in db_flows:
+ await session.refresh(db_flow)
+ return db_flows
+
+
+@router.post("/upload/", response_model=list[FlowRead], status_code=201)
+async def upload_file(
+ *,
+ session: DbSession,
+ file: Annotated[UploadFile, File(...)],
+ current_user: CurrentActiveUser,
+ folder_id: UUID | None = None,
+):
+ """Upload flows from a file."""
+ contents = await file.read()
+ data = orjson.loads(contents)
+ response_list = []
+ flow_list = FlowListCreate(**data) if "flows" in data else FlowListCreate(flows=[FlowCreate(**data)])
+ # Now we set the user_id for all flows
+ for flow in flow_list.flows:
+ flow.user_id = current_user.id
+ if folder_id:
+ flow.folder_id = folder_id
+ response = await _new_flow(session=session, flow=flow, user_id=current_user.id)
+ response_list.append(response)
+
+ try:
+ await session.commit()
+ for db_flow in response_list:
+ await session.refresh(db_flow)
+ except Exception as e:
+ if "UNIQUE constraint failed" in str(e):
+ # Get the name of the column that failed
+ columns = str(e).split("UNIQUE constraint failed: ")[1].split(".")[1].split("\n")[0]
+ # UNIQUE constraint failed: flow.user_id, flow.name
+ # or UNIQUE constraint failed: flow.name
+ # if the column has id in it, we want the other column
+ column = columns.split(",")[1] if "id" in columns.split(",")[0] else columns.split(",")[0]
+
+ raise HTTPException(
+ status_code=400, detail=f"{column.capitalize().replace('_', ' ')} must be unique"
+ ) from e
+ if isinstance(e, HTTPException):
+ raise
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return response_list
+
+
+@router.delete("/")
+async def delete_multiple_flows(
+ flow_ids: list[UUID],
+ user: CurrentActiveUser,
+ db: DbSession,
+):
+ """Delete multiple flows by their IDs.
+
+ Args:
+ flow_ids (List[str]): The list of flow IDs to delete.
+ user (User, optional): The user making the request. Defaults to the current active user.
+ db (Session, optional): The database session.
+
+ Returns:
+ dict: A dictionary containing the number of flows deleted.
+
+ """
+ try:
+ flows_to_delete = (
+ await db.exec(select(Flow).where(col(Flow.id).in_(flow_ids)).where(Flow.user_id == user.id))
+ ).all()
+ for flow in flows_to_delete:
+ await cascade_delete_flow(db, flow.id)
+
+ await db.commit()
+ return {"deleted": len(flows_to_delete)}
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+@router.post("/download/", status_code=200)
+async def download_multiple_file(
+ flow_ids: list[UUID],
+ user: CurrentActiveUser,
+ db: DbSession,
+):
+ """Download all flows as a zip file."""
+ flows = (await db.exec(select(Flow).where(and_(Flow.user_id == user.id, Flow.id.in_(flow_ids))))).all() # type: ignore[attr-defined]
+
+ if not flows:
+ raise HTTPException(status_code=404, detail="No flows found.")
+
+ flows_without_api_keys = [remove_api_keys(flow.model_dump()) for flow in flows]
+
+ if len(flows_without_api_keys) > 1:
+ # Create a byte stream to hold the ZIP file
+ zip_stream = io.BytesIO()
+
+ # Create a ZIP file
+ with zipfile.ZipFile(zip_stream, "w") as zip_file:
+ for flow in flows_without_api_keys:
+ # Convert the flow object to JSON
+ flow_json = json.dumps(jsonable_encoder(flow))
+
+ # Write the JSON to the ZIP file
+ zip_file.writestr(f"{flow['name']}.json", flow_json)
+
+ # Seek to the beginning of the byte stream
+ zip_stream.seek(0)
+
+ # Generate the filename with the current datetime
+ current_time = datetime.now(tz=timezone.utc).astimezone().strftime("%Y%m%d_%H%M%S")
+ filename = f"{current_time}_langflow_flows.zip"
+
+ return StreamingResponse(
+ zip_stream,
+ media_type="application/x-zip-compressed",
+ headers={"Content-Disposition": f"attachment; filename={filename}"},
+ )
+ return flows_without_api_keys[0]
+
+
+@router.get("/basic_examples/", response_model=list[FlowRead], status_code=200)
+async def read_basic_examples(
+ *,
+ session: DbSession,
+):
+ """Retrieve a list of basic example flows.
+
+ Args:
+ session (Session): The database session.
+
+ Returns:
+ list[FlowRead]: A list of basic example flows.
+ """
+ try:
+ # Get the starter folder
+ starter_folder = (await session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME))).first()
+
+ if not starter_folder:
+ return []
+
+ # Get all flows in the starter folder
+ return (await session.exec(select(Flow).where(Flow.folder_id == starter_folder.id))).all()
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
diff --git a/langflow/src/backend/base/langflow/api/v1/folders.py b/langflow/src/backend/base/langflow/api/v1/folders.py
new file mode 100644
index 0000000..324a720
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/folders.py
@@ -0,0 +1,347 @@
+import io
+import json
+import zipfile
+from datetime import datetime, timezone
+from typing import Annotated
+from uuid import UUID
+
+import orjson
+from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
+from fastapi.encoders import jsonable_encoder
+from fastapi.responses import StreamingResponse
+from fastapi_pagination import Params
+from fastapi_pagination.ext.sqlmodel import paginate
+from sqlalchemy import or_, update
+from sqlalchemy.orm import selectinload
+from sqlmodel import select
+
+from langflow.api.utils import CurrentActiveUser, DbSession, cascade_delete_flow, custom_params, remove_api_keys
+from langflow.api.v1.flows import create_flows
+from langflow.api.v1.schemas import FlowListCreate
+from langflow.helpers.flow import generate_unique_flow_name
+from langflow.helpers.folders import generate_unique_folder_name
+from langflow.initial_setup.constants import STARTER_FOLDER_NAME
+from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
+from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
+from langflow.services.database.models.folder.model import (
+ Folder,
+ FolderCreate,
+ FolderRead,
+ FolderReadWithFlows,
+ FolderUpdate,
+)
+from langflow.services.database.models.folder.pagination_model import FolderWithPaginatedFlows
+
+router = APIRouter(prefix="/folders", tags=["Folders"])
+
+
+@router.post("/", response_model=FolderRead, status_code=201)
+async def create_folder(
+ *,
+ session: DbSession,
+ folder: FolderCreate,
+ current_user: CurrentActiveUser,
+):
+ try:
+ new_folder = Folder.model_validate(folder, from_attributes=True)
+ new_folder.user_id = current_user.id
+ # First check if the folder.name is unique
+ # there might be flows with name like: "MyFlow", "MyFlow (1)", "MyFlow (2)"
+ # so we need to check if the name is unique with `like` operator
+ # if we find a flow with the same name, we add a number to the end of the name
+ # based on the highest number found
+ if (
+ await session.exec(
+ statement=select(Folder).where(Folder.name == new_folder.name).where(Folder.user_id == current_user.id)
+ )
+ ).first():
+ folder_results = await session.exec(
+ select(Folder).where(
+ Folder.name.like(f"{new_folder.name}%"), # type: ignore[attr-defined]
+ Folder.user_id == current_user.id,
+ )
+ )
+ if folder_results:
+ folder_names = [folder.name for folder in folder_results]
+ folder_numbers = [int(name.split("(")[-1].split(")")[0]) for name in folder_names if "(" in name]
+ if folder_numbers:
+ new_folder.name = f"{new_folder.name} ({max(folder_numbers) + 1})"
+ else:
+ new_folder.name = f"{new_folder.name} (1)"
+
+ session.add(new_folder)
+ await session.commit()
+ await session.refresh(new_folder)
+
+ if folder.components_list:
+ update_statement_components = (
+ update(Flow).where(Flow.id.in_(folder.components_list)).values(folder_id=new_folder.id) # type: ignore[attr-defined]
+ )
+ await session.exec(update_statement_components)
+ await session.commit()
+
+ if folder.flows_list:
+ update_statement_flows = update(Flow).where(Flow.id.in_(folder.flows_list)).values(folder_id=new_folder.id) # type: ignore[attr-defined]
+ await session.exec(update_statement_flows)
+ await session.commit()
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return new_folder
+
+
+@router.get("/", response_model=list[FolderRead], status_code=200)
+async def read_folders(
+ *,
+ session: DbSession,
+ current_user: CurrentActiveUser,
+):
+ try:
+ folders = (
+ await session.exec(
+ select(Folder).where(
+ or_(Folder.user_id == current_user.id, Folder.user_id == None) # noqa: E711
+ )
+ )
+ ).all()
+ folders = [folder for folder in folders if folder.name != STARTER_FOLDER_NAME]
+ return sorted(folders, key=lambda x: x.name != DEFAULT_FOLDER_NAME)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/{folder_id}", response_model=FolderWithPaginatedFlows | FolderReadWithFlows, status_code=200)
+async def read_folder(
+ *,
+ session: DbSession,
+ folder_id: UUID,
+ current_user: CurrentActiveUser,
+ params: Annotated[Params | None, Depends(custom_params)],
+ is_component: bool = False,
+ is_flow: bool = False,
+ search: str = "",
+):
+ try:
+ folder = (
+ await session.exec(
+ select(Folder)
+ .options(selectinload(Folder.flows))
+ .where(Folder.id == folder_id, Folder.user_id == current_user.id)
+ )
+ ).first()
+ except Exception as e:
+ if "No result found" in str(e):
+ raise HTTPException(status_code=404, detail="Folder not found") from e
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not folder:
+ raise HTTPException(status_code=404, detail="Folder not found")
+
+ try:
+ if params and params.page and params.size:
+ stmt = select(Flow).where(Flow.folder_id == folder_id)
+
+ if Flow.updated_at is not None:
+ stmt = stmt.order_by(Flow.updated_at.desc()) # type: ignore[attr-defined]
+ if is_component:
+ stmt = stmt.where(Flow.is_component == True) # noqa: E712
+ if is_flow:
+ stmt = stmt.where(Flow.is_component == False) # noqa: E712
+ if search:
+ stmt = stmt.where(Flow.name.like(f"%{search}%")) # type: ignore[attr-defined]
+ paginated_flows = await paginate(session, stmt, params=params)
+
+ return FolderWithPaginatedFlows(folder=FolderRead.model_validate(folder), flows=paginated_flows)
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ flows_from_current_user_in_folder = [flow for flow in folder.flows if flow.user_id == current_user.id]
+ folder.flows = flows_from_current_user_in_folder
+ return folder
+
+
+@router.patch("/{folder_id}", response_model=FolderRead, status_code=200)
+async def update_folder(
+ *,
+ session: DbSession,
+ folder_id: UUID,
+ folder: FolderUpdate, # Assuming FolderUpdate is a Pydantic model defining updatable fields
+ current_user: CurrentActiveUser,
+):
+ try:
+ existing_folder = (
+ await session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id))
+ ).first()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not existing_folder:
+ raise HTTPException(status_code=404, detail="Folder not found")
+
+ try:
+ if folder.name and folder.name != existing_folder.name:
+ existing_folder.name = folder.name
+ session.add(existing_folder)
+ await session.commit()
+ await session.refresh(existing_folder)
+ return existing_folder
+
+ folder_data = existing_folder.model_dump(exclude_unset=True)
+ for key, value in folder_data.items():
+ if key not in {"components", "flows"}:
+ setattr(existing_folder, key, value)
+ session.add(existing_folder)
+ await session.commit()
+ await session.refresh(existing_folder)
+
+ concat_folder_components = folder.components + folder.flows
+
+ flows_ids = (await session.exec(select(Flow.id).where(Flow.folder_id == existing_folder.id))).all()
+
+ excluded_flows = list(set(flows_ids) - set(concat_folder_components))
+
+ my_collection_folder = (await session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME))).first()
+ if my_collection_folder:
+ update_statement_my_collection = (
+ update(Flow).where(Flow.id.in_(excluded_flows)).values(folder_id=my_collection_folder.id) # type: ignore[attr-defined]
+ )
+ await session.exec(update_statement_my_collection)
+ await session.commit()
+
+ if concat_folder_components:
+ update_statement_components = (
+ update(Flow).where(Flow.id.in_(concat_folder_components)).values(folder_id=existing_folder.id) # type: ignore[attr-defined]
+ )
+ await session.exec(update_statement_components)
+ await session.commit()
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return existing_folder
+
+
+@router.delete("/{folder_id}", status_code=204)
+async def delete_folder(
+ *,
+ session: DbSession,
+ folder_id: UUID,
+ current_user: CurrentActiveUser,
+):
+ try:
+ flows = (
+ await session.exec(select(Flow).where(Flow.folder_id == folder_id, Flow.user_id == current_user.id))
+ ).all()
+ if len(flows) > 0:
+ for flow in flows:
+ await cascade_delete_flow(session, flow.id)
+
+ folder = (
+ await session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id))
+ ).first()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not folder:
+ raise HTTPException(status_code=404, detail="Folder not found")
+
+ try:
+ await session.delete(folder)
+ await session.commit()
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/download/{folder_id}", status_code=200)
+async def download_file(
+ *,
+ session: DbSession,
+ folder_id: UUID,
+ current_user: CurrentActiveUser,
+):
+ """Download all flows from folder as a zip file."""
+ try:
+ query = select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)
+ result = await session.exec(query)
+ folder = result.first()
+
+ if not folder:
+ raise HTTPException(status_code=404, detail="Folder not found")
+
+ flows_query = select(Flow).where(Flow.folder_id == folder_id)
+ flows_result = await session.exec(flows_query)
+ flows = [FlowRead.model_validate(flow, from_attributes=True) for flow in flows_result.all()]
+
+ if not flows:
+ raise HTTPException(status_code=404, detail="No flows found in folder")
+
+ flows_without_api_keys = [remove_api_keys(flow.model_dump()) for flow in flows]
+ zip_stream = io.BytesIO()
+
+ with zipfile.ZipFile(zip_stream, "w") as zip_file:
+ for flow in flows_without_api_keys:
+ flow_json = json.dumps(jsonable_encoder(flow))
+ zip_file.writestr(f"{flow['name']}.json", flow_json)
+
+ zip_stream.seek(0)
+
+ current_time = datetime.now(tz=timezone.utc).astimezone().strftime("%Y%m%d_%H%M%S")
+ filename = f"{current_time}_{folder.name}_flows.zip"
+
+ return StreamingResponse(
+ zip_stream,
+ media_type="application/x-zip-compressed",
+ headers={"Content-Disposition": f"attachment; filename={filename}"},
+ )
+
+ except Exception as e:
+ if "No result found" in str(e):
+ raise HTTPException(status_code=404, detail="Folder not found") from e
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.post("/upload/", response_model=list[FlowRead], status_code=201)
+async def upload_file(
+ *,
+ session: DbSession,
+ file: Annotated[UploadFile, File(...)],
+ current_user: CurrentActiveUser,
+):
+ """Upload flows from a file."""
+ contents = await file.read()
+ data = orjson.loads(contents)
+
+ if not data:
+ raise HTTPException(status_code=400, detail="No flows found in the file")
+
+ folder_name = await generate_unique_folder_name(data["folder_name"], current_user.id, session)
+
+ data["folder_name"] = folder_name
+
+ folder = FolderCreate(name=data["folder_name"], description=data["folder_description"])
+
+ new_folder = Folder.model_validate(folder, from_attributes=True)
+ new_folder.id = None
+ new_folder.user_id = current_user.id
+ session.add(new_folder)
+ await session.commit()
+ await session.refresh(new_folder)
+
+ del data["folder_name"]
+ del data["folder_description"]
+
+ if "flows" in data:
+ flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data["flows"]])
+ else:
+ raise HTTPException(status_code=400, detail="No flows found in the data")
+ # Now we set the user_id for all flows
+ for flow in flow_list.flows:
+ flow_name = await generate_unique_flow_name(flow.name, current_user.id, session)
+ flow.name = flow_name
+ flow.user_id = current_user.id
+ flow.folder_id = new_folder.id
+
+ return await create_flows(session=session, flow_list=flow_list, current_user=current_user)
diff --git a/langflow/src/backend/base/langflow/api/v1/login.py b/langflow/src/backend/base/langflow/api/v1/login.py
new file mode 100644
index 0000000..2671809
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/login.py
@@ -0,0 +1,166 @@
+from __future__ import annotations
+
+from typing import Annotated
+
+from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
+from fastapi.security import OAuth2PasswordRequestForm
+
+from langflow.api.utils import DbSession
+from langflow.api.v1.schemas import Token
+from langflow.initial_setup.setup import get_or_create_default_folder
+from langflow.services.auth.utils import (
+ authenticate_user,
+ create_refresh_token,
+ create_user_longterm_token,
+ create_user_tokens,
+)
+from langflow.services.database.models.user.crud import get_user_by_id
+from langflow.services.deps import get_settings_service, get_variable_service
+
+router = APIRouter(tags=["Login"])
+
+
+@router.post("/login", response_model=Token)
+async def login_to_get_access_token(
+ response: Response,
+ form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
+ db: DbSession,
+):
+ auth_settings = get_settings_service().auth_settings
+ try:
+ user = await authenticate_user(form_data.username, form_data.password, db)
+ except Exception as exc:
+ if isinstance(exc, HTTPException):
+ raise
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=str(exc),
+ ) from exc
+
+ if user:
+ tokens = await create_user_tokens(user_id=user.id, db=db, update_last_login=True)
+ response.set_cookie(
+ "refresh_token_lf",
+ tokens["refresh_token"],
+ httponly=auth_settings.REFRESH_HTTPONLY,
+ samesite=auth_settings.REFRESH_SAME_SITE,
+ secure=auth_settings.REFRESH_SECURE,
+ expires=auth_settings.REFRESH_TOKEN_EXPIRE_SECONDS,
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+ response.set_cookie(
+ "access_token_lf",
+ tokens["access_token"],
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+ response.set_cookie(
+ "apikey_tkn_lflw",
+ str(user.store_api_key),
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=None, # Set to None to make it a session cookie
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+ await get_variable_service().initialize_user_variables(user.id, db)
+ # Create default folder for user if it doesn't exist
+ _ = await get_or_create_default_folder(db, user.id)
+ return tokens
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+
+@router.get("/auto_login")
+async def auto_login(response: Response, db: DbSession):
+ auth_settings = get_settings_service().auth_settings
+
+ if auth_settings.AUTO_LOGIN:
+ user_id, tokens = await create_user_longterm_token(db)
+ response.set_cookie(
+ "access_token_lf",
+ tokens["access_token"],
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=None, # Set to None to make it a session cookie
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+
+ user = await get_user_by_id(db, user_id)
+
+ if user:
+ if user.store_api_key is None:
+ user.store_api_key = ""
+
+ response.set_cookie(
+ "apikey_tkn_lflw",
+ str(user.store_api_key), # Ensure it's a string
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=None, # Set to None to make it a session cookie
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+
+ return tokens
+
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail={
+ "message": "Auto login is disabled. Please enable it in the settings",
+ "auto_login": False,
+ },
+ )
+
+
+@router.post("/refresh")
+async def refresh_token(
+ request: Request,
+ response: Response,
+ db: DbSession,
+):
+ auth_settings = get_settings_service().auth_settings
+
+ token = request.cookies.get("refresh_token_lf")
+
+ if token:
+ tokens = await create_refresh_token(token, db)
+ response.set_cookie(
+ "refresh_token_lf",
+ tokens["refresh_token"],
+ httponly=auth_settings.REFRESH_HTTPONLY,
+ samesite=auth_settings.REFRESH_SAME_SITE,
+ secure=auth_settings.REFRESH_SECURE,
+ expires=auth_settings.REFRESH_TOKEN_EXPIRE_SECONDS,
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+ response.set_cookie(
+ "access_token_lf",
+ tokens["access_token"],
+ httponly=auth_settings.ACCESS_HTTPONLY,
+ samesite=auth_settings.ACCESS_SAME_SITE,
+ secure=auth_settings.ACCESS_SECURE,
+ expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
+ domain=auth_settings.COOKIE_DOMAIN,
+ )
+ return tokens
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid refresh token",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+
+@router.post("/logout")
+async def logout(response: Response):
+ response.delete_cookie("refresh_token_lf")
+ response.delete_cookie("access_token_lf")
+ response.delete_cookie("apikey_tkn_lflw")
+ return {"message": "Logout successful"}
diff --git a/langflow/src/backend/base/langflow/api/v1/mcp.py b/langflow/src/backend/base/langflow/api/v1/mcp.py
new file mode 100644
index 0000000..5db1f83
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/mcp.py
@@ -0,0 +1,349 @@
+import asyncio
+import base64
+import json
+import logging
+import traceback
+from contextvars import ContextVar
+from typing import Annotated
+from urllib.parse import quote, unquote, urlparse
+from uuid import UUID, uuid4
+
+import pydantic
+from anyio import BrokenResourceError
+from fastapi import APIRouter, Depends, Request
+from fastapi.responses import StreamingResponse
+from mcp import types
+from mcp.server import NotificationOptions, Server
+from mcp.server.sse import SseServerTransport
+from sqlmodel import select
+from starlette.background import BackgroundTasks
+
+from langflow.api.v1.chat import build_flow
+from langflow.api.v1.schemas import InputValueRequest
+from langflow.helpers.flow import json_schema_from_flow
+from langflow.services.auth.utils import get_current_active_user
+from langflow.services.database.models import Flow, User
+from langflow.services.deps import get_db_service, get_session, get_settings_service, get_storage_service
+from langflow.services.storage.utils import build_content_type_from_extension
+
+logger = logging.getLogger(__name__)
+if False:
+ logger.setLevel(logging.DEBUG)
+ if not logger.handlers:
+ handler = logging.StreamHandler()
+ handler.setLevel(logging.DEBUG)
+ formatter = logging.Formatter("[%(asctime)s][%(levelname)s] %(message)s")
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+
+ # Enable debug logging for MCP package
+ mcp_logger = logging.getLogger("mcp")
+ mcp_logger.setLevel(logging.DEBUG)
+ if not mcp_logger.handlers:
+ mcp_logger.addHandler(handler)
+
+ logger.debug("MCP module loaded - debug logging enabled")
+
+
+router = APIRouter(prefix="/mcp", tags=["mcp"])
+
+server = Server("langflow-mcp-server")
+
+# Create a context variable to store the current user
+current_user_ctx: ContextVar[User] = ContextVar("current_user_ctx")
+
+# Define constants
+MAX_RETRIES = 2
+
+
+def get_enable_progress_notifications() -> bool:
+ return get_settings_service().settings.mcp_server_enable_progress_notifications
+
+
+@server.list_prompts()
+async def handle_list_prompts():
+ return []
+
+
+@server.list_resources()
+async def handle_list_resources():
+ resources = []
+ try:
+ session = await anext(get_session())
+ storage_service = get_storage_service()
+ settings_service = get_settings_service()
+
+ # Build full URL from settings
+ host = getattr(settings_service.settings, "holst", "localhost")
+ port = getattr(settings_service.settings, "port", 3000)
+
+ base_url = f"http://{host}:{port}".rstrip("/")
+
+ flows = (await session.exec(select(Flow))).all()
+
+ for flow in flows:
+ if flow.id:
+ try:
+ files = await storage_service.list_files(flow_id=str(flow.id))
+ for file_name in files:
+ # URL encode the filename
+ safe_filename = quote(file_name)
+ resource = types.Resource(
+ uri=f"{base_url}/api/v1/files/{flow.id}/{safe_filename}",
+ name=file_name,
+ description=f"File in flow: {flow.name}",
+ mimeType=build_content_type_from_extension(file_name),
+ )
+ resources.append(resource)
+ except FileNotFoundError as e:
+ msg = f"Error listing files for flow {flow.id}: {e}"
+ logger.debug(msg)
+ continue
+ except Exception as e:
+ msg = f"Error in listing resources: {e!s}"
+ logger.exception(msg)
+ trace = traceback.format_exc()
+ logger.exception(trace)
+ raise
+ return resources
+
+
+@server.read_resource()
+async def handle_read_resource(uri: str) -> bytes:
+ """Handle resource read requests."""
+ try:
+ # Parse the URI properly
+ parsed_uri = urlparse(str(uri))
+ # Path will be like /api/v1/files/{flow_id}/{filename}
+ path_parts = parsed_uri.path.split("/")
+ # Remove empty strings from split
+ path_parts = [p for p in path_parts if p]
+
+ # The flow_id and filename should be the last two parts
+ two = 2
+ if len(path_parts) < two:
+ msg = f"Invalid URI format: {uri}"
+ raise ValueError(msg)
+
+ flow_id = path_parts[-2]
+ filename = unquote(path_parts[-1]) # URL decode the filename
+
+ storage_service = get_storage_service()
+
+ # Read the file content
+ content = await storage_service.get_file(flow_id=flow_id, file_name=filename)
+ if not content:
+ msg = f"File {filename} not found in flow {flow_id}"
+ raise ValueError(msg)
+
+ # Ensure content is base64 encoded
+ if isinstance(content, str):
+ content = content.encode()
+ return base64.b64encode(content)
+ except Exception as e:
+ msg = f"Error reading resource {uri}: {e!s}"
+ logger.exception(msg)
+ trace = traceback.format_exc()
+ logger.exception(trace)
+ raise
+
+
+@server.list_tools()
+async def handle_list_tools():
+ tools = []
+ try:
+ session = await anext(get_session())
+ flows = (await session.exec(select(Flow))).all()
+
+ for flow in flows:
+ if flow.user_id is None:
+ continue
+
+ tool = types.Tool(
+ name=str(flow.id), # Use flow.id instead of name
+ description=f"{flow.name}: {flow.description}"
+ if flow.description
+ else f"Tool generated from flow: {flow.name}",
+ inputSchema=json_schema_from_flow(flow),
+ )
+ tools.append(tool)
+ except Exception as e:
+ msg = f"Error in listing tools: {e!s}"
+ logger.exception(msg)
+ trace = traceback.format_exc()
+ logger.exception(trace)
+ raise
+ return tools
+
+
+@server.call_tool()
+async def handle_call_tool(
+ name: str, arguments: dict, *, enable_progress_notifications: bool = Depends(get_enable_progress_notifications)
+) -> list[types.TextContent]:
+ """Handle tool execution requests."""
+ try:
+ session = await anext(get_session())
+ background_tasks = BackgroundTasks()
+
+ current_user = current_user_ctx.get()
+ flow = (await session.exec(select(Flow).where(Flow.id == UUID(name)))).first()
+
+ if not flow:
+ msg = f"Flow with id '{name}' not found"
+ raise ValueError(msg)
+
+ # Process inputs
+ processed_inputs = dict(arguments)
+
+ # Initial progress notification
+ if enable_progress_notifications and (progress_token := server.request_context.meta.progressToken):
+ await server.request_context.session.send_progress_notification(
+ progress_token=progress_token, progress=0.0, total=1.0
+ )
+
+ conversation_id = str(uuid4())
+ input_request = InputValueRequest(
+ input_value=processed_inputs.get("input_value", ""), components=[], type="chat", session=conversation_id
+ )
+
+ async def send_progress_updates():
+ if not (enable_progress_notifications and server.request_context.meta.progressToken):
+ return
+
+ try:
+ progress = 0.0
+ while True:
+ await server.request_context.session.send_progress_notification(
+ progress_token=progress_token, progress=min(0.9, progress), total=1.0
+ )
+ progress += 0.1
+ await asyncio.sleep(1.0)
+ except asyncio.CancelledError:
+ # Send final 100% progress
+ if enable_progress_notifications:
+ await server.request_context.session.send_progress_notification(
+ progress_token=progress_token, progress=1.0, total=1.0
+ )
+ raise
+
+ db_service = get_db_service()
+ collected_results = []
+ async with db_service.with_session() as async_session:
+ try:
+ progress_task = asyncio.create_task(send_progress_updates())
+
+ try:
+ response = await build_flow(
+ flow_id=UUID(name),
+ inputs=input_request,
+ background_tasks=background_tasks,
+ current_user=current_user,
+ session=async_session,
+ )
+
+ async for line in response.body_iterator:
+ if not line:
+ continue
+ try:
+ event_data = json.loads(line)
+ if event_data.get("event") == "end_vertex":
+ message = (
+ event_data.get("data", {})
+ .get("build_data", {})
+ .get("data", {})
+ .get("results", {})
+ .get("message", {})
+ .get("text", "")
+ )
+ if message:
+ collected_results.append(types.TextContent(type="text", text=str(message)))
+ except json.JSONDecodeError:
+ msg = f"Failed to parse event data: {line}"
+ logger.warning(msg)
+ continue
+
+ return collected_results
+ finally:
+ progress_task.cancel()
+ await asyncio.wait([progress_task])
+ if not progress_task.cancelled() and (exc := progress_task.exception()) is not None:
+ raise exc
+ except Exception as e:
+ msg = f"Error in async session: {e}"
+ logger.exception(msg)
+ raise
+
+ except Exception as e:
+ context = server.request_context
+ # Send error progress if there's an exception
+ if enable_progress_notifications and (progress_token := context.meta.progressToken):
+ await server.request_context.session.send_progress_notification(
+ progress_token=progress_token, progress=1.0, total=1.0
+ )
+ msg = f"Error executing tool {name}: {e!s}"
+ logger.exception(msg)
+ trace = traceback.format_exc()
+ logger.exception(trace)
+ raise
+
+
+sse = SseServerTransport("/api/v1/mcp/")
+
+
+def find_validation_error(exc):
+ """Searches for a pydantic.ValidationError in the exception chain."""
+ while exc:
+ if isinstance(exc, pydantic.ValidationError):
+ return exc
+ exc = getattr(exc, "__cause__", None) or getattr(exc, "__context__", None)
+ return None
+
+
+@router.get("/sse", response_class=StreamingResponse)
+async def handle_sse(request: Request, current_user: Annotated[User, Depends(get_current_active_user)]):
+ token = current_user_ctx.set(current_user)
+ try:
+ async with sse.connect_sse(request.scope, request.receive, request._send) as streams:
+ try:
+ msg = "Starting SSE connection"
+ logger.debug(msg)
+ msg = f"Stream types: read={type(streams[0])}, write={type(streams[1])}"
+ logger.debug(msg)
+
+ notification_options = NotificationOptions(
+ prompts_changed=True, resources_changed=True, tools_changed=True
+ )
+ init_options = server.create_initialization_options(notification_options)
+ msg = f"Initialization options: {init_options}"
+ logger.debug(msg)
+
+ try:
+ await server.run(streams[0], streams[1], init_options)
+ except Exception as exc: # noqa: BLE001
+ validation_error = find_validation_error(exc)
+ if validation_error:
+ msg = "Validation error in MCP:" + str(validation_error)
+ logger.debug(msg)
+ else:
+ msg = f"Error in MCP: {exc!s}"
+ logger.debug(msg)
+ return
+ except BrokenResourceError:
+ # Handle gracefully when client disconnects
+ logger.info("Client disconnected from SSE connection")
+ except asyncio.CancelledError:
+ logger.info("SSE connection was cancelled")
+ raise
+ except Exception as e:
+ msg = f"Error in MCP: {e!s}"
+ logger.exception(msg)
+ trace = traceback.format_exc()
+ logger.exception(trace)
+ raise
+ finally:
+ current_user_ctx.reset(token)
+
+
+@router.post("/")
+async def handle_messages(request: Request):
+ await sse.handle_post_message(request.scope, request.receive, request._send)
diff --git a/langflow/src/backend/base/langflow/api/v1/monitor.py b/langflow/src/backend/base/langflow/api/v1/monitor.py
new file mode 100644
index 0000000..718f426
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/monitor.py
@@ -0,0 +1,176 @@
+from typing import Annotated
+from uuid import UUID
+
+from fastapi import APIRouter, Depends, HTTPException, Query
+from fastapi_pagination import Page, Params
+from fastapi_pagination.ext.sqlmodel import paginate
+from sqlalchemy import delete
+from sqlmodel import col, select
+
+from langflow.api.utils import DbSession, custom_params
+from langflow.schema.message import MessageResponse
+from langflow.services.auth.utils import get_current_active_user
+from langflow.services.database.models.message.model import MessageRead, MessageTable, MessageUpdate
+from langflow.services.database.models.transactions.crud import transform_transaction_table
+from langflow.services.database.models.transactions.model import TransactionTable
+from langflow.services.database.models.vertex_builds.crud import (
+ delete_vertex_builds_by_flow_id,
+ get_vertex_builds_by_flow_id,
+)
+from langflow.services.database.models.vertex_builds.model import VertexBuildMapModel
+
+router = APIRouter(prefix="/monitor", tags=["Monitor"])
+
+
+@router.get("/builds")
+async def get_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSession) -> VertexBuildMapModel:
+ try:
+ vertex_builds = await get_vertex_builds_by_flow_id(session, flow_id)
+ return VertexBuildMapModel.from_list_of_dicts(vertex_builds)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.delete("/builds", status_code=204)
+async def delete_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSession) -> None:
+ try:
+ await delete_vertex_builds_by_flow_id(session, flow_id)
+ await session.commit()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/messages")
+async def get_messages(
+ session: DbSession,
+ flow_id: Annotated[UUID | None, Query()] = None,
+ session_id: Annotated[str | None, Query()] = None,
+ sender: Annotated[str | None, Query()] = None,
+ sender_name: Annotated[str | None, Query()] = None,
+ order_by: Annotated[str | None, Query()] = "timestamp",
+) -> list[MessageResponse]:
+ try:
+ stmt = select(MessageTable)
+ if flow_id:
+ stmt = stmt.where(MessageTable.flow_id == flow_id)
+ if session_id:
+ stmt = stmt.where(MessageTable.session_id == session_id)
+ if sender:
+ stmt = stmt.where(MessageTable.sender == sender)
+ if sender_name:
+ stmt = stmt.where(MessageTable.sender_name == sender_name)
+ if order_by:
+ col = getattr(MessageTable, order_by).asc()
+ stmt = stmt.order_by(col)
+ messages = await session.exec(stmt)
+ return [MessageResponse.model_validate(d, from_attributes=True) for d in messages]
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.delete("/messages", status_code=204, dependencies=[Depends(get_current_active_user)])
+async def delete_messages(message_ids: list[UUID], session: DbSession) -> None:
+ try:
+ await session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore[attr-defined]
+ await session.commit()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.put("/messages/{message_id}", dependencies=[Depends(get_current_active_user)], response_model=MessageRead)
+async def update_message(
+ message_id: UUID,
+ message: MessageUpdate,
+ session: DbSession,
+):
+ try:
+ db_message = await session.get(MessageTable, message_id)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not db_message:
+ raise HTTPException(status_code=404, detail="Message not found")
+
+ try:
+ message_dict = message.model_dump(exclude_unset=True, exclude_none=True)
+ if "text" in message_dict and message_dict["text"] != db_message.text:
+ message_dict["edit"] = True
+ db_message.sqlmodel_update(message_dict)
+ session.add(db_message)
+ await session.commit()
+ await session.refresh(db_message)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+ return db_message
+
+
+@router.patch(
+ "/messages/session/{old_session_id}",
+ dependencies=[Depends(get_current_active_user)],
+)
+async def update_session_id(
+ old_session_id: str,
+ new_session_id: Annotated[str, Query(..., description="The new session ID to update to")],
+ session: DbSession,
+) -> list[MessageResponse]:
+ try:
+ # Get all messages with the old session ID
+ stmt = select(MessageTable).where(MessageTable.session_id == old_session_id)
+ messages = (await session.exec(stmt)).all()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ if not messages:
+ raise HTTPException(status_code=404, detail="No messages found with the given session ID")
+
+ try:
+ # Update all messages with the new session ID
+ for message in messages:
+ message.session_id = new_session_id
+
+ session.add_all(messages)
+
+ await session.commit()
+ message_responses = []
+ for message in messages:
+ await session.refresh(message)
+ message_responses.append(MessageResponse.model_validate(message, from_attributes=True))
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return message_responses
+
+
+@router.delete("/messages/session/{session_id}", status_code=204)
+async def delete_messages_session(
+ session_id: str,
+ session: DbSession,
+):
+ try:
+ await session.exec(
+ delete(MessageTable)
+ .where(col(MessageTable.session_id) == session_id)
+ .execution_options(synchronize_session="fetch")
+ )
+ await session.commit()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+ return {"message": "Messages deleted successfully"}
+
+
+@router.get("/transactions")
+async def get_transactions(
+ flow_id: Annotated[UUID, Query()],
+ session: DbSession,
+ params: Annotated[Params | None, Depends(custom_params)],
+) -> Page[TransactionTable]:
+ try:
+ stmt = (
+ select(TransactionTable)
+ .where(TransactionTable.flow_id == flow_id)
+ .order_by(col(TransactionTable.timestamp))
+ )
+ return await paginate(session, stmt, params=params, transformer=transform_transaction_table)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
diff --git a/langflow/src/backend/base/langflow/api/v1/schemas.py b/langflow/src/backend/base/langflow/api/v1/schemas.py
new file mode 100644
index 0000000..8ff419d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/schemas.py
@@ -0,0 +1,386 @@
+from datetime import datetime, timezone
+from enum import Enum
+from pathlib import Path
+from typing import Any, Literal
+from uuid import UUID
+
+from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer
+
+from langflow.graph.schema import RunOutputs
+from langflow.schema import dotdict
+from langflow.schema.graph import Tweaks
+from langflow.schema.schema import InputType, OutputType, OutputValue
+from langflow.serialization.constants import MAX_ITEMS_LENGTH, MAX_TEXT_LENGTH
+from langflow.serialization.serialization import serialize
+from langflow.services.database.models.api_key.model import ApiKeyRead
+from langflow.services.database.models.base import orjson_dumps
+from langflow.services.database.models.flow import FlowCreate, FlowRead
+from langflow.services.database.models.user import UserRead
+from langflow.services.settings.feature_flags import FeatureFlags
+from langflow.services.tracing.schema import Log
+from langflow.utils.util_strings import truncate_long_strings
+
+
+class BuildStatus(Enum):
+ """Status of the build."""
+
+ SUCCESS = "success"
+ FAILURE = "failure"
+ STARTED = "started"
+ IN_PROGRESS = "in_progress"
+
+
+class TweaksRequest(BaseModel):
+ tweaks: dict[str, dict[str, Any]] | None = Field(default_factory=dict)
+
+
+class UpdateTemplateRequest(BaseModel):
+ template: dict
+
+
+class TaskResponse(BaseModel):
+ """Task response schema."""
+
+ id: str | None = Field(None)
+ href: str | None = Field(None)
+
+
+class ProcessResponse(BaseModel):
+ """Process response schema."""
+
+ result: Any
+ status: str | None = None
+ task: TaskResponse | None = None
+ session_id: str | None = None
+ backend: str | None = None
+
+
+class RunResponse(BaseModel):
+ """Run response schema."""
+
+ outputs: list[RunOutputs] | None = []
+ session_id: str | None = None
+
+ @model_serializer(mode="plain")
+ def serialize(self):
+ # Serialize all the outputs if they are base models
+ serialized = {"session_id": self.session_id, "outputs": []}
+ if self.outputs:
+ serialized_outputs = []
+ for output in self.outputs:
+ if isinstance(output, BaseModel) and not isinstance(output, RunOutputs):
+ serialized_outputs.append(output.model_dump(exclude_none=True))
+ else:
+ serialized_outputs.append(output)
+ serialized["outputs"] = serialized_outputs
+ return serialized
+
+
+class PreloadResponse(BaseModel):
+ """Preload response schema."""
+
+ session_id: str | None = None
+ is_clear: bool | None = None
+
+
+class TaskStatusResponse(BaseModel):
+ """Task status response schema."""
+
+ status: str
+ result: Any | None = None
+
+
+class ChatMessage(BaseModel):
+ """Chat message schema."""
+
+ is_bot: bool = False
+ message: str | None | dict = None
+ chat_key: str | None = Field(None, serialization_alias="chatKey")
+ type: str = "human"
+
+
+class ChatResponse(ChatMessage):
+ """Chat response schema."""
+
+ intermediate_steps: str
+
+ type: str
+ is_bot: bool = True
+ files: list = []
+
+ @field_validator("type")
+ @classmethod
+ def validate_message_type(cls, v):
+ if v not in {"start", "stream", "end", "error", "info", "file"}:
+ msg = "type must be start, stream, end, error, info, or file"
+ raise ValueError(msg)
+ return v
+
+
+class PromptResponse(ChatMessage):
+ """Prompt response schema."""
+
+ prompt: str
+ type: str = "prompt"
+ is_bot: bool = True
+
+
+class FileResponse(ChatMessage):
+ """File response schema."""
+
+ data: Any = None
+ data_type: str
+ type: str = "file"
+ is_bot: bool = True
+
+ @field_validator("data_type")
+ @classmethod
+ def validate_data_type(cls, v):
+ if v not in {"image", "csv"}:
+ msg = "data_type must be image or csv"
+ raise ValueError(msg)
+ return v
+
+
+class FlowListCreate(BaseModel):
+ flows: list[FlowCreate]
+
+
+class FlowListIds(BaseModel):
+ flow_ids: list[str]
+
+
+class FlowListRead(BaseModel):
+ flows: list[FlowRead]
+
+
+class FlowListReadWithFolderName(BaseModel):
+ flows: list[FlowRead]
+ folder_name: str
+ description: str
+
+
+class InitResponse(BaseModel):
+ flow_id: str = Field(serialization_alias="flowId")
+
+
+class BuiltResponse(BaseModel):
+ built: bool
+
+
+class UploadFileResponse(BaseModel):
+ """Upload file response schema."""
+
+ flow_id: str = Field(serialization_alias="flowId")
+ file_path: Path
+
+
+class StreamData(BaseModel):
+ event: str
+ data: dict
+
+ def __str__(self) -> str:
+ return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
+
+
+class CustomComponentRequest(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ code: str
+ frontend_node: dict | None = None
+
+
+class CustomComponentResponse(BaseModel):
+ data: dict
+ type: str
+
+
+class UpdateCustomComponentRequest(CustomComponentRequest):
+ field: str
+ field_value: str | int | float | bool | dict | list | None = None
+ template: dict
+ tool_mode: bool = False
+
+ def get_template(self):
+ return dotdict(self.template)
+
+
+class CustomComponentResponseError(BaseModel):
+ detail: str
+ traceback: str
+
+
+class ComponentListCreate(BaseModel):
+ flows: list[FlowCreate]
+
+
+class ComponentListRead(BaseModel):
+ flows: list[FlowRead]
+
+
+class UsersResponse(BaseModel):
+ total_count: int
+ users: list[UserRead]
+
+
+class ApiKeyResponse(BaseModel):
+ id: str
+ api_key: str
+ name: str
+ created_at: str
+ last_used_at: str
+
+
+class ApiKeysResponse(BaseModel):
+ total_count: int
+ user_id: UUID
+ api_keys: list[ApiKeyRead]
+
+
+class CreateApiKeyRequest(BaseModel):
+ name: str
+
+
+class Token(BaseModel):
+ access_token: str
+ refresh_token: str
+ token_type: str
+
+
+class ApiKeyCreateRequest(BaseModel):
+ api_key: str
+
+
+class VerticesOrderResponse(BaseModel):
+ ids: list[str]
+ run_id: UUID
+ vertices_to_run: list[str]
+
+
+class ResultDataResponse(BaseModel):
+ results: Any | None = Field(default_factory=dict)
+ outputs: dict[str, OutputValue] = Field(default_factory=dict)
+ logs: dict[str, list[Log]] = Field(default_factory=dict)
+ message: Any | None = Field(default_factory=dict)
+ artifacts: Any | None = Field(default_factory=dict)
+ timedelta: float | None = None
+ duration: str | None = None
+ used_frozen_result: bool | None = False
+
+ @field_serializer("results")
+ @classmethod
+ def serialize_results(cls, v):
+ """Serialize results with custom handling for special types and truncation."""
+ return serialize(v, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
+
+ @model_serializer(mode="plain")
+ def serialize_model(self) -> dict:
+ """Custom serializer for the entire model."""
+ return {
+ "results": self.serialize_results(self.results),
+ "outputs": serialize(self.outputs, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH),
+ "logs": serialize(self.logs, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH),
+ "message": serialize(self.message, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH),
+ "artifacts": serialize(self.artifacts, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH),
+ "timedelta": self.timedelta,
+ "duration": self.duration,
+ "used_frozen_result": self.used_frozen_result,
+ }
+
+
+class VertexBuildResponse(BaseModel):
+ id: str | None = None
+ inactivated_vertices: list[str] | None = None
+ next_vertices_ids: list[str] | None = None
+ top_level_vertices: list[str] | None = None
+ valid: bool
+ params: Any | None = Field(default_factory=dict)
+ """JSON string of the params."""
+ data: ResultDataResponse
+ """Mapping of vertex ids to result dict containing the param name and result value."""
+ timestamp: datetime | None = Field(default_factory=lambda: datetime.now(timezone.utc))
+ """Timestamp of the build."""
+
+ @field_serializer("data")
+ def serialize_data(self, data: ResultDataResponse) -> dict:
+ data_dict = data.model_dump() if isinstance(data, BaseModel) else data
+ return truncate_long_strings(data_dict)
+
+
+class VerticesBuiltResponse(BaseModel):
+ vertices: list[VertexBuildResponse]
+
+
+class InputValueRequest(BaseModel):
+ components: list[str] | None = []
+ input_value: str | None = None
+ session: str | None = None
+ type: InputType | None = Field(
+ "any",
+ description="Defines on which components the input value should be applied. "
+ "'any' applies to all input components.",
+ )
+
+ # add an example
+ model_config = ConfigDict(
+ json_schema_extra={
+ "examples": [
+ {
+ "components": ["components_id", "Component Name"],
+ "input_value": "input_value",
+ "session": "session_id",
+ },
+ {"components": ["Component Name"], "input_value": "input_value"},
+ {"input_value": "input_value"},
+ {
+ "components": ["Component Name"],
+ "input_value": "input_value",
+ "session": "session_id",
+ },
+ {"input_value": "input_value", "session": "session_id"},
+ {"type": "chat", "input_value": "input_value"},
+ {"type": "json", "input_value": '{"key": "value"}'},
+ ]
+ },
+ extra="forbid",
+ )
+
+
+class SimplifiedAPIRequest(BaseModel):
+ input_value: str | None = Field(default=None, description="The input value")
+ input_type: InputType | None = Field(default="chat", description="The input type")
+ output_type: OutputType | None = Field(default="chat", description="The output type")
+ output_component: str | None = Field(
+ default="",
+ description="If there are multiple output components, you can specify the component to get the output from.",
+ )
+ tweaks: Tweaks | None = Field(default=None, description="The tweaks")
+ session_id: str | None = Field(default=None, description="The session id")
+
+
+# (alias) type ReactFlowJsonObject = {
+# nodes: Node[];
+# edges: Edge[];
+# viewport: Viewport;
+# }
+# import ReactFlowJsonObject
+class FlowDataRequest(BaseModel):
+ nodes: list[dict]
+ edges: list[dict]
+ viewport: dict | None = None
+
+
+class ConfigResponse(BaseModel):
+ feature_flags: FeatureFlags
+ frontend_timeout: int
+ auto_saving: bool
+ auto_saving_interval: int
+ health_check_max_retries: int
+ max_file_size_upload: int
+ event_delivery: Literal["polling", "streaming"]
+
+
+class CancelFlowResponse(BaseModel):
+ """Response model for flow build cancellation."""
+
+ success: bool
+ message: str
diff --git a/langflow/src/backend/base/langflow/api/v1/starter_projects.py b/langflow/src/backend/base/langflow/api/v1/starter_projects.py
new file mode 100644
index 0000000..8e8b99a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/starter_projects.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter, Depends, HTTPException
+
+from langflow.graph.graph.schema import GraphDump
+from langflow.services.auth.utils import get_current_active_user
+
+router = APIRouter(prefix="/starter-projects", tags=["Flows"])
+
+
+@router.get("/", dependencies=[Depends(get_current_active_user)], status_code=200)
+async def get_starter_projects() -> list[GraphDump]:
+ """Get a list of starter projects."""
+ from langflow.initial_setup.load import get_starter_projects_dump
+
+ try:
+ return get_starter_projects_dump()
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
diff --git a/langflow/src/backend/base/langflow/api/v1/store.py b/langflow/src/backend/base/langflow/api/v1/store.py
new file mode 100644
index 0000000..23023da
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/store.py
@@ -0,0 +1,180 @@
+from typing import Annotated
+from uuid import UUID
+
+from fastapi import APIRouter, Depends, HTTPException, Query
+from loguru import logger
+
+from langflow.api.utils import CurrentActiveUser, check_langflow_version
+from langflow.services.auth import utils as auth_utils
+from langflow.services.deps import get_settings_service, get_store_service
+from langflow.services.store.exceptions import CustomError
+from langflow.services.store.schema import (
+ CreateComponentResponse,
+ DownloadComponentResponse,
+ ListComponentResponseModel,
+ StoreComponentCreate,
+ TagResponse,
+ UsersLikesResponse,
+)
+
+router = APIRouter(prefix="/store", tags=["Components Store"])
+
+
+def get_user_store_api_key(user: CurrentActiveUser):
+ if not user.store_api_key:
+ raise HTTPException(status_code=400, detail="You must have a store API key set.")
+ try:
+ return auth_utils.decrypt_api_key(user.store_api_key, get_settings_service())
+ except Exception as e:
+ raise HTTPException(status_code=500, detail="Failed to decrypt API key. Please set a new one.") from e
+
+
+def get_optional_user_store_api_key(user: CurrentActiveUser):
+ if not user.store_api_key:
+ return None
+ try:
+ return auth_utils.decrypt_api_key(user.store_api_key, get_settings_service())
+ except Exception: # noqa: BLE001
+ logger.exception("Failed to decrypt API key")
+ return user.store_api_key
+
+
+@router.get("/check/")
+async def check_if_store_is_enabled():
+ return {
+ "enabled": get_settings_service().settings.store,
+ }
+
+
+@router.get("/check/api_key")
+async def check_if_store_has_api_key(
+ api_key: Annotated[str | None, Depends(get_optional_user_store_api_key)],
+):
+ if api_key is None:
+ return {"has_api_key": False, "is_valid": False}
+
+ try:
+ is_valid = await get_store_service().check_api_key(api_key)
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+ return {"has_api_key": api_key is not None, "is_valid": is_valid}
+
+
+@router.post("/components/", status_code=201)
+async def share_component(
+ component: StoreComponentCreate,
+ store_api_key: Annotated[str, Depends(get_user_store_api_key)],
+) -> CreateComponentResponse:
+ try:
+ await check_langflow_version(component)
+ return await get_store_service().upload(store_api_key, component)
+ except Exception as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+
+
+@router.patch("/components/{component_id}", status_code=201)
+async def update_shared_component(
+ component_id: UUID,
+ component: StoreComponentCreate,
+ store_api_key: Annotated[str, Depends(get_user_store_api_key)],
+) -> CreateComponentResponse:
+ try:
+ await check_langflow_version(component)
+ return await get_store_service().update(store_api_key, component_id, component)
+ except Exception as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+
+
+@router.get("/components/")
+async def get_components(
+ *,
+ component_id: Annotated[str | None, Query()] = None,
+ search: Annotated[str | None, Query()] = None,
+ private: Annotated[bool | None, Query()] = None,
+ is_component: Annotated[bool | None, Query()] = None,
+ tags: Annotated[list[str] | None, Query()] = None,
+ sort: Annotated[list[str] | None, Query()] = None,
+ liked: Annotated[bool, Query()] = False,
+ filter_by_user: Annotated[bool, Query()] = False,
+ fields: Annotated[list[str] | None, Query()] = None,
+ page: int = 1,
+ limit: int = 10,
+ store_api_key: Annotated[str | None, Depends(get_optional_user_store_api_key)],
+) -> ListComponentResponseModel:
+ try:
+ return await get_store_service().get_list_component_response_model(
+ component_id=component_id,
+ search=search,
+ private=private,
+ is_component=is_component,
+ fields=fields,
+ tags=tags,
+ sort=sort,
+ liked=liked,
+ filter_by_user=filter_by_user,
+ page=page,
+ limit=limit,
+ store_api_key=store_api_key,
+ )
+ except CustomError as exc:
+ raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+@router.get("/components/{component_id}")
+async def download_component(
+ component_id: UUID,
+ store_api_key: Annotated[str, Depends(get_user_store_api_key)],
+) -> DownloadComponentResponse:
+ try:
+ component = await get_store_service().download(store_api_key, component_id)
+ except CustomError as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+ if component is None:
+ raise HTTPException(status_code=400, detail="Component not found")
+
+ return component
+
+
+@router.get("/tags", response_model=list[TagResponse])
+async def get_tags():
+ try:
+ return await get_store_service().get_tags()
+ except CustomError as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+@router.get("/users/likes", response_model=list[UsersLikesResponse])
+async def get_list_of_components_liked_by_user(
+ store_api_key: Annotated[str, Depends(get_user_store_api_key)],
+):
+ try:
+ return await get_store_service().get_user_likes(store_api_key)
+ except CustomError as exc:
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+@router.post("/users/likes/{component_id}")
+async def like_component(
+ component_id: UUID,
+ store_api_key: Annotated[str, Depends(get_user_store_api_key)],
+) -> UsersLikesResponse:
+ try:
+ store_service = get_store_service()
+ result = await store_service.like_component(store_api_key, str(component_id))
+ likes_count = await store_service.get_component_likes_count(str(component_id), store_api_key)
+
+ return UsersLikesResponse(likes_count=likes_count, liked_by_user=result)
+ except CustomError as exc:
+ raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
diff --git a/langflow/src/backend/base/langflow/api/v1/users.py b/langflow/src/backend/base/langflow/api/v1/users.py
new file mode 100644
index 0000000..e0c545f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/users.py
@@ -0,0 +1,146 @@
+from typing import Annotated
+from uuid import UUID
+
+from fastapi import APIRouter, Depends, HTTPException
+from sqlalchemy import func
+from sqlalchemy.exc import IntegrityError
+from sqlmodel import select
+from sqlmodel.sql.expression import SelectOfScalar
+
+from langflow.api.utils import CurrentActiveUser, DbSession
+from langflow.api.v1.schemas import UsersResponse
+from langflow.initial_setup.setup import get_or_create_default_folder
+from langflow.services.auth.utils import (
+ get_current_active_superuser,
+ get_password_hash,
+ verify_password,
+)
+from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate
+from langflow.services.database.models.user.crud import get_user_by_id, update_user
+from langflow.services.deps import get_settings_service
+
+router = APIRouter(tags=["Users"], prefix="/users")
+
+
+@router.post("/", response_model=UserRead, status_code=201)
+async def add_user(
+ user: UserCreate,
+ session: DbSession,
+) -> User:
+ """Add a new user to the database."""
+ new_user = User.model_validate(user, from_attributes=True)
+ try:
+ new_user.password = get_password_hash(user.password)
+ new_user.is_active = get_settings_service().auth_settings.NEW_USER_IS_ACTIVE
+ session.add(new_user)
+ await session.commit()
+ await session.refresh(new_user)
+ folder = await get_or_create_default_folder(session, new_user.id)
+ if not folder:
+ raise HTTPException(status_code=500, detail="Error creating default folder")
+ except IntegrityError as e:
+ await session.rollback()
+ raise HTTPException(status_code=400, detail="This username is unavailable.") from e
+
+ return new_user
+
+
+@router.get("/whoami", response_model=UserRead)
+async def read_current_user(
+ current_user: CurrentActiveUser,
+) -> User:
+ """Retrieve the current user's data."""
+ return current_user
+
+
+@router.get("/", dependencies=[Depends(get_current_active_superuser)])
+async def read_all_users(
+ *,
+ skip: int = 0,
+ limit: int = 10,
+ session: DbSession,
+) -> UsersResponse:
+ """Retrieve a list of users from the database with pagination."""
+ query: SelectOfScalar = select(User).offset(skip).limit(limit)
+ users = (await session.exec(query)).fetchall()
+
+ count_query = select(func.count()).select_from(User)
+ total_count = (await session.exec(count_query)).first()
+
+ return UsersResponse(
+ total_count=total_count,
+ users=[UserRead(**user.model_dump()) for user in users],
+ )
+
+
+@router.patch("/{user_id}", response_model=UserRead)
+async def patch_user(
+ user_id: UUID,
+ user_update: UserUpdate,
+ user: CurrentActiveUser,
+ session: DbSession,
+) -> User:
+ """Update an existing user's data."""
+ update_password = bool(user_update.password)
+
+ if not user.is_superuser and user_update.is_superuser:
+ raise HTTPException(status_code=403, detail="Permission denied")
+
+ if not user.is_superuser and user.id != user_id:
+ raise HTTPException(status_code=403, detail="Permission denied")
+ if update_password:
+ if not user.is_superuser:
+ raise HTTPException(status_code=400, detail="You can't change your password here")
+ user_update.password = get_password_hash(user_update.password)
+
+ if user_db := await get_user_by_id(session, user_id):
+ if not update_password:
+ user_update.password = user_db.password
+ return await update_user(user_db, user_update, session)
+ raise HTTPException(status_code=404, detail="User not found")
+
+
+@router.patch("/{user_id}/reset-password", response_model=UserRead)
+async def reset_password(
+ user_id: UUID,
+ user_update: UserUpdate,
+ user: CurrentActiveUser,
+ session: DbSession,
+) -> User:
+ """Reset a user's password."""
+ if user_id != user.id:
+ raise HTTPException(status_code=400, detail="You can't change another user's password")
+
+ if not user:
+ raise HTTPException(status_code=404, detail="User not found")
+ if verify_password(user_update.password, user.password):
+ raise HTTPException(status_code=400, detail="You can't use your current password")
+ new_password = get_password_hash(user_update.password)
+ user.password = new_password
+ await session.commit()
+ await session.refresh(user)
+
+ return user
+
+
+@router.delete("/{user_id}")
+async def delete_user(
+ user_id: UUID,
+ current_user: Annotated[User, Depends(get_current_active_superuser)],
+ session: DbSession,
+) -> dict:
+ """Delete a user from the database."""
+ if current_user.id == user_id:
+ raise HTTPException(status_code=400, detail="You can't delete your own user account")
+ if not current_user.is_superuser:
+ raise HTTPException(status_code=403, detail="Permission denied")
+
+ stmt = select(User).where(User.id == user_id)
+ user_db = (await session.exec(stmt)).first()
+ if not user_db:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ await session.delete(user_db)
+ await session.commit()
+
+ return {"detail": "User deleted"}
diff --git a/langflow/src/backend/base/langflow/api/v1/validate.py b/langflow/src/backend/base/langflow/api/v1/validate.py
new file mode 100644
index 0000000..1bc8219
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/validate.py
@@ -0,0 +1,48 @@
+from fastapi import APIRouter, HTTPException
+from loguru import logger
+
+from langflow.api.utils import CurrentActiveUser
+from langflow.api.v1.base import Code, CodeValidationResponse, PromptValidationResponse, ValidatePromptRequest
+from langflow.base.prompts.api_utils import process_prompt_template
+from langflow.utils.validate import validate_code
+
+# build router
+router = APIRouter(prefix="/validate", tags=["Validate"])
+
+
+@router.post("/code", status_code=200)
+async def post_validate_code(code: Code, _current_user: CurrentActiveUser) -> CodeValidationResponse:
+ try:
+ errors = validate_code(code.code)
+ return CodeValidationResponse(
+ imports=errors.get("imports", {}),
+ function=errors.get("function", {}),
+ )
+ except Exception as e:
+ logger.opt(exception=True).debug("Error validating code")
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.post("/prompt", status_code=200)
+async def post_validate_prompt(prompt_request: ValidatePromptRequest) -> PromptValidationResponse:
+ try:
+ if not prompt_request.frontend_node:
+ return PromptValidationResponse(
+ input_variables=[],
+ frontend_node=None,
+ )
+
+ # Process the prompt template using direct attributes
+ input_variables = process_prompt_template(
+ template=prompt_request.template,
+ name=prompt_request.name,
+ custom_fields=prompt_request.frontend_node.custom_fields,
+ frontend_node_template=prompt_request.frontend_node.template,
+ )
+
+ return PromptValidationResponse(
+ input_variables=input_variables,
+ frontend_node=prompt_request.frontend_node,
+ )
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
diff --git a/langflow/src/backend/base/langflow/api/v1/variable.py b/langflow/src/backend/base/langflow/api/v1/variable.py
new file mode 100644
index 0000000..c0f05bf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v1/variable.py
@@ -0,0 +1,106 @@
+from uuid import UUID
+
+from fastapi import APIRouter, HTTPException
+from sqlalchemy.exc import NoResultFound
+
+from langflow.api.utils import CurrentActiveUser, DbSession
+from langflow.services.database.models.variable import VariableCreate, VariableRead, VariableUpdate
+from langflow.services.deps import get_variable_service
+from langflow.services.variable.constants import CREDENTIAL_TYPE
+from langflow.services.variable.service import DatabaseVariableService
+
+router = APIRouter(prefix="/variables", tags=["Variables"])
+
+
+@router.post("/", response_model=VariableRead, status_code=201)
+async def create_variable(
+ *,
+ session: DbSession,
+ variable: VariableCreate,
+ current_user: CurrentActiveUser,
+):
+ """Create a new variable."""
+ variable_service = get_variable_service()
+ if not variable.name and not variable.value:
+ raise HTTPException(status_code=400, detail="Variable name and value cannot be empty")
+
+ if not variable.name:
+ raise HTTPException(status_code=400, detail="Variable name cannot be empty")
+
+ if not variable.value:
+ raise HTTPException(status_code=400, detail="Variable value cannot be empty")
+
+ if variable.name in await variable_service.list_variables(user_id=current_user.id, session=session):
+ raise HTTPException(status_code=400, detail="Variable name already exists")
+ try:
+ return await variable_service.create_variable(
+ user_id=current_user.id,
+ name=variable.name,
+ value=variable.value,
+ default_fields=variable.default_fields or [],
+ type_=variable.type or CREDENTIAL_TYPE,
+ session=session,
+ )
+ except Exception as e:
+ if isinstance(e, HTTPException):
+ raise
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.get("/", response_model=list[VariableRead], status_code=200)
+async def read_variables(
+ *,
+ session: DbSession,
+ current_user: CurrentActiveUser,
+):
+ """Read all variables."""
+ variable_service = get_variable_service()
+ if not isinstance(variable_service, DatabaseVariableService):
+ msg = "Variable service is not an instance of DatabaseVariableService"
+ raise TypeError(msg)
+ try:
+ return await variable_service.get_all(user_id=current_user.id, session=session)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.patch("/{variable_id}", response_model=VariableRead, status_code=200)
+async def update_variable(
+ *,
+ session: DbSession,
+ variable_id: UUID,
+ variable: VariableUpdate,
+ current_user: CurrentActiveUser,
+):
+ """Update a variable."""
+ variable_service = get_variable_service()
+ if not isinstance(variable_service, DatabaseVariableService):
+ msg = "Variable service is not an instance of DatabaseVariableService"
+ raise TypeError(msg)
+ try:
+ return await variable_service.update_variable_fields(
+ user_id=current_user.id,
+ variable_id=variable_id,
+ variable=variable,
+ session=session,
+ )
+ except NoResultFound as e:
+ raise HTTPException(status_code=404, detail="Variable not found") from e
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
+
+
+@router.delete("/{variable_id}", status_code=204)
+async def delete_variable(
+ *,
+ session: DbSession,
+ variable_id: UUID,
+ current_user: CurrentActiveUser,
+) -> None:
+ """Delete a variable."""
+ variable_service = get_variable_service()
+ try:
+ await variable_service.delete_variable_by_id(user_id=current_user.id, variable_id=variable_id, session=session)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e)) from e
diff --git a/langflow/src/backend/base/langflow/api/v2/__init__.py b/langflow/src/backend/base/langflow/api/v2/__init__.py
new file mode 100644
index 0000000..2ada31e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v2/__init__.py
@@ -0,0 +1,5 @@
+from langflow.api.v2.files import router as files_router
+
+__all__ = [
+ "files_router",
+]
diff --git a/langflow/src/backend/base/langflow/api/v2/files.py b/langflow/src/backend/base/langflow/api/v2/files.py
new file mode 100644
index 0000000..c76e5ce
--- /dev/null
+++ b/langflow/src/backend/base/langflow/api/v2/files.py
@@ -0,0 +1,267 @@
+import re
+import uuid
+from collections.abc import AsyncGenerator
+from http import HTTPStatus
+from pathlib import Path
+from typing import Annotated
+
+from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
+from fastapi.responses import StreamingResponse
+from sqlmodel import String, cast, select
+
+from langflow.api.schemas import UploadFileResponse
+from langflow.api.utils import CurrentActiveUser, DbSession
+from langflow.services.database.models.file import File as UserFile
+from langflow.services.deps import get_settings_service, get_storage_service
+from langflow.services.storage.service import StorageService
+
+router = APIRouter(tags=["Files"], prefix="/files")
+
+
+async def byte_stream_generator(file_bytes: bytes, chunk_size: int = 8192) -> AsyncGenerator[bytes, None]:
+ """Convert bytes object into an async generator that yields chunks."""
+ for i in range(0, len(file_bytes), chunk_size):
+ yield file_bytes[i : i + chunk_size]
+
+
+async def fetch_file_object(file_id: uuid.UUID, current_user: CurrentActiveUser, session: DbSession):
+ # Fetch the file from the DB
+ stmt = select(UserFile).where(UserFile.id == file_id)
+ results = await session.exec(stmt)
+ file = results.first()
+
+ # Check if the file exists
+ if not file:
+ raise HTTPException(status_code=404, detail="File not found")
+
+ # Make sure the user has access to the file
+ if file.user_id != current_user.id:
+ raise HTTPException(status_code=403, detail="You don't have access to this file")
+
+ return file
+
+
+@router.post("", status_code=HTTPStatus.CREATED)
+async def upload_user_file(
+ file: Annotated[UploadFile, File(...)],
+ session: DbSession,
+ current_user: CurrentActiveUser,
+ storage_service=Depends(get_storage_service),
+ settings_service=Depends(get_settings_service),
+) -> UploadFileResponse:
+ """Upload a file for the current user and track it in the database."""
+ # Get the max allowed file size from settings (in MB)
+ try:
+ max_file_size_upload = settings_service.settings.max_file_size_upload
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Settings error: {e}") from e
+
+ # Validate that a file is actually provided
+ if not file or not file.filename:
+ raise HTTPException(status_code=400, detail="No file provided")
+
+ # Validate file size (convert MB to bytes)
+ if file.size > max_file_size_upload * 1024 * 1024:
+ raise HTTPException(
+ status_code=413,
+ detail=f"File size is larger than the maximum file size {max_file_size_upload}MB.",
+ )
+
+ # Read file content and create a unique file name
+ try:
+ # Create a unique file name
+ file_id = uuid.uuid4()
+ file_content = await file.read()
+
+ # Get file extension of the file
+ file_extension = "." + file.filename.split(".")[-1] if file.filename and "." in file.filename else ""
+ anonymized_file_name = f"{file_id!s}{file_extension}"
+
+ # Here we use the current user's id as the folder name
+ folder = str(current_user.id)
+ # Save the file using the storage service.
+ await storage_service.save_file(flow_id=folder, file_name=anonymized_file_name, data=file_content)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error saving file: {e}") from e
+
+ # Create a new database record for the uploaded file.
+ try:
+ # Enforce unique constraint on name
+ # Name it as filename (1), (2), etc.
+ # Check if the file name already exists
+ new_filename = file.filename
+ try:
+ root_filename, _ = new_filename.rsplit(".", 1)
+ except ValueError:
+ root_filename, _ = new_filename, ""
+
+ # Check if there are files with the same name
+ stmt = select(UserFile).where(cast(UserFile.name, String).like(f"{root_filename}%"))
+ existing_files = await session.exec(stmt)
+ files = existing_files.all() # Fetch all matching records
+
+ # If there are files with the same name, append a count to the filename
+ if files:
+ counts = []
+
+ # Extract the count from the filename
+ for my_file in files:
+ match = re.search(r"\((\d+)\)(?=\.\w+$|$)", my_file.name) # Match (number) before extension or at end
+ if match:
+ counts.append(int(match.group(1)))
+
+ # Get the max count and increment by 1
+ count = max(counts) if counts else 0 # Default to 0 if no matches found
+
+ # Split the extension from the filename
+ root_filename = f"{root_filename} ({count + 1})"
+
+ # Compute the file size based on the path
+ file_size = await storage_service.get_file_size(flow_id=folder, file_name=anonymized_file_name)
+
+ # Compute the file path
+ file_path = f"{folder}/{anonymized_file_name}"
+
+ # Create a new file record
+ new_file = UserFile(
+ id=file_id,
+ user_id=current_user.id,
+ name=root_filename,
+ path=file_path,
+ size=file_size,
+ )
+ session.add(new_file)
+
+ await session.commit()
+ await session.refresh(new_file)
+ except Exception as e:
+ # Optionally, you could also delete the file from disk if the DB insert fails.
+ raise HTTPException(status_code=500, detail=f"Database error: {e}") from e
+
+ return UploadFileResponse(id=new_file.id, name=new_file.name, path=Path(new_file.path), size=new_file.size)
+
+
+@router.get("")
+async def list_files(
+ current_user: CurrentActiveUser,
+ session: DbSession,
+) -> list[UserFile]:
+ """List the files available to the current user."""
+ try:
+ # Fetch from the UserFile table
+ stmt = select(UserFile).where(UserFile.user_id == current_user.id)
+ results = await session.exec(stmt)
+
+ return list(results)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error listing files: {e}") from e
+
+
+@router.get("/{file_id}")
+async def download_file(
+ file_id: uuid.UUID,
+ current_user: CurrentActiveUser,
+ session: DbSession,
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+):
+ """Download a file by its ID."""
+ try:
+ # Fetch the file from the DB
+ file = await fetch_file_object(file_id, current_user, session)
+
+ # Get the basename of the file path
+ file_name = file.path.split("/")[-1]
+
+ # Get file stream
+ file_stream = await storage_service.get_file(flow_id=str(current_user.id), file_name=file_name)
+
+ # Ensure file_stream is an async iterator returning bytes
+ byte_stream = byte_stream_generator(file_stream)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error downloading file: {e}") from e
+
+ # Return the file as a streaming response
+ return StreamingResponse(
+ byte_stream,
+ media_type="application/octet-stream",
+ headers={"Content-Disposition": f'attachment; filename="{file.name}"'},
+ )
+
+
+@router.put("/{file_id}")
+async def edit_file_name(
+ file_id: uuid.UUID,
+ name: str,
+ current_user: CurrentActiveUser,
+ session: DbSession,
+) -> UploadFileResponse:
+ """Edit the name of a file by its ID."""
+ try:
+ # Fetch the file from the DB
+ file = await fetch_file_object(file_id, current_user, session)
+
+ # Update the file name
+ file.name = name
+ await session.commit()
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error editing file: {e}") from e
+
+ return UploadFileResponse(id=file.id, name=file.name, path=file.path, size=file.size)
+
+
+@router.delete("/{file_id}")
+async def delete_file(
+ file_id: uuid.UUID,
+ current_user: CurrentActiveUser,
+ session: DbSession,
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+):
+ """Delete a file by its ID."""
+ try:
+ # Fetch the file from the DB
+ file = await fetch_file_object(file_id, current_user, session)
+ if not file:
+ raise HTTPException(status_code=404, detail="File not found")
+
+ # Delete the file from the storage service
+ await storage_service.delete_file(flow_id=str(current_user.id), file_name=file.path)
+
+ # Delete from the database
+ await session.delete(file)
+ await session.flush() # Ensures delete is staged
+ await session.commit() # Commit deletion
+
+ except Exception as e:
+ await session.rollback() # Rollback on failure
+ raise HTTPException(status_code=500, detail=f"Error deleting file: {e}") from e
+
+ return {"message": "File deleted successfully"}
+
+
+@router.delete("")
+async def delete_all_files(
+ current_user: CurrentActiveUser,
+ session: DbSession,
+ storage_service: Annotated[StorageService, Depends(get_storage_service)],
+):
+ """Delete all files for the current user."""
+ try:
+ # Fetch all files from the DB
+ stmt = select(UserFile).where(UserFile.user_id == current_user.id)
+ results = await session.exec(stmt)
+ files = results.all()
+
+ # Delete all files from the storage service
+ for file in files:
+ await storage_service.delete_file(flow_id=str(current_user.id), file_name=file.path)
+ await session.delete(file)
+
+ # Delete all files from the database
+ await session.flush() # Ensures delete is staged
+ await session.commit() # Commit deletion
+
+ except Exception as e:
+ await session.rollback() # Rollback on failure
+ raise HTTPException(status_code=500, detail=f"Error deleting files: {e}") from e
+
+ return {"message": "All files deleted successfully"}
diff --git a/langflow/src/backend/base/langflow/base/__init__.py b/langflow/src/backend/base/langflow/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/agents/__init__.py b/langflow/src/backend/base/langflow/base/agents/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/agents/agent.py b/langflow/src/backend/base/langflow/base/agents/agent.py
new file mode 100644
index 0000000..33c95e1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/agent.py
@@ -0,0 +1,252 @@
+import re
+from abc import abstractmethod
+from typing import TYPE_CHECKING, cast
+
+from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent
+from langchain.agents.agent import RunnableAgent
+from langchain_core.runnables import Runnable
+
+from langflow.base.agents.callback import AgentAsyncHandler
+from langflow.base.agents.events import ExceptionWithMessageError, process_agent_events
+from langflow.base.agents.utils import data_to_messages
+from langflow.custom import Component
+from langflow.custom.custom_component.component import _get_component_toolkit
+from langflow.field_typing import Tool
+from langflow.inputs.inputs import InputTypes, MultilineInput
+from langflow.io import BoolInput, HandleInput, IntInput, MessageTextInput
+from langflow.logging import logger
+from langflow.memory import delete_message
+from langflow.schema import Data
+from langflow.schema.content_block import ContentBlock
+from langflow.schema.message import Message
+from langflow.template import Output
+from langflow.utils.constants import MESSAGE_SENDER_AI
+
+if TYPE_CHECKING:
+ from langchain_core.messages import BaseMessage
+
+ from langflow.schema.log import SendMessageFunctionType
+
+
+DEFAULT_TOOLS_DESCRIPTION = "A helpful assistant with access to the following tools:"
+DEFAULT_AGENT_NAME = "Agent ({tools_names})"
+
+
+class LCAgentComponent(Component):
+ trace_type = "agent"
+ _base_inputs: list[InputTypes] = [
+ MessageTextInput(
+ name="input_value",
+ display_name="Input",
+ info="The input provided by the user for the agent to process.",
+ tool_mode=True,
+ ),
+ BoolInput(
+ name="handle_parsing_errors",
+ display_name="Handle Parse Errors",
+ value=True,
+ advanced=True,
+ info="Should the Agent fix errors when reading user input for better processing?",
+ ),
+ BoolInput(name="verbose", display_name="Verbose", value=True, advanced=True),
+ IntInput(
+ name="max_iterations",
+ display_name="Max Iterations",
+ value=15,
+ advanced=True,
+ info="The maximum number of attempts the agent can make to complete its task before it stops.",
+ ),
+ MultilineInput(
+ name="agent_description",
+ display_name="Agent Description [Deprecated]",
+ info=(
+ "The description of the agent. This is only used when in Tool Mode. "
+ f"Defaults to '{DEFAULT_TOOLS_DESCRIPTION}' and tools are added dynamically. "
+ "This feature is deprecated and will be removed in future versions."
+ ),
+ advanced=True,
+ value=DEFAULT_TOOLS_DESCRIPTION,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Agent", name="agent", method="build_agent", hidden=True, tool_mode=False),
+ Output(display_name="Response", name="response", method="message_response"),
+ ]
+
+ @abstractmethod
+ def build_agent(self) -> AgentExecutor:
+ """Create the agent."""
+
+ async def message_response(self) -> Message:
+ """Run the agent and return the response."""
+ agent = self.build_agent()
+ message = await self.run_agent(agent=agent)
+
+ self.status = message
+ return message
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["build_agent"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def get_agent_kwargs(self, *, flatten: bool = False) -> dict:
+ base = {
+ "handle_parsing_errors": self.handle_parsing_errors,
+ "verbose": self.verbose,
+ "allow_dangerous_code": True,
+ }
+ agent_kwargs = {
+ "handle_parsing_errors": self.handle_parsing_errors,
+ "max_iterations": self.max_iterations,
+ }
+ if flatten:
+ return {
+ **base,
+ **agent_kwargs,
+ }
+ return {**base, "agent_executor_kwargs": agent_kwargs}
+
+ def get_chat_history_data(self) -> list[Data] | None:
+ # might be overridden in subclasses
+ return None
+
+ async def run_agent(
+ self,
+ agent: Runnable | BaseSingleActionAgent | BaseMultiActionAgent | AgentExecutor,
+ ) -> Message:
+ if isinstance(agent, AgentExecutor):
+ runnable = agent
+ else:
+ if not hasattr(self, "tools") or not self.tools:
+ msg = "Tools are required to run the agent."
+ raise ValueError(msg)
+ handle_parsing_errors = hasattr(self, "handle_parsing_errors") and self.handle_parsing_errors
+ verbose = hasattr(self, "verbose") and self.verbose
+ max_iterations = hasattr(self, "max_iterations") and self.max_iterations
+ runnable = AgentExecutor.from_agent_and_tools(
+ agent=agent,
+ tools=self.tools,
+ handle_parsing_errors=handle_parsing_errors,
+ verbose=verbose,
+ max_iterations=max_iterations,
+ )
+ input_dict: dict[str, str | list[BaseMessage]] = {"input": self.input_value}
+ if hasattr(self, "system_prompt"):
+ input_dict["system_prompt"] = self.system_prompt
+ if hasattr(self, "chat_history") and self.chat_history:
+ input_dict["chat_history"] = data_to_messages(self.chat_history)
+
+ if hasattr(self, "graph"):
+ session_id = self.graph.session_id
+ elif hasattr(self, "_session_id"):
+ session_id = self._session_id
+ else:
+ session_id = None
+
+ agent_message = Message(
+ sender=MESSAGE_SENDER_AI,
+ sender_name=self.display_name or "Agent",
+ properties={"icon": "Bot", "state": "partial"},
+ content_blocks=[ContentBlock(title="Agent Steps", contents=[])],
+ session_id=session_id,
+ )
+ try:
+ result = await process_agent_events(
+ runnable.astream_events(
+ input_dict,
+ config={"callbacks": [AgentAsyncHandler(self.log), *self.get_langchain_callbacks()]},
+ version="v2",
+ ),
+ agent_message,
+ cast("SendMessageFunctionType", self.send_message),
+ )
+ except ExceptionWithMessageError as e:
+ if hasattr(e, "agent_message") and hasattr(e.agent_message, "id"):
+ msg_id = e.agent_message.id
+ await delete_message(id_=msg_id)
+ await self._send_message_event(e.agent_message, category="remove_message")
+ logger.error(f"ExceptionWithMessageError: {e}")
+ raise
+ except Exception as e:
+ # Log or handle any other exceptions
+ logger.error(f"Error: {e}")
+ raise
+
+ self.status = result
+ return result
+
+ @abstractmethod
+ def create_agent_runnable(self) -> Runnable:
+ """Create the agent."""
+
+ def validate_tool_names(self) -> None:
+ """Validate tool names to ensure they match the required pattern."""
+ pattern = re.compile(r"^[a-zA-Z0-9_-]+$")
+ if hasattr(self, "tools") and self.tools:
+ for tool in self.tools:
+ if not pattern.match(tool.name):
+ msg = (
+ f"Invalid tool name '{tool.name}': must only contain letters, numbers, underscores, dashes,"
+ " and cannot contain spaces."
+ )
+ raise ValueError(msg)
+
+
+class LCToolsAgentComponent(LCAgentComponent):
+ _base_inputs = [
+ HandleInput(
+ name="tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ required=False,
+ info="These are the tools that the agent can use to help with tasks.",
+ ),
+ *LCAgentComponent._base_inputs,
+ ]
+
+ def build_agent(self) -> AgentExecutor:
+ self.validate_tool_names()
+ agent = self.create_agent_runnable()
+ return AgentExecutor.from_agent_and_tools(
+ agent=RunnableAgent(runnable=agent, input_keys_arg=["input"], return_keys_arg=["output"]),
+ tools=self.tools,
+ **self.get_agent_kwargs(flatten=True),
+ )
+
+ @abstractmethod
+ def create_agent_runnable(self) -> Runnable:
+ """Create the agent."""
+
+ def get_tool_name(self) -> str:
+ return self.display_name or "Agent"
+
+ def get_tool_description(self) -> str:
+ return self.agent_description or DEFAULT_TOOLS_DESCRIPTION
+
+ def _build_tools_names(self):
+ tools_names = ""
+ if self.tools:
+ tools_names = ", ".join([tool.name for tool in self.tools])
+ return tools_names
+
+ async def to_toolkit(self) -> list[Tool]:
+ component_toolkit = _get_component_toolkit()
+ tools_names = self._build_tools_names()
+ agent_description = self.get_tool_description()
+ # TODO: Agent Description Depreciated Feature to be removed
+ description = f"{agent_description}{tools_names}"
+ tools = component_toolkit(component=self).get_tools(
+ tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()
+ )
+ if hasattr(self, "tools_metadata"):
+ tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
+ return tools
diff --git a/langflow/src/backend/base/langflow/base/agents/callback.py b/langflow/src/backend/base/langflow/base/agents/callback.py
new file mode 100644
index 0000000..1ff6d2c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/callback.py
@@ -0,0 +1,130 @@
+from typing import Any
+from uuid import UUID
+
+from langchain.callbacks.base import AsyncCallbackHandler
+from langchain_core.agents import AgentAction, AgentFinish
+
+from langflow.schema.log import LogFunctionType
+
+
+class AgentAsyncHandler(AsyncCallbackHandler):
+ """Async callback handler that can be used to handle callbacks from langchain."""
+
+ def __init__(self, log_function: LogFunctionType | None = None):
+ self.log_function = log_function
+
+ async def on_chain_start(
+ self,
+ serialized: dict[str, Any],
+ inputs: dict[str, Any],
+ *,
+ run_id: UUID,
+ parent_run_id: UUID | None = None,
+ tags: list[str] | None = None,
+ metadata: dict[str, Any] | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if self.log_function is None:
+ return
+ self.log_function(
+ {
+ "type": "chain_start",
+ "serialized": serialized,
+ "inputs": inputs,
+ "run_id": run_id,
+ "parent_run_id": parent_run_id,
+ "tags": tags,
+ "metadata": metadata,
+ **kwargs,
+ },
+ name="Chain Start",
+ )
+
+ async def on_tool_start(
+ self,
+ serialized: dict[str, Any],
+ input_str: str,
+ *,
+ run_id: UUID,
+ parent_run_id: UUID | None = None,
+ tags: list[str] | None = None,
+ metadata: dict[str, Any] | None = None,
+ inputs: dict[str, Any] | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if self.log_function is None:
+ return
+ self.log_function(
+ {
+ "type": "tool_start",
+ "serialized": serialized,
+ "input_str": input_str,
+ "run_id": run_id,
+ "parent_run_id": parent_run_id,
+ "tags": tags,
+ "metadata": metadata,
+ "inputs": inputs,
+ **kwargs,
+ },
+ name="Tool Start",
+ )
+
+ async def on_tool_end(self, output: Any, *, run_id: UUID, parent_run_id: UUID | None = None, **kwargs: Any) -> None:
+ if self.log_function is None:
+ return
+ self.log_function(
+ {
+ "type": "tool_end",
+ "output": output,
+ "run_id": run_id,
+ "parent_run_id": parent_run_id,
+ **kwargs,
+ },
+ name="Tool End",
+ )
+
+ async def on_agent_action(
+ self,
+ action: AgentAction,
+ *,
+ run_id: UUID,
+ parent_run_id: UUID | None = None,
+ tags: list[str] | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if self.log_function is None:
+ return
+ self.log_function(
+ {
+ "type": "agent_action",
+ "action": action,
+ "run_id": run_id,
+ "parent_run_id": parent_run_id,
+ "tags": tags,
+ **kwargs,
+ },
+ name="Agent Action",
+ )
+
+ async def on_agent_finish(
+ self,
+ finish: AgentFinish,
+ *,
+ run_id: UUID,
+ parent_run_id: UUID | None = None,
+ tags: list[str] | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if self.log_function is None:
+ return
+ self.log_function(
+ {
+ "type": "agent_finish",
+ "finish": finish,
+ "run_id": run_id,
+ "parent_run_id": parent_run_id,
+ "tags": tags,
+ **kwargs,
+ },
+ name="Agent Finish",
+ )
diff --git a/langflow/src/backend/base/langflow/base/agents/context.py b/langflow/src/backend/base/langflow/base/agents/context.py
new file mode 100644
index 0000000..8e4961e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/context.py
@@ -0,0 +1,109 @@
+from datetime import datetime, timezone
+from typing import Any
+
+from langchain_core.language_models import BaseLanguageModel, BaseLLM
+from langchain_core.language_models.chat_models import BaseChatModel
+from pydantic import BaseModel, Field, field_validator, model_serializer
+
+from langflow.field_typing import LanguageModel
+from langflow.schema.data import Data
+
+
+class AgentContext(BaseModel):
+ tools: dict[str, Any]
+ llm: Any
+ context: str = ""
+ iteration: int = 0
+ max_iterations: int = 5
+ thought: str = ""
+ last_action: Any = None
+ last_action_result: Any = None
+ final_answer: Any = ""
+ context_history: list[tuple[str, str, str]] = Field(default_factory=list)
+
+ @model_serializer(mode="plain")
+ def serialize_agent_context(self):
+ serliazed_llm = self.llm.to_json() if hasattr(self.llm, "to_json") else str(self.llm)
+ serliazed_tools = {k: v.to_json() if hasattr(v, "to_json") else str(v) for k, v in self.tools.items()}
+ return {
+ "tools": serliazed_tools,
+ "llm": serliazed_llm,
+ "context": self.context,
+ "iteration": self.iteration,
+ "max_iterations": self.max_iterations,
+ "thought": self.thought,
+ "last_action": self.last_action.to_json()
+ if hasattr(self.last_action, "to_json")
+ else str(self.last_action),
+ "action_result": self.last_action_result.to_json()
+ if hasattr(self.last_action_result, "to_json")
+ else str(self.last_action_result),
+ "final_answer": self.final_answer,
+ "context_history": self.context_history,
+ }
+
+ @field_validator("llm", mode="before")
+ @classmethod
+ def validate_llm(cls, v) -> LanguageModel:
+ if not isinstance(v, BaseLLM | BaseChatModel | BaseLanguageModel):
+ msg = "llm must be an instance of LanguageModel"
+ raise TypeError(msg)
+ return v
+
+ def to_data_repr(self):
+ data_objs = []
+ for name, val, time_str in self.context_history:
+ content = val.content if hasattr(val, "content") else val
+ data_objs.append(Data(name=name, value=content, timestamp=time_str))
+
+ sorted_data_objs = sorted(data_objs, key=lambda x: datetime.fromisoformat(x.timestamp), reverse=True)
+
+ sorted_data_objs.append(
+ Data(
+ name="Formatted Context",
+ value=self.get_full_context(),
+ )
+ )
+ return sorted_data_objs
+
+ def _build_tools_context(self):
+ tool_context = ""
+ for tool_name, tool_obj in self.tools.items():
+ tool_context += f"{tool_name}: {tool_obj.description}\n"
+ return tool_context
+
+ def _build_init_context(self):
+ return f"""
+{self.context}
+
+"""
+
+ def model_post_init(self, _context: Any) -> None:
+ if hasattr(self.llm, "bind_tools"):
+ self.llm = self.llm.bind_tools(self.tools.values())
+ if self.context:
+ self.update_context("Initial Context", self.context)
+
+ def update_context(self, key: str, value: str):
+ self.context_history.insert(0, (key, value, datetime.now(tz=timezone.utc).astimezone().isoformat()))
+
+ def _serialize_context_history_tuple(self, context_history_tuple: tuple[str, str, str]) -> str:
+ name, value, _ = context_history_tuple
+ if hasattr(value, "content"):
+ value = value.content
+ elif hasattr(value, "log"):
+ value = value.log
+ return f"{name}: {value}"
+
+ def get_full_context(self) -> str:
+ context_history_reversed = self.context_history[::-1]
+ context_formatted = "\n".join(
+ [
+ self._serialize_context_history_tuple(context_history_tuple)
+ for context_history_tuple in context_history_reversed
+ ]
+ )
+ return f"""
+Context:
+{context_formatted}
+"""
diff --git a/langflow/src/backend/base/langflow/base/agents/crewai/__init__.py b/langflow/src/backend/base/langflow/base/agents/crewai/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/agents/crewai/crew.py b/langflow/src/backend/base/langflow/base/agents/crewai/crew.py
new file mode 100644
index 0000000..7e97ce5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/crewai/crew.py
@@ -0,0 +1,211 @@
+from collections.abc import Callable
+from typing import Any, cast
+
+import litellm
+from crewai import LLM, Agent, Crew, Process, Task
+from crewai.task import TaskOutput
+from crewai.tools.base_tool import Tool
+from langchain_core.agents import AgentAction, AgentFinish
+from pydantic import SecretStr
+
+from langflow.custom import Component
+from langflow.inputs.inputs import HandleInput, InputTypes
+from langflow.io import BoolInput, IntInput, Output
+from langflow.schema.data import Data
+from langflow.schema.message import Message
+from langflow.utils.constants import MESSAGE_SENDER_AI
+
+
+def _find_api_key(model):
+ """Attempts to find the API key attribute for a LangChain LLM model instance using partial matching.
+
+ Args:
+ model: LangChain LLM model instance.
+
+ Returns:
+ The API key if found, otherwise None.
+ """
+ # Define the possible API key attribute patterns
+ key_patterns = ["key", "token"]
+
+ # Iterate over the model attributes
+ for attr in dir(model):
+ attr_lower = attr.lower()
+
+ # Check if the attribute name contains any of the key patterns
+ if any(pattern in attr_lower for pattern in key_patterns):
+ value = getattr(model, attr, None)
+
+ # Check if the value is a non-empty string
+ if isinstance(value, str):
+ return value
+ if isinstance(value, SecretStr):
+ return value.get_secret_value()
+
+ return None
+
+
+def convert_llm(llm: Any, excluded_keys=None) -> LLM:
+ """Converts a LangChain LLM object to a CrewAI-compatible LLM object.
+
+ Args:
+ llm: A LangChain LLM object.
+ excluded_keys: A set of keys to exclude from the conversion.
+
+ Returns:
+ A CrewAI-compatible LLM object
+ """
+ if not llm:
+ return None
+
+ # Check if this is already an LLM object
+ if isinstance(llm, LLM):
+ return llm
+
+ # Check if we should use model_name model, or something else
+ if hasattr(llm, "model_name") and llm.model_name:
+ model_name = llm.model_name
+ elif hasattr(llm, "model") and llm.model:
+ model_name = llm.model
+ elif hasattr(llm, "deployment_name") and llm.deployment_name:
+ model_name = llm.deployment_name
+ else:
+ msg = "Could not find model name in the LLM object"
+ raise ValueError(msg)
+
+ # Normalize to the LLM model name
+ # Remove langchain_ prefix if present
+ provider = llm.get_lc_namespace()[0]
+ api_base = None
+ if provider.startswith("langchain_"):
+ provider = provider[10:]
+ model_name = f"{provider}/{model_name}"
+ elif hasattr(llm, "azure_endpoint"):
+ api_base = llm.azure_endpoint
+ model_name = f"azure/{model_name}"
+
+ # Retrieve the API Key from the LLM
+ if excluded_keys is None:
+ excluded_keys = {"model", "model_name", "_type", "api_key", "azure_deployment"}
+
+ # Find the API key in the LLM
+ api_key = _find_api_key(llm)
+
+ # Convert Langchain LLM to CrewAI-compatible LLM object
+ return LLM(
+ model=model_name,
+ api_key=api_key,
+ api_base=api_base,
+ **{k: v for k, v in llm.dict().items() if k not in excluded_keys},
+ )
+
+
+def convert_tools(tools):
+ """Converts LangChain tools to CrewAI-compatible tools.
+
+ Args:
+ tools: A LangChain tools list.
+
+ Returns:
+ A CrewAI-compatible tools list.
+ """
+ if not tools:
+ return []
+
+ return [Tool.from_langchain(tool) for tool in tools]
+
+
+class BaseCrewComponent(Component):
+ description: str = (
+ "Represents a group of agents, defining how they should collaborate and the tasks they should perform."
+ )
+ icon = "CrewAI"
+
+ _base_inputs: list[InputTypes] = [
+ IntInput(name="verbose", display_name="Verbose", value=0, advanced=True),
+ BoolInput(name="memory", display_name="Memory", value=False, advanced=True),
+ BoolInput(name="use_cache", display_name="Cache", value=True, advanced=True),
+ IntInput(name="max_rpm", display_name="Max RPM", value=100, advanced=True),
+ BoolInput(name="share_crew", display_name="Share Crew", value=False, advanced=True),
+ HandleInput(
+ name="function_calling_llm",
+ display_name="Function Calling LLM",
+ input_types=["LanguageModel"],
+ info="Turns the ReAct CrewAI agent into a function-calling agent",
+ required=False,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+
+ # Model properties to exclude when creating a CrewAI LLM object
+ manager_llm: LLM | None
+
+ def task_is_valid(self, task_data: Data, crew_type: Process) -> Task:
+ return "task_type" in task_data and task_data.task_type == crew_type
+
+ def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
+ # Allow passing a custom list of agents
+ if not agents_list:
+ agents_list = self.agents or []
+
+ # Set all the agents llm attribute to the crewai llm
+ for agent in agents_list:
+ # Convert Agent LLM and Tools to proper format
+ agent.llm = convert_llm(agent.llm)
+ agent.tools = convert_tools(agent.tools)
+
+ return self.tasks, agents_list
+
+ def get_manager_llm(self) -> LLM | None:
+ if not self.manager_llm:
+ return None
+
+ self.manager_llm = convert_llm(self.manager_llm)
+
+ return self.manager_llm
+
+ def build_crew(self) -> Crew:
+ msg = "build_crew must be implemented in subclasses"
+ raise NotImplementedError(msg)
+
+ def get_task_callback(
+ self,
+ ) -> Callable:
+ def task_callback(task_output: TaskOutput) -> None:
+ vertex_id = self._vertex.id if self._vertex else self.display_name or self.__class__.__name__
+ self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}")
+
+ return task_callback
+
+ def get_step_callback(
+ self,
+ ) -> Callable:
+ def step_callback(agent_output: AgentFinish | list[tuple[AgentAction, str]]) -> None:
+ id_ = self._vertex.id if self._vertex else self.display_name
+ if isinstance(agent_output, AgentFinish):
+ messages = agent_output.messages
+ self.log(cast("dict", messages[0].to_json()), name=f"Finish (Agent: {id_})")
+ elif isinstance(agent_output, list):
+ messages_dict_ = {f"Action {i}": action.messages for i, (action, _) in enumerate(agent_output)}
+ # Serialize the messages with to_json() to avoid issues with circular references
+ serializable_dict = {k: [m.to_json() for m in v] for k, v in messages_dict_.items()}
+ messages_dict = {k: v[0] if len(v) == 1 else v for k, v in serializable_dict.items()}
+ self.log(messages_dict, name=f"Step (Agent: {id_})")
+
+ return step_callback
+
+ async def build_output(self) -> Message:
+ try:
+ crew = self.build_crew()
+ result = await crew.kickoff_async()
+ message = Message(text=result.raw, sender=MESSAGE_SENDER_AI)
+ except litellm.exceptions.BadRequestError as e:
+ raise ValueError(e) from e
+
+ self.status = message
+
+ return message
diff --git a/langflow/src/backend/base/langflow/base/agents/crewai/tasks.py b/langflow/src/backend/base/langflow/base/agents/crewai/tasks.py
new file mode 100644
index 0000000..b2f5a5a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/crewai/tasks.py
@@ -0,0 +1,9 @@
+from crewai import Task
+
+
+class SequentialTask(Task):
+ pass
+
+
+class HierarchicalTask(Task):
+ pass
diff --git a/langflow/src/backend/base/langflow/base/agents/default_prompts.py b/langflow/src/backend/base/langflow/base/agents/default_prompts.py
new file mode 100644
index 0000000..49d184a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/default_prompts.py
@@ -0,0 +1,23 @@
+XML_AGENT_PROMPT = """You are a helpful assistant. Help the user answer any questions.
+
+ You have access to the following tools:
+
+ {tools}
+
+ In order to use a tool, you can use and tags. You will then get back a response in the form
+ For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:
+
+ search weather in SF
+ 64 degrees
+
+ When you are done, respond with a final answer between . For example:
+
+ The weather in SF is 64 degrees
+
+ Begin!
+
+ Previous Conversation:
+ {chat_history}
+
+ Question: {input}
+ {agent_scratchpad}""" # noqa: E501
diff --git a/langflow/src/backend/base/langflow/base/agents/errors.py b/langflow/src/backend/base/langflow/base/agents/errors.py
new file mode 100644
index 0000000..fc43b19
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/errors.py
@@ -0,0 +1,15 @@
+from anthropic import BadRequestError as AnthropicBadRequestError
+from cohere import BadRequestError as CohereBadRequestError
+from httpx import HTTPStatusError
+
+from langflow.schema.message import Message
+
+
+class CustomBadRequestError(AnthropicBadRequestError, CohereBadRequestError, HTTPStatusError):
+ def __init__(self, agent_message: Message | None, message: str):
+ super().__init__(message)
+ self.message = message
+ self.agent_message = agent_message
+
+ def __str__(self):
+ return f"{self.message}"
diff --git a/langflow/src/backend/base/langflow/base/agents/events.py b/langflow/src/backend/base/langflow/base/agents/events.py
new file mode 100644
index 0000000..4d60e0d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/events.py
@@ -0,0 +1,285 @@
+# Add helper functions for each event type
+from collections.abc import AsyncIterator
+from time import perf_counter
+from typing import Any, Protocol
+
+from langchain_core.agents import AgentFinish
+from langchain_core.messages import BaseMessage
+from typing_extensions import TypedDict
+
+from langflow.schema.content_block import ContentBlock
+from langflow.schema.content_types import TextContent, ToolContent
+from langflow.schema.log import SendMessageFunctionType
+from langflow.schema.message import Message
+
+
+class ExceptionWithMessageError(Exception):
+ def __init__(self, agent_message: Message, message: str):
+ self.agent_message = agent_message
+ super().__init__(message)
+ self.message = message
+
+ def __str__(self):
+ return (
+ f"Agent message: {self.agent_message.text} \nError: {self.message}."
+ if self.agent_message.error or self.agent_message.text
+ else f"{self.message}."
+ )
+
+
+class InputDict(TypedDict):
+ input: str
+ chat_history: list[BaseMessage]
+
+
+def _build_agent_input_text_content(agent_input_dict: InputDict) -> str:
+ final_input = agent_input_dict.get("input", "")
+ return f"**Input**: {final_input}"
+
+
+def _calculate_duration(start_time: float) -> int:
+ """Calculate duration in milliseconds from start time to now."""
+ # Handle the calculation
+ current_time = perf_counter()
+ if isinstance(start_time, int):
+ # If we got an integer, treat it as milliseconds
+ duration = current_time - (start_time / 1000)
+ result = int(duration * 1000)
+ else:
+ # If we got a float, treat it as perf_counter time
+ result = int((current_time - start_time) * 1000)
+
+ return result
+
+
+async def handle_on_chain_start(
+ event: dict[str, Any], agent_message: Message, send_message_method: SendMessageFunctionType, start_time: float
+) -> tuple[Message, float]:
+ # Create content blocks if they don't exist
+ if not agent_message.content_blocks:
+ agent_message.content_blocks = [ContentBlock(title="Agent Steps", contents=[])]
+
+ if event["data"].get("input"):
+ input_data = event["data"].get("input")
+ if isinstance(input_data, dict) and "input" in input_data:
+ # Cast the input_data to InputDict
+ input_dict: InputDict = {
+ "input": str(input_data.get("input", "")),
+ "chat_history": input_data.get("chat_history", []),
+ }
+ text_content = TextContent(
+ type="text",
+ text=_build_agent_input_text_content(input_dict),
+ duration=_calculate_duration(start_time),
+ header={"title": "Input", "icon": "MessageSquare"},
+ )
+ agent_message.content_blocks[0].contents.append(text_content)
+ agent_message = await send_message_method(message=agent_message)
+ start_time = perf_counter()
+ return agent_message, start_time
+
+
+def _extract_output_text(output: str | list) -> str:
+ if isinstance(output, str):
+ text = output
+ elif isinstance(output, list) and len(output) == 1 and isinstance(output[0], dict) and "text" in output[0]:
+ text = output[0]["text"]
+ else:
+ msg = f"Output is not a string or list of dictionaries with 'text' key: {output}"
+ raise ValueError(msg)
+ return text
+
+
+async def handle_on_chain_end(
+ event: dict[str, Any], agent_message: Message, send_message_method: SendMessageFunctionType, start_time: float
+) -> tuple[Message, float]:
+ data_output = event["data"].get("output")
+ if data_output and isinstance(data_output, AgentFinish) and data_output.return_values.get("output"):
+ output = data_output.return_values.get("output")
+
+ agent_message.text = _extract_output_text(output)
+ agent_message.properties.state = "complete"
+ # Add duration to the last content if it exists
+ if agent_message.content_blocks:
+ duration = _calculate_duration(start_time)
+ text_content = TextContent(
+ type="text",
+ text=agent_message.text,
+ duration=duration,
+ header={"title": "Output", "icon": "MessageSquare"},
+ )
+ agent_message.content_blocks[0].contents.append(text_content)
+ agent_message = await send_message_method(message=agent_message)
+ start_time = perf_counter()
+ return agent_message, start_time
+
+
+async def handle_on_tool_start(
+ event: dict[str, Any],
+ agent_message: Message,
+ tool_blocks_map: dict[str, ToolContent],
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+) -> tuple[Message, float]:
+ tool_name = event["name"]
+ tool_input = event["data"].get("input")
+ run_id = event.get("run_id", "")
+ tool_key = f"{tool_name}_{run_id}"
+
+ # Create content blocks if they don't exist
+ if not agent_message.content_blocks:
+ agent_message.content_blocks = [ContentBlock(title="Agent Steps", contents=[])]
+
+ duration = _calculate_duration(start_time)
+ new_start_time = perf_counter() # Get new start time for next operation
+
+ # Create new tool content with the input exactly as received
+ tool_content = ToolContent(
+ type="tool_use",
+ name=tool_name,
+ input=tool_input,
+ output=None,
+ error=None,
+ header={"title": f"Accessing **{tool_name}**", "icon": "Hammer"},
+ duration=duration, # Store the actual duration
+ )
+
+ # Store in map and append to message
+ tool_blocks_map[tool_key] = tool_content
+ agent_message.content_blocks[0].contents.append(tool_content)
+
+ agent_message = await send_message_method(message=agent_message)
+ if agent_message.content_blocks and agent_message.content_blocks[0].contents:
+ tool_blocks_map[tool_key] = agent_message.content_blocks[0].contents[-1]
+ return agent_message, new_start_time
+
+
+async def handle_on_tool_end(
+ event: dict[str, Any],
+ agent_message: Message,
+ tool_blocks_map: dict[str, ToolContent],
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+) -> tuple[Message, float]:
+ run_id = event.get("run_id", "")
+ tool_name = event.get("name", "")
+ tool_key = f"{tool_name}_{run_id}"
+ tool_content = tool_blocks_map.get(tool_key)
+
+ if tool_content and isinstance(tool_content, ToolContent):
+ tool_content.output = event["data"].get("output")
+ duration = _calculate_duration(start_time)
+ tool_content.duration = duration
+ tool_content.header = {"title": f"Executed **{tool_content.name}**", "icon": "Hammer"}
+
+ agent_message = await send_message_method(message=agent_message)
+ new_start_time = perf_counter() # Get new start time for next operation
+ return agent_message, new_start_time
+ return agent_message, start_time
+
+
+async def handle_on_tool_error(
+ event: dict[str, Any],
+ agent_message: Message,
+ tool_blocks_map: dict[str, ToolContent],
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+) -> tuple[Message, float]:
+ run_id = event.get("run_id", "")
+ tool_name = event.get("name", "")
+ tool_key = f"{tool_name}_{run_id}"
+ tool_content = tool_blocks_map.get(tool_key)
+
+ if tool_content and isinstance(tool_content, ToolContent):
+ tool_content.error = event["data"].get("error", "Unknown error")
+ tool_content.duration = _calculate_duration(start_time)
+ tool_content.header = {"title": f"Error using **{tool_content.name}**", "icon": "Hammer"}
+ agent_message = await send_message_method(message=agent_message)
+ start_time = perf_counter()
+ return agent_message, start_time
+
+
+async def handle_on_chain_stream(
+ event: dict[str, Any],
+ agent_message: Message,
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+) -> tuple[Message, float]:
+ data_chunk = event["data"].get("chunk", {})
+ if isinstance(data_chunk, dict) and data_chunk.get("output"):
+ output = data_chunk.get("output")
+ if output and isinstance(output, str | list):
+ agent_message.text = _extract_output_text(output)
+ agent_message.properties.state = "complete"
+ agent_message = await send_message_method(message=agent_message)
+ start_time = perf_counter()
+ return agent_message, start_time
+
+
+class ToolEventHandler(Protocol):
+ async def __call__(
+ self,
+ event: dict[str, Any],
+ agent_message: Message,
+ tool_blocks_map: dict[str, ContentBlock],
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+ ) -> tuple[Message, float]: ...
+
+
+class ChainEventHandler(Protocol):
+ async def __call__(
+ self,
+ event: dict[str, Any],
+ agent_message: Message,
+ send_message_method: SendMessageFunctionType,
+ start_time: float,
+ ) -> tuple[Message, float]: ...
+
+
+EventHandler = ToolEventHandler | ChainEventHandler
+
+# Define separate mappings of event types to their respective handler functions
+CHAIN_EVENT_HANDLERS: dict[str, ChainEventHandler] = {
+ "on_chain_start": handle_on_chain_start,
+ "on_chain_end": handle_on_chain_end,
+ "on_chain_stream": handle_on_chain_stream,
+}
+
+TOOL_EVENT_HANDLERS: dict[str, ToolEventHandler] = {
+ "on_tool_start": handle_on_tool_start,
+ "on_tool_end": handle_on_tool_end,
+ "on_tool_error": handle_on_tool_error,
+}
+
+
+async def process_agent_events(
+ agent_executor: AsyncIterator[dict[str, Any]],
+ agent_message: Message,
+ send_message_method: SendMessageFunctionType,
+) -> Message:
+ """Process agent events and return the final output."""
+ if isinstance(agent_message.properties, dict):
+ agent_message.properties.update({"icon": "Bot", "state": "partial"})
+ else:
+ agent_message.properties.icon = "Bot"
+ agent_message.properties.state = "partial"
+ # Store the initial message
+ agent_message = await send_message_method(message=agent_message)
+ try:
+ # Create a mapping of run_ids to tool contents
+ tool_blocks_map: dict[str, ToolContent] = {}
+ start_time = perf_counter()
+ async for event in agent_executor:
+ if event["event"] in TOOL_EVENT_HANDLERS:
+ tool_handler = TOOL_EVENT_HANDLERS[event["event"]]
+ agent_message, start_time = await tool_handler(
+ event, agent_message, tool_blocks_map, send_message_method, start_time
+ )
+ elif event["event"] in CHAIN_EVENT_HANDLERS:
+ chain_handler = CHAIN_EVENT_HANDLERS[event["event"]]
+ agent_message, start_time = await chain_handler(event, agent_message, send_message_method, start_time)
+ agent_message.properties.state = "complete"
+ except Exception as e:
+ raise ExceptionWithMessageError(agent_message, str(e)) from e
+ return await Message.create(**agent_message.model_dump())
diff --git a/langflow/src/backend/base/langflow/base/agents/utils.py b/langflow/src/backend/base/langflow/base/agents/utils.py
new file mode 100644
index 0000000..29c0e52
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/agents/utils.py
@@ -0,0 +1,143 @@
+from collections.abc import Callable, Sequence
+from typing import Any
+
+from langchain.agents import (
+ create_json_chat_agent,
+ create_openai_tools_agent,
+ create_tool_calling_agent,
+ create_xml_agent,
+)
+from langchain.agents.xml.base import render_text_description
+from langchain_core.language_models import BaseLanguageModel
+from langchain_core.messages import BaseMessage
+from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate
+from langchain_core.tools import BaseTool
+from pydantic import BaseModel
+
+from langflow.schema import Data
+
+from .default_prompts import XML_AGENT_PROMPT
+
+
+class AgentSpec(BaseModel):
+ func: Callable[
+ [
+ BaseLanguageModel,
+ Sequence[BaseTool],
+ BasePromptTemplate | ChatPromptTemplate,
+ Callable[[list[BaseTool]], str] | None,
+ bool | list[str] | None,
+ ],
+ Any,
+ ]
+ prompt: Any | None = None
+ fields: list[str]
+ hub_repo: str | None = None
+
+
+def data_to_messages(data: list[Data]) -> list[BaseMessage]:
+ """Convert a list of data to a list of messages.
+
+ Args:
+ data (List[Data]): The data to convert.
+
+ Returns:
+ List[Message]: The data as messages.
+ """
+ return [value.to_lc_message() for value in data]
+
+
+def validate_and_create_xml_agent(
+ llm: BaseLanguageModel,
+ tools: Sequence[BaseTool],
+ prompt: BasePromptTemplate,
+ tools_renderer: Callable[[list[BaseTool]], str] = render_text_description,
+ *,
+ stop_sequence: bool | list[str] = True,
+):
+ return create_xml_agent(
+ llm=llm,
+ tools=tools,
+ prompt=prompt,
+ tools_renderer=tools_renderer,
+ stop_sequence=stop_sequence,
+ )
+
+
+def validate_and_create_openai_tools_agent(
+ llm: BaseLanguageModel,
+ tools: Sequence[BaseTool],
+ prompt: ChatPromptTemplate,
+ _tools_renderer: Callable[[list[BaseTool]], str] = render_text_description,
+ *,
+ _stop_sequence: bool | list[str] = True,
+):
+ return create_openai_tools_agent(
+ llm=llm,
+ tools=tools,
+ prompt=prompt,
+ )
+
+
+def validate_and_create_tool_calling_agent(
+ llm: BaseLanguageModel,
+ tools: Sequence[BaseTool],
+ prompt: ChatPromptTemplate,
+ _tools_renderer: Callable[[list[BaseTool]], str] = render_text_description,
+ *,
+ _stop_sequence: bool | list[str] = True,
+):
+ return create_tool_calling_agent(
+ llm=llm,
+ tools=tools,
+ prompt=prompt,
+ )
+
+
+def validate_and_create_json_chat_agent(
+ llm: BaseLanguageModel,
+ tools: Sequence[BaseTool],
+ prompt: ChatPromptTemplate,
+ tools_renderer: Callable[[list[BaseTool]], str] = render_text_description,
+ *,
+ stop_sequence: bool | list[str] = True,
+):
+ return create_json_chat_agent(
+ llm=llm,
+ tools=tools,
+ prompt=prompt,
+ tools_renderer=tools_renderer,
+ stop_sequence=stop_sequence,
+ )
+
+
+AGENTS: dict[str, AgentSpec] = {
+ "Tool Calling Agent": AgentSpec(
+ func=validate_and_create_tool_calling_agent,
+ prompt=None,
+ fields=["llm", "tools", "prompt"],
+ hub_repo=None,
+ ),
+ "XML Agent": AgentSpec(
+ func=validate_and_create_xml_agent,
+ prompt=XML_AGENT_PROMPT, # Ensure XML_AGENT_PROMPT is properly defined and typed.
+ fields=["llm", "tools", "prompt", "tools_renderer", "stop_sequence"],
+ hub_repo="hwchase17/xml-agent-convo",
+ ),
+ "OpenAI Tools Agent": AgentSpec(
+ func=validate_and_create_openai_tools_agent,
+ prompt=None,
+ fields=["llm", "tools", "prompt"],
+ hub_repo=None,
+ ),
+ "JSON Chat Agent": AgentSpec(
+ func=validate_and_create_json_chat_agent,
+ prompt=None,
+ fields=["llm", "tools", "prompt", "tools_renderer", "stop_sequence"],
+ hub_repo="hwchase17/react-chat-json",
+ ),
+}
+
+
+def get_agents_list():
+ return list(AGENTS.keys())
diff --git a/langflow/src/backend/base/langflow/base/astra_assistants/__init__.py b/langflow/src/backend/base/langflow/base/astra_assistants/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/astra_assistants/util.py b/langflow/src/backend/base/langflow/base/astra_assistants/util.py
new file mode 100644
index 0000000..2cde2d6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/astra_assistants/util.py
@@ -0,0 +1,164 @@
+import importlib
+import inspect
+import json
+import os
+import pkgutil
+import threading
+import uuid
+from json.decoder import JSONDecodeError
+from pathlib import Path
+from typing import Any
+
+import astra_assistants.tools as astra_assistants_tools
+import requests
+from astra_assistants import OpenAIWithDefaultKey, patch
+from astra_assistants.tools.tool_interface import ToolInterface
+from langchain_core.tools import BaseTool
+from pydantic import BaseModel
+from requests.exceptions import RequestException
+
+from langflow.components.tools.mcp_stdio import create_input_schema_from_json_schema
+from langflow.services.cache.utils import CacheMiss
+
+client_lock = threading.Lock()
+client = None
+
+
+def get_patched_openai_client(shared_component_cache):
+ os.environ["ASTRA_ASSISTANTS_QUIET"] = "true"
+ client = shared_component_cache.get("client")
+ if isinstance(client, CacheMiss):
+ client = patch(OpenAIWithDefaultKey())
+ shared_component_cache.set("client", client)
+ return client
+
+
+url = "https://raw.githubusercontent.com/BerriAI/litellm/refs/heads/main/model_prices_and_context_window.json"
+try:
+ response = requests.get(url, timeout=10)
+ response.raise_for_status()
+ data = json.loads(response.text)
+except RequestException:
+ data = {}
+except JSONDecodeError:
+ data = {}
+
+# Extract the model names into a Python list
+litellm_model_names = [model for model in data if model != "sample_spec"]
+
+
+# To store the class names that extend ToolInterface
+tool_names = []
+tools_and_names = {}
+
+
+def tools_from_package(your_package) -> None:
+ # Iterate over all modules in the package
+ package_name = your_package.__name__
+ for module_info in pkgutil.iter_modules(your_package.__path__):
+ module_name = f"{package_name}.{module_info.name}"
+
+ # Dynamically import the module
+ module = importlib.import_module(module_name)
+
+ # Iterate over all members of the module
+ for name, obj in inspect.getmembers(module, inspect.isclass):
+ # Check if the class is a subclass of ToolInterface and is not ToolInterface itself
+ if issubclass(obj, ToolInterface) and obj is not ToolInterface:
+ tool_names.append(name)
+ tools_and_names[name] = obj
+
+
+tools_from_package(astra_assistants_tools)
+
+
+def wrap_base_tool_as_tool_interface(base_tool: BaseTool) -> ToolInterface:
+ """wrap_Base_tool_ass_tool_interface.
+
+ Wrap a BaseTool instance in a new class implementing ToolInterface,
+ building a dynamic Pydantic model from its args_schema (if any).
+ We only call `args_schema()` if it's truly a function/method,
+ avoiding accidental calls on a Pydantic model class (which is also callable).
+ """
+ raw_args_schema = getattr(base_tool, "args_schema", None)
+
+ # --- 1) Distinguish between a function/method vs. class/dict/None ---
+ if inspect.isfunction(raw_args_schema) or inspect.ismethod(raw_args_schema):
+ # It's actually a function -> call it once to get a class or dict
+ raw_args_schema = raw_args_schema()
+ # Otherwise, if it's a class or dict, do nothing here
+
+ # Now `raw_args_schema` might be:
+ # - A Pydantic model class (subclass of BaseModel)
+ # - A dict (JSON schema)
+ # - None
+ # - Something unexpected => raise error
+
+ # --- 2) Convert the schema or model class to a JSON schema dict ---
+ if raw_args_schema is None:
+ # No schema => minimal
+ schema_dict = {"type": "object", "properties": {}}
+
+ elif isinstance(raw_args_schema, dict):
+ # Already a JSON schema
+ schema_dict = raw_args_schema
+
+ elif inspect.isclass(raw_args_schema) and issubclass(raw_args_schema, BaseModel):
+ # It's a Pydantic model class -> convert to JSON schema
+ schema_dict = raw_args_schema.schema()
+
+ else:
+ msg = f"args_schema must be a Pydantic model class, a JSON schema dict, or None. Got: {raw_args_schema!r}"
+ raise TypeError(msg)
+
+ # --- 3) Build our dynamic Pydantic model from the JSON schema ---
+ InputSchema: type[BaseModel] = create_input_schema_from_json_schema(schema_dict) # noqa: N806
+
+ # --- 4) Define a wrapper class that uses composition ---
+ class WrappedDynamicTool(ToolInterface):
+ """WrappedDynamicTool.
+
+ Uses composition to delegate logic to the original base_tool,
+ but sets `call(..., arguments: InputSchema)` so we have a real model.
+ """
+
+ def __init__(self, tool: BaseTool):
+ self._tool = tool
+
+ def call(self, arguments: InputSchema) -> dict: # type: ignore # noqa: PGH003
+ output = self._tool.invoke(arguments.dict()) # type: ignore # noqa: PGH003
+ result = ""
+ if "error" in output[0].data:
+ result = output[0].data["error"]
+ elif "result" in output[0].data:
+ result = output[0].data["result"]
+ return {"cache_id": str(uuid.uuid4()), "output": result}
+
+ def run(self, tool_input: Any) -> str:
+ return self._tool.run(tool_input)
+
+ def name(self) -> str:
+ """Return the base tool's name if it exists."""
+ if hasattr(self._tool, "name"):
+ return str(self._tool.name)
+ return super().name()
+
+ def to_function(self):
+ """Incorporate the base tool's description if present."""
+ params = InputSchema.schema()
+ description = getattr(self._tool, "description", "A dynamically wrapped tool")
+ return {
+ "type": "function",
+ "function": {"name": self.name(), "description": description, "parameters": params},
+ }
+
+ # Return an instance of our newly minted class
+ return WrappedDynamicTool(base_tool)
+
+
+def sync_upload(file_path, client):
+ with Path(file_path).open("rb") as sync_file_handle:
+ return client.files.create(
+ file=sync_file_handle, # Pass the sync file handle
+ purpose="assistants",
+ )
diff --git a/langflow/src/backend/base/langflow/base/chains/__init__.py b/langflow/src/backend/base/langflow/base/chains/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/chains/model.py b/langflow/src/backend/base/langflow/base/chains/model.py
new file mode 100644
index 0000000..4f9d190
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/chains/model.py
@@ -0,0 +1,19 @@
+from langflow.custom import Component
+from langflow.template import Output
+
+
+class LCChainComponent(Component):
+ trace_type = "chain"
+
+ outputs = [Output(display_name="Text", name="text", method="invoke_chain")]
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["invoke_chain"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
diff --git a/langflow/src/backend/base/langflow/base/compressors/__init__.py b/langflow/src/backend/base/langflow/base/compressors/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/compressors/model.py b/langflow/src/backend/base/langflow/base/compressors/model.py
new file mode 100644
index 0000000..fb52e64
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/compressors/model.py
@@ -0,0 +1,60 @@
+from abc import abstractmethod
+
+from langflow.custom import Component
+from langflow.field_typing import BaseDocumentCompressor
+from langflow.io import DataInput, IntInput, MultilineInput
+from langflow.schema import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.template.field.base import Output
+
+
+class LCCompressorComponent(Component):
+ inputs = [
+ MultilineInput(
+ name="search_query",
+ display_name="Search Query",
+ tool_mode=True,
+ ),
+ DataInput(
+ name="search_results",
+ display_name="Search Results",
+ info="Search Results from a Vector Store.",
+ is_list=True,
+ ),
+ IntInput(name="top_n", display_name="Top N", value=3, advanced=True),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Data",
+ name="compressed_documents",
+ method="Compressed Documents",
+ ),
+ Output(
+ display_name="DataFrame",
+ name="compressed_documents_as_dataframe",
+ method="Compressed Documents as DataFrame",
+ ),
+ ]
+
+ @abstractmethod
+ def build_compressor(self) -> BaseDocumentCompressor:
+ """Builds the Base Document Compressor object."""
+ msg = "build_compressor method must be implemented."
+ raise NotImplementedError(msg)
+
+ async def compress_documents(self) -> list[Data]:
+ """Compresses the documents retrieved from the vector store."""
+ compressor = self.build_compressor()
+ documents = compressor.compress_documents(
+ query=self.search_query,
+ documents=[passage.to_lc_document() for passage in self.search_results if isinstance(passage, Data)],
+ )
+ data = self.to_data(documents)
+ self.status = data
+ return data
+
+ async def compress_documents_as_dataframe(self) -> DataFrame:
+ """Compresses the documents retrieved from the vector store and returns a pandas DataFrame."""
+ data_objs = await self.compress_documents()
+ return DataFrame(data=data_objs)
diff --git a/langflow/src/backend/base/langflow/base/constants.py b/langflow/src/backend/base/langflow/base/constants.py
new file mode 100644
index 0000000..3e2f2f6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/constants.py
@@ -0,0 +1,43 @@
+"""This module contains constants used in the Langflow base module.
+
+Constants:
+- STREAM_INFO_TEXT: A string representing the information about streaming the response from the model.
+- NODE_FORMAT_ATTRIBUTES: A list of attributes used for formatting nodes.
+- FIELD_FORMAT_ATTRIBUTES: A list of attributes used for formatting fields.
+"""
+
+import orjson
+
+STREAM_INFO_TEXT = "Stream the response from the model. Streaming works only in Chat."
+
+NODE_FORMAT_ATTRIBUTES = [
+ "beta",
+ "legacy",
+ "icon",
+ "output_types",
+ "edited",
+ "metadata",
+ # remove display_name to prevent overwriting the display_name from the latest template
+ # "display_name",
+]
+
+
+FIELD_FORMAT_ATTRIBUTES = [
+ "info",
+ "display_name",
+ "required",
+ "list",
+ "multiline",
+ "combobox",
+ "fileTypes",
+ "password",
+ "input_types",
+ "title_case",
+ "real_time_refresh",
+ "refresh_button",
+ "refresh_button_text",
+ "options",
+ "advanced",
+]
+
+ORJSON_OPTIONS = orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS | orjson.OPT_OMIT_MICROSECONDS
diff --git a/langflow/src/backend/base/langflow/base/curl/__init__.py b/langflow/src/backend/base/langflow/base/curl/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/curl/parse.py b/langflow/src/backend/base/langflow/base/curl/parse.py
new file mode 100644
index 0000000..4a5ccaf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/curl/parse.py
@@ -0,0 +1,184 @@
+r"""This file contains a fix for the implementation of the `uncurl` library, which is available at https://github.com/spulec/uncurl.git.
+
+The `uncurl` library provides a way to parse and convert cURL commands into Python requests.
+However, there are some issues with the original implementation that this file aims to fix.
+
+The `parse_context` function in this file takes a cURL command as input and returns a `ParsedContext` object,
+which contains the parsed information from the cURL command, such as the HTTP method, URL, headers, cookies, etc.
+
+The `normalize_newlines` function is a helper function that replaces the line continuation character ("\")
+followed by a newline with a space.
+
+
+"""
+
+import re
+import shlex
+from collections import OrderedDict
+from http.cookies import SimpleCookie
+from typing import NamedTuple
+
+
+class ParsedArgs(NamedTuple):
+ command: str | None
+ url: str | None
+ data: str | None
+ data_binary: str | None
+ method: str
+ headers: list[str]
+ compressed: bool
+ insecure: bool
+ user: tuple[str, str]
+ include: bool
+ silent: bool
+ proxy: str | None
+ proxy_user: str | None
+ cookies: dict[str, str]
+
+
+class ParsedContext(NamedTuple):
+ method: str
+ url: str
+ data: str | None
+ headers: dict[str, str]
+ cookies: dict[str, str]
+ verify: bool
+ auth: tuple[str, str] | None
+ proxy: dict[str, str] | None
+
+
+def normalize_newlines(multiline_text):
+ return multiline_text.replace(" \\\n", " ")
+
+
+def parse_curl_command(curl_command):
+ tokens = shlex.split(normalize_newlines(curl_command))
+ tokens = [token for token in tokens if token and token != " "] # noqa: S105
+ if tokens and "curl" not in tokens[0]:
+ msg = "Invalid curl command"
+ raise ValueError(msg)
+ args_template = {
+ "command": None,
+ "url": None,
+ "data": None,
+ "data_binary": None,
+ "method": "get",
+ "headers": [],
+ "compressed": False,
+ "insecure": False,
+ "user": (),
+ "include": False,
+ "silent": False,
+ "proxy": None,
+ "proxy_user": None,
+ "cookies": {},
+ }
+ args = args_template.copy()
+ method_on_curl = None
+ i = 0
+ while i < len(tokens):
+ token = tokens[i]
+ if token == "-X": # noqa: S105
+ i += 1
+ args["method"] = tokens[i].lower()
+ method_on_curl = tokens[i].lower()
+ elif token in {"-d", "--data"}:
+ i += 1
+ args["data"] = tokens[i]
+ elif token in {"-b", "--data-binary", "--data-raw"}:
+ i += 1
+ args["data_binary"] = tokens[i]
+ elif token in {"-H", "--header"}:
+ i += 1
+ args["headers"].append(tokens[i])
+ elif token == "--compressed": # noqa: S105
+ args["compressed"] = True
+ elif token in {"-k", "--insecure"}:
+ args["insecure"] = True
+ elif token in {"-u", "--user"}:
+ i += 1
+ args["user"] = tuple(tokens[i].split(":"))
+ elif token in {"-I", "--include"}:
+ args["include"] = True
+ elif token in {"-s", "--silent"}:
+ args["silent"] = True
+ elif token in {"-x", "--proxy"}:
+ i += 1
+ args["proxy"] = tokens[i]
+ elif token in {"-U", "--proxy-user"}:
+ i += 1
+ args["proxy_user"] = tokens[i]
+ elif not token.startswith("-"):
+ if args["command"] is None:
+ args["command"] = token
+ else:
+ args["url"] = token
+ i += 1
+
+ args["method"] = method_on_curl or args["method"]
+
+ return ParsedArgs(**args)
+
+
+def parse_context(curl_command):
+ method = "get"
+ if not curl_command:
+ return ParsedContext(
+ method=method, url="", data=None, headers={}, cookies={}, verify=True, auth=None, proxy=None
+ )
+
+ parsed_args: ParsedArgs = parse_curl_command(curl_command)
+
+ post_data = parsed_args.data or parsed_args.data_binary
+ if post_data:
+ method = "post"
+
+ if parsed_args.method:
+ method = parsed_args.method.lower()
+
+ cookie_dict = OrderedDict()
+ quoted_headers = OrderedDict()
+
+ for curl_header in parsed_args.headers:
+ if curl_header.startswith(":"):
+ occurrence = [m.start() for m in re.finditer(r":", curl_header)]
+ header_key, header_value = curl_header[: occurrence[1]], curl_header[occurrence[1] + 1 :]
+ else:
+ header_key, header_value = curl_header.split(":", 1)
+
+ if header_key.lower().strip("$") == "cookie":
+ cookie = SimpleCookie(bytes(header_value, "ascii").decode("unicode-escape"))
+ for key in cookie:
+ cookie_dict[key] = cookie[key].value
+ else:
+ quoted_headers[header_key] = header_value.strip()
+
+ # add auth
+ user = parsed_args.user
+ if parsed_args.user:
+ user = tuple(user.split(":"))
+
+ # add proxy and its authentication if it's available.
+ proxies = parsed_args.proxy
+ # proxy_auth = parsed_args.proxy_user
+ if parsed_args.proxy and parsed_args.proxy_user:
+ proxies = {
+ "http": f"http://{parsed_args.proxy_user}@{parsed_args.proxy}/",
+ "https": f"http://{parsed_args.proxy_user}@{parsed_args.proxy}/",
+ }
+ elif parsed_args.proxy:
+ proxies = {
+ "http": f"http://{parsed_args.proxy}/",
+ "https": f"http://{parsed_args.proxy}/",
+ }
+
+ return ParsedContext(
+ method=method,
+ url=parsed_args.url,
+ data=post_data,
+ headers=quoted_headers,
+ cookies=cookie_dict,
+ verify=parsed_args.insecure,
+ auth=user,
+ proxy=proxies,
+ )
diff --git a/langflow/src/backend/base/langflow/base/data/__init__.py b/langflow/src/backend/base/langflow/base/data/__init__.py
new file mode 100644
index 0000000..8a92e12
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/data/__init__.py
@@ -0,0 +1,5 @@
+from .base_file import BaseFileComponent
+
+__all__ = [
+ "BaseFileComponent",
+]
diff --git a/langflow/src/backend/base/langflow/base/data/base_file.py b/langflow/src/backend/base/langflow/base/data/base_file.py
new file mode 100644
index 0000000..cd7848e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/data/base_file.py
@@ -0,0 +1,509 @@
+import shutil
+import tarfile
+from abc import ABC, abstractmethod
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from zipfile import ZipFile, is_zipfile
+
+from langflow.custom import Component
+from langflow.io import BoolInput, FileInput, HandleInput, Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class BaseFileComponent(Component, ABC):
+ """Base class for handling file processing components.
+
+ This class provides common functionality for resolving, validating, and
+ processing file paths. Child classes must define valid file extensions
+ and implement the `process_files` method.
+ """
+
+ class BaseFile:
+ """Internal class to represent a file with additional metadata."""
+
+ def __init__(
+ self,
+ data: Data | list[Data],
+ path: Path,
+ *,
+ delete_after_processing: bool = False,
+ silent_errors: bool = False,
+ ):
+ self._data = data if isinstance(data, list) else [data]
+ self.path = path
+ self.delete_after_processing = delete_after_processing
+ self._silent_errors = silent_errors
+
+ @property
+ def data(self) -> list[Data]:
+ return self._data or []
+
+ @data.setter
+ def data(self, value: Data | list[Data]):
+ if isinstance(value, Data):
+ self._data = [value]
+ elif isinstance(value, list) and all(isinstance(item, Data) for item in value):
+ self._data = value
+ else:
+ msg = f"data must be a Data object or a list of Data objects. Got: {type(value)}"
+ if not self._silent_errors:
+ raise ValueError(msg)
+
+ def merge_data(self, new_data: Data | list[Data] | None) -> list[Data]:
+ r"""Generate a new list of Data objects by merging `new_data` into the current `data`.
+
+ Args:
+ new_data (Data | list[Data] | None): The new Data object(s) to merge into each existing Data object.
+ If None, the current `data` is returned unchanged.
+
+ Returns:
+ list[Data]: A new list of Data objects with `new_data` merged.
+ """
+ if new_data is None:
+ return self.data
+
+ if isinstance(new_data, Data):
+ new_data_list = [new_data]
+ elif isinstance(new_data, list) and all(isinstance(item, Data) for item in new_data):
+ new_data_list = new_data
+ else:
+ msg = "new_data must be a Data object, a list of Data objects, or None."
+ if not self._silent_errors:
+ raise ValueError(msg)
+ return self.data
+
+ return [
+ Data(data={**data.data, **new_data_item.data}) for data in self.data for new_data_item in new_data_list
+ ]
+
+ def __str__(self):
+ if len(self.data) == 0:
+ text_preview = ""
+ elif len(self.data) == 1:
+ max_text_length = 50
+ text_preview = self.data.get_text()[:max_text_length]
+ if len(self.data.get_text()) > max_text_length:
+ text_preview += "..."
+ text_preview = f"text_preview='{text_preview}'"
+ else:
+ text_preview = f"{len(self.data)} data objects"
+ return f"BaseFile(path={self.path}, delete_after_processing={self.delete_after_processing}, {text_preview}"
+
+ # Subclasses can override these class variables
+ VALID_EXTENSIONS: list[str] = [] # To be overridden by child classes
+ IGNORE_STARTS_WITH = [".", "__MACOSX"]
+
+ SERVER_FILE_PATH_FIELDNAME = "file_path"
+ SUPPORTED_BUNDLE_EXTENSIONS = ["zip", "tar", "tgz", "bz2", "gz"]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # Dynamically update FileInput to include valid extensions and bundles
+ self._base_inputs[0].file_types = [
+ *self.valid_extensions,
+ *self.SUPPORTED_BUNDLE_EXTENSIONS,
+ ]
+
+ file_types = ", ".join(self.valid_extensions)
+ bundles = ", ".join(self.SUPPORTED_BUNDLE_EXTENSIONS)
+ self._base_inputs[
+ 0
+ ].info = f"Supported file extensions: {file_types}; optionally bundled in file extensions: {bundles}"
+
+ _base_inputs = [
+ FileInput(
+ name="path",
+ display_name="Path",
+ fileTypes=[], # Dynamically set in __init__
+ info="", # Dynamically set in __init__
+ required=False,
+ value="",
+ ),
+ HandleInput(
+ name="file_path",
+ display_name="Server File Path",
+ info=(
+ f"Data object with a '{SERVER_FILE_PATH_FIELDNAME}' property pointing to server file"
+ " or a Message object with a path to the file. Supercedes 'Path' but supports same file types."
+ ),
+ required=False,
+ input_types=["Data", "Message"],
+ is_list=True,
+ advanced=True,
+ ),
+ BoolInput(
+ name="silent_errors",
+ display_name="Silent Errors",
+ advanced=True,
+ info="If true, errors will not raise an exception.",
+ ),
+ BoolInput(
+ name="delete_server_file_after_processing",
+ display_name="Delete Server File After Processing",
+ advanced=True,
+ value=True,
+ info="If true, the Server File Path will be deleted after processing.",
+ ),
+ BoolInput(
+ name="ignore_unsupported_extensions",
+ display_name="Ignore Unsupported Extensions",
+ advanced=True,
+ value=True,
+ info="If true, files with unsupported extensions will not be processed.",
+ ),
+ BoolInput(
+ name="ignore_unspecified_files",
+ display_name="Ignore Unspecified Files",
+ advanced=True,
+ value=False,
+ info=f"If true, Data with no '{SERVER_FILE_PATH_FIELDNAME}' property will be ignored.",
+ ),
+ ]
+
+ _base_outputs = [Output(display_name="Data", name="data", method="load_files")]
+
+ @abstractmethod
+ def process_files(self, file_list: list[BaseFile]) -> list[BaseFile]:
+ """Processes a list of files.
+
+ Args:
+ file_list (list[BaseFile]): A list of file objects.
+
+ Returns:
+ list[BaseFile]: A list of BaseFile objects with updated `data`.
+ """
+
+ def load_files(self) -> list[Data]:
+ """Loads and parses file(s), including unpacked file bundles.
+
+ Returns:
+ list[Data]: Parsed data from the processed files.
+ """
+ self._temp_dirs: list[TemporaryDirectory] = []
+ final_files = [] # Initialize to avoid UnboundLocalError
+ try:
+ # Step 1: Validate the provided paths
+ files = self._validate_and_resolve_paths()
+
+ # Step 2: Handle bundles recursively
+ all_files = self._unpack_and_collect_files(files)
+
+ # Step 3: Final validation of file types
+ final_files = self._filter_and_mark_files(all_files)
+
+ # Step 4: Process files
+ processed_files = self.process_files(final_files)
+
+ # Extract and flatten Data objects to return
+ return [data for file in processed_files for data in file.data if file.data]
+
+ finally:
+ # Delete temporary directories
+ for temp_dir in self._temp_dirs:
+ temp_dir.cleanup()
+
+ # Delete files marked for deletion
+ for file in final_files:
+ if file.delete_after_processing and file.path.exists():
+ if file.path.is_dir():
+ shutil.rmtree(file.path)
+ else:
+ file.path.unlink()
+
+ @property
+ def valid_extensions(self) -> list[str]:
+ """Returns valid file extensions for the class.
+
+ This property can be overridden by child classes to provide specific
+ extensions.
+
+ Returns:
+ list[str]: A list of valid file extensions without the leading dot.
+ """
+ return self.VALID_EXTENSIONS
+
+ @property
+ def ignore_starts_with(self) -> list[str]:
+ """Returns prefixes to ignore when unpacking file bundles.
+
+ Returns:
+ list[str]: A list of prefixes to ignore when unpacking file bundles.
+ """
+ return self.IGNORE_STARTS_WITH
+
+ def rollup_data(
+ self,
+ base_files: list[BaseFile],
+ data_list: list[Data | None],
+ path_field: str = SERVER_FILE_PATH_FIELDNAME,
+ ) -> list[BaseFile]:
+ r"""Rolls up Data objects into corresponding BaseFile objects in order given by `base_files`.
+
+ Args:
+ base_files (list[BaseFile]): The original BaseFile objects.
+ data_list (list[Data | None]): The list of data to be aggregated into the BaseFile objects.
+ path_field (str): The field name on the data_list objects that holds the file path as a string.
+
+ Returns:
+ list[BaseFile]: A new list of BaseFile objects with merged `data` attributes.
+ """
+
+ def _build_data_dict(data_list: list[Data | None], data_list_field: str) -> dict[str, list[Data]]:
+ """Builds a dictionary grouping Data objects by a specified field."""
+ data_dict: dict[str, list[Data]] = {}
+ for data in data_list:
+ if data is None:
+ continue
+ key = data.data.get(data_list_field)
+ if key is None:
+ msg = f"Data object missing required field '{data_list_field}': {data}"
+ self.log(msg)
+ if not self.silent_errors:
+ msg = f"Data object missing required field '{data_list_field}': {data}"
+ self.log(msg)
+ raise ValueError(msg)
+ continue
+ data_dict.setdefault(key, []).append(data)
+ return data_dict
+
+ # Build the data dictionary from the provided data_list
+ data_dict = _build_data_dict(data_list, path_field)
+
+ # Generate the updated list of BaseFile objects, preserving the order of base_files
+ updated_base_files = []
+ for base_file in base_files:
+ new_data_list = data_dict.get(str(base_file.path), [])
+ merged_data_list = base_file.merge_data(new_data_list)
+ updated_base_files.append(
+ BaseFileComponent.BaseFile(
+ data=merged_data_list,
+ path=base_file.path,
+ delete_after_processing=base_file.delete_after_processing,
+ )
+ )
+
+ return updated_base_files
+
+ def _file_path_as_list(self) -> list[Data]:
+ file_path = self.file_path
+ if not file_path:
+ return []
+
+ def _message_to_data(message: Message) -> Data:
+ return Data(**{self.SERVER_FILE_PATH_FIELDNAME: message.text})
+
+ if isinstance(file_path, Data):
+ file_path = [file_path]
+ elif isinstance(file_path, Message):
+ file_path = [_message_to_data(file_path)]
+ elif not isinstance(file_path, list):
+ msg = f"Expected list of Data objects in file_path but got {type(file_path)}."
+ self.log(msg)
+ if not self.silent_errors:
+ raise ValueError(msg)
+ return []
+
+ file_paths = []
+ for obj in file_path:
+ data_obj = _message_to_data(obj) if isinstance(obj, Message) else obj
+
+ if not isinstance(data_obj, Data):
+ msg = f"Expected Data object in file_path but got {type(data_obj)}."
+ self.log(msg)
+ if not self.silent_errors:
+ raise ValueError(msg)
+ continue
+ file_paths.append(data_obj)
+
+ return file_paths
+
+ def _validate_and_resolve_paths(self) -> list[BaseFile]:
+ """Validate that all input paths exist and are valid, and create BaseFile instances.
+
+ Returns:
+ list[BaseFile]: A list of valid BaseFile instances.
+
+ Raises:
+ ValueError: If any path does not exist.
+ """
+ resolved_files = []
+
+ def add_file(data: Data, path: str | Path, *, delete_after_processing: bool):
+ resolved_path = Path(self.resolve_path(str(path)))
+
+ if not resolved_path.exists():
+ msg = f"File or directory not found: {path}"
+ self.log(msg)
+ if not self.silent_errors:
+ raise ValueError(msg)
+ resolved_files.append(
+ BaseFileComponent.BaseFile(data, resolved_path, delete_after_processing=delete_after_processing)
+ )
+
+ file_path = self._file_path_as_list()
+
+ if self.path and not file_path:
+ # Wrap self.path into a Data object
+ if isinstance(self.path, list):
+ for path in self.path:
+ data_obj = Data(data={self.SERVER_FILE_PATH_FIELDNAME: path})
+ add_file(data=data_obj, path=path, delete_after_processing=False)
+ else:
+ data_obj = Data(data={self.SERVER_FILE_PATH_FIELDNAME: self.path})
+ add_file(data=data_obj, path=self.path, delete_after_processing=False)
+ elif file_path:
+ for obj in file_path:
+ server_file_path = obj.data.get(self.SERVER_FILE_PATH_FIELDNAME)
+ if server_file_path:
+ add_file(
+ data=obj,
+ path=server_file_path,
+ delete_after_processing=self.delete_server_file_after_processing,
+ )
+ elif not self.ignore_unspecified_files:
+ msg = f"Data object missing '{self.SERVER_FILE_PATH_FIELDNAME}' property."
+ self.log(msg)
+ if not self.silent_errors:
+ raise ValueError(msg)
+ else:
+ msg = f"Ignoring Data object missing '{self.SERVER_FILE_PATH_FIELDNAME}' property:\n{obj}"
+ self.log(msg)
+
+ return resolved_files
+
+ def _unpack_and_collect_files(self, files: list[BaseFile]) -> list[BaseFile]:
+ """Recursively unpack bundles and collect files into BaseFile instances.
+
+ Args:
+ files (list[BaseFile]): List of BaseFile instances to process.
+
+ Returns:
+ list[BaseFile]: Updated list of BaseFile instances.
+ """
+ collected_files = []
+
+ for file in files:
+ path = file.path
+ delete_after_processing = file.delete_after_processing
+ data = file.data
+
+ if path.is_dir():
+ # Recurse into directories
+ collected_files.extend(
+ [
+ BaseFileComponent.BaseFile(
+ data,
+ sub_path,
+ delete_after_processing=delete_after_processing,
+ )
+ for sub_path in path.rglob("*")
+ if sub_path.is_file()
+ ]
+ )
+ elif path.suffix[1:] in self.SUPPORTED_BUNDLE_EXTENSIONS:
+ # Unpack supported bundles
+ temp_dir = TemporaryDirectory()
+ self._temp_dirs.append(temp_dir)
+ temp_dir_path = Path(temp_dir.name)
+ self._unpack_bundle(path, temp_dir_path)
+ subpaths = list(temp_dir_path.iterdir())
+ self.log(f"Unpacked bundle {path.name} into {subpaths}")
+ collected_files.extend(
+ [
+ BaseFileComponent.BaseFile(
+ data,
+ sub_path,
+ delete_after_processing=delete_after_processing,
+ )
+ for sub_path in subpaths
+ ]
+ )
+ else:
+ collected_files.append(file)
+
+ # Recurse again if any directories or bundles are left in the list
+ if any(
+ file.path.is_dir() or file.path.suffix[1:] in self.SUPPORTED_BUNDLE_EXTENSIONS for file in collected_files
+ ):
+ return self._unpack_and_collect_files(collected_files)
+
+ return collected_files
+
+ def _unpack_bundle(self, bundle_path: Path, output_dir: Path):
+ """Unpack a bundle into a temporary directory.
+
+ Args:
+ bundle_path (Path): Path to the bundle.
+ output_dir (Path): Directory where files will be extracted.
+
+ Raises:
+ ValueError: If the bundle format is unsupported or cannot be read.
+ """
+
+ def _safe_extract_zip(bundle: ZipFile, output_dir: Path):
+ """Safely extract ZIP files."""
+ for member in bundle.namelist():
+ member_path = output_dir / member
+ # Ensure no path traversal outside `output_dir`
+ if not member_path.resolve().is_relative_to(output_dir.resolve()):
+ msg = f"Attempted Path Traversal in ZIP File: {member}"
+ raise ValueError(msg)
+ bundle.extract(member, path=output_dir)
+
+ def _safe_extract_tar(bundle: tarfile.TarFile, output_dir: Path):
+ """Safely extract TAR files."""
+ for member in bundle.getmembers():
+ member_path = output_dir / member.name
+ # Ensure no path traversal outside `output_dir`
+ if not member_path.resolve().is_relative_to(output_dir.resolve()):
+ msg = f"Attempted Path Traversal in TAR File: {member.name}"
+ raise ValueError(msg)
+ bundle.extract(member, path=output_dir)
+
+ # Check and extract based on file type
+ if is_zipfile(bundle_path):
+ with ZipFile(bundle_path, "r") as zip_bundle:
+ _safe_extract_zip(zip_bundle, output_dir)
+ elif tarfile.is_tarfile(bundle_path):
+ with tarfile.open(bundle_path, "r:*") as tar_bundle:
+ _safe_extract_tar(tar_bundle, output_dir)
+ else:
+ msg = f"Unsupported bundle format: {bundle_path.suffix}"
+ raise ValueError(msg)
+
+ def _filter_and_mark_files(self, files: list[BaseFile]) -> list[BaseFile]:
+ """Validate file types and mark files for removal.
+
+ Args:
+ files (list[BaseFile]): List of BaseFile instances.
+
+ Returns:
+ list[BaseFile]: Validated BaseFile instances.
+
+ Raises:
+ ValueError: If unsupported files are encountered and `ignore_unsupported_extensions` is False.
+ """
+ final_files = []
+ ignored_files = []
+
+ for file in files:
+ if not file.path.is_file():
+ self.log(f"Not a file: {file.path.name}")
+ continue
+
+ if file.path.suffix[1:].lower() not in self.valid_extensions:
+ if self.ignore_unsupported_extensions:
+ ignored_files.append(file.path.name)
+ continue
+ msg = f"Unsupported file extension: {file.path.suffix}"
+ self.log(msg)
+ if not self.silent_errors:
+ raise ValueError(msg)
+
+ final_files.append(file)
+
+ if ignored_files:
+ self.log(f"Ignored files: {ignored_files}")
+
+ return final_files
diff --git a/langflow/src/backend/base/langflow/base/data/utils.py b/langflow/src/backend/base/langflow/base/data/utils.py
new file mode 100644
index 0000000..9a414cd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/data/utils.py
@@ -0,0 +1,199 @@
+import unicodedata
+from collections.abc import Callable
+from concurrent import futures
+from pathlib import Path
+
+import chardet
+import orjson
+import yaml
+from defusedxml import ElementTree
+
+from langflow.schema import Data
+
+# Types of files that can be read simply by file.read()
+# and have 100% to be completely readable
+TEXT_FILE_TYPES = [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+]
+
+IMG_FILE_TYPES = ["jpg", "jpeg", "png", "bmp", "image"]
+
+
+def normalize_text(text):
+ return unicodedata.normalize("NFKD", text)
+
+
+def is_hidden(path: Path) -> bool:
+ return path.name.startswith(".")
+
+
+def format_directory_path(path: str) -> str:
+ """Format a directory path to ensure it's properly escaped and valid.
+
+ Args:
+ path (str): The input path string.
+
+ Returns:
+ str: A properly formatted path string.
+ """
+ return path.replace("\n", "\\n")
+
+
+# Ignoring FBT001 because the DirectoryComponent in 1.0.19
+# calls this function without keyword arguments
+def retrieve_file_paths(
+ path: str,
+ load_hidden: bool, # noqa: FBT001
+ recursive: bool, # noqa: FBT001
+ depth: int,
+ types: list[str] = TEXT_FILE_TYPES,
+) -> list[str]:
+ path = format_directory_path(path)
+ path_obj = Path(path)
+ if not path_obj.exists() or not path_obj.is_dir():
+ msg = f"Path {path} must exist and be a directory."
+ raise ValueError(msg)
+
+ def match_types(p: Path) -> bool:
+ return any(p.suffix == f".{t}" for t in types) if types else True
+
+ def is_not_hidden(p: Path) -> bool:
+ return not is_hidden(p) or load_hidden
+
+ def walk_level(directory: Path, max_depth: int):
+ directory = directory.resolve()
+ prefix_length = len(directory.parts)
+ for p in directory.rglob("*" if recursive else "[!.]*"):
+ if len(p.parts) - prefix_length <= max_depth:
+ yield p
+
+ glob = "**/*" if recursive else "*"
+ paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
+ return [str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
+
+
+def partition_file_to_data(file_path: str, *, silent_errors: bool) -> Data | None:
+ # Use the partition function to load the file
+ from unstructured.partition.auto import partition
+
+ try:
+ elements = partition(file_path)
+ except Exception as e:
+ if not silent_errors:
+ msg = f"Error loading file {file_path}: {e}"
+ raise ValueError(msg) from e
+ return None
+
+ # Create a Data
+ text = "\n\n".join([str(el) for el in elements])
+ metadata = elements.metadata if hasattr(elements, "metadata") else {}
+ metadata["file_path"] = file_path
+ return Data(text=text, data=metadata)
+
+
+def read_text_file(file_path: str) -> str:
+ file_path_ = Path(file_path)
+ raw_data = file_path_.read_bytes()
+ result = chardet.detect(raw_data)
+ encoding = result["encoding"]
+
+ if encoding in {"Windows-1252", "Windows-1254", "MacRoman"}:
+ encoding = "utf-8"
+
+ return file_path_.read_text(encoding=encoding)
+
+
+def read_docx_file(file_path: str) -> str:
+ from docx import Document
+
+ doc = Document(file_path)
+ return "\n\n".join([p.text for p in doc.paragraphs])
+
+
+def parse_pdf_to_text(file_path: str) -> str:
+ from pypdf import PdfReader
+
+ with Path(file_path).open("rb") as f:
+ reader = PdfReader(f)
+ return "\n\n".join([page.extract_text() for page in reader.pages])
+
+
+def parse_text_file_to_data(file_path: str, *, silent_errors: bool) -> Data | None:
+ try:
+ if file_path.endswith(".pdf"):
+ text = parse_pdf_to_text(file_path)
+ elif file_path.endswith(".docx"):
+ text = read_docx_file(file_path)
+ else:
+ text = read_text_file(file_path)
+
+ # if file is json, yaml, or xml, we can parse it
+ if file_path.endswith(".json"):
+ text = orjson.loads(text)
+ if isinstance(text, dict):
+ text = {k: normalize_text(v) if isinstance(v, str) else v for k, v in text.items()}
+ elif isinstance(text, list):
+ text = [normalize_text(item) if isinstance(item, str) else item for item in text]
+ text = orjson.dumps(text).decode("utf-8")
+
+ elif file_path.endswith((".yaml", ".yml")):
+ text = yaml.safe_load(text)
+ elif file_path.endswith(".xml"):
+ xml_element = ElementTree.fromstring(text)
+ text = ElementTree.tostring(xml_element, encoding="unicode")
+ except Exception as e:
+ if not silent_errors:
+ msg = f"Error loading file {file_path}: {e}"
+ raise ValueError(msg) from e
+ return None
+
+ return Data(data={"file_path": file_path, "text": text})
+
+
+# ! Removing unstructured dependency until
+# ! 3.12 is supported
+# def get_elements(
+# file_paths: List[str],
+# silent_errors: bool,
+# max_concurrency: int,
+# use_multithreading: bool,
+# ) -> List[Optional[Data]]:
+# if use_multithreading:
+# data = parallel_load_data(file_paths, silent_errors, max_concurrency)
+# else:
+# data = [partition_file_to_data(file_path, silent_errors) for file_path in file_paths]
+# data = list(filter(None, data))
+# return data
+
+
+def parallel_load_data(
+ file_paths: list[str],
+ *,
+ silent_errors: bool,
+ max_concurrency: int,
+ load_function: Callable = parse_text_file_to_data,
+) -> list[Data | None]:
+ with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
+ loaded_files = executor.map(
+ lambda file_path: load_function(file_path, silent_errors=silent_errors),
+ file_paths,
+ )
+ # loaded_files is an iterator, so we need to convert it to a list
+ return list(loaded_files)
diff --git a/langflow/src/backend/base/langflow/base/document_transformers/__init__.py b/langflow/src/backend/base/langflow/base/document_transformers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/document_transformers/model.py b/langflow/src/backend/base/langflow/base/document_transformers/model.py
new file mode 100644
index 0000000..f4ecb30
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/document_transformers/model.py
@@ -0,0 +1,43 @@
+from abc import abstractmethod
+from typing import Any
+
+from langchain_core.documents import BaseDocumentTransformer
+
+from langflow.custom import Component
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.utils.util import build_loader_repr_from_data
+
+
+class LCDocumentTransformerComponent(Component):
+ trace_type = "document_transformer"
+ outputs = [
+ Output(display_name="Data", name="data", method="transform_data"),
+ ]
+
+ def transform_data(self) -> list[Data]:
+ data_input = self.get_data_input()
+ documents = []
+
+ if not isinstance(data_input, list):
+ data_input = [data_input]
+
+ for _input in data_input:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ transformer = self.build_document_transformer()
+ docs = transformer.transform_documents(documents)
+ data = self.to_data(docs)
+ self.repr_value = build_loader_repr_from_data(data)
+ return data
+
+ @abstractmethod
+ def get_data_input(self) -> Any:
+ """Get the data input."""
+
+ @abstractmethod
+ def build_document_transformer(self) -> BaseDocumentTransformer:
+ """Build the text splitter."""
diff --git a/langflow/src/backend/base/langflow/base/embeddings/__init__.py b/langflow/src/backend/base/langflow/base/embeddings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/embeddings/aiml_embeddings.py b/langflow/src/backend/base/langflow/base/embeddings/aiml_embeddings.py
new file mode 100644
index 0000000..de908e7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/embeddings/aiml_embeddings.py
@@ -0,0 +1,62 @@
+import concurrent.futures
+import json
+
+import httpx
+from loguru import logger
+from pydantic import BaseModel, SecretStr
+
+from langflow.field_typing import Embeddings
+
+
+class AIMLEmbeddingsImpl(BaseModel, Embeddings):
+ embeddings_completion_url: str = "https://api.aimlapi.com/v1/embeddings"
+
+ api_key: SecretStr
+ model: str
+
+ def embed_documents(self, texts: list[str]) -> list[list[float]]:
+ embeddings = [None] * len(texts)
+ headers = {
+ "Content-Type": "application/json",
+ "Authorization": f"Bearer {self.api_key.get_secret_value()}",
+ }
+
+ with httpx.Client() as client, concurrent.futures.ThreadPoolExecutor() as executor:
+ futures = []
+ for i, text in enumerate(texts):
+ futures.append((i, executor.submit(self._embed_text, client, headers, text)))
+
+ for index, future in futures:
+ try:
+ result_data = future.result()
+ if len(result_data["data"]) != 1:
+ msg = f"Expected one embedding, got {len(result_data['data'])}"
+ raise ValueError(msg)
+ embeddings[index] = result_data["data"][0]["embedding"]
+ except (
+ httpx.HTTPStatusError,
+ httpx.RequestError,
+ json.JSONDecodeError,
+ KeyError,
+ ValueError,
+ ):
+ logger.exception("Error occurred")
+ raise
+
+ return embeddings # type: ignore[return-value]
+
+ def _embed_text(self, client: httpx.Client, headers: dict, text: str) -> dict:
+ payload = {
+ "model": self.model,
+ "input": text,
+ }
+ response = client.post(
+ self.embeddings_completion_url,
+ headers=headers,
+ json=payload,
+ )
+ response.raise_for_status()
+ return response.json()
+
+ def embed_query(self, text: str) -> list[float]:
+ return self.embed_documents([text])[0]
diff --git a/langflow/src/backend/base/langflow/base/embeddings/model.py b/langflow/src/backend/base/langflow/base/embeddings/model.py
new file mode 100644
index 0000000..182762b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/embeddings/model.py
@@ -0,0 +1,26 @@
+from langflow.custom import Component
+from langflow.field_typing import Embeddings
+from langflow.io import Output
+
+
+class LCEmbeddingsModel(Component):
+ trace_type = "embedding"
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["build_embeddings"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def build_embeddings(self) -> Embeddings:
+ msg = "You must implement the build_embeddings method in your class."
+ raise NotImplementedError(msg)
diff --git a/langflow/src/backend/base/langflow/base/flow_processing/__init__.py b/langflow/src/backend/base/langflow/base/flow_processing/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/flow_processing/utils.py b/langflow/src/backend/base/langflow/base/flow_processing/utils.py
new file mode 100644
index 0000000..f1e7fdc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/flow_processing/utils.py
@@ -0,0 +1,87 @@
+from loguru import logger
+
+from langflow.graph.schema import ResultData, RunOutputs
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+def build_data_from_run_outputs(run_outputs: RunOutputs) -> list[Data]:
+ """Build a list of data from the given RunOutputs.
+
+ Args:
+ run_outputs (RunOutputs): The RunOutputs object containing the output data.
+
+ Returns:
+ List[Data]: A list of data built from the RunOutputs.
+
+ """
+ if not run_outputs:
+ return []
+ data = []
+ for result_data in run_outputs.outputs:
+ if result_data:
+ data.extend(build_data_from_result_data(result_data))
+ return data
+
+
+def build_data_from_result_data(result_data: ResultData) -> list[Data]:
+ """Build a list of data from the given ResultData.
+
+ Args:
+ result_data (ResultData): The ResultData object containing the result data.
+
+ Returns:
+ List[Data]: A list of data built from the ResultData.
+
+ """
+ messages = result_data.messages
+
+ if not messages:
+ return []
+ data = []
+
+ # Handle results without chat messages (calling flow)
+ if not messages:
+ # Result with a single record
+ if isinstance(result_data.artifacts, dict):
+ data.append(Data(data=result_data.artifacts))
+ # List of artifacts
+ elif isinstance(result_data.artifacts, list):
+ for artifact in result_data.artifacts:
+ # If multiple records are found as artifacts, return as-is
+ if isinstance(artifact, Data):
+ data.append(artifact)
+ else:
+ # Warn about unknown output type
+ logger.warning(f"Unable to build record output from unknown ResultData.artifact: {artifact}")
+ # Chat or text output
+ elif result_data.results:
+ data.append(Data(data={"result": result_data.results}, text_key="result"))
+ return data
+ else:
+ return []
+
+ if isinstance(result_data.results, dict):
+ for name, result in result_data.results.items():
+ dataobj: Data | Message | None
+ dataobj = result if isinstance(result, Message) else Data(data=result, text_key=name)
+
+ data.append(dataobj)
+ else:
+ data.append(Data(data=result_data.results))
+ return data
+
+
+def format_flow_output_data(data: list[Data]) -> str:
+ """Format the flow output data into a string.
+
+ Args:
+ data (List[Data]): The list of data to format.
+
+ Returns:
+ str: The formatted flow output data.
+
+ """
+ result = "Flow run output:\n"
+ results = "\n".join([value.get_text() if hasattr(value, "get_text") else str(value) for value in data])
+ return result + results
diff --git a/langflow/src/backend/base/langflow/base/huggingface/__init__.py b/langflow/src/backend/base/langflow/base/huggingface/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/huggingface/model_bridge.py b/langflow/src/backend/base/langflow/base/huggingface/model_bridge.py
new file mode 100644
index 0000000..6a08f10
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/huggingface/model_bridge.py
@@ -0,0 +1,133 @@
+# Import LangChain base
+from langchain_core.language_models.chat_models import BaseChatModel
+from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolCall
+from langchain_core.tools import BaseTool
+
+# Import HuggingFace Model base
+from smolagents import Model, Tool
+from smolagents.models import ChatMessage, ChatMessageToolCall, ChatMessageToolCallDefinition
+
+
+def _lc_tool_call_to_hf_tool_call(tool_call: ToolCall) -> ChatMessageToolCall:
+ """Convert a LangChain ToolCall to a HuggingFace ChatMessageToolCall.
+
+ Args:
+ tool_call (ToolCall): LangChain tool call to convert
+
+ Returns:
+ ChatMessageToolCall: Equivalent HuggingFace tool call
+ """
+ return ChatMessageToolCall(
+ function=ChatMessageToolCallDefinition(name=tool_call.name, arguments=tool_call.args),
+ id=tool_call.id,
+ )
+
+
+def _hf_tool_to_lc_tool(tool) -> BaseTool:
+ """Convert a HuggingFace Tool to a LangChain BaseTool.
+
+ Args:
+ tool (Tool): HuggingFace tool to convert
+
+ Returns:
+ BaseTool: Equivalent LangChain tool
+ """
+ if not hasattr(tool, "langchain_tool"):
+ msg = "HuggingFace Tool does not have a langchain_tool attribute"
+ raise ValueError(msg)
+ return tool.langchain_tool
+
+
+class LangChainHFModel(Model):
+ """A class bridging HuggingFace's `Model` interface with a LangChain `BaseChatModel`.
+
+ This adapter allows using any LangChain chat model with the HuggingFace interface.
+ It handles conversion of message formats and tool calls between the two frameworks.
+
+ Usage:
+ >>> lc_model = LangChainChatModel(...) # any BaseChatModel
+ >>> hf_model = LangChainHFModel(lc_model)
+ >>> hf_model(messages=[{"role": "user", "content": "Hello!"}])
+ """
+
+ def __init__(self, chat_model: BaseChatModel, **kwargs):
+ """Initialize the bridge model.
+
+ Args:
+ chat_model (BaseChatModel): LangChain chat model to wrap
+ **kwargs: Additional arguments passed to Model.__init__
+ """
+ super().__init__(**kwargs)
+ self.chat_model = chat_model
+
+ def __call__(
+ self,
+ messages: list[dict[str, str]],
+ stop_sequences: list[str] | None = None,
+ grammar: str | None = None,
+ tools_to_call_from: list[Tool] | None = None,
+ **kwargs,
+ ) -> ChatMessage:
+ """Process messages through the LangChain model and return HuggingFace format.
+
+ Args:
+ messages: List of message dictionaries with 'role' and 'content' keys
+ stop_sequences: Optional list of strings to stop generation
+ grammar: Optional grammar specification (not used)
+ tools_to_call_from: Optional list of available tools (not used)
+ **kwargs: Additional arguments passed to the LangChain model
+
+ Returns:
+ ChatMessage: Response in HuggingFace format
+ """
+ if grammar:
+ msg = "Grammar is not yet supported."
+ raise ValueError(msg)
+
+ # Convert HF messages to LangChain messages
+ lc_messages = []
+ for m in messages:
+ role = m["role"]
+ content = m["content"]
+ if role == "system":
+ lc_messages.append(SystemMessage(content=content))
+ elif role == "assistant":
+ lc_messages.append(AIMessage(content=content))
+ else:
+ # Default any unknown role to "user"
+ lc_messages.append(HumanMessage(content=content))
+
+ # Convert tools to LangChain tools
+ if tools_to_call_from:
+ tools_to_call_from = [_hf_tool_to_lc_tool(tool) for tool in tools_to_call_from]
+
+ model = self.chat_model.bind_tools(tools_to_call_from) if tools_to_call_from else self.chat_model
+
+ # Call the LangChain model
+ result_msg: AIMessage = model.invoke(lc_messages, stop=stop_sequences, **kwargs)
+
+ # Convert the AIMessage into an HF ChatMessage
+ return ChatMessage(
+ role="assistant",
+ content=result_msg.content or "",
+ tool_calls=[_lc_tool_call_to_hf_tool_call(tool_call) for tool_call in result_msg.tool_calls],
+ )
+
+
+# How to use
+# if __name__ == "__main__":
+# from langchain_community.tools import DuckDuckGoSearchRun
+# from langchain_openai import ChatOpenAI
+# from rich import rprint
+# from smolagents import CodeAgent
+
+# # Example usage
+# model = LangChainHFModel(chat_model=ChatOpenAI(model="gpt-4o-mini"))
+# search_tool = DuckDuckGoSearchRun()
+# hf_tool = Tool.from_langchain(search_tool)
+
+# code_agent = CodeAgent(
+# model=model,
+# tools=[hf_tool],
+# )
+# rprint(code_agent.run("Search for Langflow on DuckDuckGo and return the first result"))
diff --git a/langflow/src/backend/base/langflow/base/io/__init__.py b/langflow/src/backend/base/langflow/base/io/__init__.py
new file mode 100644
index 0000000..dc9fd4c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/io/__init__.py
@@ -0,0 +1 @@
+# noqa: A005
diff --git a/langflow/src/backend/base/langflow/base/io/chat.py b/langflow/src/backend/base/langflow/base/io/chat.py
new file mode 100644
index 0000000..c0448e1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/io/chat.py
@@ -0,0 +1,20 @@
+from langflow.custom import Component
+
+
+class ChatComponent(Component):
+ display_name = "Chat Component"
+ description = "Use as base for chat components."
+
+ def get_properties_from_source_component(self):
+ if hasattr(self, "_vertex") and hasattr(self._vertex, "incoming_edges") and self._vertex.incoming_edges:
+ source_id = self._vertex.incoming_edges[0].source_id
+ source_vertex = self.graph.get_vertex(source_id)
+ component = source_vertex.custom_component
+ source = component.display_name
+ icon = component.icon
+ possible_attributes = ["model_name", "model_id", "model"]
+ for attribute in possible_attributes:
+ if hasattr(component, attribute) and getattr(component, attribute):
+ return getattr(component, attribute), icon, source, component._id
+ return source, icon, component.display_name, component._id
+ return None, None, None, None
diff --git a/langflow/src/backend/base/langflow/base/io/text.py b/langflow/src/backend/base/langflow/base/io/text.py
new file mode 100644
index 0000000..d2854a8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/io/text.py
@@ -0,0 +1,22 @@
+from langflow.custom import Component
+
+
+class TextComponent(Component):
+ display_name = "Text Component"
+ description = "Used to pass text to the next component."
+
+ def build_config(self):
+ return {
+ "input_value": {
+ "display_name": "Value",
+ "input_types": ["Message", "Data"],
+ "info": "Text or Data to be passed.",
+ },
+ "data_template": {
+ "display_name": "Data Template",
+ "multiline": True,
+ "info": "Template to convert Data to Text. "
+ "If left empty, it will be dynamically set to the Data's text key.",
+ "advanced": True,
+ },
+ }
diff --git a/langflow/src/backend/base/langflow/base/langchain_utilities/__init__.py b/langflow/src/backend/base/langflow/base/langchain_utilities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/langchain_utilities/model.py b/langflow/src/backend/base/langflow/base/langchain_utilities/model.py
new file mode 100644
index 0000000..b8fca27
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/langchain_utilities/model.py
@@ -0,0 +1,34 @@
+from abc import abstractmethod
+from collections.abc import Sequence
+
+from langflow.custom import Component
+from langflow.field_typing import Tool
+from langflow.io import Output
+from langflow.schema import Data
+
+
+class LCToolComponent(Component):
+ trace_type = "tool"
+ outputs = [
+ Output(name="api_run_model", display_name="Data", method="run_model"),
+ Output(name="api_build_tool", display_name="Tool", method="build_tool"),
+ ]
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["run_model", "build_tool"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ @abstractmethod
+ def run_model(self) -> Data | list[Data]:
+ """Run model and return the output."""
+
+ @abstractmethod
+ def build_tool(self) -> Tool | Sequence[Tool]:
+ """Build the tool."""
diff --git a/langflow/src/backend/base/langflow/base/langchain_utilities/spider_constants.py b/langflow/src/backend/base/langflow/base/langchain_utilities/spider_constants.py
new file mode 100644
index 0000000..8630e94
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/langchain_utilities/spider_constants.py
@@ -0,0 +1 @@
+MODES = ["scrape", "crawl"]
diff --git a/langflow/src/backend/base/langflow/base/mcp/__init__.py b/langflow/src/backend/base/langflow/base/mcp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/mcp/util.py b/langflow/src/backend/base/langflow/base/mcp/util.py
new file mode 100644
index 0000000..e4b867b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/mcp/util.py
@@ -0,0 +1,26 @@
+import asyncio
+from collections.abc import Awaitable, Callable
+
+from langflow.helpers.base_model import BaseModel
+
+
+def create_tool_coroutine(tool_name: str, arg_schema: type[BaseModel], session) -> Callable[[dict], Awaitable]:
+ async def tool_coroutine(*args):
+ if len(args) == 0:
+ msg = f"at least one positional argument is required {args}"
+ raise ValueError(msg)
+ arg_dict = dict(zip(arg_schema.model_fields.keys(), args, strict=False))
+ return await session.call_tool(tool_name, arguments=arg_dict)
+
+ return tool_coroutine
+
+
+def create_tool_func(tool_name: str, session) -> Callable[..., str]:
+ def tool_func(**kwargs):
+ if len(kwargs) == 0:
+ msg = f"at least one named argument is required {kwargs}"
+ raise ValueError(msg)
+ loop = asyncio.get_event_loop()
+ return loop.run_until_complete(session.call_tool(tool_name, arguments=kwargs))
+
+ return tool_func
diff --git a/langflow/src/backend/base/langflow/base/memory/__init__.py b/langflow/src/backend/base/langflow/base/memory/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/memory/memory.py b/langflow/src/backend/base/langflow/base/memory/memory.py
new file mode 100644
index 0000000..c90b250
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/memory/memory.py
@@ -0,0 +1,49 @@
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
+
+
+class BaseMemoryComponent(CustomComponent):
+ display_name = "Chat Memory"
+ description = "Retrieves stored chat messages given a specific Session ID."
+ beta: bool = True
+ icon = "history"
+
+ def build_config(self):
+ return {
+ "sender": {
+ "options": [MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, "Machine and User"],
+ "display_name": "Sender Type",
+ },
+ "sender_name": {"display_name": "Sender Name", "advanced": True},
+ "n_messages": {
+ "display_name": "Number of Messages",
+ "info": "Number of messages to retrieve.",
+ },
+ "session_id": {
+ "display_name": "Session ID",
+ "info": "Session ID of the chat history.",
+ "input_types": ["Message"],
+ },
+ "order": {
+ "options": ["Ascending", "Descending"],
+ "display_name": "Order",
+ "info": "Order of the messages.",
+ "advanced": True,
+ },
+ "data_template": {
+ "display_name": "Data Template",
+ "multiline": True,
+ "info": "Template to convert Data to Text. "
+ "If left empty, it will be dynamically set to the Data's text key.",
+ "advanced": True,
+ },
+ }
+
+ def get_messages(self, **kwargs) -> list[Data]:
+ raise NotImplementedError
+
+ def add_message(
+ self, sender: str, sender_name: str, text: str, session_id: str, metadata: dict | None = None, **kwargs
+ ) -> None:
+ raise NotImplementedError
diff --git a/langflow/src/backend/base/langflow/base/memory/model.py b/langflow/src/backend/base/langflow/base/memory/model.py
new file mode 100644
index 0000000..c48227c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/memory/model.py
@@ -0,0 +1,37 @@
+from abc import abstractmethod
+
+from langchain.memory import ConversationBufferMemory
+
+from langflow.custom import Component
+from langflow.field_typing import BaseChatMemory
+from langflow.field_typing.constants import Memory
+from langflow.template import Output
+
+
+class LCChatMemoryComponent(Component):
+ trace_type = "chat_memory"
+ outputs = [
+ Output(
+ display_name="Memory",
+ name="memory",
+ method="build_message_history",
+ )
+ ]
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["build_message_history"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def build_base_memory(self) -> BaseChatMemory:
+ return ConversationBufferMemory(chat_memory=self.build_message_history())
+
+ @abstractmethod
+ def build_message_history(self) -> Memory:
+ """Builds the chat message history memory."""
diff --git a/langflow/src/backend/base/langflow/base/models/__init__.py b/langflow/src/backend/base/langflow/base/models/__init__.py
new file mode 100644
index 0000000..921f103
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/__init__.py
@@ -0,0 +1,3 @@
+from .model import LCModelComponent
+
+__all__ = ["LCModelComponent"]
diff --git a/langflow/src/backend/base/langflow/base/models/aiml_constants.py b/langflow/src/backend/base/langflow/base/models/aiml_constants.py
new file mode 100644
index 0000000..1d45917
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/aiml_constants.py
@@ -0,0 +1,51 @@
+import httpx
+from openai import APIConnectionError, APIError
+
+
+class AimlModels:
+ def __init__(self):
+ self.chat_models = []
+ self.image_models = []
+ self.embedding_models = []
+ self.stt_models = []
+ self.tts_models = []
+ self.language_models = []
+
+ def get_aiml_models(self):
+ try:
+ with httpx.Client() as client:
+ response = client.get("https://api.aimlapi.com/models")
+ response.raise_for_status()
+ except httpx.RequestError as e:
+ msg = "Failed to connect to the AIML API."
+ raise APIConnectionError(msg) from e
+ except httpx.HTTPStatusError as e:
+ msg = f"AIML API responded with status code: {e.response.status_code}"
+ raise APIError(
+ message=msg,
+ body=None,
+ request=e.request,
+ ) from e
+
+ try:
+ models = response.json().get("data", [])
+ self.separate_models_by_type(models)
+ except (ValueError, KeyError, TypeError) as e:
+ msg = "Failed to parse response data from AIML API. The format may be incorrect."
+ raise ValueError(msg) from e
+
+ def separate_models_by_type(self, models):
+ model_type_mapping = {
+ "chat-completion": self.chat_models,
+ "image": self.image_models,
+ "embedding": self.embedding_models,
+ "stt": self.stt_models,
+ "tts": self.tts_models,
+ "language-completion": self.language_models,
+ }
+
+ for model in models:
+ model_type = model.get("type")
+ model_id = model.get("id")
+ if model_type in model_type_mapping:
+ model_type_mapping[model_type].append(model_id)
diff --git a/langflow/src/backend/base/langflow/base/models/anthropic_constants.py b/langflow/src/backend/base/langflow/base/models/anthropic_constants.py
new file mode 100644
index 0000000..6ecdba2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/anthropic_constants.py
@@ -0,0 +1,10 @@
+ANTHROPIC_MODELS = [
+ "claude-3-5-sonnet-latest",
+ "claude-3-5-haiku-latest",
+ "claude-3-opus-latest",
+ "claude-3-5-sonnet-20240620",
+ "claude-3-5-sonnet-20241022",
+ "claude-3-5-haiku-20241022",
+ "claude-3-sonnet-20240229",
+ "claude-3-haiku-20240307",
+]
diff --git a/langflow/src/backend/base/langflow/base/models/aws_constants.py b/langflow/src/backend/base/langflow/base/models/aws_constants.py
new file mode 100644
index 0000000..23fd90d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/aws_constants.py
@@ -0,0 +1,91 @@
+AWS_MODEL_IDs = [
+ # Amazon Titan Models
+ "amazon.titan-text-express-v1",
+ "amazon.titan-text-lite-v1",
+ "amazon.titan-text-premier-v1:0",
+ # Anthropic Models
+ "anthropic.claude-v2",
+ "anthropic.claude-v2:1",
+ "anthropic.claude-3-sonnet-20240229-v1:0",
+ "anthropic.claude-3-5-sonnet-20240620-v1:0",
+ "anthropic.claude-3-5-sonnet-20241022-v2:0",
+ "anthropic.claude-3-haiku-20240307-v1:0",
+ "anthropic.claude-3-5-haiku-20241022-v1:0",
+ "anthropic.claude-3-opus-20240229-v1:0",
+ "anthropic.claude-instant-v1",
+ # AI21 Labs Models
+ "ai21.jamba-instruct-v1:0",
+ "ai21.j2-mid-v1",
+ "ai21.j2-ultra-v1",
+ "ai21.jamba-1-5-large-v1:0",
+ "ai21.jamba-1-5-mini-v1:0",
+ # Cohere Models
+ "cohere.command-text-v14",
+ "cohere.command-light-text-v14",
+ "cohere.command-r-v1:0",
+ "cohere.command-r-plus-v1:0",
+ # Meta Models
+ "meta.llama2-13b-chat-v1",
+ "meta.llama2-70b-chat-v1",
+ "meta.llama3-8b-instruct-v1:0",
+ "meta.llama3-70b-instruct-v1:0",
+ "meta.llama3-1-8b-instruct-v1:0",
+ "meta.llama3-1-70b-instruct-v1:0",
+ "meta.llama3-1-405b-instruct-v1:0",
+ "meta.llama3-2-1b-instruct-v1:0",
+ "meta.llama3-2-3b-instruct-v1:0",
+ "meta.llama3-2-11b-instruct-v1:0",
+ "meta.llama3-2-90b-instruct-v1:0",
+ # Mistral AI Models
+ "mistral.mistral-7b-instruct-v0:2",
+ "mistral.mixtral-8x7b-instruct-v0:1",
+ "mistral.mistral-large-2402-v1:0",
+ "mistral.mistral-large-2407-v1:0",
+ "mistral.mistral-small-2402-v1:0",
+]
+
+AWS_EMBEDDING_MODEL_IDS = [
+ # Amazon Titan Embedding Models
+ "amazon.titan-embed-text-v1",
+ "amazon.titan-embed-text-v2:0",
+ # Cohere Embedding Models
+ "cohere.embed-english-v3",
+ "cohere.embed-multilingual-v3",
+]
+
+AWS_REGIONS = [
+ "us-west-2",
+ "us-west-1",
+ "us-gov-west-1",
+ "us-gov-east-1",
+ "us-east-2",
+ "us-east-1",
+ "sa-east-1",
+ "me-south-1",
+ "me-central-1",
+ "il-central-1",
+ "eu-west-3",
+ "eu-west-2",
+ "eu-west-1",
+ "eu-south-2",
+ "eu-south-1",
+ "eu-north-1",
+ "eu-central-2",
+ "eu-central-1",
+ "cn-northwest-1",
+ "cn-north-1",
+ "ca-west-1",
+ "ca-central-1",
+ "ap-southeast-5",
+ "ap-southeast-4",
+ "ap-southeast-3",
+ "ap-southeast-2",
+ "ap-southeast-1",
+ "ap-south-2",
+ "ap-south-1",
+ "ap-northeast-3",
+ "ap-northeast-2",
+ "ap-northeast-1",
+ "ap-east-1",
+ "af-south-1",
+]
diff --git a/langflow/src/backend/base/langflow/base/models/chat_result.py b/langflow/src/backend/base/langflow/base/models/chat_result.py
new file mode 100644
index 0000000..288f241
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/chat_result.py
@@ -0,0 +1,76 @@
+import warnings
+
+from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
+
+from langflow.field_typing.constants import LanguageModel
+from langflow.schema.message import Message
+
+
+def build_messages_and_runnable(
+ input_value: str | Message, system_message: str | None, original_runnable: LanguageModel
+) -> tuple[list[BaseMessage], LanguageModel]:
+ messages: list[BaseMessage] = []
+ system_message_added = False
+ runnable = original_runnable
+
+ if input_value:
+ if isinstance(input_value, Message):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ if "prompt" in input_value:
+ prompt = input_value.load_lc_prompt()
+ if system_message:
+ prompt.messages = [
+ SystemMessage(content=system_message),
+ *prompt.messages, # type: ignore[has-type]
+ ]
+ system_message_added = True
+ runnable = prompt | runnable
+ else:
+ messages.append(input_value.to_lc_message())
+ else:
+ messages.append(HumanMessage(content=input_value))
+
+ if system_message and not system_message_added:
+ messages.insert(0, SystemMessage(content=system_message))
+
+ return messages, runnable
+
+
+def get_chat_result(
+ runnable: LanguageModel,
+ input_value: str | Message,
+ system_message: str | None = None,
+ config: dict | None = None,
+ *,
+ stream: bool = False,
+):
+ if not input_value and not system_message:
+ msg = "The message you want to send to the model is empty."
+ raise ValueError(msg)
+
+ messages, runnable = build_messages_and_runnable(
+ input_value=input_value, system_message=system_message, original_runnable=runnable
+ )
+
+ inputs: list | dict = messages or {}
+ try:
+ if config and config.get("output_parser") is not None:
+ runnable |= config["output_parser"]
+
+ if config:
+ runnable = runnable.with_config(
+ {
+ "run_name": config.get("display_name", ""),
+ "project_name": config.get("get_project_name", lambda: "")(),
+ "callbacks": config.get("get_langchain_callbacks", list)(),
+ }
+ )
+ if stream:
+ return runnable.stream(inputs)
+ message = runnable.invoke(inputs)
+ return message.content if hasattr(message, "content") else message
+ except Exception as e:
+ if config and config.get("_get_exception_message") and (message := config["_get_exception_message"](e)):
+ raise ValueError(message) from e
+ raise
diff --git a/langflow/src/backend/base/langflow/base/models/google_generative_ai_constants.py b/langflow/src/backend/base/langflow/base/models/google_generative_ai_constants.py
new file mode 100644
index 0000000..957f1c5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/google_generative_ai_constants.py
@@ -0,0 +1,15 @@
+GOOGLE_GENERATIVE_AI_MODELS = [
+ # GEMINI 1.5
+ "gemini-1.5-pro",
+ "gemini-1.5-flash",
+ "gemini-1.5-flash-8b",
+ # PREVIEW
+ "gemini-2.0-flash",
+ "gemini-exp-1206",
+ "gemini-2.0-flash-thinking-exp-01-21",
+ "learnlm-1.5-pro-experimental",
+ # GEMMA
+ "gemma-2-2b",
+ "gemma-2-9b",
+ "gemma-2-27b",
+]
diff --git a/langflow/src/backend/base/langflow/base/models/groq_constants.py b/langflow/src/backend/base/langflow/base/models/groq_constants.py
new file mode 100644
index 0000000..c9dc9dc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/groq_constants.py
@@ -0,0 +1,23 @@
+GROQ_MODELS = [
+ "distil-whisper-large-v3-en", # HuggingFace
+ "gemma2-9b-it", # Google
+ "gemma-7b-it", # Google
+ "llama3-groq-70b-8192-tool-use-preview", # Groq
+ "llama3-groq-8b-8192-tool-use-preview", # Groq
+ "llama-3.1-70b-versatile", # Meta
+ "llama-3.1-8b-instant", # Meta
+ "llama-3.2-1b-preview", # Meta
+ "llama-3.2-3b-preview", # Meta
+ "llama-3.2-11b-vision-preview", # Meta
+ "llama-3.2-90b-vision-preview", # Meta
+ "llama-3.3-70b-specdec", # Meta
+ "llama-3.3-70b-versatile", # Meta
+ "deepseek-r1-distill-llama-70b" # DeepSeek
+ "llama-guard-3-8b", # Meta
+ "llama3-70b-8192", # Meta
+ "llama3-8b-8192", # Meta
+ "mixtral-8x7b-32768", # Mistral
+ "whisper-large-v3", # OpenAI
+ "whisper-large-v3-turbo", # OpenAI
+]
+MODEL_NAMES = GROQ_MODELS # reverse compatibility
diff --git a/langflow/src/backend/base/langflow/base/models/model.py b/langflow/src/backend/base/langflow/base/models/model.py
new file mode 100644
index 0000000..f7c47c6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/model.py
@@ -0,0 +1,285 @@
+import importlib
+import json
+import warnings
+from abc import abstractmethod
+
+from langchain_core.language_models import BaseChatModel
+from langchain_core.language_models.llms import LLM
+from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
+from langchain_core.output_parsers import BaseOutputParser
+
+from langflow.base.constants import STREAM_INFO_TEXT
+from langflow.custom import Component
+from langflow.field_typing import LanguageModel
+from langflow.inputs import MessageInput
+from langflow.inputs.inputs import BoolInput, InputTypes, MultilineInput
+from langflow.schema.message import Message
+from langflow.template.field.base import Output
+
+
+class LCModelComponent(Component):
+ display_name: str = "Model Name"
+ description: str = "Model Description"
+ trace_type = "llm"
+
+ # Optional output parser to pass to the runnable. Subclasses may allow the user to input an `output_parser`
+ output_parser: BaseOutputParser | None = None
+
+ _base_inputs: list[InputTypes] = [
+ MessageInput(name="input_value", display_name="Input"),
+ MultilineInput(
+ name="system_message",
+ display_name="System Message",
+ info="System message to pass to the model.",
+ advanced=False,
+ ),
+ BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=False),
+ ]
+
+ outputs = [
+ Output(display_name="Message", name="text_output", method="text_response"),
+ Output(display_name="Language Model", name="model_output", method="build_model"),
+ ]
+
+ def _get_exception_message(self, e: Exception):
+ return str(e)
+
+ def supports_tool_calling(self, model: LanguageModel) -> bool:
+ try:
+ # Check if the bind_tools method is the same as the base class's method
+ if model.bind_tools is BaseChatModel.bind_tools:
+ return False
+
+ def test_tool(x: int) -> int:
+ return x
+
+ model_with_tool = model.bind_tools([test_tool])
+ return hasattr(model_with_tool, "tools") and len(model_with_tool.tools) > 0
+ except (AttributeError, TypeError, ValueError):
+ return False
+
+ def _validate_outputs(self) -> None:
+ # At least these two outputs must be defined
+ required_output_methods = ["text_response", "build_model"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def text_response(self) -> Message:
+ input_value = self.input_value
+ stream = self.stream
+ system_message = self.system_message
+ output = self.build_model()
+ result = self.get_chat_result(
+ runnable=output, stream=stream, input_value=input_value, system_message=system_message
+ )
+ self.status = result
+ return result
+
+ def get_result(self, *, runnable: LLM, stream: bool, input_value: str):
+ """Retrieves the result from the output of a Runnable object.
+
+ Args:
+ runnable (Runnable): The runnable to retrieve the result from.
+ stream (bool): Indicates whether to use streaming or invocation mode.
+ input_value (str): The input value to pass to the output object.
+
+ Returns:
+ The result obtained from the output object.
+ """
+ try:
+ if stream:
+ result = runnable.stream(input_value)
+ else:
+ message = runnable.invoke(input_value)
+ result = message.content if hasattr(message, "content") else message
+ self.status = result
+ except Exception as e:
+ if message := self._get_exception_message(e):
+ raise ValueError(message) from e
+ raise
+
+ return result
+
+ def build_status_message(self, message: AIMessage):
+ """Builds a status message from an AIMessage object.
+
+ Args:
+ message (AIMessage): The AIMessage object to build the status message from.
+
+ Returns:
+ The status message.
+ """
+ if message.response_metadata:
+ # Build a well formatted status message
+ content = message.content
+ response_metadata = message.response_metadata
+ openai_keys = ["token_usage", "model_name", "finish_reason"]
+ inner_openai_keys = ["completion_tokens", "prompt_tokens", "total_tokens"]
+ anthropic_keys = ["model", "usage", "stop_reason"]
+ inner_anthropic_keys = ["input_tokens", "output_tokens"]
+ if all(key in response_metadata for key in openai_keys) and all(
+ key in response_metadata["token_usage"] for key in inner_openai_keys
+ ):
+ token_usage = response_metadata["token_usage"]
+ status_message = {
+ "tokens": {
+ "input": token_usage["prompt_tokens"],
+ "output": token_usage["completion_tokens"],
+ "total": token_usage["total_tokens"],
+ "stop_reason": response_metadata["finish_reason"],
+ "response": content,
+ }
+ }
+
+ elif all(key in response_metadata for key in anthropic_keys) and all(
+ key in response_metadata["usage"] for key in inner_anthropic_keys
+ ):
+ usage = response_metadata["usage"]
+ status_message = {
+ "tokens": {
+ "input": usage["input_tokens"],
+ "output": usage["output_tokens"],
+ "stop_reason": response_metadata["stop_reason"],
+ "response": content,
+ }
+ }
+ else:
+ status_message = f"Response: {content}" # type: ignore[assignment]
+ else:
+ status_message = f"Response: {message.content}" # type: ignore[assignment]
+ return status_message
+
+ def get_chat_result(
+ self,
+ *,
+ runnable: LanguageModel,
+ stream: bool,
+ input_value: str | Message,
+ system_message: str | None = None,
+ ):
+ messages: list[BaseMessage] = []
+ if not input_value and not system_message:
+ msg = "The message you want to send to the model is empty."
+ raise ValueError(msg)
+ system_message_added = False
+ if input_value:
+ if isinstance(input_value, Message):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ if "prompt" in input_value:
+ prompt = input_value.load_lc_prompt()
+ if system_message:
+ prompt.messages = [
+ SystemMessage(content=system_message),
+ *prompt.messages, # type: ignore[has-type]
+ ]
+ system_message_added = True
+ runnable = prompt | runnable
+ else:
+ messages.append(input_value.to_lc_message())
+ else:
+ messages.append(HumanMessage(content=input_value))
+
+ if system_message and not system_message_added:
+ messages.insert(0, SystemMessage(content=system_message))
+ inputs: list | dict = messages or {}
+ try:
+ # TODO: Depreciated Feature to be removed in upcoming release
+ if hasattr(self, "output_parser") and self.output_parser is not None:
+ runnable |= self.output_parser
+
+ runnable = runnable.with_config(
+ {
+ "run_name": self.display_name,
+ "project_name": self.get_project_name(),
+ "callbacks": self.get_langchain_callbacks(),
+ }
+ )
+ if stream:
+ return runnable.stream(inputs)
+ message = runnable.invoke(inputs)
+ result = message.content if hasattr(message, "content") else message
+ if isinstance(message, AIMessage):
+ status_message = self.build_status_message(message)
+ self.status = status_message
+ elif isinstance(result, dict):
+ result = json.dumps(message, indent=4)
+ self.status = result
+ else:
+ self.status = result
+ except Exception as e:
+ if message := self._get_exception_message(e):
+ raise ValueError(message) from e
+ raise
+
+ return result
+
+ @abstractmethod
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ """Implement this method to build the model."""
+
+ def get_llm(self, provider_name: str, model_info: dict[str, dict[str, str | list[InputTypes]]]) -> LanguageModel:
+ """Get LLM model based on provider name and inputs.
+
+ Args:
+ provider_name: Name of the model provider (e.g., "OpenAI", "Azure OpenAI")
+ inputs: Dictionary of input parameters for the model
+ model_info: Dictionary of model information
+
+ Returns:
+ Built LLM model instance
+ """
+ try:
+ if provider_name not in [model.get("display_name") for model in model_info.values()]:
+ msg = f"Unknown model provider: {provider_name}"
+ raise ValueError(msg)
+
+ # Find the component class name from MODEL_INFO in a single iteration
+ component_info, module_name = next(
+ ((info, key) for key, info in model_info.items() if info.get("display_name") == provider_name),
+ (None, None),
+ )
+ if not component_info:
+ msg = f"Component information not found for {provider_name}"
+ raise ValueError(msg)
+ component_inputs = component_info.get("inputs", [])
+ # Get the component class from the models module
+ # Ensure component_inputs is a list of the expected types
+ if not isinstance(component_inputs, list):
+ component_inputs = []
+ models_module = importlib.import_module("langflow.components.models")
+ component_class = getattr(models_module, str(module_name))
+ component = component_class()
+
+ return self.build_llm_model_from_inputs(component, component_inputs)
+ except Exception as e:
+ msg = f"Error building {provider_name} language model"
+ raise ValueError(msg) from e
+
+ def build_llm_model_from_inputs(
+ self, component: Component, inputs: list[InputTypes], prefix: str = ""
+ ) -> LanguageModel:
+ """Build LLM model from component and inputs.
+
+ Args:
+ component: LLM component instance
+ inputs: Dictionary of input parameters for the model
+ prefix: Prefix for the input names
+ Returns:
+ Built LLM model instance
+ """
+ # Ensure prefix is a string
+ prefix = prefix or ""
+ # Filter inputs to only include valid component input names
+ input_data = {
+ str(component_input.name): getattr(self, f"{prefix}{component_input.name}", None)
+ for component_input in inputs
+ }
+
+ return component.set(**input_data).build_model()
diff --git a/langflow/src/backend/base/langflow/base/models/model_input_constants.py b/langflow/src/backend/base/langflow/base/models/model_input_constants.py
new file mode 100644
index 0000000..ee7aad7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/model_input_constants.py
@@ -0,0 +1,271 @@
+from typing_extensions import TypedDict
+
+from langflow.base.models.model import LCModelComponent
+from langflow.components.models.amazon_bedrock import AmazonBedrockComponent
+from langflow.components.models.anthropic import AnthropicModelComponent
+from langflow.components.models.azure_openai import AzureChatOpenAIComponent
+from langflow.components.models.google_generative_ai import GoogleGenerativeAIComponent
+from langflow.components.models.groq import GroqModel
+from langflow.components.models.nvidia import NVIDIAModelComponent
+from langflow.components.models.openai import OpenAIModelComponent
+from langflow.components.models.sambanova import SambaNovaComponent
+from langflow.inputs.inputs import InputTypes, SecretStrInput
+from langflow.template.field.base import Input
+
+
+class ModelProvidersDict(TypedDict):
+ fields: dict
+ inputs: list[InputTypes]
+ prefix: str
+ component_class: LCModelComponent
+ icon: str
+
+
+def get_filtered_inputs(component_class):
+ base_input_names = {field.name for field in LCModelComponent._base_inputs}
+ component_instance = component_class()
+
+ return [process_inputs(input_) for input_ in component_instance.inputs if input_.name not in base_input_names]
+
+
+def process_inputs(component_data: Input):
+ if isinstance(component_data, SecretStrInput):
+ component_data.value = ""
+ component_data.load_from_db = False
+ elif component_data.name in {"temperature", "tool_model_enabled", "base_url"}:
+ component_data = set_advanced_true(component_data)
+ elif component_data.name == "model_name":
+ component_data = set_real_time_refresh_false(component_data)
+ component_data = add_combobox_true(component_data)
+ component_data = add_info(
+ component_data,
+ "To see the model names, first choose a provider. Then, enter your API key and click the refresh button "
+ "next to the model name.",
+ )
+ return component_data
+
+
+def set_advanced_true(component_input):
+ component_input.advanced = True
+ return component_input
+
+
+def set_real_time_refresh_false(component_input):
+ component_input.real_time_refresh = False
+ return component_input
+
+
+def add_info(component_input, info_str: str):
+ component_input.info = info_str
+ return component_input
+
+
+def add_combobox_true(component_input):
+ component_input.combobox = True
+ return component_input
+
+
+def create_input_fields_dict(inputs: list[Input], prefix: str) -> dict[str, Input]:
+ return {f"{prefix}{input_.name}": input_.to_dict() for input_ in inputs}
+
+
+def _get_google_generative_ai_inputs_and_fields():
+ try:
+ from langflow.components.models.google_generative_ai import GoogleGenerativeAIComponent
+
+ google_generative_ai_inputs = get_filtered_inputs(GoogleGenerativeAIComponent)
+ except ImportError as e:
+ msg = (
+ "Google Generative AI is not installed. Please install it with "
+ "`pip install langchain-google-generative-ai`."
+ )
+ raise ImportError(msg) from e
+ return google_generative_ai_inputs, create_input_fields_dict(google_generative_ai_inputs, "")
+
+
+def _get_openai_inputs_and_fields():
+ try:
+ from langflow.components.models.openai import OpenAIModelComponent
+
+ openai_inputs = get_filtered_inputs(OpenAIModelComponent)
+ except ImportError as e:
+ msg = "OpenAI is not installed. Please install it with `pip install langchain-openai`."
+ raise ImportError(msg) from e
+ return openai_inputs, create_input_fields_dict(openai_inputs, "")
+
+
+def _get_azure_inputs_and_fields():
+ try:
+ from langflow.components.models.azure_openai import AzureChatOpenAIComponent
+
+ azure_inputs = get_filtered_inputs(AzureChatOpenAIComponent)
+ except ImportError as e:
+ msg = "Azure OpenAI is not installed. Please install it with `pip install langchain-azure-openai`."
+ raise ImportError(msg) from e
+ return azure_inputs, create_input_fields_dict(azure_inputs, "")
+
+
+def _get_groq_inputs_and_fields():
+ try:
+ from langflow.components.models.groq import GroqModel
+
+ groq_inputs = get_filtered_inputs(GroqModel)
+ except ImportError as e:
+ msg = "Groq is not installed. Please install it with `pip install langchain-groq`."
+ raise ImportError(msg) from e
+ return groq_inputs, create_input_fields_dict(groq_inputs, "")
+
+
+def _get_anthropic_inputs_and_fields():
+ try:
+ from langflow.components.models.anthropic import AnthropicModelComponent
+
+ anthropic_inputs = get_filtered_inputs(AnthropicModelComponent)
+ except ImportError as e:
+ msg = "Anthropic is not installed. Please install it with `pip install langchain-anthropic`."
+ raise ImportError(msg) from e
+ return anthropic_inputs, create_input_fields_dict(anthropic_inputs, "")
+
+
+def _get_nvidia_inputs_and_fields():
+ try:
+ from langflow.components.models.nvidia import NVIDIAModelComponent
+
+ nvidia_inputs = get_filtered_inputs(NVIDIAModelComponent)
+ except ImportError as e:
+ msg = "NVIDIA is not installed. Please install it with `pip install langchain-nvidia`."
+ raise ImportError(msg) from e
+ return nvidia_inputs, create_input_fields_dict(nvidia_inputs, "")
+
+
+def _get_amazon_bedrock_inputs_and_fields():
+ try:
+ from langflow.components.models.amazon_bedrock import AmazonBedrockComponent
+
+ amazon_bedrock_inputs = get_filtered_inputs(AmazonBedrockComponent)
+ except ImportError as e:
+ msg = "Amazon Bedrock is not installed. Please install it with `pip install langchain-amazon-bedrock`."
+ raise ImportError(msg) from e
+ return amazon_bedrock_inputs, create_input_fields_dict(amazon_bedrock_inputs, "")
+
+
+def _get_sambanova_inputs_and_fields():
+ try:
+ from langflow.components.models.sambanova import SambaNovaComponent
+
+ sambanova_inputs = get_filtered_inputs(SambaNovaComponent)
+ except ImportError as e:
+ msg = "SambaNova is not installed. Please install it with `pip install langchain-sambanova`."
+ raise ImportError(msg) from e
+ return sambanova_inputs, create_input_fields_dict(sambanova_inputs, "")
+
+
+MODEL_PROVIDERS_DICT: dict[str, ModelProvidersDict] = {}
+
+# Try to add each provider
+try:
+ openai_inputs, openai_fields = _get_openai_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["OpenAI"] = {
+ "fields": openai_fields,
+ "inputs": openai_inputs,
+ "prefix": "",
+ "component_class": OpenAIModelComponent(),
+ "icon": OpenAIModelComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ azure_inputs, azure_fields = _get_azure_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["Azure OpenAI"] = {
+ "fields": azure_fields,
+ "inputs": azure_inputs,
+ "prefix": "",
+ "component_class": AzureChatOpenAIComponent(),
+ "icon": AzureChatOpenAIComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ groq_inputs, groq_fields = _get_groq_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["Groq"] = {
+ "fields": groq_fields,
+ "inputs": groq_inputs,
+ "prefix": "",
+ "component_class": GroqModel(),
+ "icon": GroqModel.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ anthropic_inputs, anthropic_fields = _get_anthropic_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["Anthropic"] = {
+ "fields": anthropic_fields,
+ "inputs": anthropic_inputs,
+ "prefix": "",
+ "component_class": AnthropicModelComponent(),
+ "icon": AnthropicModelComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ nvidia_inputs, nvidia_fields = _get_nvidia_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["NVIDIA"] = {
+ "fields": nvidia_fields,
+ "inputs": nvidia_inputs,
+ "prefix": "",
+ "component_class": NVIDIAModelComponent(),
+ "icon": NVIDIAModelComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ bedrock_inputs, bedrock_fields = _get_amazon_bedrock_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["Amazon Bedrock"] = {
+ "fields": bedrock_fields,
+ "inputs": bedrock_inputs,
+ "prefix": "",
+ "component_class": AmazonBedrockComponent(),
+ "icon": AmazonBedrockComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ google_generative_ai_inputs, google_generative_ai_fields = _get_google_generative_ai_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["Google Generative AI"] = {
+ "fields": google_generative_ai_fields,
+ "inputs": google_generative_ai_inputs,
+ "prefix": "",
+ "component_class": GoogleGenerativeAIComponent(),
+ "icon": GoogleGenerativeAIComponent.icon,
+ }
+except ImportError:
+ pass
+
+try:
+ sambanova_inputs, sambanova_fields = _get_sambanova_inputs_and_fields()
+ MODEL_PROVIDERS_DICT["SambaNova"] = {
+ "fields": sambanova_fields,
+ "inputs": sambanova_inputs,
+ "prefix": "",
+ "component_class": SambaNovaComponent(),
+ "icon": SambaNovaComponent.icon,
+ }
+except ImportError:
+ pass
+
+MODEL_PROVIDERS = list(MODEL_PROVIDERS_DICT.keys())
+ALL_PROVIDER_FIELDS: list[str] = [field for provider in MODEL_PROVIDERS_DICT.values() for field in provider["fields"]]
+
+MODEL_DYNAMIC_UPDATE_FIELDS = ["api_key", "model", "tool_model_enabled", "base_url", "model_name"]
+
+
+MODELS_METADATA = {
+ key: {"icon": MODEL_PROVIDERS_DICT[key]["icon"] if key in MODEL_PROVIDERS_DICT else None}
+ for key in MODEL_PROVIDERS_DICT
+}
diff --git a/langflow/src/backend/base/langflow/base/models/model_utils.py b/langflow/src/backend/base/langflow/base/models/model_utils.py
new file mode 100644
index 0000000..4f33ac6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/model_utils.py
@@ -0,0 +1,8 @@
+def get_model_name(llm, display_name: str | None = "Custom"):
+ attributes_to_check = ["model_name", "model", "model_id", "deployment_name"]
+
+ # Use a generator expression with next() to find the first matching attribute
+ model_name = next((getattr(llm, attr) for attr in attributes_to_check if hasattr(llm, attr)), None)
+
+ # If no matching attribute is found, return the class name as a fallback
+ return model_name if model_name is not None else display_name
diff --git a/langflow/src/backend/base/langflow/base/models/novita_constants.py b/langflow/src/backend/base/langflow/base/models/novita_constants.py
new file mode 100644
index 0000000..7cca009
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/novita_constants.py
@@ -0,0 +1,35 @@
+NOVITA_MODELS = [
+ "deepseek/deepseek-r1",
+ "deepseek/deepseek_v3",
+ "meta-llama/llama-3.3-70b-instruct",
+ "meta-llama/llama-3.1-8b-instruct",
+ "meta-llama/llama-3.1-70b-instruct",
+ "mistralai/mistral-nemo",
+ "Sao10K/L3-8B-Stheno-v3.2",
+ "gryphe/mythomax-l2-13b",
+ "qwen/qwen-2.5-72b-instruct",
+ "meta-llama/llama-3-8b-instruct",
+ "microsoft/wizardlm-2-8x22b",
+ "google/gemma-2-9b-it",
+ "mistralai/mistral-7b-instruct",
+ "meta-llama/llama-3-70b-instruct",
+ "openchat/openchat-7b",
+ "nousresearch/hermes-2-pro-llama-3-8b",
+ "sao10k/l3-70b-euryale-v2.1",
+ "cognitivecomputations/dolphin-mixtral-8x22b",
+ "jondurbin/airoboros-l2-70b",
+ "nousresearch/nous-hermes-llama2-13b",
+ "teknium/openhermes-2.5-mistral-7b",
+ "sophosympatheia/midnight-rose-70b",
+ "meta-llama/llama-3.1-8b-instruct-max",
+ "sao10k/l3-8b-lunaris",
+ "qwen/qwen-2-vl-72b-instruct",
+ "meta-llama/llama-3.2-1b-instruct",
+ "meta-llama/llama-3.2-11b-vision-instruct",
+ "meta-llama/llama-3.2-3b-instruct",
+ "meta-llama/llama-3.1-8b-instruct-bf16",
+ "sao10k/l31-70b-euryale-v2.2",
+ "qwen/qwen-2-7b-instruct",
+ "qwen/qwen-2-72b-instruct",
+]
+MODEL_NAMES = NOVITA_MODELS # reverse compatibility
diff --git a/langflow/src/backend/base/langflow/base/models/ollama_constants.py b/langflow/src/backend/base/langflow/base/models/ollama_constants.py
new file mode 100644
index 0000000..8620c7d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/ollama_constants.py
@@ -0,0 +1,47 @@
+# https://ollama.com/search?c=embedding
+OLLAMA_EMBEDDING_MODELS = [
+ "nomic-embed-text",
+ "mxbai-embed-large",
+ "snowflake-arctic-embed",
+ "all-minilm",
+ "bge-m3",
+ "paraphrase-multilingual",
+ "granite-embedding",
+ "jina-embeddings-v2-base-en",
+]
+# https://ollama.com/search?c=tools
+OLLAMA_TOOL_MODELS_BASE = [
+ "llama3.3",
+ "qwq",
+ "llama3.2",
+ "llama3.1",
+ "mistral",
+ "qwen2",
+ "qwen2.5",
+ "qwen2.5-coder",
+ "mistral-nemo",
+ "mixtral",
+ "command-r",
+ "command-r-plus",
+ "mistral-large",
+ "smollm2",
+ "hermes3",
+ "athene-v2",
+ "mistral-small",
+ "nemotron-mini",
+ "nemotron",
+ "llama3-groq-tool-use",
+ "granite3-dense",
+ "granite3.1-dense",
+ "aya-expanse",
+ "granite3-moe",
+ "firefunction-v2",
+]
+
+
+URL_LIST = [
+ "http://localhost:11434",
+ "http://host.docker.internal:11434",
+ "http://127.0.0.1:11434",
+ "http://0.0.0.0:11434",
+]
diff --git a/langflow/src/backend/base/langflow/base/models/openai_constants.py b/langflow/src/backend/base/langflow/base/models/openai_constants.py
new file mode 100644
index 0000000..c93a2b3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/openai_constants.py
@@ -0,0 +1,17 @@
+OPENAI_MODEL_NAMES = [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo",
+]
+OPENAI_EMBEDDING_MODEL_NAMES = [
+ "text-embedding-3-small",
+ "text-embedding-3-large",
+ "text-embedding-ada-002",
+]
+
+# Backwards compatibility
+MODEL_NAMES = OPENAI_MODEL_NAMES
diff --git a/langflow/src/backend/base/langflow/base/models/sambanova_constants.py b/langflow/src/backend/base/langflow/base/models/sambanova_constants.py
new file mode 100644
index 0000000..23d7f38
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/models/sambanova_constants.py
@@ -0,0 +1,16 @@
+SAMBANOVA_MODEL_NAMES = [
+ "Meta-Llama-3.3-70B-Instruct",
+ "Meta-Llama-3.1-8B-Instruct",
+ "Meta-Llama-3.1-70B-Instruct",
+ "Meta-Llama-3.1-405B-Instruct",
+ "Meta-Llama-3.2-1B-Instruct",
+ "Meta-Llama-3.2-3B-Instruct",
+ "Llama-3.2-11B-Vision-Instruct",
+ "Llama-3.2-90B-Vision-Instruct",
+ "Qwen2.5-Coder-32B-Instruct",
+ "Qwen2.5-72B-Instruct",
+ "QwQ-32B-Preview",
+ "Qwen2-Audio-7B-Instruct",
+]
+
+MODEL_NAMES = SAMBANOVA_MODEL_NAMES
diff --git a/langflow/src/backend/base/langflow/base/prompts/__init__.py b/langflow/src/backend/base/langflow/base/prompts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/prompts/api_utils.py b/langflow/src/backend/base/langflow/base/prompts/api_utils.py
new file mode 100644
index 0000000..011135b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/prompts/api_utils.py
@@ -0,0 +1,223 @@
+from collections import defaultdict
+from typing import Any
+
+from fastapi import HTTPException
+from langchain_core.prompts import PromptTemplate
+from loguru import logger
+
+from langflow.inputs.inputs import DefaultPromptField
+from langflow.interface.utils import extract_input_variables_from_prompt
+
+_INVALID_CHARACTERS = {
+ " ",
+ ",",
+ ".",
+ ":",
+ ";",
+ "!",
+ "?",
+ "/",
+ "\\",
+ "(",
+ ")",
+ "[",
+ "]",
+}
+
+_INVALID_NAMES = {
+ "input_variables",
+ "output_parser",
+ "partial_variables",
+ "template",
+ "template_format",
+ "validate_template",
+}
+
+
+def _is_json_like(var):
+ if var.startswith("{{") and var.endswith("}}"):
+ # If it is a double brance variable
+ # we don't want to validate any of its content
+ return True
+ # the above doesn't work on all cases because the json string can be multiline
+ # or indented which can add \n or spaces at the start or end of the string
+ # test_case_3 new_var == '\n{{\n "test": "hello",\n "text": "world"\n}}\n'
+ # what we can do is to remove the \n and spaces from the start and end of the string
+ # and then check if the string starts with {{ and ends with }}
+ var = var.strip()
+ var = var.replace("\n", "")
+ var = var.replace(" ", "")
+ # Now it should be a valid json string
+ return var.startswith("{{") and var.endswith("}}")
+
+
+def _fix_variable(var, invalid_chars, wrong_variables):
+ if not var:
+ return var, invalid_chars, wrong_variables
+ new_var = var
+
+ # Handle variables starting with a number
+ if var[0].isdigit():
+ invalid_chars.append(var[0])
+ new_var, invalid_chars, wrong_variables = _fix_variable(var[1:], invalid_chars, wrong_variables)
+
+ # Temporarily replace {{ and }} to avoid treating them as invalid
+ new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ") # noqa: RUF001
+
+ # Remove invalid characters
+ for char in new_var:
+ if char in _INVALID_CHARACTERS:
+ invalid_chars.append(char)
+ new_var = new_var.replace(char, "")
+ if var not in wrong_variables: # Avoid duplicating entries
+ wrong_variables.append(var)
+
+ # Restore {{ and }}
+ new_var = new_var.replace("ᴛᴇᴍᴘᴏᴘᴇɴ", "{{").replace("ᴛᴇᴍᴘᴄʟᴏsᴇ", "}}") # noqa: RUF001
+
+ return new_var, invalid_chars, wrong_variables
+
+
+def _check_variable(var, invalid_chars, wrong_variables, empty_variables):
+ if any(char in invalid_chars for char in var):
+ wrong_variables.append(var)
+ elif var == "":
+ empty_variables.append(var)
+ return wrong_variables, empty_variables
+
+
+def _check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables) -> None:
+ if any(var for var in input_variables if var not in fixed_variables):
+ error_message = (
+ f"Error: Input variables contain invalid characters or formats. \n"
+ f"Invalid variables: {', '.join(wrong_variables)}.\n"
+ f"Empty variables: {', '.join(empty_variables)}. \n"
+ f"Fixed variables: {', '.join(fixed_variables)}."
+ )
+ raise ValueError(error_message)
+
+
+def _check_input_variables(input_variables):
+ invalid_chars = []
+ fixed_variables = []
+ wrong_variables = []
+ empty_variables = []
+ variables_to_check = []
+
+ for var in input_variables:
+ # First, let's check if the variable is a JSON string
+ # because if it is, it won't be considered a variable
+ # and we don't need to validate it
+ if _is_json_like(var):
+ continue
+
+ new_var, wrong_variables, empty_variables = _fix_variable(var, invalid_chars, wrong_variables)
+ wrong_variables, empty_variables = _check_variable(var, _INVALID_CHARACTERS, wrong_variables, empty_variables)
+ fixed_variables.append(new_var)
+ variables_to_check.append(var)
+
+ _check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
+
+ return fixed_variables
+
+
+def validate_prompt(prompt_template: str, *, silent_errors: bool = False) -> list[str]:
+ input_variables = extract_input_variables_from_prompt(prompt_template)
+
+ # Check if there are invalid characters in the input_variables
+ input_variables = _check_input_variables(input_variables)
+ if any(var in _INVALID_NAMES for var in input_variables):
+ msg = f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
+ raise ValueError(msg)
+
+ try:
+ PromptTemplate(template=prompt_template, input_variables=input_variables)
+ except Exception as exc:
+ msg = f"Invalid prompt: {exc}"
+ logger.exception(msg)
+ if not silent_errors:
+ raise ValueError(msg) from exc
+
+ return input_variables
+
+
+def get_old_custom_fields(custom_fields, name):
+ try:
+ if len(custom_fields) == 1 and name == "":
+ # If there is only one custom field and the name is empty string
+ # then we are dealing with the first prompt request after the node was created
+ name = next(iter(custom_fields.keys()))
+
+ old_custom_fields = custom_fields[name]
+ if not old_custom_fields:
+ old_custom_fields = []
+
+ old_custom_fields = old_custom_fields.copy()
+ except KeyError:
+ old_custom_fields = []
+ custom_fields[name] = []
+ return old_custom_fields
+
+
+def add_new_variables_to_template(input_variables, custom_fields, template, name) -> None:
+ for variable in input_variables:
+ try:
+ template_field = DefaultPromptField(name=variable, display_name=variable)
+ if variable in template:
+ # Set the new field with the old value
+ template_field.value = template[variable]["value"]
+
+ template[variable] = template_field.to_dict()
+
+ # Check if variable is not already in the list before appending
+ if variable not in custom_fields[name]:
+ custom_fields[name].append(variable)
+
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name) -> None:
+ for variable in old_custom_fields:
+ if variable not in input_variables:
+ try:
+ # Remove the variable from custom_fields associated with the given name
+ if variable in custom_fields[name]:
+ custom_fields[name].remove(variable)
+
+ # Remove the variable from the template
+ template.pop(variable, None)
+
+ except Exception as exc:
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+def update_input_variables_field(input_variables, template) -> None:
+ if "input_variables" in template:
+ template["input_variables"]["value"] = input_variables
+
+
+def process_prompt_template(
+ template: str, name: str, custom_fields: dict[str, list[str]] | None, frontend_node_template: dict[str, Any]
+):
+ """Process and validate prompt template, update template and custom fields."""
+ # Validate the prompt template and extract input variables
+ input_variables = validate_prompt(template)
+
+ # Initialize custom_fields if None
+ if custom_fields is None:
+ custom_fields = defaultdict(list)
+
+ # Retrieve old custom fields
+ old_custom_fields = get_old_custom_fields(custom_fields, name)
+
+ # Add new variables to the template
+ add_new_variables_to_template(input_variables, custom_fields, frontend_node_template, name)
+
+ # Remove old variables from the template
+ remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, frontend_node_template, name)
+
+ # Update the input variables field in the template
+ update_input_variables_field(input_variables, frontend_node_template)
+
+ return input_variables
diff --git a/langflow/src/backend/base/langflow/base/prompts/utils.py b/langflow/src/backend/base/langflow/base/prompts/utils.py
new file mode 100644
index 0000000..07bc692
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/prompts/utils.py
@@ -0,0 +1,61 @@
+from copy import deepcopy
+
+from langchain_core.documents import Document
+
+from langflow.schema import Data
+
+
+def data_to_string(record: Data) -> str:
+ """Convert a record to a string.
+
+ Args:
+ record (Data): The record to convert.
+
+ Returns:
+ str: The record as a string.
+ """
+ return record.get_text()
+
+
+def dict_values_to_string(d: dict) -> dict:
+ """Converts the values of a dictionary to strings.
+
+ Args:
+ d (dict): The dictionary whose values need to be converted.
+
+ Returns:
+ dict: The dictionary with values converted to strings.
+ """
+ from langflow.schema.message import Message
+
+ # Do something similar to the above
+ d_copy = deepcopy(d)
+ for key, value in d_copy.items():
+ # it could be a list of data or documents or strings
+ if isinstance(value, list):
+ for i, item in enumerate(value):
+ if isinstance(item, Message):
+ d_copy[key][i] = item.text
+ elif isinstance(item, Data):
+ d_copy[key][i] = data_to_string(item)
+ elif isinstance(item, Document):
+ d_copy[key][i] = document_to_string(item)
+ elif isinstance(value, Message):
+ d_copy[key] = value.text
+ elif isinstance(value, Data):
+ d_copy[key] = data_to_string(value)
+ elif isinstance(value, Document):
+ d_copy[key] = document_to_string(value)
+ return d_copy
+
+
+def document_to_string(document: Document) -> str:
+ """Convert a document to a string.
+
+ Args:
+ document (Document): The document to convert.
+
+ Returns:
+ str: The document as a string.
+ """
+ return document.page_content
diff --git a/langflow/src/backend/base/langflow/base/textsplitters/__init__.py b/langflow/src/backend/base/langflow/base/textsplitters/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/textsplitters/model.py b/langflow/src/backend/base/langflow/base/textsplitters/model.py
new file mode 100644
index 0000000..40d3b92
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/textsplitters/model.py
@@ -0,0 +1,28 @@
+from abc import abstractmethod
+
+from langchain_core.documents import BaseDocumentTransformer
+from langchain_text_splitters import TextSplitter
+
+from langflow.base.document_transformers.model import LCDocumentTransformerComponent
+
+
+class LCTextSplitterComponent(LCDocumentTransformerComponent):
+ trace_type = "text_splitter"
+
+ def _validate_outputs(self) -> None:
+ required_output_methods = ["text_splitter"]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def build_document_transformer(self) -> BaseDocumentTransformer:
+ return self.build_text_splitter()
+
+ @abstractmethod
+ def build_text_splitter(self) -> TextSplitter:
+ """Build the text splitter."""
diff --git a/langflow/src/backend/base/langflow/base/tools/__init__.py b/langflow/src/backend/base/langflow/base/tools/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/tools/base.py b/langflow/src/backend/base/langflow/base/tools/base.py
new file mode 100644
index 0000000..e442ce4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/tools/base.py
@@ -0,0 +1,26 @@
+from langflow.field_typing import Tool
+
+
+def build_status_from_tool(tool: Tool):
+ """Builds a status string representation of a tool.
+
+ Args:
+ tool (Tool): The tool object to build the status for.
+
+ Returns:
+ str: The status string representation of the tool, including its name, description, arguments (if any),
+ and args_schema (if any).
+ """
+ description_repr = repr(tool.description).strip("'")
+ args_str = "\n".join(
+ [
+ f"- {arg_name}: {arg_data['description']}"
+ for arg_name, arg_data in tool.args.items()
+ if "description" in arg_data
+ ]
+ )
+ # Include args_schema information
+ args_schema_str = repr(tool.args_schema) if tool.args_schema else "None"
+ status = f"Name: {tool.name}\nDescription: {description_repr}"
+ status += f"\nArgs Schema: {args_schema_str}"
+ return status + (f"\nArguments:\n{args_str}" if args_str else "")
diff --git a/langflow/src/backend/base/langflow/base/tools/component_tool.py b/langflow/src/backend/base/langflow/base/tools/component_tool.py
new file mode 100644
index 0000000..24b5a61
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/tools/component_tool.py
@@ -0,0 +1,327 @@
+from __future__ import annotations
+
+import asyncio
+import re
+from typing import TYPE_CHECKING, Literal
+
+import pandas as pd
+from langchain_core.tools import BaseTool, ToolException
+from langchain_core.tools.structured import StructuredTool
+from loguru import logger
+from pydantic import BaseModel
+
+from langflow.base.tools.constants import TOOL_OUTPUT_NAME
+from langflow.io.schema import create_input_schema, create_input_schema_from_dict
+from langflow.schema.data import Data
+from langflow.schema.message import Message
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from langchain_core.callbacks import Callbacks
+
+ from langflow.custom.custom_component.component import Component
+ from langflow.events.event_manager import EventManager
+ from langflow.inputs.inputs import InputTypes
+ from langflow.io import Output
+ from langflow.schema.content_block import ContentBlock
+ from langflow.schema.dotdict import dotdict
+
+
+TOOL_TYPES_SET = {"Tool", "BaseTool", "StructuredTool"}
+
+
+def _get_input_type(input_: InputTypes):
+ if input_.input_types:
+ if len(input_.input_types) == 1:
+ return input_.input_types[0]
+ return " | ".join(input_.input_types)
+ return input_.field_type
+
+
+def build_description(component: Component, output: Output) -> str:
+ if not output.required_inputs:
+ logger.warning(f"Output {output.name} does not have required inputs defined")
+
+ if output.required_inputs:
+ args = ", ".join(
+ sorted(
+ [
+ f"{input_name}: {_get_input_type(component._inputs[input_name])}"
+ for input_name in output.required_inputs
+ ]
+ )
+ )
+ else:
+ args = ""
+ return f"{output.method}({args}) - {component.description}"
+
+
+async def send_message_noop(
+ message: Message,
+ text: str | None = None, # noqa: ARG001
+ background_color: str | None = None, # noqa: ARG001
+ text_color: str | None = None, # noqa: ARG001
+ icon: str | None = None, # noqa: ARG001
+ content_blocks: list[ContentBlock] | None = None, # noqa: ARG001
+ format_type: Literal["default", "error", "warning", "info"] = "default", # noqa: ARG001
+ id_: str | None = None, # noqa: ARG001
+ *,
+ allow_markdown: bool = True, # noqa: ARG001
+) -> Message:
+ """No-op implementation of send_message."""
+ return message
+
+
+def patch_components_send_message(component: Component):
+ old_send_message = component.send_message
+ component.send_message = send_message_noop # type: ignore[method-assign, assignment]
+ return old_send_message
+
+
+def _patch_send_message_decorator(component, func):
+ """Decorator to patch the send_message method of a component.
+
+ This is useful when we want to use a component as a tool, but we don't want to
+ send any messages to the UI. With this only the Component calling the tool
+ will send messages to the UI.
+ """
+
+ async def async_wrapper(*args, **kwargs):
+ original_send_message = component.send_message
+ component.send_message = send_message_noop
+ try:
+ return await func(*args, **kwargs)
+ finally:
+ component.send_message = original_send_message
+
+ def sync_wrapper(*args, **kwargs):
+ original_send_message = component.send_message
+ component.send_message = send_message_noop
+ try:
+ return func(*args, **kwargs)
+ finally:
+ component.send_message = original_send_message
+
+ return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
+
+
+def _build_output_function(component: Component, output_method: Callable, event_manager: EventManager | None = None):
+ def output_function(*args, **kwargs):
+ try:
+ if event_manager:
+ event_manager.on_build_start(data={"id": component._id})
+ component.set(*args, **kwargs)
+ result = output_method()
+ if event_manager:
+ event_manager.on_build_end(data={"id": component._id})
+ except Exception as e:
+ raise ToolException(e) from e
+
+ if isinstance(result, Message):
+ return result.get_text()
+ if isinstance(result, Data):
+ return result.data
+ if isinstance(result, BaseModel):
+ return result.model_dump()
+ return result
+
+ return _patch_send_message_decorator(component, output_function)
+
+
+def _build_output_async_function(
+ component: Component, output_method: Callable, event_manager: EventManager | None = None
+):
+ async def output_function(*args, **kwargs):
+ try:
+ if event_manager:
+ await asyncio.to_thread(event_manager.on_build_start, data={"id": component._id})
+ component.set(*args, **kwargs)
+ result = await output_method()
+ if event_manager:
+ await asyncio.to_thread(event_manager.on_build_end, data={"id": component._id})
+ except Exception as e:
+ raise ToolException(e) from e
+ if isinstance(result, Message):
+ return result.get_text()
+ if isinstance(result, Data):
+ return result.data
+ if isinstance(result, BaseModel):
+ return result.model_dump()
+ return result
+
+ return _patch_send_message_decorator(component, output_function)
+
+
+def _format_tool_name(name: str):
+ # format to '^[a-zA-Z0-9_-]+$'."
+ # to do that we must remove all non-alphanumeric characters
+
+ return re.sub(r"[^a-zA-Z0-9_-]", "-", name)
+
+
+def _add_commands_to_tool_description(tool_description: str, commands: str):
+ return f"very_time you see one of those commands {commands} run the tool. tool description is {tool_description}"
+
+
+class ComponentToolkit:
+ def __init__(self, component: Component, metadata: pd.DataFrame | None = None):
+ self.component = component
+ self.metadata = metadata
+
+ def _should_skip_output(self, output: Output) -> bool:
+ """Determines if an output should be skipped when creating tools.
+
+ Args:
+ output (Output): The output to check.
+
+ Returns:
+ bool: True if the output should be skipped, False otherwise.
+
+ The output will be skipped if:
+ - tool_mode is False (output is not meant to be used as a tool)
+ - output name matches TOOL_OUTPUT_NAME
+ - output types contain any of the tool types in TOOL_TYPES_SET
+ """
+ return not output.tool_mode or (
+ output.name == TOOL_OUTPUT_NAME or any(tool_type in output.types for tool_type in TOOL_TYPES_SET)
+ )
+
+ def get_tools(
+ self,
+ tool_name: str | None = None,
+ tool_description: str | None = None,
+ callbacks: Callbacks | None = None,
+ flow_mode_inputs: list[dotdict] | None = None,
+ ) -> list[BaseTool]:
+ tools = []
+ for output in self.component.outputs:
+ if self._should_skip_output(output):
+ continue
+
+ if not output.method:
+ msg = f"Output {output.name} does not have a method defined"
+ raise ValueError(msg)
+
+ output_method: Callable = getattr(self.component, output.method)
+ args_schema = None
+ tool_mode_inputs = [_input for _input in self.component.inputs if getattr(_input, "tool_mode", False)]
+ if flow_mode_inputs:
+ args_schema = create_input_schema_from_dict(
+ inputs=flow_mode_inputs,
+ param_key="flow_tweak_data",
+ )
+ elif output.required_inputs:
+ inputs = [
+ self.component._inputs[input_name]
+ for input_name in output.required_inputs
+ if getattr(self.component, input_name) is None
+ ]
+ # If any of the required inputs are not in tool mode, this means
+ # that when the tool is called it will raise an error.
+ # so we should raise an error here.
+ if not all(getattr(_input, "tool_mode", False) for _input in inputs):
+ non_tool_mode_inputs = [
+ input_.name
+ for input_ in inputs
+ if not getattr(input_, "tool_mode", False) and input_.name is not None
+ ]
+ non_tool_mode_inputs_str = ", ".join(non_tool_mode_inputs)
+ msg = (
+ f"Output '{output.name}' requires inputs that are not in tool mode. "
+ f"The following inputs are not in tool mode: {non_tool_mode_inputs_str}. "
+ "Please ensure all required inputs are set to tool mode."
+ )
+ raise ValueError(msg)
+ args_schema = create_input_schema(inputs)
+ elif tool_mode_inputs:
+ args_schema = create_input_schema(tool_mode_inputs)
+ else:
+ args_schema = create_input_schema(self.component.inputs)
+
+ name = f"{self.component.name or self.component.__class__.__name__ or ''}.{output.method}".strip(".")
+ formatted_name = _format_tool_name(name)
+ event_manager = self.component._event_manager
+ if asyncio.iscoroutinefunction(output_method):
+ tools.append(
+ StructuredTool(
+ name=formatted_name,
+ description=build_description(self.component, output),
+ coroutine=_build_output_async_function(self.component, output_method, event_manager),
+ args_schema=args_schema,
+ handle_tool_error=True,
+ callbacks=callbacks,
+ tags=[formatted_name],
+ )
+ )
+ else:
+ tools.append(
+ StructuredTool(
+ name=formatted_name,
+ description=build_description(self.component, output),
+ func=_build_output_function(self.component, output_method, event_manager),
+ args_schema=args_schema,
+ handle_tool_error=True,
+ callbacks=callbacks,
+ tags=[formatted_name],
+ )
+ )
+ if len(tools) == 1 and (tool_name or tool_description):
+ tool = tools[0]
+ tool.name = _format_tool_name(str(tool_name)) or tool.name
+ tool.description = tool_description or tool.description
+ tool.tags = [tool.name]
+ elif flow_mode_inputs and (tool_name or tool_description):
+ for tool in tools:
+ tool.name = _format_tool_name(str(tool_name) + "_" + str(tool.name)) or tool.name
+ tool.description = (
+ str(tool_description) + " Output details: " + str(tool.description)
+ ) or tool.description
+ tool.tags = [tool.name]
+ elif tool_name or tool_description:
+ msg = (
+ "When passing a tool name or description, there must be only one tool, "
+ f"but {len(tools)} tools were found."
+ )
+ raise ValueError(msg)
+ return tools
+
+ def get_tools_metadata_dictionary(self) -> dict:
+ if isinstance(self.metadata, pd.DataFrame):
+ try:
+ return {
+ record["tags"][0]: record
+ for record in self.metadata.to_dict(orient="records")
+ if record.get("tags")
+ }
+ except (KeyError, IndexError) as e:
+ msg = "Error processing metadata records: " + str(e)
+ raise ValueError(msg) from e
+ return {}
+
+ def update_tools_metadata(
+ self,
+ tools: list[BaseTool | StructuredTool],
+ ) -> list[BaseTool]:
+ # update the tool_name and description according to the name and secriotion mentioned in the list
+ if isinstance(self.metadata, pd.DataFrame):
+ metadata_dict = self.get_tools_metadata_dictionary()
+ for tool in tools:
+ if isinstance(tool, StructuredTool | BaseTool) and tool.tags:
+ try:
+ tag = tool.tags[0]
+ except IndexError:
+ msg = "Tool tags cannot be empty."
+ raise ValueError(msg) from None
+ if tag in metadata_dict:
+ tool_metadata = metadata_dict[tag]
+ tool.name = tool_metadata.get("name", tool.name)
+ tool.description = tool_metadata.get("description", tool.description)
+ if tool_metadata.get("commands"):
+ tool.description = _add_commands_to_tool_description(
+ tool.description, tool_metadata.get("commands")
+ )
+ else:
+ msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}"
+ raise TypeError(msg)
+ return tools
diff --git a/langflow/src/backend/base/langflow/base/tools/constants.py b/langflow/src/backend/base/langflow/base/tools/constants.py
new file mode 100644
index 0000000..cfc1bcf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/tools/constants.py
@@ -0,0 +1,40 @@
+from langflow.schema.table import EditMode
+
+TOOL_OUTPUT_NAME = "component_as_tool"
+TOOL_OUTPUT_DISPLAY_NAME = "Toolset"
+TOOLS_METADATA_INPUT_NAME = "tools_metadata"
+TOOL_TABLE_SCHEMA = [
+ {
+ "name": "name",
+ "display_name": "Tool Name",
+ "type": "str",
+ "description": "Specify the name of the tool.",
+ "sortable": False,
+ "filterable": False,
+ "edit_mode": EditMode.INLINE,
+ "hidden": False,
+ },
+ {
+ "name": "description",
+ "display_name": "Tool Description",
+ "type": "str",
+ "description": "Describe the purpose of the tool.",
+ "sortable": False,
+ "filterable": False,
+ "edit_mode": EditMode.POPOVER,
+ "hidden": False,
+ },
+ {
+ "name": "tags",
+ "display_name": "Tool Identifiers",
+ "type": "str",
+ "description": ("The default identifiers for the tools and cannot be changed."),
+ "disable_edit": True,
+ "sortable": False,
+ "filterable": False,
+ "edit_mode": EditMode.INLINE,
+ "hidden": True,
+ },
+]
+
+TOOLS_METADATA_INFO = "Modify tool names and descriptions to help agents understand when to use each tool."
diff --git a/langflow/src/backend/base/langflow/base/tools/flow_tool.py b/langflow/src/backend/base/langflow/base/tools/flow_tool.py
new file mode 100644
index 0000000..53a43c6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/tools/flow_tool.py
@@ -0,0 +1,131 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from langchain_core.tools import BaseTool, ToolException
+from loguru import logger
+from typing_extensions import override
+
+from langflow.base.flow_processing.utils import build_data_from_result_data, format_flow_output_data
+from langflow.graph.graph.base import Graph # cannot be a part of TYPE_CHECKING # noqa: TC001
+from langflow.graph.vertex.base import Vertex # cannot be a part of TYPE_CHECKING # noqa: TC001
+from langflow.helpers.flow import build_schema_from_inputs, get_arg_names, get_flow_inputs, run_flow
+from langflow.utils.async_helpers import run_until_complete
+
+if TYPE_CHECKING:
+ from langchain_core.runnables import RunnableConfig
+ from pydantic.v1 import BaseModel
+
+
+class FlowTool(BaseTool):
+ name: str
+ description: str
+ graph: Graph | None = None
+ flow_id: str | None = None
+ user_id: str | None = None
+ session_id: str | None = None
+ inputs: list[Vertex] = []
+ get_final_results_only: bool = True
+
+ @property
+ def args(self) -> dict:
+ schema = self.get_input_schema()
+ return schema.schema()["properties"]
+
+ @override
+ def get_input_schema( # type: ignore[misc]
+ self, config: RunnableConfig | None = None
+ ) -> type[BaseModel]:
+ """The tool's input schema."""
+ if self.args_schema is not None:
+ return self.args_schema
+ if self.graph is not None:
+ return build_schema_from_inputs(self.name, get_flow_inputs(self.graph))
+ msg = "No input schema available."
+ raise ToolException(msg)
+
+ def _run(
+ self,
+ *args: Any,
+ **kwargs: Any,
+ ) -> str:
+ """Use the tool."""
+ args_names = get_arg_names(self.inputs)
+ if len(args_names) == len(args):
+ kwargs = {arg["arg_name"]: arg_value for arg, arg_value in zip(args_names, args, strict=True)}
+ elif len(args_names) != len(args) and len(args) != 0:
+ msg = "Number of arguments does not match the number of inputs. Pass keyword arguments instead."
+ raise ToolException(msg)
+ tweaks = {arg["component_name"]: kwargs[arg["arg_name"]] for arg in args_names}
+
+ run_outputs = run_until_complete(
+ run_flow(
+ graph=self.graph,
+ tweaks={key: {"input_value": value} for key, value in tweaks.items()},
+ flow_id=self.flow_id,
+ user_id=self.user_id,
+ session_id=self.session_id,
+ )
+ )
+ if not run_outputs:
+ return "No output"
+ run_output = run_outputs[0]
+
+ data = []
+ if run_output is not None:
+ for output in run_output.outputs:
+ if output:
+ data.extend(build_data_from_result_data(output))
+ return format_flow_output_data(data)
+
+ def validate_inputs(self, args_names: list[dict[str, str]], args: Any, kwargs: Any):
+ """Validate the inputs."""
+ if len(args) > 0 and len(args) != len(args_names):
+ msg = "Number of positional arguments does not match the number of inputs. Pass keyword arguments instead."
+ raise ToolException(msg)
+
+ if len(args) == len(args_names):
+ kwargs = {arg_name["arg_name"]: arg_value for arg_name, arg_value in zip(args_names, args, strict=True)}
+
+ missing_args = [arg["arg_name"] for arg in args_names if arg["arg_name"] not in kwargs]
+ if missing_args:
+ msg = f"Missing required arguments: {', '.join(missing_args)}"
+ raise ToolException(msg)
+
+ return kwargs
+
+ def build_tweaks_dict(self, args, kwargs):
+ args_names = get_arg_names(self.inputs)
+ kwargs = self.validate_inputs(args_names=args_names, args=args, kwargs=kwargs)
+ return {arg["component_name"]: kwargs[arg["arg_name"]] for arg in args_names}
+
+ async def _arun(
+ self,
+ *args: Any,
+ **kwargs: Any,
+ ) -> str:
+ """Use the tool asynchronously."""
+ tweaks = self.build_tweaks_dict(args, kwargs)
+ try:
+ run_id = self.graph.run_id if hasattr(self, "graph") and self.graph else None
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).warning("Failed to set run_id")
+ run_id = None
+ run_outputs = await run_flow(
+ tweaks={key: {"input_value": value} for key, value in tweaks.items()},
+ flow_id=self.flow_id,
+ user_id=self.user_id,
+ run_id=run_id,
+ session_id=self.session_id,
+ graph=self.graph,
+ )
+ if not run_outputs:
+ return "No output"
+ run_output = run_outputs[0]
+
+ data = []
+ if run_output is not None:
+ for output in run_output.outputs:
+ if output:
+ data.extend(build_data_from_result_data(output))
+ return format_flow_output_data(data)
diff --git a/langflow/src/backend/base/langflow/base/tools/run_flow.py b/langflow/src/backend/base/langflow/base/tools/run_flow.py
new file mode 100644
index 0000000..d13aa45
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/tools/run_flow.py
@@ -0,0 +1,219 @@
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+
+from loguru import logger
+
+from langflow.base.tools.constants import TOOLS_METADATA_INPUT_NAME
+from langflow.custom import Component
+from langflow.custom.custom_component.component import _get_component_toolkit
+from langflow.field_typing import Tool
+from langflow.graph.graph.base import Graph
+from langflow.graph.vertex.base import Vertex
+from langflow.helpers.flow import get_flow_inputs
+from langflow.inputs.inputs import (
+ DropdownInput,
+ InputTypes,
+ MessageInput,
+)
+from langflow.schema import Data, dotdict
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.message import Message
+from langflow.template import Output
+
+if TYPE_CHECKING:
+ from langflow.base.tools.component_tool import ComponentToolkit
+
+
+class RunFlowBaseComponent(Component):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.add_tool_output = True
+
+ _base_inputs: list[InputTypes] = [
+ DropdownInput(
+ name="flow_name_selected",
+ display_name="Flow Name",
+ info="The name of the flow to run.",
+ options=[],
+ real_time_refresh=True,
+ refresh_button=True,
+ value=None,
+ ),
+ MessageInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID to run the flow in.",
+ value="",
+ advanced=True,
+ ),
+ ]
+ _base_outputs: list[Output] = [
+ Output(name="flow_outputs_data", display_name="Flow Data Output", method="data_output", hidden=True),
+ Output(
+ name="flow_outputs_dataframe", display_name="Flow Dataframe Output", method="dataframe_output", hidden=True
+ ),
+ Output(name="flow_outputs_message", display_name="Flow Message Output", method="message_output"),
+ ]
+ default_keys = ["code", "_type", "flow_name_selected", "session_id"]
+ FLOW_INPUTS: list[dotdict] = []
+ flow_tweak_data: dict = {}
+
+ @abstractmethod
+ async def run_flow_with_tweaks(self) -> list[Data]:
+ """Run the flow with tweaks."""
+
+ async def data_output(self) -> Data:
+ """Return the data output."""
+ run_outputs = await self.run_flow_with_tweaks()
+ first_output = run_outputs[0]
+
+ if isinstance(first_output, Data):
+ return first_output
+
+ message_data = first_output.outputs[0].results["message"].data
+ return Data(data=message_data)
+
+ async def dataframe_output(self) -> DataFrame:
+ """Return the dataframe output."""
+ run_outputs = await self.run_flow_with_tweaks()
+ first_output = run_outputs[0]
+
+ if isinstance(first_output, DataFrame):
+ return first_output
+
+ message_data = first_output.outputs[0].results["message"].data
+ return DataFrame(data=message_data if isinstance(message_data, list) else [message_data])
+
+ async def message_output(self) -> Message:
+ """Return the message output."""
+ run_outputs = await self.run_flow_with_tweaks()
+ message_result = run_outputs[0].outputs[0].results["message"]
+
+ if isinstance(message_result, Message):
+ return message_result
+
+ if isinstance(message_result, str):
+ return Message(content=message_result)
+
+ return Message(content=message_result.data["text"])
+
+ async def get_flow_names(self) -> list[str]:
+ # TODO: get flfow ID with flow name
+ flow_data = await self.alist_flows()
+ return [flow_data.data["name"] for flow_data in flow_data]
+
+ async def get_flow(self, flow_name_selected: str) -> Data | None:
+ # get flow from flow id
+ flow_datas = await self.alist_flows()
+ for flow_data in flow_datas:
+ if flow_data.data["name"] == flow_name_selected:
+ return flow_data
+ return None
+
+ async def get_graph(self, flow_name_selected: str | None = None) -> Graph:
+ if flow_name_selected:
+ flow_data = await self.get_flow(flow_name_selected)
+ if flow_data:
+ return Graph.from_payload(flow_data.data["data"])
+ msg = "Flow not found"
+ raise ValueError(msg)
+ # Ensure a Graph is always returned or an exception is raised
+ msg = "No valid flow JSON or flow name selected."
+ raise ValueError(msg)
+
+ def get_new_fields_from_graph(self, graph: Graph) -> list[dotdict]:
+ inputs = get_flow_inputs(graph)
+ return self.get_new_fields(inputs)
+
+ def update_build_config_from_graph(self, build_config: dotdict, graph: Graph):
+ try:
+ # Get all inputs from the graph
+ new_fields = self.get_new_fields_from_graph(graph)
+ old_fields = self.get_old_fields(build_config, new_fields)
+ self.delete_fields(build_config, old_fields)
+ build_config = self.add_new_fields(build_config, new_fields)
+
+ except Exception as e:
+ msg = "Error updating build config from graph"
+ logger.exception(msg)
+ raise RuntimeError(msg) from e
+
+ def get_new_fields(self, inputs_vertex: list[Vertex]) -> list[dotdict]:
+ new_fields: list[dotdict] = []
+
+ for vertex in inputs_vertex:
+ field_template = vertex.data.get("node", {}).get("template", {})
+ field_order = vertex.data.get("node", {}).get("field_order", [])
+ if field_order and field_template:
+ new_vertex_inputs = [
+ dotdict(
+ {
+ **field_template[input_name],
+ "display_name": vertex.display_name + " - " + field_template[input_name]["display_name"],
+ "name": f"{vertex.id}~{input_name}",
+ "tool_mode": not (field_template[input_name].get("advanced", False)),
+ }
+ )
+ for input_name in field_order
+ if input_name in field_template
+ ]
+ new_fields += new_vertex_inputs
+ return new_fields
+
+ def add_new_fields(self, build_config: dotdict, new_fields: list[dotdict]) -> dotdict:
+ """Add new fields to the build_config."""
+ for field in new_fields:
+ build_config[field["name"]] = field
+ return build_config
+
+ def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:
+ """Delete specified fields from build_config."""
+ if isinstance(fields, dict):
+ fields = list(fields.keys())
+ for field in fields:
+ build_config.pop(field, None)
+
+ def get_old_fields(self, build_config: dotdict, new_fields: list[dotdict]) -> list[str]:
+ """Get fields that are in build_config but not in new_fields."""
+ return [
+ field
+ for field in build_config
+ if field not in [new_field["name"] for new_field in new_fields] + self.default_keys
+ ]
+
+ async def get_required_data(self, flow_name_selected):
+ self.flow_data = await self.alist_flows()
+ for flow_data in self.flow_data:
+ if flow_data.data["name"] == flow_name_selected:
+ graph = Graph.from_payload(flow_data.data["data"])
+ new_fields = self.get_new_fields_from_graph(graph)
+ new_fields = self.update_input_types(new_fields)
+
+ return flow_data.data["description"], [field for field in new_fields if field.get("tool_mode") is True]
+ return None
+
+ def update_input_types(self, fields: list[dotdict]) -> list[dotdict]:
+ for field in fields:
+ if isinstance(field, dict):
+ if field.get("input_types") is None:
+ field["input_types"] = []
+ elif hasattr(field, "input_types") and field.input_types is None:
+ field.input_types = []
+ return fields
+
+ async def to_toolkit(self) -> list[Tool]:
+ component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
+ flow_description, tool_mode_inputs = await self.get_required_data(self.flow_name_selected)
+ # # convert list of dicts to list of dotdicts
+ tool_mode_inputs = [dotdict(field) for field in tool_mode_inputs]
+ tools = component_toolkit(component=self).get_tools(
+ tool_name=f"{self.flow_name_selected}_tool",
+ tool_description=(
+ f"Tool designed to execute the flow '{self.flow_name_selected}'. Flow details: {flow_description}."
+ ),
+ callbacks=self.get_langchain_callbacks(),
+ flow_mode_inputs=tool_mode_inputs,
+ )
+ if hasattr(self, TOOLS_METADATA_INPUT_NAME):
+ tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
+ return tools
diff --git a/langflow/src/backend/base/langflow/base/vectorstores/__init__.py b/langflow/src/backend/base/langflow/base/vectorstores/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/base/vectorstores/model.py b/langflow/src/backend/base/langflow/base/vectorstores/model.py
new file mode 100644
index 0000000..c203aad
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/vectorstores/model.py
@@ -0,0 +1,170 @@
+from abc import abstractmethod
+from functools import wraps
+from typing import TYPE_CHECKING
+
+from langflow.custom import Component
+from langflow.field_typing import Text, VectorStore
+from langflow.helpers.data import docs_to_data
+from langflow.inputs.inputs import BoolInput
+from langflow.io import DataInput, MultilineInput, Output
+from langflow.schema import Data, DataFrame
+
+if TYPE_CHECKING:
+ from langchain_core.documents import Document
+
+
+def check_cached_vector_store(f):
+ """Decorator to check for cached vector stores, and returns them if they exist.
+
+ Note: caching only occurs during the execution of a component - they do not persist
+ across separate invocations of the component. This method exists so that components with
+ multiple output methods share the same vector store during the same invocation of the
+ component.
+ """
+
+ @wraps(f)
+ def check_cached(self, *args, **kwargs):
+ should_cache = getattr(self, "should_cache_vector_store", True)
+
+ if should_cache and self._cached_vector_store is not None:
+ return self._cached_vector_store
+
+ result = f(self, *args, **kwargs)
+ self._cached_vector_store = result
+ return result
+
+ check_cached.is_cached_vector_store_checked = True
+ return check_cached
+
+
+class LCVectorStoreComponent(Component):
+ # Used to ensure a single vector store is built for each run of the flow
+ _cached_vector_store: VectorStore | None = None
+
+ def __init_subclass__(cls, **kwargs):
+ """Enforces the check cached decorator on all subclasses."""
+ super().__init_subclass__(**kwargs)
+ if hasattr(cls, "build_vector_store"):
+ method = cls.build_vector_store
+ if not hasattr(method, "is_cached_vector_store_checked"):
+ msg = (
+ f"The method 'build_vector_store' in class {cls.__name__} "
+ "must be decorated with @check_cached_vector_store"
+ )
+ raise TypeError(msg)
+
+ trace_type = "retriever"
+
+ inputs = [
+ DataInput(
+ name="ingest_data",
+ display_name="Ingest Data",
+ ),
+ MultilineInput(
+ name="search_query",
+ display_name="Search Query",
+ tool_mode=True,
+ ),
+ BoolInput(
+ name="should_cache_vector_store",
+ display_name="Cache Vector Store",
+ value=True,
+ advanced=True,
+ info="If True, the vector store will be cached for the current build of the component. "
+ "This is useful for components that have multiple output methods and want to share the same vector store.",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Search Results",
+ name="search_results",
+ method="search_documents",
+ ),
+ Output(display_name="DataFrame", name="dataframe", method="as_dataframe"),
+ ]
+
+ def _validate_outputs(self) -> None:
+ # At least these three outputs must be defined
+ required_output_methods = [
+ "search_documents",
+ "build_vector_store",
+ ]
+ output_names = [output.name for output in self.outputs]
+ for method_name in required_output_methods:
+ if method_name not in output_names:
+ msg = f"Output with name '{method_name}' must be defined."
+ raise ValueError(msg)
+ if not hasattr(self, method_name):
+ msg = f"Method '{method_name}' must be defined."
+ raise ValueError(msg)
+
+ def search_with_vector_store(
+ self,
+ input_value: Text,
+ search_type: str,
+ vector_store: VectorStore,
+ k=10,
+ **kwargs,
+ ) -> list[Data]:
+ """Search for data in the vector store based on the input value and search type.
+
+ Args:
+ input_value (Text): The input value to search for.
+ search_type (str): The type of search to perform.
+ vector_store (VectorStore): The vector store to search in.
+ k (int): The number of results to return.
+ **kwargs: Additional keyword arguments to pass to the vector store search method.
+
+ Returns:
+ List[Data]: A list of data matching the search criteria.
+
+ Raises:
+ ValueError: If invalid inputs are provided.
+ """
+ docs: list[Document] = []
+ if input_value and isinstance(input_value, str) and hasattr(vector_store, "search"):
+ docs = vector_store.search(query=input_value, search_type=search_type.lower(), k=k, **kwargs)
+ else:
+ msg = "Invalid inputs provided."
+ raise ValueError(msg)
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+
+ def search_documents(self) -> list[Data]:
+ """Search for documents in the vector store."""
+ if self._cached_vector_store is not None:
+ vector_store = self._cached_vector_store
+ else:
+ vector_store = self.build_vector_store()
+ self._cached_vector_store = vector_store
+
+ search_query: str = self.search_query
+ if not search_query:
+ self.status = ""
+ return []
+
+ self.log(f"Search input: {search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ search_results = self.search_with_vector_store(
+ search_query, self.search_type, vector_store, k=self.number_of_results
+ )
+ self.status = search_results
+ return search_results
+
+ def as_dataframe(self) -> DataFrame:
+ return DataFrame(self.search_documents())
+
+ def get_retriever_kwargs(self):
+ """Get the retriever kwargs. Implementations can override this method to provide custom retriever kwargs."""
+ return {}
+
+ @abstractmethod
+ @check_cached_vector_store
+ def build_vector_store(self) -> VectorStore:
+ """Builds the Vector Store object."""
+ msg = "build_vector_store method must be implemented."
+ raise NotImplementedError(msg)
diff --git a/langflow/src/backend/base/langflow/base/vectorstores/utils.py b/langflow/src/backend/base/langflow/base/vectorstores/utils.py
new file mode 100644
index 0000000..d64891e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/base/vectorstores/utils.py
@@ -0,0 +1,22 @@
+from langflow.schema import Data
+
+
+def chroma_collection_to_data(collection_dict: dict):
+ """Converts a collection of chroma vectors into a list of data.
+
+ Args:
+ collection_dict (dict): A dictionary containing the collection of chroma vectors.
+
+ Returns:
+ list: A list of data, where each record represents a document in the collection.
+ """
+ data = []
+ for i, doc in enumerate(collection_dict["documents"]):
+ data_dict = {
+ "id": collection_dict["ids"][i],
+ "text": doc,
+ }
+ if ("metadatas" in collection_dict) and collection_dict["metadatas"][i]:
+ data_dict.update(collection_dict["metadatas"][i].items())
+ data.append(Data(**data_dict))
+ return data
diff --git a/langflow/src/backend/base/langflow/components/Notion/__init__.py b/langflow/src/backend/base/langflow/components/Notion/__init__.py
new file mode 100644
index 0000000..9e50918
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/__init__.py
@@ -0,0 +1,19 @@
+from .add_content_to_page import AddContentToPage
+from .create_page import NotionPageCreator
+from .list_database_properties import NotionDatabaseProperties
+from .list_pages import NotionListPages
+from .list_users import NotionUserList
+from .page_content_viewer import NotionPageContent
+from .search import NotionSearch
+from .update_page_property import NotionPageUpdate
+
+__all__ = [
+ "AddContentToPage",
+ "NotionDatabaseProperties",
+ "NotionListPages",
+ "NotionPageContent",
+ "NotionPageCreator",
+ "NotionPageUpdate",
+ "NotionSearch",
+ "NotionUserList",
+]
diff --git a/langflow/src/backend/base/langflow/components/Notion/add_content_to_page.py b/langflow/src/backend/base/langflow/components/Notion/add_content_to_page.py
new file mode 100644
index 0000000..935f1e5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/add_content_to_page.py
@@ -0,0 +1,269 @@
+import json
+from typing import Any
+
+import requests
+from bs4 import BeautifulSoup
+from langchain.tools import StructuredTool
+from loguru import logger
+from markdown import markdown
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+MIN_ROWS_IN_TABLE = 3
+
+
+class AddContentToPage(LCToolComponent):
+ display_name: str = "Add Content to Page "
+ description: str = "Convert markdown text to Notion blocks and append them to a Notion page."
+ documentation: str = "https://developers.notion.com/reference/patch-block-children"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ MultilineInput(
+ name="markdown_text",
+ display_name="Markdown Text",
+ info="The markdown text to convert to Notion blocks.",
+ ),
+ StrInput(
+ name="block_id",
+ display_name="Page/Block ID",
+ info="The ID of the page/block to add the content.",
+ ),
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ ]
+
+ class AddContentToPageSchema(BaseModel):
+ markdown_text: str = Field(..., description="The markdown text to convert to Notion blocks.")
+ block_id: str = Field(..., description="The ID of the page/block to add the content.")
+
+ def run_model(self) -> Data:
+ result = self._add_content_to_page(self.markdown_text, self.block_id)
+ return Data(data=result, text=json.dumps(result))
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="add_content_to_notion_page",
+ description="Convert markdown text to Notion blocks and append them to a Notion page.",
+ func=self._add_content_to_page,
+ args_schema=self.AddContentToPageSchema,
+ )
+
+ def _add_content_to_page(self, markdown_text: str, block_id: str) -> dict[str, Any] | str:
+ try:
+ html_text = markdown(markdown_text)
+ soup = BeautifulSoup(html_text, "html.parser")
+ blocks = self.process_node(soup)
+
+ url = f"https://api.notion.com/v1/blocks/{block_id}/children"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Content-Type": "application/json",
+ "Notion-Version": "2022-06-28",
+ }
+
+ data = {
+ "children": blocks,
+ }
+
+ response = requests.patch(url, headers=headers, json=data, timeout=10)
+ response.raise_for_status()
+
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ error_message = f"Error: Failed to add content to Notion page. {e}"
+ if hasattr(e, "response") and e.response is not None:
+ error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
+ return error_message
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error adding content to Notion page")
+ return f"Error: An unexpected error occurred while adding content to Notion page. {e}"
+
+ def process_node(self, node):
+ blocks = []
+ if isinstance(node, str):
+ text = node.strip()
+ if text:
+ if text.startswith("#"):
+ heading_level = text.count("#", 0, 6)
+ heading_text = text[heading_level:].strip()
+ if heading_level in range(3):
+ blocks.append(self.create_block(f"heading_{heading_level + 1}", heading_text))
+ else:
+ blocks.append(self.create_block("paragraph", text))
+ elif node.name == "h1":
+ blocks.append(self.create_block("heading_1", node.get_text(strip=True)))
+ elif node.name == "h2":
+ blocks.append(self.create_block("heading_2", node.get_text(strip=True)))
+ elif node.name == "h3":
+ blocks.append(self.create_block("heading_3", node.get_text(strip=True)))
+ elif node.name == "p":
+ code_node = node.find("code")
+ if code_node:
+ code_text = code_node.get_text()
+ language, code = self.extract_language_and_code(code_text)
+ blocks.append(self.create_block("code", code, language=language))
+ elif self.is_table(str(node)):
+ blocks.extend(self.process_table(node))
+ else:
+ blocks.append(self.create_block("paragraph", node.get_text(strip=True)))
+ elif node.name == "ul":
+ blocks.extend(self.process_list(node, "bulleted_list_item"))
+ elif node.name == "ol":
+ blocks.extend(self.process_list(node, "numbered_list_item"))
+ elif node.name == "blockquote":
+ blocks.append(self.create_block("quote", node.get_text(strip=True)))
+ elif node.name == "hr":
+ blocks.append(self.create_block("divider", ""))
+ elif node.name == "img":
+ blocks.append(self.create_block("image", "", image_url=node.get("src")))
+ elif node.name == "a":
+ blocks.append(self.create_block("bookmark", node.get_text(strip=True), link_url=node.get("href")))
+ elif node.name == "table":
+ blocks.extend(self.process_table(node))
+
+ for child in node.children:
+ if isinstance(child, str):
+ continue
+ blocks.extend(self.process_node(child))
+
+ return blocks
+
+ def extract_language_and_code(self, code_text):
+ lines = code_text.split("\n")
+ language = lines[0].strip()
+ code = "\n".join(lines[1:]).strip()
+ return language, code
+
+ def is_code_block(self, text):
+ return text.startswith("```")
+
+ def extract_code_block(self, text):
+ lines = text.split("\n")
+ language = lines[0].strip("`").strip()
+ code = "\n".join(lines[1:]).strip("`").strip()
+ return language, code
+
+ def is_table(self, text):
+ rows = text.split("\n")
+ if len(rows) < MIN_ROWS_IN_TABLE:
+ return False
+
+ has_separator = False
+ for i, row in enumerate(rows):
+ if "|" in row:
+ cells = [cell.strip() for cell in row.split("|")]
+ cells = [cell for cell in cells if cell] # Remove empty cells
+ if i == 1 and all(set(cell) <= set("-|") for cell in cells):
+ has_separator = True
+ elif not cells:
+ return False
+
+ return has_separator
+
+ def process_list(self, node, list_type):
+ blocks = []
+ for item in node.find_all("li"):
+ item_text = item.get_text(strip=True)
+ checked = item_text.startswith("[x]")
+ is_checklist = item_text.startswith("[ ]") or checked
+
+ if is_checklist:
+ item_text = item_text.replace("[x]", "").replace("[ ]", "").strip()
+ blocks.append(self.create_block("to_do", item_text, checked=checked))
+ else:
+ blocks.append(self.create_block(list_type, item_text))
+ return blocks
+
+ def process_table(self, node):
+ blocks = []
+ header_row = node.find("thead").find("tr") if node.find("thead") else None
+ body_rows = node.find("tbody").find_all("tr") if node.find("tbody") else []
+
+ if header_row or body_rows:
+ table_width = max(
+ len(header_row.find_all(["th", "td"])) if header_row else 0,
+ *(len(row.find_all(["th", "td"])) for row in body_rows),
+ )
+
+ table_block = self.create_block("table", "", table_width=table_width, has_column_header=bool(header_row))
+ blocks.append(table_block)
+
+ if header_row:
+ header_cells = [cell.get_text(strip=True) for cell in header_row.find_all(["th", "td"])]
+ header_row_block = self.create_block("table_row", header_cells)
+ blocks.append(header_row_block)
+
+ for row in body_rows:
+ cells = [cell.get_text(strip=True) for cell in row.find_all(["th", "td"])]
+ row_block = self.create_block("table_row", cells)
+ blocks.append(row_block)
+
+ return blocks
+
+ def create_block(self, block_type: str, content: str, **kwargs) -> dict[str, Any]:
+ block: dict[str, Any] = {
+ "object": "block",
+ "type": block_type,
+ block_type: {},
+ }
+
+ if block_type in {
+ "paragraph",
+ "heading_1",
+ "heading_2",
+ "heading_3",
+ "bulleted_list_item",
+ "numbered_list_item",
+ "quote",
+ }:
+ block[block_type]["rich_text"] = [
+ {
+ "type": "text",
+ "text": {
+ "content": content,
+ },
+ }
+ ]
+ elif block_type == "to_do":
+ block[block_type]["rich_text"] = [
+ {
+ "type": "text",
+ "text": {
+ "content": content,
+ },
+ }
+ ]
+ block[block_type]["checked"] = kwargs.get("checked", False)
+ elif block_type == "code":
+ block[block_type]["rich_text"] = [
+ {
+ "type": "text",
+ "text": {
+ "content": content,
+ },
+ }
+ ]
+ block[block_type]["language"] = kwargs.get("language", "plain text")
+ elif block_type == "image":
+ block[block_type] = {"type": "external", "external": {"url": kwargs.get("image_url", "")}}
+ elif block_type == "divider":
+ pass
+ elif block_type == "bookmark":
+ block[block_type]["url"] = kwargs.get("link_url", "")
+ elif block_type == "table":
+ block[block_type]["table_width"] = kwargs.get("table_width", 0)
+ block[block_type]["has_column_header"] = kwargs.get("has_column_header", False)
+ block[block_type]["has_row_header"] = kwargs.get("has_row_header", False)
+ elif block_type == "table_row":
+ block[block_type]["cells"] = [[{"type": "text", "text": {"content": cell}} for cell in content]]
+
+ return block
diff --git a/langflow/src/backend/base/langflow/components/Notion/create_page.py b/langflow/src/backend/base/langflow/components/Notion/create_page.py
new file mode 100644
index 0000000..d38a989
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/create_page.py
@@ -0,0 +1,94 @@
+import json
+from typing import Any
+
+import requests
+from langchain.tools import StructuredTool
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionPageCreator(LCToolComponent):
+ display_name: str = "Create Page "
+ description: str = "A component for creating Notion pages."
+ documentation: str = "https://docs.langflow.org/integrations/notion/page-create"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ StrInput(
+ name="database_id",
+ display_name="Database ID",
+ info="The ID of the Notion database.",
+ ),
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ MultilineInput(
+ name="properties_json",
+ display_name="Properties (JSON)",
+ info="The properties of the new page as a JSON string.",
+ ),
+ ]
+
+ class NotionPageCreatorSchema(BaseModel):
+ database_id: str = Field(..., description="The ID of the Notion database.")
+ properties_json: str = Field(..., description="The properties of the new page as a JSON string.")
+
+ def run_model(self) -> Data:
+ result = self._create_notion_page(self.database_id, self.properties_json)
+ if isinstance(result, str):
+ # An error occurred, return it as text
+ return Data(text=result)
+ # Success, return the created page data
+ output = "Created page properties:\n"
+ for prop_name, prop_value in result.get("properties", {}).items():
+ output += f"{prop_name}: {prop_value}\n"
+ return Data(text=output, data=result)
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="create_notion_page",
+ description="Create a new page in a Notion database. "
+ "IMPORTANT: Use the tool to check the Database properties for more details before using this tool.",
+ func=self._create_notion_page,
+ args_schema=self.NotionPageCreatorSchema,
+ )
+
+ def _create_notion_page(self, database_id: str, properties_json: str) -> dict[str, Any] | str:
+ if not database_id or not properties_json:
+ return "Invalid input. Please provide 'database_id' and 'properties_json'."
+
+ try:
+ properties = json.loads(properties_json)
+ except json.JSONDecodeError as e:
+ return f"Invalid properties format. Please provide a valid JSON string. Error: {e}"
+
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Content-Type": "application/json",
+ "Notion-Version": "2022-06-28",
+ }
+
+ data = {
+ "parent": {"database_id": database_id},
+ "properties": properties,
+ }
+
+ try:
+ response = requests.post("https://api.notion.com/v1/pages", headers=headers, json=data, timeout=10)
+ response.raise_for_status()
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ error_message = f"Failed to create Notion page. Error: {e}"
+ if hasattr(e, "response") and e.response is not None:
+ error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
+ return error_message
+
+ def __call__(self, *args, **kwargs):
+ return self._create_notion_page(*args, **kwargs)
diff --git a/langflow/src/backend/base/langflow/components/Notion/list_database_properties.py b/langflow/src/backend/base/langflow/components/Notion/list_database_properties.py
new file mode 100644
index 0000000..07e9cbb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/list_database_properties.py
@@ -0,0 +1,68 @@
+import requests
+from langchain.tools import StructuredTool
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionDatabaseProperties(LCToolComponent):
+ display_name: str = "List Database Properties "
+ description: str = "Retrieve properties of a Notion database."
+ documentation: str = "https://docs.langflow.org/integrations/notion/list-database-properties"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ StrInput(
+ name="database_id",
+ display_name="Database ID",
+ info="The ID of the Notion database.",
+ ),
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ ]
+
+ class NotionDatabasePropertiesSchema(BaseModel):
+ database_id: str = Field(..., description="The ID of the Notion database.")
+
+ def run_model(self) -> Data:
+ result = self._fetch_database_properties(self.database_id)
+ if isinstance(result, str):
+ # An error occurred, return it as text
+ return Data(text=result)
+ # Success, return the properties
+ return Data(text=str(result), data=result)
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="notion_database_properties",
+ description="Retrieve properties of a Notion database. Input should include the database ID.",
+ func=self._fetch_database_properties,
+ args_schema=self.NotionDatabasePropertiesSchema,
+ )
+
+ def _fetch_database_properties(self, database_id: str) -> dict | str:
+ url = f"https://api.notion.com/v1/databases/{database_id}"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Notion-Version": "2022-06-28", # Use the latest supported version
+ }
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+ data = response.json()
+ return data.get("properties", {})
+ except requests.exceptions.RequestException as e:
+ return f"Error fetching Notion database properties: {e}"
+ except ValueError as e:
+ return f"Error parsing Notion API response: {e}"
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error fetching Notion database properties")
+ return f"An unexpected error occurred: {e}"
diff --git a/langflow/src/backend/base/langflow/components/Notion/list_pages.py b/langflow/src/backend/base/langflow/components/Notion/list_pages.py
new file mode 100644
index 0000000..b46b8ca
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/list_pages.py
@@ -0,0 +1,122 @@
+import json
+from typing import Any
+
+import requests
+from langchain.tools import StructuredTool
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionListPages(LCToolComponent):
+ display_name: str = "List Pages "
+ description: str = (
+ "Query a Notion database with filtering and sorting. "
+ "The input should be a JSON string containing the 'filter' and 'sorts' objects. "
+ "Example input:\n"
+ '{"filter": {"property": "Status", "select": {"equals": "Done"}}, '
+ '"sorts": [{"timestamp": "created_time", "direction": "descending"}]}'
+ )
+ documentation: str = "https://docs.langflow.org/integrations/notion/list-pages"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ StrInput(
+ name="database_id",
+ display_name="Database ID",
+ info="The ID of the Notion database to query.",
+ ),
+ MultilineInput(
+ name="query_json",
+ display_name="Database query (JSON)",
+ info="A JSON string containing the filters and sorts that will be used for querying the database. "
+ "Leave empty for no filters or sorts.",
+ ),
+ ]
+
+ class NotionListPagesSchema(BaseModel):
+ database_id: str = Field(..., description="The ID of the Notion database to query.")
+ query_json: str | None = Field(
+ default="",
+ description="A JSON string containing the filters and sorts for querying the database. "
+ "Leave empty for no filters or sorts.",
+ )
+
+ def run_model(self) -> list[Data]:
+ result = self._query_notion_database(self.database_id, self.query_json)
+
+ if isinstance(result, str):
+ # An error occurred, return it as a single record
+ return [Data(text=result)]
+
+ records = []
+ combined_text = f"Pages found: {len(result)}\n\n"
+
+ for page in result:
+ page_data = {
+ "id": page["id"],
+ "url": page["url"],
+ "created_time": page["created_time"],
+ "last_edited_time": page["last_edited_time"],
+ "properties": page["properties"],
+ }
+
+ text = (
+ f"id: {page['id']}\n"
+ f"url: {page['url']}\n"
+ f"created_time: {page['created_time']}\n"
+ f"last_edited_time: {page['last_edited_time']}\n"
+ f"properties: {json.dumps(page['properties'], indent=2)}\n\n"
+ )
+
+ combined_text += text
+ records.append(Data(text=text, **page_data))
+
+ self.status = records
+ return records
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="notion_list_pages",
+ description=self.description,
+ func=self._query_notion_database,
+ args_schema=self.NotionListPagesSchema,
+ )
+
+ def _query_notion_database(self, database_id: str, query_json: str | None = None) -> list[dict[str, Any]] | str:
+ url = f"https://api.notion.com/v1/databases/{database_id}/query"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Content-Type": "application/json",
+ "Notion-Version": "2022-06-28",
+ }
+
+ query_payload = {}
+ if query_json and query_json.strip():
+ try:
+ query_payload = json.loads(query_json)
+ except json.JSONDecodeError as e:
+ return f"Invalid JSON format for query: {e}"
+
+ try:
+ response = requests.post(url, headers=headers, json=query_payload, timeout=10)
+ response.raise_for_status()
+ results = response.json()
+ return results["results"]
+ except requests.exceptions.RequestException as e:
+ return f"Error querying Notion database: {e}"
+ except KeyError:
+ return "Unexpected response format from Notion API"
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error querying Notion database")
+ return f"An unexpected error occurred: {e}"
diff --git a/langflow/src/backend/base/langflow/components/Notion/list_users.py b/langflow/src/backend/base/langflow/components/Notion/list_users.py
new file mode 100644
index 0000000..d99a71e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/list_users.py
@@ -0,0 +1,77 @@
+import requests
+from langchain.tools import StructuredTool
+from pydantic import BaseModel
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import SecretStrInput
+from langflow.schema import Data
+
+
+class NotionUserList(LCToolComponent):
+ display_name = "List Users "
+ description = "Retrieve users from Notion."
+ documentation = "https://docs.langflow.org/integrations/notion/list-users"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ ]
+
+ class NotionUserListSchema(BaseModel):
+ pass
+
+ def run_model(self) -> list[Data]:
+ users = self._list_users()
+ records = []
+ combined_text = ""
+
+ for user in users:
+ output = "User:\n"
+ for key, value in user.items():
+ output += f"{key.replace('_', ' ').title()}: {value}\n"
+ output += "________________________\n"
+
+ combined_text += output
+ records.append(Data(text=output, data=user))
+
+ self.status = records
+ return records
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="notion_list_users",
+ description="Retrieve users from Notion.",
+ func=self._list_users,
+ args_schema=self.NotionUserListSchema,
+ )
+
+ def _list_users(self) -> list[dict]:
+ url = "https://api.notion.com/v1/users"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Notion-Version": "2022-06-28",
+ }
+
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+
+ data = response.json()
+ results = data["results"]
+
+ users = []
+ for user in results:
+ user_data = {
+ "id": user["id"],
+ "type": user["type"],
+ "name": user.get("name", ""),
+ "avatar_url": user.get("avatar_url", ""),
+ }
+ users.append(user_data)
+
+ return users
diff --git a/langflow/src/backend/base/langflow/components/Notion/page_content_viewer.py b/langflow/src/backend/base/langflow/components/Notion/page_content_viewer.py
new file mode 100644
index 0000000..f334ecd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/page_content_viewer.py
@@ -0,0 +1,93 @@
+import requests
+from langchain.tools import StructuredTool
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionPageContent(LCToolComponent):
+ display_name = "Page Content Viewer "
+ description = "Retrieve the content of a Notion page as plain text."
+ documentation = "https://docs.langflow.org/integrations/notion/page-content-viewer"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ StrInput(
+ name="page_id",
+ display_name="Page ID",
+ info="The ID of the Notion page to retrieve.",
+ ),
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ ]
+
+ class NotionPageContentSchema(BaseModel):
+ page_id: str = Field(..., description="The ID of the Notion page to retrieve.")
+
+ def run_model(self) -> Data:
+ result = self._retrieve_page_content(self.page_id)
+ if isinstance(result, str) and result.startswith("Error:"):
+ # An error occurred, return it as text
+ return Data(text=result)
+ # Success, return the content
+ return Data(text=result, data={"content": result})
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="notion_page_content",
+ description="Retrieve the content of a Notion page as plain text.",
+ func=self._retrieve_page_content,
+ args_schema=self.NotionPageContentSchema,
+ )
+
+ def _retrieve_page_content(self, page_id: str) -> str:
+ blocks_url = f"https://api.notion.com/v1/blocks/{page_id}/children?page_size=100"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Notion-Version": "2022-06-28",
+ }
+ try:
+ blocks_response = requests.get(blocks_url, headers=headers, timeout=10)
+ blocks_response.raise_for_status()
+ blocks_data = blocks_response.json()
+ return self.parse_blocks(blocks_data.get("results", []))
+ except requests.exceptions.RequestException as e:
+ error_message = f"Error: Failed to retrieve Notion page content. {e}"
+ if hasattr(e, "response") and e.response is not None:
+ error_message += f" Status code: {e.response.status_code}, Response: {e.response.text}"
+ return error_message
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error retrieving Notion page content")
+ return f"Error: An unexpected error occurred while retrieving Notion page content. {e}"
+
+ def parse_blocks(self, blocks: list) -> str:
+ content = ""
+ for block in blocks:
+ block_type = block.get("type")
+ if block_type in {"paragraph", "heading_1", "heading_2", "heading_3", "quote"}:
+ content += self.parse_rich_text(block[block_type].get("rich_text", [])) + "\n\n"
+ elif block_type in {"bulleted_list_item", "numbered_list_item"}:
+ content += self.parse_rich_text(block[block_type].get("rich_text", [])) + "\n"
+ elif block_type == "to_do":
+ content += self.parse_rich_text(block["to_do"].get("rich_text", [])) + "\n"
+ elif block_type == "code":
+ content += self.parse_rich_text(block["code"].get("rich_text", [])) + "\n\n"
+ elif block_type == "image":
+ content += f"[Image: {block['image'].get('external', {}).get('url', 'No URL')}]\n\n"
+ elif block_type == "divider":
+ content += "---\n\n"
+ return content.strip()
+
+ def parse_rich_text(self, rich_text: list) -> str:
+ return "".join(segment.get("plain_text", "") for segment in rich_text)
+
+ def __call__(self, *args, **kwargs):
+ return self._retrieve_page_content(*args, **kwargs)
diff --git a/langflow/src/backend/base/langflow/components/Notion/search.py b/langflow/src/backend/base/langflow/components/Notion/search.py
new file mode 100644
index 0000000..7d93a3f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/search.py
@@ -0,0 +1,111 @@
+from typing import Any
+
+import requests
+from langchain.tools import StructuredTool
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import DropdownInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionSearch(LCToolComponent):
+ display_name: str = "Search "
+ description: str = "Searches all pages and databases that have been shared with an integration."
+ documentation: str = "https://docs.langflow.org/integrations/notion/search"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ StrInput(
+ name="query",
+ display_name="Search Query",
+ info="The text that the API compares page and database titles against.",
+ ),
+ DropdownInput(
+ name="filter_value",
+ display_name="Filter Type",
+ info="Limits the results to either only pages or only databases.",
+ options=["page", "database"],
+ value="page",
+ ),
+ DropdownInput(
+ name="sort_direction",
+ display_name="Sort Direction",
+ info="The direction to sort the results.",
+ options=["ascending", "descending"],
+ value="descending",
+ ),
+ ]
+
+ class NotionSearchSchema(BaseModel):
+ query: str = Field(..., description="The search query text.")
+ filter_value: str = Field(default="page", description="Filter type: 'page' or 'database'.")
+ sort_direction: str = Field(default="descending", description="Sort direction: 'ascending' or 'descending'.")
+
+ def run_model(self) -> list[Data]:
+ results = self._search_notion(self.query, self.filter_value, self.sort_direction)
+ records = []
+ combined_text = f"Results found: {len(results)}\n\n"
+
+ for result in results:
+ result_data = {
+ "id": result["id"],
+ "type": result["object"],
+ "last_edited_time": result["last_edited_time"],
+ }
+
+ if result["object"] == "page":
+ result_data["title_or_url"] = result["url"]
+ text = f"id: {result['id']}\ntitle_or_url: {result['url']}\n"
+ elif result["object"] == "database":
+ if "title" in result and isinstance(result["title"], list) and len(result["title"]) > 0:
+ result_data["title_or_url"] = result["title"][0]["plain_text"]
+ text = f"id: {result['id']}\ntitle_or_url: {result['title'][0]['plain_text']}\n"
+ else:
+ result_data["title_or_url"] = "N/A"
+ text = f"id: {result['id']}\ntitle_or_url: N/A\n"
+
+ text += f"type: {result['object']}\nlast_edited_time: {result['last_edited_time']}\n\n"
+ combined_text += text
+ records.append(Data(text=text, data=result_data))
+
+ self.status = records
+ return records
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="notion_search",
+ description="Search Notion pages and databases. "
+ "Input should include the search query and optionally filter type and sort direction.",
+ func=self._search_notion,
+ args_schema=self.NotionSearchSchema,
+ )
+
+ def _search_notion(
+ self, query: str, filter_value: str = "page", sort_direction: str = "descending"
+ ) -> list[dict[str, Any]]:
+ url = "https://api.notion.com/v1/search"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Content-Type": "application/json",
+ "Notion-Version": "2022-06-28",
+ }
+
+ data = {
+ "query": query,
+ "filter": {"value": filter_value, "property": "object"},
+ "sort": {"direction": sort_direction, "timestamp": "last_edited_time"},
+ }
+
+ response = requests.post(url, headers=headers, json=data, timeout=10)
+ response.raise_for_status()
+
+ results = response.json()
+ return results["results"]
diff --git a/langflow/src/backend/base/langflow/components/Notion/update_page_property.py b/langflow/src/backend/base/langflow/components/Notion/update_page_property.py
new file mode 100644
index 0000000..fe777dc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/Notion/update_page_property.py
@@ -0,0 +1,114 @@
+import json
+from typing import Any
+
+import requests
+from langchain.tools import StructuredTool
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class NotionPageUpdate(LCToolComponent):
+ display_name: str = "Update Page Property "
+ description: str = "Update the properties of a Notion page."
+ documentation: str = "https://docs.langflow.org/integrations/notion/page-update"
+ icon = "NotionDirectoryLoader"
+
+ inputs = [
+ StrInput(
+ name="page_id",
+ display_name="Page ID",
+ info="The ID of the Notion page to update.",
+ ),
+ MultilineInput(
+ name="properties",
+ display_name="Properties",
+ info="The properties to update on the page (as a JSON string or a dictionary).",
+ ),
+ SecretStrInput(
+ name="notion_secret",
+ display_name="Notion Secret",
+ info="The Notion integration token.",
+ required=True,
+ ),
+ ]
+
+ class NotionPageUpdateSchema(BaseModel):
+ page_id: str = Field(..., description="The ID of the Notion page to update.")
+ properties: str | dict[str, Any] = Field(
+ ..., description="The properties to update on the page (as a JSON string or a dictionary)."
+ )
+
+ def run_model(self) -> Data:
+ result = self._update_notion_page(self.page_id, self.properties)
+ if isinstance(result, str):
+ # An error occurred, return it as text
+ return Data(text=result)
+ # Success, return the updated page data
+ output = "Updated page properties:\n"
+ for prop_name, prop_value in result.get("properties", {}).items():
+ output += f"{prop_name}: {prop_value}\n"
+ return Data(text=output, data=result)
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="update_notion_page",
+ description="Update the properties of a Notion page. "
+ "IMPORTANT: Use the tool to check the Database properties for more details before using this tool.",
+ func=self._update_notion_page,
+ args_schema=self.NotionPageUpdateSchema,
+ )
+
+ def _update_notion_page(self, page_id: str, properties: str | dict[str, Any]) -> dict[str, Any] | str:
+ url = f"https://api.notion.com/v1/pages/{page_id}"
+ headers = {
+ "Authorization": f"Bearer {self.notion_secret}",
+ "Content-Type": "application/json",
+ "Notion-Version": "2022-06-28", # Use the latest supported version
+ }
+
+ # Parse properties if it's a string
+ if isinstance(properties, str):
+ try:
+ parsed_properties = json.loads(properties)
+ except json.JSONDecodeError as e:
+ error_message = f"Invalid JSON format for properties: {e}"
+ logger.exception(error_message)
+ return error_message
+
+ else:
+ parsed_properties = properties
+
+ data = {"properties": parsed_properties}
+
+ try:
+ logger.info(f"Sending request to Notion API: URL: {url}, Data: {json.dumps(data)}")
+ response = requests.patch(url, headers=headers, json=data, timeout=10)
+ response.raise_for_status()
+ updated_page = response.json()
+
+ logger.info(f"Successfully updated Notion page. Response: {json.dumps(updated_page)}")
+ except requests.exceptions.HTTPError as e:
+ error_message = f"HTTP Error occurred: {e}"
+ if e.response is not None:
+ error_message += f"\nStatus code: {e.response.status_code}"
+ error_message += f"\nResponse body: {e.response.text}"
+ logger.exception(error_message)
+ return error_message
+ except requests.exceptions.RequestException as e:
+ error_message = f"An error occurred while making the request: {e}"
+ logger.exception(error_message)
+ return error_message
+ except Exception as e: # noqa: BLE001
+ error_message = f"An unexpected error occurred: {e}"
+ logger.exception(error_message)
+ return error_message
+
+ return updated_page
+
+ def __call__(self, *args, **kwargs):
+ return self._update_notion_page(*args, **kwargs)
diff --git a/langflow/src/backend/base/langflow/components/__init__.py b/langflow/src/backend/base/langflow/components/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/agentql/__init__.py b/langflow/src/backend/base/langflow/components/agentql/__init__.py
new file mode 100644
index 0000000..3fac7b5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/agentql/__init__.py
@@ -0,0 +1,3 @@
+from .agentql_api import AgentQL
+
+__all__ = ["AgentQL"]
diff --git a/langflow/src/backend/base/langflow/components/agentql/agentql_api.py b/langflow/src/backend/base/langflow/components/agentql/agentql_api.py
new file mode 100644
index 0000000..561d8e0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/agentql/agentql_api.py
@@ -0,0 +1,110 @@
+import httpx
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import (
+ DictInput,
+ IntInput,
+ MessageTextInput,
+ MultilineInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class AgentQL(Component):
+ display_name = "AgentQL Query Data"
+ description = "Uses AgentQL API to extract structured data from a given URL."
+ documentation: str = "https://docs.agentql.com/rest-api/api-reference"
+ icon = "AgentQL"
+ name = "AgentQL"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="AgentQL API Key",
+ required=True,
+ password=True,
+ info="Your AgentQL API key. Get one at https://dev.agentql.com.",
+ ),
+ MessageTextInput(
+ name="url",
+ display_name="URL",
+ required=True,
+ info="The public URL of the webpage to extract data from.",
+ tool_mode=True,
+ ),
+ MultilineInput(
+ name="query",
+ display_name="AgentQL Query",
+ required=True,
+ info="The AgentQL query to execute. Read more at https://docs.agentql.com/agentql-query.",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ info="Timeout in seconds for the request. Increase if data extraction takes too long.",
+ value=900,
+ advanced=True,
+ ),
+ DictInput(
+ name="params",
+ display_name="Additional Params",
+ info="The additional params to send with the request. For details refer to https://docs.agentql.com/rest-api/api-reference#request-body.",
+ is_list=True,
+ value={
+ "mode": "fast",
+ "wait_for": 0,
+ "is_scroll_to_bottom_enabled": False,
+ "is_screenshot_enabled": False,
+ },
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="build_output"),
+ ]
+
+ def build_output(self) -> Data:
+ endpoint = "https://api.agentql.com/v1/query-data"
+ headers = {
+ "X-API-Key": self.api_key,
+ "Content-Type": "application/json",
+ "X-TF-Request-Origin": "langflow",
+ }
+
+ payload = {
+ "url": self.url,
+ "query": self.query,
+ "params": self.params,
+ }
+
+ try:
+ response = httpx.post(endpoint, headers=headers, json=payload, timeout=self.timeout)
+ response.raise_for_status()
+
+ json = response.json()
+ data = Data(result=json["data"], metadata=json["metadata"])
+
+ except httpx.HTTPStatusError as e:
+ response = e.response
+ if response.status_code in {401, 403}:
+ self.status = "Please, provide a valid API Key. You can create one at https://dev.agentql.com."
+ else:
+ try:
+ error_json = response.json()
+ logger.error(
+ f"Failure response: '{response.status_code} {response.reason_phrase}' with body: {error_json}"
+ )
+ msg = error_json["error_info"] if "error_info" in error_json else error_json["detail"]
+ except (ValueError, TypeError):
+ msg = f"HTTP {e}."
+ self.status = msg
+ raise ValueError(self.status) from e
+
+ else:
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/agents/__init__.py b/langflow/src/backend/base/langflow/components/agents/__init__.py
new file mode 100644
index 0000000..a765520
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/agents/__init__.py
@@ -0,0 +1,3 @@
+from .agent import AgentComponent
+
+__all__ = ["AgentComponent"]
diff --git a/langflow/src/backend/base/langflow/components/agents/agent.py b/langflow/src/backend/base/langflow/components/agents/agent.py
new file mode 100644
index 0000000..0fd37bc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/agents/agent.py
@@ -0,0 +1,285 @@
+from langchain_core.tools import StructuredTool
+
+from langflow.base.agents.agent import LCToolsAgentComponent
+from langflow.base.agents.events import ExceptionWithMessageError
+from langflow.base.models.model_input_constants import (
+ ALL_PROVIDER_FIELDS,
+ MODEL_DYNAMIC_UPDATE_FIELDS,
+ MODEL_PROVIDERS_DICT,
+ MODELS_METADATA,
+)
+from langflow.base.models.model_utils import get_model_name
+from langflow.components.helpers import CurrentDateComponent
+from langflow.components.helpers.memory import MemoryComponent
+from langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent
+from langflow.custom.custom_component.component import _get_component_toolkit
+from langflow.custom.utils import update_component_build_config
+from langflow.field_typing import Tool
+from langflow.io import BoolInput, DropdownInput, MultilineInput, Output
+from langflow.logging import logger
+from langflow.schema.dotdict import dotdict
+from langflow.schema.message import Message
+
+
+def set_advanced_true(component_input):
+ component_input.advanced = True
+ return component_input
+
+
+class AgentComponent(ToolCallingAgentComponent):
+ display_name: str = "Agent"
+ description: str = "Define the agent's instructions, then enter a task to complete using tools."
+ icon = "bot"
+ beta = False
+ name = "Agent"
+
+ memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]
+
+ inputs = [
+ DropdownInput(
+ name="agent_llm",
+ display_name="Model Provider",
+ info="The provider of the language model that the agent will use to generate responses.",
+ options=[*sorted(MODEL_PROVIDERS_DICT.keys()), "Custom"],
+ value="OpenAI",
+ real_time_refresh=True,
+ input_types=[],
+ options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{"icon": "brain"}],
+ ),
+ *MODEL_PROVIDERS_DICT["OpenAI"]["inputs"],
+ MultilineInput(
+ name="system_prompt",
+ display_name="Agent Instructions",
+ info="System Prompt: Initial instructions and context provided to guide the agent's behavior.",
+ value="You are a helpful assistant that can use tools to answer questions and perform tasks.",
+ advanced=False,
+ ),
+ *LCToolsAgentComponent._base_inputs,
+ *memory_inputs,
+ BoolInput(
+ name="add_current_date_tool",
+ display_name="Current Date",
+ advanced=True,
+ info="If true, will add a tool to the agent that returns the current date.",
+ value=True,
+ ),
+ ]
+ outputs = [Output(name="response", display_name="Response", method="message_response")]
+
+ async def message_response(self) -> Message:
+ try:
+ # Get LLM model and validate
+ llm_model, display_name = self.get_llm()
+ if llm_model is None:
+ msg = "No language model selected. Please choose a model to proceed."
+ raise ValueError(msg)
+ self.model_name = get_model_name(llm_model, display_name=display_name)
+
+ # Get memory data
+ self.chat_history = await self.get_memory_data()
+
+ # Add current date tool if enabled
+ if self.add_current_date_tool:
+ if not isinstance(self.tools, list): # type: ignore[has-type]
+ self.tools = []
+ current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)
+ if not isinstance(current_date_tool, StructuredTool):
+ msg = "CurrentDateComponent must be converted to a StructuredTool"
+ raise TypeError(msg)
+ self.tools.append(current_date_tool)
+
+ # Validate tools
+ if not self.tools:
+ msg = "Tools are required to run the agent. Please add at least one tool."
+ raise ValueError(msg)
+
+ # Set up and run agent
+ self.set(
+ llm=llm_model,
+ tools=self.tools,
+ chat_history=self.chat_history,
+ input_value=self.input_value,
+ system_prompt=self.system_prompt,
+ )
+ agent = self.create_agent_runnable()
+ return await self.run_agent(agent)
+
+ except (ValueError, TypeError, KeyError) as e:
+ logger.error(f"{type(e).__name__}: {e!s}")
+ raise
+ except ExceptionWithMessageError as e:
+ logger.error(f"ExceptionWithMessageError occurred: {e}")
+ raise
+ except Exception as e:
+ logger.error(f"Unexpected error: {e!s}")
+ raise
+
+ async def get_memory_data(self):
+ memory_kwargs = {
+ component_input.name: getattr(self, f"{component_input.name}") for component_input in self.memory_inputs
+ }
+ # filter out empty values
+ memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}
+
+ return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()
+
+ def get_llm(self):
+ if not isinstance(self.agent_llm, str):
+ return self.agent_llm, None
+
+ try:
+ provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
+ if not provider_info:
+ msg = f"Invalid model provider: {self.agent_llm}"
+ raise ValueError(msg)
+
+ component_class = provider_info.get("component_class")
+ display_name = component_class.display_name
+ inputs = provider_info.get("inputs")
+ prefix = provider_info.get("prefix", "")
+
+ return self._build_llm_model(component_class, inputs, prefix), display_name
+
+ except Exception as e:
+ logger.error(f"Error building {self.agent_llm} language model: {e!s}")
+ msg = f"Failed to initialize language model: {e!s}"
+ raise ValueError(msg) from e
+
+ def _build_llm_model(self, component, inputs, prefix=""):
+ model_kwargs = {input_.name: getattr(self, f"{prefix}{input_.name}") for input_ in inputs}
+ return component.set(**model_kwargs).build_model()
+
+ def set_component_params(self, component):
+ provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
+ if provider_info:
+ inputs = provider_info.get("inputs")
+ prefix = provider_info.get("prefix")
+ model_kwargs = {input_.name: getattr(self, f"{prefix}{input_.name}") for input_ in inputs}
+
+ return component.set(**model_kwargs)
+ return component
+
+ def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:
+ """Delete specified fields from build_config."""
+ for field in fields:
+ build_config.pop(field, None)
+
+ def update_input_types(self, build_config: dotdict) -> dotdict:
+ """Update input types for all fields in build_config."""
+ for key, value in build_config.items():
+ if isinstance(value, dict):
+ if value.get("input_types") is None:
+ build_config[key]["input_types"] = []
+ elif hasattr(value, "input_types") and value.input_types is None:
+ value.input_types = []
+ return build_config
+
+ async def update_build_config(
+ self, build_config: dotdict, field_value: str, field_name: str | None = None
+ ) -> dotdict:
+ # Iterate over all providers in the MODEL_PROVIDERS_DICT
+ # Existing logic for updating build_config
+ if field_name in ("agent_llm",):
+ build_config["agent_llm"]["value"] = field_value
+ provider_info = MODEL_PROVIDERS_DICT.get(field_value)
+ if provider_info:
+ component_class = provider_info.get("component_class")
+ if component_class and hasattr(component_class, "update_build_config"):
+ # Call the component class's update_build_config method
+ build_config = await update_component_build_config(
+ component_class, build_config, field_value, "model_name"
+ )
+
+ provider_configs: dict[str, tuple[dict, list[dict]]] = {
+ provider: (
+ MODEL_PROVIDERS_DICT[provider]["fields"],
+ [
+ MODEL_PROVIDERS_DICT[other_provider]["fields"]
+ for other_provider in MODEL_PROVIDERS_DICT
+ if other_provider != provider
+ ],
+ )
+ for provider in MODEL_PROVIDERS_DICT
+ }
+ if field_value in provider_configs:
+ fields_to_add, fields_to_delete = provider_configs[field_value]
+
+ # Delete fields from other providers
+ for fields in fields_to_delete:
+ self.delete_fields(build_config, fields)
+
+ # Add provider-specific fields
+ if field_value == "OpenAI" and not any(field in build_config for field in fields_to_add):
+ build_config.update(fields_to_add)
+ else:
+ build_config.update(fields_to_add)
+ # Reset input types for agent_llm
+ build_config["agent_llm"]["input_types"] = []
+ elif field_value == "Custom":
+ # Delete all provider fields
+ self.delete_fields(build_config, ALL_PROVIDER_FIELDS)
+ # Update with custom component
+ custom_component = DropdownInput(
+ name="agent_llm",
+ display_name="Language Model",
+ options=[*sorted(MODEL_PROVIDERS_DICT.keys()), "Custom"],
+ value="Custom",
+ real_time_refresh=True,
+ input_types=["LanguageModel"],
+ options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]
+ + [{"icon": "brain"}],
+ )
+ build_config.update({"agent_llm": custom_component.to_dict()})
+ # Update input types for all fields
+ build_config = self.update_input_types(build_config)
+
+ # Validate required keys
+ default_keys = [
+ "code",
+ "_type",
+ "agent_llm",
+ "tools",
+ "input_value",
+ "add_current_date_tool",
+ "system_prompt",
+ "agent_description",
+ "max_iterations",
+ "handle_parsing_errors",
+ "verbose",
+ ]
+ missing_keys = [key for key in default_keys if key not in build_config]
+ if missing_keys:
+ msg = f"Missing required keys in build_config: {missing_keys}"
+ raise ValueError(msg)
+ if (
+ isinstance(self.agent_llm, str)
+ and self.agent_llm in MODEL_PROVIDERS_DICT
+ and field_name in MODEL_DYNAMIC_UPDATE_FIELDS
+ ):
+ provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)
+ if provider_info:
+ component_class = provider_info.get("component_class")
+ component_class = self.set_component_params(component_class)
+ prefix = provider_info.get("prefix")
+ if component_class and hasattr(component_class, "update_build_config"):
+ # Call each component class's update_build_config method
+ # remove the prefix from the field_name
+ if isinstance(field_name, str) and isinstance(prefix, str):
+ field_name = field_name.replace(prefix, "")
+ build_config = await update_component_build_config(
+ component_class, build_config, field_value, "model_name"
+ )
+ return dotdict({k: v.to_dict() if hasattr(v, "to_dict") else v for k, v in build_config.items()})
+
+ async def to_toolkit(self) -> list[Tool]:
+ component_toolkit = _get_component_toolkit()
+ tools_names = self._build_tools_names()
+ agent_description = self.get_tool_description()
+ # TODO: Agent Description Depreciated Feature to be removed
+ description = f"{agent_description}{tools_names}"
+ tools = component_toolkit(component=self).get_tools(
+ tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()
+ )
+ if hasattr(self, "tools_metadata"):
+ tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
+ return tools
diff --git a/langflow/src/backend/base/langflow/components/apify/__init__.py b/langflow/src/backend/base/langflow/components/apify/__init__.py
new file mode 100644
index 0000000..1cb5e7c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/apify/__init__.py
@@ -0,0 +1,5 @@
+from .apify_actor import ApifyActorsComponent
+
+__all__ = [
+ "ApifyActorsComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/apify/apify_actor.py b/langflow/src/backend/base/langflow/components/apify/apify_actor.py
new file mode 100644
index 0000000..1eb1705
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/apify/apify_actor.py
@@ -0,0 +1,324 @@
+import json
+import string
+from typing import Any, cast
+
+from apify_client import ApifyClient
+from langchain_community.document_loaders.apify_dataset import ApifyDatasetLoader
+from langchain_core.tools import BaseTool
+from pydantic import BaseModel, Field, field_serializer
+
+from langflow.custom import Component
+from langflow.field_typing import Tool
+from langflow.inputs.inputs import BoolInput
+from langflow.io import MultilineInput, Output, SecretStrInput, StrInput
+from langflow.schema import Data
+
+MAX_DESCRIPTION_LEN = 250
+
+
+class ApifyActorsComponent(Component):
+ display_name = "Apify Actors"
+ description = (
+ "Use Apify Actors to extract data from hundreds of places fast. "
+ "This component can be used in a flow to retrieve data or as a tool with an agent."
+ )
+ documentation: str = "http://docs.langflow.org/integrations-apify"
+ icon = "Apify"
+ name = "ApifyActors"
+
+ inputs = [
+ SecretStrInput(
+ name="apify_token",
+ display_name="Apify Token",
+ info="The API token for the Apify account.",
+ required=True,
+ password=True,
+ ),
+ StrInput(
+ name="actor_id",
+ display_name="Actor",
+ info=(
+ "Actor name from Apify store to run. For example 'apify/website-content-crawler' "
+ "to use the Website Content Crawler Actor."
+ ),
+ required=True,
+ ),
+ # multiline input is more pleasant to use than the nested dict input
+ MultilineInput(
+ name="run_input",
+ display_name="Run input",
+ info=(
+ 'The JSON input for the Actor run. For example for the "apify/website-content-crawler" Actor: '
+ '{"startUrls":[{"url":"https://docs.apify.com/academy/web-scraping-for-beginners"}],"maxCrawlDepth":0}'
+ ),
+ value="{}",
+ required=True,
+ ),
+ MultilineInput(
+ name="dataset_fields",
+ display_name="Output fields",
+ info=(
+ "Fields to extract from the dataset, split by commas. "
+ "Other fields will be ignored. Dots in nested structures will be replaced by underscores. "
+ "Sample input: 'text, metadata.title'. "
+ "Sample output: {'text': 'page content here', 'metadata_title': 'page title here'}. "
+ "For example, for the 'apify/website-content-crawler' Actor, you can extract the 'markdown' field, "
+ "which is the content of the website in markdown format."
+ ),
+ ),
+ BoolInput(
+ name="flatten_dataset",
+ display_name="Flatten output",
+ info=(
+ "The output dataset will be converted from a nested format to a flat structure. "
+ "Dots in nested structure will be replaced by underscores. "
+ "This is useful for further processing of the Data object. "
+ "For example, {'a': {'b': 1}} will be flattened to {'a_b': 1}."
+ ),
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", type_=list[Data], method="run_model"),
+ Output(display_name="Tool", name="tool", type_=Tool, method="build_tool"),
+ ]
+
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self._apify_client: ApifyClient | None = None
+
+ def run_model(self) -> list[Data]:
+ """Run the Actor and return node output."""
+ input_ = json.loads(self.run_input)
+ fields = ApifyActorsComponent.parse_dataset_fields(self.dataset_fields) if self.dataset_fields else None
+ res = self._run_actor(self.actor_id, input_, fields=fields)
+ if self.flatten_dataset:
+ res = [ApifyActorsComponent.flatten(item) for item in res]
+ data = [Data(data=item) for item in res]
+
+ self.status = data
+ return data
+
+ def build_tool(self) -> Tool:
+ """Build a tool for an agent that runs the Apify Actor."""
+ actor_id = self.actor_id
+
+ build = self._get_actor_latest_build(actor_id)
+ readme = build.get("readme", "")[:250] + "..."
+ if not (input_schema_str := build.get("inputSchema")):
+ msg = "Input schema not found"
+ raise ValueError(msg)
+ input_schema = json.loads(input_schema_str)
+ properties, required = ApifyActorsComponent.get_actor_input_schema_from_build(input_schema)
+ properties = {"run_input": properties}
+
+ # works from input schema
+ info_ = [
+ (
+ "JSON encoded as a string with input schema (STRICTLY FOLLOW JSON FORMAT AND SCHEMA):\n\n"
+ f"{json.dumps(properties, separators=(',', ':'))}"
+ )
+ ]
+ if required:
+ info_.append("\n\nRequired fields:\n" + "\n".join(required))
+
+ info = "".join(info_)
+
+ input_model_cls = ApifyActorsComponent.create_input_model_class(info)
+ tool_cls = ApifyActorsComponent.create_tool_class(self, readme, input_model_cls, actor_id)
+
+ return cast("Tool", tool_cls())
+
+ @staticmethod
+ def create_tool_class(
+ parent: "ApifyActorsComponent", readme: str, input_model: type[BaseModel], actor_id: str
+ ) -> type[BaseTool]:
+ """Create a tool class that runs an Apify Actor."""
+
+ class ApifyActorRun(BaseTool):
+ """Tool that runs Apify Actors."""
+
+ name: str = f"apify_actor_{ApifyActorsComponent.actor_id_to_tool_name(actor_id)}"
+ description: str = (
+ "Run an Apify Actor with the given input. "
+ "Here is a part of the currently loaded Actor README:\n\n"
+ f"{readme}\n\n"
+ )
+
+ args_schema: type[BaseModel] = input_model
+
+ @field_serializer("args_schema")
+ def serialize_args_schema(self, args_schema):
+ return args_schema.schema()
+
+ def _run(self, run_input: str | dict) -> str:
+ """Use the Apify Actor."""
+ input_dict = json.loads(run_input) if isinstance(run_input, str) else run_input
+
+ # retrieve if nested, just in case
+ input_dict = input_dict.get("run_input", input_dict)
+
+ res = parent._run_actor(actor_id, input_dict)
+ return "\n\n".join([ApifyActorsComponent.dict_to_json_str(item) for item in res])
+
+ return ApifyActorRun
+
+ @staticmethod
+ def create_input_model_class(description: str) -> type[BaseModel]:
+ """Create a Pydantic model class for the Actor input."""
+
+ class ActorInput(BaseModel):
+ """Input for the Apify Actor tool."""
+
+ run_input: str = Field(..., description=description)
+
+ return ActorInput
+
+ def _get_apify_client(self) -> ApifyClient:
+ """Get the Apify client.
+
+ Is created if not exists or token changes.
+ """
+ if not self.apify_token:
+ msg = "API token is required."
+ raise ValueError(msg)
+ # when token changes, create a new client
+ if self._apify_client is None or self._apify_client.token != self.apify_token:
+ self._apify_client = ApifyClient(self.apify_token)
+ if httpx_client := self._apify_client.http_client.httpx_client:
+ httpx_client.headers["user-agent"] += "; Origin/langflow"
+ return self._apify_client
+
+ def _get_actor_latest_build(self, actor_id: str) -> dict:
+ """Get the latest build of an Actor from the default build tag."""
+ client = self._get_apify_client()
+ actor = client.actor(actor_id=actor_id)
+ if not (actor_info := actor.get()):
+ msg = f"Actor {actor_id} not found."
+ raise ValueError(msg)
+
+ default_build_tag = actor_info.get("defaultRunOptions", {}).get("build")
+ latest_build_id = actor_info.get("taggedBuilds", {}).get(default_build_tag, {}).get("buildId")
+
+ if (build := client.build(latest_build_id).get()) is None:
+ msg = f"Build {latest_build_id} not found."
+ raise ValueError(msg)
+
+ return build
+
+ @staticmethod
+ def get_actor_input_schema_from_build(input_schema: dict) -> tuple[dict, list[str]]:
+ """Get the input schema from the Actor build.
+
+ Trim the description to 250 characters.
+ """
+ properties = input_schema.get("properties", {})
+ required = input_schema.get("required", [])
+
+ properties_out: dict = {}
+ for item, meta in properties.items():
+ properties_out[item] = {}
+ if desc := meta.get("description"):
+ properties_out[item]["description"] = (
+ desc[:MAX_DESCRIPTION_LEN] + "..." if len(desc) > MAX_DESCRIPTION_LEN else desc
+ )
+ for key_name in ("type", "default", "prefill", "enum"):
+ if value := meta.get(key_name):
+ properties_out[item][key_name] = value
+
+ return properties_out, required
+
+ def _get_run_dataset_id(self, run_id: str) -> str:
+ """Get the dataset id from the run id."""
+ client = self._get_apify_client()
+ run = client.run(run_id=run_id)
+ if (dataset := run.dataset().get()) is None:
+ msg = "Dataset not found"
+ raise ValueError(msg)
+ if (did := dataset.get("id")) is None:
+ msg = "Dataset id not found"
+ raise ValueError(msg)
+ return did
+
+ @staticmethod
+ def dict_to_json_str(d: dict) -> str:
+ """Convert a dictionary to a JSON string."""
+ return json.dumps(d, separators=(",", ":"), default=lambda _: "")
+
+ @staticmethod
+ def actor_id_to_tool_name(actor_id: str) -> str:
+ """Turn actor_id into a valid tool name.
+
+ Tool name must only contain letters, numbers, underscores, dashes,
+ and cannot contain spaces.
+ """
+ valid_chars = string.ascii_letters + string.digits + "_-"
+ return "".join(char if char in valid_chars else "_" for char in actor_id)
+
+ def _run_actor(self, actor_id: str, run_input: dict, fields: list[str] | None = None) -> list[dict]:
+ """Run an Apify Actor and return the output dataset.
+
+ Args:
+ actor_id: Actor name from Apify store to run.
+ run_input: JSON input for the Actor.
+ fields: List of fields to extract from the dataset. Other fields will be ignored.
+ """
+ client = self._get_apify_client()
+ if (details := client.actor(actor_id=actor_id).call(run_input=run_input, wait_secs=1)) is None:
+ msg = "Actor run details not found"
+ raise ValueError(msg)
+ if (run_id := details.get("id")) is None:
+ msg = "Run id not found"
+ raise ValueError(msg)
+
+ if (run_client := client.run(run_id)) is None:
+ msg = "Run client not found"
+ raise ValueError(msg)
+
+ # stream logs
+ with run_client.log().stream() as response:
+ if response:
+ for line in response.iter_lines():
+ self.log(line)
+ run_client.wait_for_finish()
+
+ dataset_id = self._get_run_dataset_id(run_id)
+
+ loader = ApifyDatasetLoader(
+ dataset_id=dataset_id,
+ dataset_mapping_function=lambda item: item
+ if not fields
+ else {k.replace(".", "_"): ApifyActorsComponent.get_nested_value(item, k) for k in fields},
+ )
+ return loader.load()
+
+ @staticmethod
+ def get_nested_value(data: dict[str, Any], key: str) -> Any:
+ """Get a nested value from a dictionary."""
+ keys = key.split(".")
+ value = data
+ for k in keys:
+ if not isinstance(value, dict) or k not in value:
+ return None
+ value = value[k]
+ return value
+
+ @staticmethod
+ def parse_dataset_fields(dataset_fields: str) -> list[str]:
+ """Convert a string of comma-separated fields into a list of fields."""
+ dataset_fields = dataset_fields.replace("'", "").replace('"', "").replace("`", "")
+ return [field.strip() for field in dataset_fields.split(",")]
+
+ @staticmethod
+ def flatten(d: dict) -> dict:
+ """Flatten a nested dictionary."""
+
+ def items():
+ for key, value in d.items():
+ if isinstance(value, dict):
+ for subkey, subvalue in ApifyActorsComponent.flatten(value).items():
+ yield key + "_" + subkey, subvalue
+ else:
+ yield key, value
+
+ return dict(items())
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/__init__.py b/langflow/src/backend/base/langflow/components/assemblyai/__init__.py
new file mode 100644
index 0000000..8272a8c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/__init__.py
@@ -0,0 +1,13 @@
+from .assemblyai_get_subtitles import AssemblyAIGetSubtitles
+from .assemblyai_lemur import AssemblyAILeMUR
+from .assemblyai_list_transcripts import AssemblyAIListTranscripts
+from .assemblyai_poll_transcript import AssemblyAITranscriptionJobPoller
+from .assemblyai_start_transcript import AssemblyAITranscriptionJobCreator
+
+__all__ = [
+ "AssemblyAIGetSubtitles",
+ "AssemblyAILeMUR",
+ "AssemblyAIListTranscripts",
+ "AssemblyAITranscriptionJobCreator",
+ "AssemblyAITranscriptionJobPoller",
+]
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_get_subtitles.py b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_get_subtitles.py
new file mode 100644
index 0000000..12dfb56
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_get_subtitles.py
@@ -0,0 +1,83 @@
+import assemblyai as aai
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, IntInput, Output, SecretStrInput
+from langflow.schema import Data
+
+
+class AssemblyAIGetSubtitles(Component):
+ display_name = "AssemblyAI Get Subtitles"
+ description = "Export your transcript in SRT or VTT format for subtitles and closed captions"
+ documentation = "https://www.assemblyai.com/docs"
+ icon = "AssemblyAI"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Assembly API Key",
+ info="Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ required=True,
+ ),
+ DataInput(
+ name="transcription_result",
+ display_name="Transcription Result",
+ info="The transcription result from AssemblyAI",
+ required=True,
+ ),
+ DropdownInput(
+ name="subtitle_format",
+ display_name="Subtitle Format",
+ options=["srt", "vtt"],
+ value="srt",
+ info="The format of the captions (SRT or VTT)",
+ ),
+ IntInput(
+ name="chars_per_caption",
+ display_name="Characters per Caption",
+ info="The maximum number of characters per caption (0 for no limit)",
+ value=0,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Subtitles", name="subtitles", method="get_subtitles"),
+ ]
+
+ def get_subtitles(self) -> Data:
+ aai.settings.api_key = self.api_key
+
+ # check if it's an error message from the previous step
+ if self.transcription_result.data.get("error"):
+ self.status = self.transcription_result.data["error"]
+ return self.transcription_result
+
+ try:
+ transcript_id = self.transcription_result.data["id"]
+ transcript = aai.Transcript.get_by_id(transcript_id)
+ except Exception as e: # noqa: BLE001
+ error = f"Getting transcription failed: {e}"
+ logger.opt(exception=True).debug(error)
+ self.status = error
+ return Data(data={"error": error})
+
+ if transcript.status == aai.TranscriptStatus.completed:
+ subtitles = None
+ chars_per_caption = self.chars_per_caption if self.chars_per_caption > 0 else None
+ if self.subtitle_format == "srt":
+ subtitles = transcript.export_subtitles_srt(chars_per_caption)
+ else:
+ subtitles = transcript.export_subtitles_vtt(chars_per_caption)
+
+ result = Data(
+ subtitles=subtitles,
+ format=self.subtitle_format,
+ transcript_id=transcript_id,
+ chars_per_caption=chars_per_caption,
+ )
+
+ self.status = result
+ return result
+ self.status = transcript.error
+ return Data(data={"error": transcript.error})
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_lemur.py b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_lemur.py
new file mode 100644
index 0000000..059914c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_lemur.py
@@ -0,0 +1,183 @@
+import assemblyai as aai
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, FloatInput, IntInput, MultilineInput, Output, SecretStrInput
+from langflow.schema import Data
+
+
+class AssemblyAILeMUR(Component):
+ display_name = "AssemblyAI LeMUR"
+ description = "Apply Large Language Models to spoken data using the AssemblyAI LeMUR framework"
+ documentation = "https://www.assemblyai.com/docs/lemur"
+ icon = "AssemblyAI"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Assembly API Key",
+ info="Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ advanced=False,
+ required=True,
+ ),
+ DataInput(
+ name="transcription_result",
+ display_name="Transcription Result",
+ info="The transcription result from AssemblyAI",
+ required=True,
+ ),
+ MultilineInput(name="prompt", display_name="Input Prompt", info="The text to prompt the model", required=True),
+ DropdownInput(
+ name="final_model",
+ display_name="Final Model",
+ options=["claude3_5_sonnet", "claude3_opus", "claude3_haiku", "claude3_sonnet"],
+ value="claude3_5_sonnet",
+ info="The model that is used for the final prompt after compression is performed",
+ advanced=True,
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Temperature",
+ advanced=True,
+ value=0.0,
+ info="The temperature to use for the model",
+ ),
+ IntInput(
+ name="max_output_size",
+ display_name=" Max Output Size",
+ advanced=True,
+ value=2000,
+ info="Max output size in tokens, up to 4000",
+ ),
+ DropdownInput(
+ name="endpoint",
+ display_name="Endpoint",
+ options=["task", "summary", "question-answer"],
+ value="task",
+ info=(
+ "The LeMUR endpoint to use. For 'summary' and 'question-answer',"
+ " no prompt input is needed. See https://www.assemblyai.com/docs/api-reference/lemur/ for more info."
+ ),
+ advanced=True,
+ ),
+ MultilineInput(
+ name="questions",
+ display_name="Questions",
+ info="Comma-separated list of your questions. Only used if Endpoint is 'question-answer'",
+ advanced=True,
+ ),
+ MultilineInput(
+ name="transcript_ids",
+ display_name="Transcript IDs",
+ info=(
+ "Comma-separated list of transcript IDs. LeMUR can perform actions over multiple transcripts."
+ " If provided, the Transcription Result is ignored."
+ ),
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="LeMUR Response", name="lemur_response", method="run_lemur"),
+ ]
+
+ def run_lemur(self) -> Data:
+ """Use the LeMUR task endpoint to input the LLM prompt."""
+ aai.settings.api_key = self.api_key
+
+ if not self.transcription_result and not self.transcript_ids:
+ error = "Either a Transcription Result or Transcript IDs must be provided"
+ self.status = error
+ return Data(data={"error": error})
+ if self.transcription_result and self.transcription_result.data.get("error"):
+ # error message from the previous step
+ self.status = self.transcription_result.data["error"]
+ return self.transcription_result
+ if self.endpoint == "task" and not self.prompt:
+ self.status = "No prompt specified for the task endpoint"
+ return Data(data={"error": "No prompt specified"})
+ if self.endpoint == "question-answer" and not self.questions:
+ error = "No Questions were provided for the question-answer endpoint"
+ self.status = error
+ return Data(data={"error": error})
+
+ # Check for valid transcripts
+ transcript_ids = None
+ if self.transcription_result and "id" in self.transcription_result.data:
+ transcript_ids = [self.transcription_result.data["id"]]
+ elif self.transcript_ids:
+ transcript_ids = self.transcript_ids.split(",") or []
+ transcript_ids = [t.strip() for t in transcript_ids]
+
+ if not transcript_ids:
+ error = "Either a valid Transcription Result or valid Transcript IDs must be provided"
+ self.status = error
+ return Data(data={"error": error})
+
+ # Get TranscriptGroup and check if there is any error
+ transcript_group = aai.TranscriptGroup(transcript_ids=transcript_ids)
+ transcript_group, failures = transcript_group.wait_for_completion(return_failures=True)
+ if failures:
+ error = f"Getting transcriptions failed: {failures[0]}"
+ self.status = error
+ return Data(data={"error": error})
+
+ for t in transcript_group.transcripts:
+ if t.status == aai.TranscriptStatus.error:
+ self.status = t.error
+ return Data(data={"error": t.error})
+
+ # Perform LeMUR action
+ try:
+ response = self.perform_lemur_action(transcript_group, self.endpoint)
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error running LeMUR")
+ error = f"An Error happened: {e}"
+ self.status = error
+ return Data(data={"error": error})
+
+ result = Data(data=response)
+ self.status = result
+ return result
+
+ def perform_lemur_action(self, transcript_group: aai.TranscriptGroup, endpoint: str) -> dict:
+ logger.info("Endpoint:", endpoint, type(endpoint))
+ if endpoint == "task":
+ result = transcript_group.lemur.task(
+ prompt=self.prompt,
+ final_model=self.get_final_model(self.final_model),
+ temperature=self.temperature,
+ max_output_size=self.max_output_size,
+ )
+ elif endpoint == "summary":
+ result = transcript_group.lemur.summarize(
+ final_model=self.get_final_model(self.final_model),
+ temperature=self.temperature,
+ max_output_size=self.max_output_size,
+ )
+ elif endpoint == "question-answer":
+ questions = self.questions.split(",")
+ questions = [aai.LemurQuestion(question=q) for q in questions]
+ result = transcript_group.lemur.question(
+ questions=questions,
+ final_model=self.get_final_model(self.final_model),
+ temperature=self.temperature,
+ max_output_size=self.max_output_size,
+ )
+ else:
+ msg = f"Endpoint not supported: {endpoint}"
+ raise ValueError(msg)
+
+ return result.dict()
+
+ def get_final_model(self, model_name: str) -> aai.LemurModel:
+ if model_name == "claude3_5_sonnet":
+ return aai.LemurModel.claude3_5_sonnet
+ if model_name == "claude3_opus":
+ return aai.LemurModel.claude3_opus
+ if model_name == "claude3_haiku":
+ return aai.LemurModel.claude3_haiku
+ if model_name == "claude3_sonnet":
+ return aai.LemurModel.claude3_sonnet
+ msg = f"Model name not supported: {model_name}"
+ raise ValueError(msg)
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_list_transcripts.py b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_list_transcripts.py
new file mode 100644
index 0000000..369b85d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_list_transcripts.py
@@ -0,0 +1,95 @@
+import assemblyai as aai
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
+from langflow.schema import Data
+
+
+class AssemblyAIListTranscripts(Component):
+ display_name = "AssemblyAI List Transcripts"
+ description = "Retrieve a list of transcripts from AssemblyAI with filtering options"
+ documentation = "https://www.assemblyai.com/docs"
+ icon = "AssemblyAI"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Assembly API Key",
+ info="Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ required=True,
+ ),
+ IntInput(
+ name="limit",
+ display_name="Limit",
+ info="Maximum number of transcripts to retrieve (default: 20, use 0 for all)",
+ value=20,
+ ),
+ DropdownInput(
+ name="status_filter",
+ display_name="Status Filter",
+ options=["all", "queued", "processing", "completed", "error"],
+ value="all",
+ info="Filter by transcript status",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="created_on",
+ display_name="Created On",
+ info="Only get transcripts created on this date (YYYY-MM-DD)",
+ advanced=True,
+ ),
+ BoolInput(
+ name="throttled_only",
+ display_name="Throttled Only",
+ info="Only get throttled transcripts, overrides the status filter",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Transcript List", name="transcript_list", method="list_transcripts"),
+ ]
+
+ def list_transcripts(self) -> list[Data]:
+ aai.settings.api_key = self.api_key
+
+ params = aai.ListTranscriptParameters()
+ if self.limit:
+ params.limit = self.limit
+ if self.status_filter != "all":
+ params.status = self.status_filter
+ if self.created_on and self.created_on.text:
+ params.created_on = self.created_on.text
+ if self.throttled_only:
+ params.throttled_only = True
+
+ try:
+ transcriber = aai.Transcriber()
+
+ def convert_page_to_data_list(page):
+ return [Data(**t.dict()) for t in page.transcripts]
+
+ if self.limit == 0:
+ # paginate over all pages
+ params.limit = 100
+ page = transcriber.list_transcripts(params)
+ transcripts = convert_page_to_data_list(page)
+
+ while page.page_details.before_id_of_prev_url is not None:
+ params.before_id = page.page_details.before_id_of_prev_url
+ page = transcriber.list_transcripts(params)
+ transcripts.extend(convert_page_to_data_list(page))
+ else:
+ # just one page
+ page = transcriber.list_transcripts(params)
+ transcripts = convert_page_to_data_list(page)
+
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error listing transcripts")
+ error_data = Data(data={"error": f"An error occurred: {e}"})
+ self.status = [error_data]
+ return [error_data]
+
+ self.status = transcripts
+ return transcripts
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_poll_transcript.py b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_poll_transcript.py
new file mode 100644
index 0000000..f2ab678
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_poll_transcript.py
@@ -0,0 +1,72 @@
+import assemblyai as aai
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import DataInput, FloatInput, Output, SecretStrInput
+from langflow.schema import Data
+
+
+class AssemblyAITranscriptionJobPoller(Component):
+ display_name = "AssemblyAI Poll Transcript"
+ description = "Poll for the status of a transcription job using AssemblyAI"
+ documentation = "https://www.assemblyai.com/docs"
+ icon = "AssemblyAI"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Assembly API Key",
+ info="Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ required=True,
+ ),
+ DataInput(
+ name="transcript_id",
+ display_name="Transcript ID",
+ info="The ID of the transcription job to poll",
+ required=True,
+ ),
+ FloatInput(
+ name="polling_interval",
+ display_name="Polling Interval",
+ value=3.0,
+ info="The polling interval in seconds",
+ advanced=True,
+ range_spec=RangeSpec(min=3, max=30),
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Transcription Result", name="transcription_result", method="poll_transcription_job"),
+ ]
+
+ def poll_transcription_job(self) -> Data:
+ """Polls the transcription status until completion and returns the Data."""
+ aai.settings.api_key = self.api_key
+ aai.settings.polling_interval = self.polling_interval
+
+ # check if it's an error message from the previous step
+ if self.transcript_id.data.get("error"):
+ self.status = self.transcript_id.data["error"]
+ return self.transcript_id
+
+ try:
+ transcript = aai.Transcript.get_by_id(self.transcript_id.data["transcript_id"])
+ except Exception as e: # noqa: BLE001
+ error = f"Getting transcription failed: {e}"
+ logger.opt(exception=True).debug(error)
+ self.status = error
+ return Data(data={"error": error})
+
+ if transcript.status == aai.TranscriptStatus.completed:
+ json_response = transcript.json_response
+ text = json_response.pop("text", None)
+ utterances = json_response.pop("utterances", None)
+ transcript_id = json_response.pop("id", None)
+ sorted_data = {"text": text, "utterances": utterances, "id": transcript_id}
+ sorted_data.update(json_response)
+ data = Data(data=sorted_data)
+ self.status = data
+ return data
+ self.status = transcript.error
+ return Data(data={"error": transcript.error})
diff --git a/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_start_transcript.py b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_start_transcript.py
new file mode 100644
index 0000000..48cf11e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/assemblyai/assemblyai_start_transcript.py
@@ -0,0 +1,188 @@
+from pathlib import Path
+
+import assemblyai as aai
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput
+from langflow.schema import Data
+
+
+class AssemblyAITranscriptionJobCreator(Component):
+ display_name = "AssemblyAI Start Transcript"
+ description = "Create a transcription job for an audio file using AssemblyAI with advanced options"
+ documentation = "https://www.assemblyai.com/docs"
+ icon = "AssemblyAI"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Assembly API Key",
+ info="Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ required=True,
+ ),
+ FileInput(
+ name="audio_file",
+ display_name="Audio File",
+ file_types=[
+ "3ga",
+ "8svx",
+ "aac",
+ "ac3",
+ "aif",
+ "aiff",
+ "alac",
+ "amr",
+ "ape",
+ "au",
+ "dss",
+ "flac",
+ "flv",
+ "m4a",
+ "m4b",
+ "m4p",
+ "m4r",
+ "mp3",
+ "mpga",
+ "ogg",
+ "oga",
+ "mogg",
+ "opus",
+ "qcp",
+ "tta",
+ "voc",
+ "wav",
+ "wma",
+ "wv",
+ "webm",
+ "mts",
+ "m2ts",
+ "ts",
+ "mov",
+ "mp2",
+ "mp4",
+ "m4p",
+ "m4v",
+ "mxf",
+ ],
+ info="The audio file to transcribe",
+ required=True,
+ ),
+ MessageTextInput(
+ name="audio_file_url",
+ display_name="Audio File URL",
+ info="The URL of the audio file to transcribe (Can be used instead of a File)",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="speech_model",
+ display_name="Speech Model",
+ options=[
+ "best",
+ "nano",
+ ],
+ value="best",
+ info="The speech model to use for the transcription",
+ advanced=True,
+ ),
+ BoolInput(
+ name="language_detection",
+ display_name="Automatic Language Detection",
+ info="Enable automatic language detection",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="language_code",
+ display_name="Language",
+ info=(
+ """
+ The language of the audio file. Can be set manually if automatic language detection is disabled.
+ See https://www.assemblyai.com/docs/getting-started/supported-languages """
+ "for a list of supported language codes."
+ ),
+ advanced=True,
+ ),
+ BoolInput(
+ name="speaker_labels",
+ display_name="Enable Speaker Labels",
+ info="Enable speaker diarization",
+ ),
+ MessageTextInput(
+ name="speakers_expected",
+ display_name="Expected Number of Speakers",
+ info="Set the expected number of speakers (optional, enter a number)",
+ advanced=True,
+ ),
+ BoolInput(
+ name="punctuate",
+ display_name="Punctuate",
+ info="Enable automatic punctuation",
+ advanced=True,
+ value=True,
+ ),
+ BoolInput(
+ name="format_text",
+ display_name="Format Text",
+ info="Enable text formatting",
+ advanced=True,
+ value=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Transcript ID", name="transcript_id", method="create_transcription_job"),
+ ]
+
+ def create_transcription_job(self) -> Data:
+ aai.settings.api_key = self.api_key
+
+ # Convert speakers_expected to int if it's not empty
+ speakers_expected = None
+ if self.speakers_expected and self.speakers_expected.strip():
+ try:
+ speakers_expected = int(self.speakers_expected)
+ except ValueError:
+ self.status = "Error: Expected Number of Speakers must be a valid integer"
+ return Data(data={"error": "Error: Expected Number of Speakers must be a valid integer"})
+
+ language_code = self.language_code or None
+
+ config = aai.TranscriptionConfig(
+ speech_model=self.speech_model,
+ language_detection=self.language_detection,
+ language_code=language_code,
+ speaker_labels=self.speaker_labels,
+ speakers_expected=speakers_expected,
+ punctuate=self.punctuate,
+ format_text=self.format_text,
+ )
+
+ audio = None
+ if self.audio_file:
+ if self.audio_file_url:
+ logger.warning("Both an audio file an audio URL were specified. The audio URL was ignored.")
+
+ # Check if the file exists
+ if not Path(self.audio_file).exists():
+ self.status = "Error: Audio file not found"
+ return Data(data={"error": "Error: Audio file not found"})
+ audio = self.audio_file
+ elif self.audio_file_url:
+ audio = self.audio_file_url
+ else:
+ self.status = "Error: Either an audio file or an audio URL must be specified"
+ return Data(data={"error": "Error: Either an audio file or an audio URL must be specified"})
+
+ try:
+ transcript = aai.Transcriber().submit(audio, config=config)
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error submitting transcription job")
+ self.status = f"An error occurred: {e}"
+ return Data(data={"error": f"An error occurred: {e}"})
+
+ if transcript.error:
+ self.status = transcript.error
+ return Data(data={"error": transcript.error})
+ result = Data(data={"transcript_id": transcript.id})
+ self.status = result
+ return result
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/__init__.py b/langflow/src/backend/base/langflow/components/astra_assistants/__init__.py
new file mode 100644
index 0000000..b24554a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/__init__.py
@@ -0,0 +1,19 @@
+from .astra_assistant_manager import AstraAssistantManager
+from .create_assistant import AssistantsCreateAssistant
+from .create_thread import AssistantsCreateThread
+from .dotenv import Dotenv
+from .get_assistant import AssistantsGetAssistantName
+from .getenvvar import GetEnvVar
+from .list_assistants import AssistantsListAssistants
+from .run import AssistantsRun
+
+__all__ = [
+ "AssistantsCreateAssistant",
+ "AssistantsCreateThread",
+ "AssistantsGetAssistantName",
+ "AssistantsListAssistants",
+ "AssistantsRun",
+ "AstraAssistantManager",
+ "Dotenv",
+ "GetEnvVar",
+]
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/astra_assistant_manager.py b/langflow/src/backend/base/langflow/components/astra_assistants/astra_assistant_manager.py
new file mode 100644
index 0000000..74be041
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/astra_assistant_manager.py
@@ -0,0 +1,306 @@
+import asyncio
+from asyncio import to_thread
+from typing import TYPE_CHECKING, Any, cast
+
+from astra_assistants.astra_assistants_manager import AssistantManager
+from langchain_core.agents import AgentFinish
+from loguru import logger
+
+from langflow.base.agents.events import ExceptionWithMessageError, process_agent_events
+from langflow.base.astra_assistants.util import (
+ get_patched_openai_client,
+ litellm_model_names,
+ sync_upload,
+ wrap_base_tool_as_tool_interface,
+)
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.inputs import DropdownInput, FileInput, HandleInput, MultilineInput
+from langflow.memory import delete_message
+from langflow.schema.content_block import ContentBlock
+from langflow.schema.message import Message
+from langflow.template import Output
+from langflow.utils.constants import MESSAGE_SENDER_AI
+
+if TYPE_CHECKING:
+ from langflow.schema.log import SendMessageFunctionType
+
+
+class AstraAssistantManager(ComponentWithCache):
+ display_name = "Astra Assistant Agent"
+ name = "Astra Assistant Agent"
+ description = "Manages Assistant Interactions"
+ icon = "AstraDB"
+
+ inputs = [
+ DropdownInput(
+ name="model_name",
+ display_name="Model",
+ advanced=False,
+ options=litellm_model_names,
+ value="gpt-4o-mini",
+ ),
+ MultilineInput(
+ name="instructions",
+ display_name="Agent Instructions",
+ info="Instructions for the assistant, think of these as the system prompt.",
+ ),
+ HandleInput(
+ name="input_tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ required=False,
+ info="These are the tools that the agent can use to help with tasks.",
+ ),
+ # DropdownInput(
+ # display_name="Tools",
+ # name="tool",
+ # options=tool_names,
+ # ),
+ MultilineInput(
+ name="user_message", display_name="User Message", info="User message to pass to the run.", tool_mode=True
+ ),
+ FileInput(
+ name="file",
+ display_name="File(s) for retrieval",
+ list=True,
+ info="Files to be sent with the message.",
+ required=False,
+ show=True,
+ file_types=[
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image",
+ "zip",
+ "tar",
+ "tgz",
+ "bz2",
+ "gz",
+ "c",
+ "cpp",
+ "cs",
+ "css",
+ "go",
+ "java",
+ "php",
+ "rb",
+ "tex",
+ "doc",
+ "docx",
+ "ppt",
+ "pptx",
+ "xls",
+ "xlsx",
+ "jsonl",
+ ],
+ ),
+ MultilineInput(
+ name="input_thread_id",
+ display_name="Thread ID (optional)",
+ info="ID of the thread",
+ advanced=True,
+ ),
+ MultilineInput(
+ name="input_assistant_id",
+ display_name="Assistant ID (optional)",
+ info="ID of the assistant",
+ advanced=True,
+ ),
+ MultilineInput(
+ name="env_set",
+ display_name="Environment Set",
+ info="Dummy input to allow chaining with Dotenv Component.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Assistant Response", name="assistant_response", method="get_assistant_response"),
+ Output(display_name="Tool output", name="tool_output", method="get_tool_output", hidden=True),
+ Output(display_name="Thread Id", name="output_thread_id", method="get_thread_id", hidden=True),
+ Output(display_name="Assistant Id", name="output_assistant_id", method="get_assistant_id", hidden=True),
+ Output(display_name="Vector Store Id", name="output_vs_id", method="get_vs_id", hidden=True),
+ ]
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.lock = asyncio.Lock()
+ self.initialized: bool = False
+ self._assistant_response: Message = None # type: ignore[assignment]
+ self._tool_output: Message = None # type: ignore[assignment]
+ self._thread_id: Message = None # type: ignore[assignment]
+ self._assistant_id: Message = None # type: ignore[assignment]
+ self._vs_id: Message = None # type: ignore[assignment]
+ self.client = get_patched_openai_client(self._shared_component_cache)
+ self.input_tools: list[Any]
+
+ async def get_assistant_response(self) -> Message:
+ await self.initialize()
+ self.status = self._assistant_response
+ return self._assistant_response
+
+ async def get_vs_id(self) -> Message:
+ await self.initialize()
+ self.status = self._vs_id
+ return self._vs_id
+
+ async def get_tool_output(self) -> Message:
+ await self.initialize()
+ self.status = self._tool_output
+ return self._tool_output
+
+ async def get_thread_id(self) -> Message:
+ await self.initialize()
+ self.status = self._thread_id
+ return self._thread_id
+
+ async def get_assistant_id(self) -> Message:
+ await self.initialize()
+ self.status = self._assistant_id
+ return self._assistant_id
+
+ async def initialize(self) -> None:
+ async with self.lock:
+ if not self.initialized:
+ await self.process_inputs()
+ self.initialized = True
+
+ async def process_inputs(self) -> None:
+ logger.info(f"env_set is {self.env_set}")
+ logger.info(self.input_tools)
+ tools = []
+ tool_obj = None
+ if self.input_tools is None:
+ self.input_tools = []
+ for tool in self.input_tools:
+ tool_obj = wrap_base_tool_as_tool_interface(tool)
+ tools.append(tool_obj)
+
+ assistant_id = None
+ thread_id = None
+ if self.input_assistant_id:
+ assistant_id = self.input_assistant_id
+ if self.input_thread_id:
+ thread_id = self.input_thread_id
+
+ if hasattr(self, "graph"):
+ session_id = self.graph.session_id
+ elif hasattr(self, "_session_id"):
+ session_id = self._session_id
+ else:
+ session_id = None
+
+ agent_message = Message(
+ sender=MESSAGE_SENDER_AI,
+ sender_name=self.display_name or "Astra Assistant",
+ properties={"icon": "Bot", "state": "partial"},
+ content_blocks=[ContentBlock(title="Assistant Steps", contents=[])],
+ session_id=session_id,
+ )
+
+ assistant_manager = AssistantManager(
+ instructions=self.instructions,
+ model=self.model_name,
+ name="managed_assistant",
+ tools=tools,
+ client=self.client,
+ thread_id=thread_id,
+ assistant_id=assistant_id,
+ )
+
+ if self.file:
+ file = await to_thread(sync_upload, self.file, assistant_manager.client)
+ vector_store = assistant_manager.client.beta.vector_stores.create(name="my_vs", file_ids=[file.id])
+ assistant_tools = assistant_manager.assistant.tools
+ assistant_tools += [{"type": "file_search"}]
+ assistant = assistant_manager.client.beta.assistants.update(
+ assistant_manager.assistant.id,
+ tools=assistant_tools,
+ tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}},
+ )
+ assistant_manager.assistant = assistant
+
+ async def step_iterator():
+ # Initial event
+ yield {"event": "on_chain_start", "name": "AstraAssistant", "data": {"input": {"text": self.user_message}}}
+
+ content = self.user_message
+ result = await assistant_manager.run_thread(content=content, tool=tool_obj)
+
+ # Tool usage if present
+ if "output" in result and "arguments" in result:
+ yield {"event": "on_tool_start", "name": "tool", "data": {"input": {"text": str(result["arguments"])}}}
+ yield {"event": "on_tool_end", "name": "tool", "data": {"output": result["output"]}}
+
+ if "file_search" in result and result["file_search"] is not None:
+ yield {"event": "on_tool_start", "name": "tool", "data": {"input": {"text": self.user_message}}}
+ file_search_str = ""
+ for chunk in result["file_search"].to_dict().get("chunks", []):
+ file_search_str += f"## Chunk ID: `{chunk['chunk_id']}`\n"
+ file_search_str += f"**Content:**\n\n```\n{chunk['content']}\n```\n\n"
+ if "score" in chunk:
+ file_search_str += f"**Score:** {chunk['score']}\n\n"
+ if "file_id" in chunk:
+ file_search_str += f"**File ID:** `{chunk['file_id']}`\n\n"
+ if "file_name" in chunk:
+ file_search_str += f"**File Name:** `{chunk['file_name']}`\n\n"
+ if "bytes" in chunk:
+ file_search_str += f"**Bytes:** {chunk['bytes']}\n\n"
+ if "search_string" in chunk:
+ file_search_str += f"**Search String:** {chunk['search_string']}\n\n"
+ yield {"event": "on_tool_end", "name": "tool", "data": {"output": file_search_str}}
+
+ if "text" not in result:
+ msg = f"No text in result, {result}"
+ raise ValueError(msg)
+
+ self._assistant_response = Message(text=result["text"])
+ if "decision" in result:
+ self._tool_output = Message(text=str(result["decision"].is_complete))
+ else:
+ self._tool_output = Message(text=result["text"])
+ self._thread_id = Message(text=assistant_manager.thread.id)
+ self._assistant_id = Message(text=assistant_manager.assistant.id)
+
+ # Final event - format it like AgentFinish to match the expected format
+ yield {
+ "event": "on_chain_end",
+ "name": "AstraAssistant",
+ "data": {"output": AgentFinish(return_values={"output": result["text"]}, log="")},
+ }
+
+ try:
+ if hasattr(self, "send_message"):
+ processed_result = await process_agent_events(
+ step_iterator(),
+ agent_message,
+ cast("SendMessageFunctionType", self.send_message),
+ )
+ self.status = processed_result
+ except ExceptionWithMessageError as e:
+ msg_id = e.agent_message.id
+ await delete_message(id_=msg_id)
+ await self._send_message_event(e.agent_message, category="remove_message")
+ raise
+ except Exception:
+ raise
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/create_assistant.py b/langflow/src/backend/base/langflow/components/astra_assistants/create_assistant.py
new file mode 100644
index 0000000..1798892
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/create_assistant.py
@@ -0,0 +1,59 @@
+from loguru import logger
+
+from langflow.base.astra_assistants.util import get_patched_openai_client
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.inputs import MultilineInput, StrInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class AssistantsCreateAssistant(ComponentWithCache):
+ icon = "AstraDB"
+ display_name = "Create Assistant"
+ description = "Creates an Assistant and returns it's id"
+
+ inputs = [
+ StrInput(
+ name="assistant_name",
+ display_name="Assistant Name",
+ info="Name for the assistant being created",
+ ),
+ StrInput(
+ name="instructions",
+ display_name="Instructions",
+ info="Instructions for the assistant, think of these as the system prompt.",
+ ),
+ StrInput(
+ name="model",
+ display_name="Model name",
+ info=(
+ "Model for the assistant.\n\n"
+ "Environment variables for provider credentials can be set with the Dotenv Component.\n\n"
+ "Models are supported via LiteLLM, "
+ "see (https://docs.litellm.ai/docs/providers) for supported model names and env vars."
+ ),
+ # refresh_model=True
+ ),
+ MultilineInput(
+ name="env_set",
+ display_name="Environment Set",
+ info="Dummy input to allow chaining with Dotenv Component.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Assistant ID", name="assistant_id", method="process_inputs"),
+ ]
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.client = get_patched_openai_client(self._shared_component_cache)
+
+ def process_inputs(self) -> Message:
+ logger.info(f"env_set is {self.env_set}")
+ assistant = self.client.beta.assistants.create(
+ name=self.assistant_name,
+ instructions=self.instructions,
+ model=self.model,
+ )
+ return Message(text=assistant.id)
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/create_thread.py b/langflow/src/backend/base/langflow/components/astra_assistants/create_thread.py
new file mode 100644
index 0000000..c41bee0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/create_thread.py
@@ -0,0 +1,32 @@
+from langflow.base.astra_assistants.util import get_patched_openai_client
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.inputs import MultilineInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class AssistantsCreateThread(ComponentWithCache):
+ display_name = "Create Assistant Thread"
+ description = "Creates a thread and returns the thread id"
+ icon = "AstraDB"
+ inputs = [
+ MultilineInput(
+ name="env_set",
+ display_name="Environment Set",
+ info="Dummy input to allow chaining with Dotenv Component.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Thread ID", name="thread_id", method="process_inputs"),
+ ]
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.client = get_patched_openai_client(self._shared_component_cache)
+
+ def process_inputs(self) -> Message:
+ thread = self.client.beta.threads.create()
+ thread_id = thread.id
+
+ return Message(text=thread_id)
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/dotenv.py b/langflow/src/backend/base/langflow/components/astra_assistants/dotenv.py
new file mode 100644
index 0000000..83ba991
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/dotenv.py
@@ -0,0 +1,35 @@
+import io
+
+from dotenv import load_dotenv
+
+from langflow.custom import Component
+from langflow.inputs import MultilineSecretInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class Dotenv(Component):
+ display_name = "Dotenv"
+ description = "Load .env file into env vars"
+ icon = "AstraDB"
+ inputs = [
+ MultilineSecretInput(
+ name="dotenv_file_content",
+ display_name="Dotenv file content",
+ info="Paste the content of your .env file directly, since contents are sensitive, "
+ "using a Global variable set as 'password' is recommended",
+ )
+ ]
+
+ outputs = [
+ Output(display_name="env_set", name="env_set", method="process_inputs"),
+ ]
+
+ def process_inputs(self) -> Message:
+ fake_file = io.StringIO(self.dotenv_file_content)
+ result = load_dotenv(stream=fake_file, override=True)
+
+ message = Message(text="No variables found in .env")
+ if result:
+ message = Message(text="Loaded .env")
+ return message
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/get_assistant.py b/langflow/src/backend/base/langflow/components/astra_assistants/get_assistant.py
new file mode 100644
index 0000000..8d6f05e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/get_assistant.py
@@ -0,0 +1,37 @@
+from langflow.base.astra_assistants.util import get_patched_openai_client
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.inputs import MultilineInput, StrInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class AssistantsGetAssistantName(ComponentWithCache):
+ display_name = "Get Assistant name"
+ description = "Assistant by id"
+ icon = "AstraDB"
+ inputs = [
+ StrInput(
+ name="assistant_id",
+ display_name="Assistant ID",
+ info="ID of the assistant",
+ ),
+ MultilineInput(
+ name="env_set",
+ display_name="Environment Set",
+ info="Dummy input to allow chaining with Dotenv Component.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Assistant Name", name="assistant_name", method="process_inputs"),
+ ]
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.client = get_patched_openai_client(self._shared_component_cache)
+
+ def process_inputs(self) -> Message:
+ assistant = self.client.beta.assistants.retrieve(
+ assistant_id=self.assistant_id,
+ )
+ return Message(text=assistant.name)
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/getenvvar.py b/langflow/src/backend/base/langflow/components/astra_assistants/getenvvar.py
new file mode 100644
index 0000000..0076cd6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/getenvvar.py
@@ -0,0 +1,30 @@
+import os
+
+from langflow.custom import Component
+from langflow.inputs import StrInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class GetEnvVar(Component):
+ display_name = "Get env var"
+ description = "Get env var"
+ icon = "AstraDB"
+
+ inputs = [
+ StrInput(
+ name="env_var_name",
+ display_name="Env var name",
+ info="Name of the environment variable to get",
+ )
+ ]
+
+ outputs = [
+ Output(display_name="Env var value", name="env_var_value", method="process_inputs"),
+ ]
+
+ def process_inputs(self) -> Message:
+ if self.env_var_name not in os.environ:
+ msg = f"Environment variable {self.env_var_name} not set"
+ raise ValueError(msg)
+ return Message(text=os.environ[self.env_var_name])
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/list_assistants.py b/langflow/src/backend/base/langflow/components/astra_assistants/list_assistants.py
new file mode 100644
index 0000000..40db4db
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/list_assistants.py
@@ -0,0 +1,25 @@
+from langflow.base.astra_assistants.util import get_patched_openai_client
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.schema.message import Message
+from langflow.template.field.base import Output
+
+
+class AssistantsListAssistants(ComponentWithCache):
+ display_name = "List Assistants"
+ description = "Returns a list of assistant id's"
+ icon = "AstraDB"
+ outputs = [
+ Output(display_name="Assistants", name="assistants", method="process_inputs"),
+ ]
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.client = get_patched_openai_client(self._shared_component_cache)
+
+ def process_inputs(self) -> Message:
+ assistants = self.client.beta.assistants.list().data
+ id_list = [assistant.id for assistant in assistants]
+ return Message(
+ # get text from list
+ text="\n".join(id_list)
+ )
diff --git a/langflow/src/backend/base/langflow/components/astra_assistants/run.py b/langflow/src/backend/base/langflow/components/astra_assistants/run.py
new file mode 100644
index 0000000..e065563
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/astra_assistants/run.py
@@ -0,0 +1,89 @@
+from typing import Any
+
+from openai.lib.streaming import AssistantEventHandler
+
+from langflow.base.astra_assistants.util import get_patched_openai_client
+from langflow.custom.custom_component.component_with_cache import ComponentWithCache
+from langflow.inputs import MultilineInput
+from langflow.schema import dotdict
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class AssistantsRun(ComponentWithCache):
+ display_name = "Run Assistant"
+ description = "Executes an Assistant Run against a thread"
+ icon = "AstraDB"
+
+ def __init__(self, **kwargs) -> None:
+ super().__init__(**kwargs)
+ self.client = get_patched_openai_client(self._shared_component_cache)
+ self.thread_id = None
+
+ def update_build_config(
+ self,
+ build_config: dotdict,
+ field_value: Any,
+ field_name: str | None = None,
+ ) -> None:
+ if field_name == "thread_id":
+ if field_value is None:
+ thread = self.client.beta.threads.create()
+ self.thread_id = thread.id
+ build_config["thread_id"] = field_value
+
+ inputs = [
+ MultilineInput(
+ name="assistant_id",
+ display_name="Assistant ID",
+ info=(
+ "The ID of the assistant to run. \n\n"
+ "Can be retrieved using the List Assistants component or created with the Create Assistant component."
+ ),
+ ),
+ MultilineInput(
+ name="user_message",
+ display_name="User Message",
+ info="User message to pass to the run.",
+ ),
+ MultilineInput(
+ name="thread_id",
+ display_name="Thread ID",
+ required=False,
+ info="Thread ID to use with the run. If not provided, a new thread will be created.",
+ ),
+ MultilineInput(
+ name="env_set",
+ display_name="Environment Set",
+ info="Dummy input to allow chaining with Dotenv Component.",
+ ),
+ ]
+
+ outputs = [Output(display_name="Assistant Response", name="assistant_response", method="process_inputs")]
+
+ def process_inputs(self) -> Message:
+ text = ""
+
+ if self.thread_id is None:
+ thread = self.client.beta.threads.create()
+ self.thread_id = thread.id
+
+ # add the user message
+ self.client.beta.threads.messages.create(thread_id=self.thread_id, role="user", content=self.user_message)
+
+ class EventHandler(AssistantEventHandler):
+ def __init__(self) -> None:
+ super().__init__()
+
+ def on_exception(self, exception: Exception) -> None:
+ raise exception
+
+ event_handler = EventHandler()
+ with self.client.beta.threads.runs.create_and_stream(
+ thread_id=self.thread_id,
+ assistant_id=self.assistant_id,
+ event_handler=event_handler,
+ ) as stream:
+ for part in stream.text_deltas:
+ text += part
+ return Message(text=text)
diff --git a/langflow/src/backend/base/langflow/components/chains/__init__.py b/langflow/src/backend/base/langflow/components/chains/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/cohere/__init__.py b/langflow/src/backend/base/langflow/components/cohere/__init__.py
new file mode 100644
index 0000000..91945ee
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/cohere/__init__.py
@@ -0,0 +1,3 @@
+from .cohere_rerank import CohereRerankComponent
+
+__all__ = ["CohereRerankComponent"]
diff --git a/langflow/src/backend/base/langflow/components/cohere/cohere_rerank.py b/langflow/src/backend/base/langflow/components/cohere/cohere_rerank.py
new file mode 100644
index 0000000..0f6838f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/cohere/cohere_rerank.py
@@ -0,0 +1,51 @@
+from langflow.base.compressors.model import LCCompressorComponent
+from langflow.field_typing import BaseDocumentCompressor
+from langflow.inputs.inputs import SecretStrInput
+from langflow.io import DropdownInput
+from langflow.template.field.base import Output
+
+
+class CohereRerankComponent(LCCompressorComponent):
+ display_name = "Cohere Rerank"
+ description = "Rerank documents using the Cohere API."
+ name = "CohereRerank"
+ icon = "Cohere"
+
+ inputs = [
+ *LCCompressorComponent.inputs,
+ SecretStrInput(
+ name="api_key",
+ display_name="Cohere API Key",
+ ),
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ options=[
+ "rerank-english-v3.0",
+ "rerank-multilingual-v3.0",
+ "rerank-english-v2.0",
+ "rerank-multilingual-v2.0",
+ ],
+ value="rerank-english-v3.0",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Reranked Documents",
+ name="reranked_documents",
+ method="compress_documents",
+ ),
+ ]
+
+ def build_compressor(self) -> BaseDocumentCompressor: # type: ignore[type-var]
+ try:
+ from langchain_cohere import CohereRerank
+ except ImportError as e:
+ msg = "Please install langchain-cohere to use the Cohere model."
+ raise ImportError(msg) from e
+ return CohereRerank(
+ cohere_api_key=self.api_key,
+ model=self.model,
+ top_n=self.top_n,
+ )
diff --git a/langflow/src/backend/base/langflow/components/composio/__init__.py b/langflow/src/backend/base/langflow/components/composio/__init__.py
new file mode 100644
index 0000000..24e4381
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/composio/__init__.py
@@ -0,0 +1,3 @@
+from .composio_api import ComposioAPIComponent
+
+__all__ = ["ComposioAPIComponent"]
diff --git a/langflow/src/backend/base/langflow/components/composio/composio_api.py b/langflow/src/backend/base/langflow/components/composio/composio_api.py
new file mode 100644
index 0000000..fcd69e0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/composio/composio_api.py
@@ -0,0 +1,376 @@
+# Standard library imports
+from collections.abc import Sequence
+from typing import Any
+
+import requests
+
+# Third-party imports
+from composio.client.collections import AppAuthScheme
+from composio.client.exceptions import NoItemsFound
+from composio_langchain import Action, ComposioToolSet
+from langchain_core.tools import Tool
+from loguru import logger
+
+# Local imports
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.inputs import DropdownInput, LinkInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput
+from langflow.io import Output
+
+
+class ComposioAPIComponent(LCToolComponent):
+ display_name: str = "Composio Tools"
+ description: str = "Use Composio toolset to run actions with your agent"
+ name = "ComposioAPI"
+ icon = "Composio"
+ documentation: str = "https://docs.composio.dev"
+
+ inputs = [
+ # Basic configuration inputs
+ MessageTextInput(name="entity_id", display_name="Entity ID", value="default", advanced=True),
+ SecretStrInput(
+ name="api_key",
+ display_name="Composio API Key",
+ required=True,
+ info="Refer to https://docs.composio.dev/faq/api_key/api_key",
+ real_time_refresh=True,
+ ),
+ DropdownInput(
+ name="app_names",
+ display_name="App Name",
+ options=[],
+ value="",
+ info="The app name to use. Please refresh after selecting app name",
+ refresh_button=True,
+ required=True,
+ ),
+ # Authentication-related inputs (initially hidden)
+ SecretStrInput(
+ name="app_credentials",
+ display_name="App Credentials",
+ required=False,
+ dynamic=True,
+ show=False,
+ info="Credentials for app authentication (API Key, Password, etc)",
+ load_from_db=False,
+ ),
+ MessageTextInput(
+ name="username",
+ display_name="Username",
+ required=False,
+ dynamic=True,
+ show=False,
+ info="Username for Basic authentication",
+ ),
+ LinkInput(
+ name="auth_link",
+ display_name="Authentication Link",
+ value="",
+ info="Click to authenticate with OAuth2",
+ dynamic=True,
+ show=False,
+ placeholder="Click to authenticate",
+ ),
+ StrInput(
+ name="auth_status",
+ display_name="Auth Status",
+ value="Not Connected",
+ info="Current authentication status",
+ dynamic=True,
+ show=False,
+ ),
+ MultiselectInput(
+ name="action_names",
+ display_name="Actions to use",
+ required=True,
+ options=[],
+ value=[],
+ info="The actions to pass to agent to execute",
+ dynamic=True,
+ show=False,
+ ),
+ ]
+
+ outputs = [
+ Output(name="tools", display_name="Tools", method="build_tool"),
+ ]
+
+ def _check_for_authorization(self, app: str) -> str:
+ """Checks if the app is authorized.
+
+ Args:
+ app (str): The app name to check authorization for.
+
+ Returns:
+ str: The authorization status or URL.
+ """
+ toolset = self._build_wrapper()
+ entity = toolset.client.get_entity(id=self.entity_id)
+ try:
+ # Check if user is already connected
+ entity.get_connection(app=app)
+ except NoItemsFound:
+ # Get auth scheme for the app
+ auth_scheme = self._get_auth_scheme(app)
+ return self._handle_auth_by_scheme(entity, app, auth_scheme)
+ except Exception: # noqa: BLE001
+ logger.exception("Authorization error")
+ return "Error checking authorization"
+ else:
+ return f"{app} CONNECTED"
+
+ def _get_auth_scheme(self, app_name: str) -> AppAuthScheme:
+ """Get the primary auth scheme for an app.
+
+ Args:
+ app_name (str): The name of the app to get auth scheme for.
+
+ Returns:
+ AppAuthScheme: The auth scheme details.
+ """
+ toolset = self._build_wrapper()
+ try:
+ return toolset.get_auth_scheme_for_app(app=app_name.lower())
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error getting auth scheme for {app_name}")
+ return None
+
+ def _get_oauth_apps(self, api_key: str) -> list[str]:
+ """Fetch OAuth-enabled apps from Composio API.
+
+ Args:
+ api_key (str): The Composio API key.
+
+ Returns:
+ list[str]: A list containing OAuth-enabled app names.
+ """
+ oauth_apps = []
+ try:
+ url = "https://backend.composio.dev/api/v1/apps"
+ headers = {"x-api-key": api_key}
+ params = {
+ "includeLocal": "true",
+ "additionalFields": "auth_schemes",
+ "sortBy": "alphabet",
+ }
+
+ response = requests.get(url, headers=headers, params=params, timeout=20)
+ data = response.json()
+
+ for item in data.get("items", []):
+ for auth_scheme in item.get("auth_schemes", []):
+ if auth_scheme.get("mode") in {"OAUTH1", "OAUTH2"}:
+ oauth_apps.append(item["key"].upper())
+ break
+ except requests.RequestException as e:
+ logger.error(f"Error fetching OAuth apps: {e}")
+ return []
+ else:
+ return oauth_apps
+
+ def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthScheme) -> str:
+ """Handle authentication based on the auth scheme.
+
+ Args:
+ entity (Any): The entity instance.
+ app (str): The app name.
+ auth_scheme (AppAuthScheme): The auth scheme details.
+
+ Returns:
+ str: The authentication status or URL.
+ """
+ auth_mode = auth_scheme.auth_mode
+
+ try:
+ # First check if already connected
+ entity.get_connection(app=app)
+ except NoItemsFound:
+ # If not connected, handle new connection based on auth mode
+ if auth_mode == "API_KEY":
+ if hasattr(self, "app_credentials") and self.app_credentials:
+ try:
+ entity.initiate_connection(
+ app_name=app,
+ auth_mode="API_KEY",
+ auth_config={"api_key": self.app_credentials},
+ use_composio_auth=False,
+ force_new_integration=True,
+ )
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Error connecting with API Key: {e}")
+ return "Invalid API Key"
+ else:
+ return f"{app} CONNECTED"
+ return "Enter API Key"
+
+ if (
+ auth_mode == "BASIC"
+ and hasattr(self, "username")
+ and hasattr(self, "app_credentials")
+ and self.username
+ and self.app_credentials
+ ):
+ try:
+ entity.initiate_connection(
+ app_name=app,
+ auth_mode="BASIC",
+ auth_config={"username": self.username, "password": self.app_credentials},
+ use_composio_auth=False,
+ force_new_integration=True,
+ )
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Error connecting with Basic Auth: {e}")
+ return "Invalid credentials"
+ else:
+ return f"{app} CONNECTED"
+ elif auth_mode == "BASIC":
+ return "Enter Username and Password"
+
+ if auth_mode == "OAUTH2":
+ try:
+ return self._initiate_default_connection(entity, app)
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Error initiating OAuth2: {e}")
+ return "OAuth2 initialization failed"
+
+ return "Unsupported auth mode"
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Error checking connection status: {e}")
+ return f"Error: {e!s}"
+ else:
+ return f"{app} CONNECTED"
+
+ def _initiate_default_connection(self, entity: Any, app: str) -> str:
+ connection = entity.initiate_connection(app_name=app, use_composio_auth=True, force_new_integration=True)
+ return connection.redirectUrl
+
+ def _get_connected_app_names_for_entity(self) -> list[str]:
+ toolset = self._build_wrapper()
+ connections = toolset.client.get_entity(id=self.entity_id).get_connections()
+ return list({connection.appUniqueId for connection in connections})
+
+ def _get_normalized_app_name(self) -> str:
+ """Get app name without connection status suffix.
+
+ Returns:
+ str: Normalized app name.
+ """
+ return self.app_names.replace(" ✅", "").replace("_connected", "")
+
+ def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: # noqa: ARG002
+ # Update the available apps options from the API
+ if hasattr(self, "api_key") and self.api_key != "":
+ toolset = self._build_wrapper()
+ build_config["app_names"]["options"] = self._get_oauth_apps(api_key=self.api_key)
+
+ # First, ensure all dynamic fields are hidden by default
+ dynamic_fields = ["app_credentials", "username", "auth_link", "auth_status", "action_names"]
+ for field in dynamic_fields:
+ if field in build_config:
+ if build_config[field]["value"] is None or build_config[field]["value"] == "":
+ build_config[field]["show"] = False
+ build_config[field]["advanced"] = True
+ build_config[field]["load_from_db"] = False
+ else:
+ build_config[field]["show"] = True
+ build_config[field]["advanced"] = False
+
+ if field_name == "app_names" and (not hasattr(self, "app_names") or not self.app_names):
+ build_config["auth_status"]["show"] = True
+ build_config["auth_status"]["value"] = "Please select an app first"
+ return build_config
+
+ if field_name == "app_names" and hasattr(self, "api_key") and self.api_key != "":
+ # app_name = self._get_normalized_app_name()
+ app_name = self.app_names
+ try:
+ toolset = self._build_wrapper()
+ entity = toolset.client.get_entity(id=self.entity_id)
+
+ # Always show auth_status when app is selected
+ build_config["auth_status"]["show"] = True
+ build_config["auth_status"]["advanced"] = False
+
+ try:
+ # Check if already connected
+ entity.get_connection(app=app_name)
+ build_config["auth_status"]["value"] = "✅"
+ build_config["auth_link"]["show"] = False
+ # Show action selection for connected apps
+ build_config["action_names"]["show"] = True
+ build_config["action_names"]["advanced"] = False
+
+ except NoItemsFound:
+ # Get auth scheme and show relevant fields
+ auth_scheme = self._get_auth_scheme(app_name)
+ auth_mode = auth_scheme.auth_mode
+ logger.info(f"Auth mode for {app_name}: {auth_mode}")
+
+ if auth_mode == "API_KEY":
+ build_config["app_credentials"]["show"] = True
+ build_config["app_credentials"]["advanced"] = False
+ build_config["app_credentials"]["display_name"] = "API Key"
+ build_config["auth_status"]["value"] = "Enter API Key"
+
+ elif auth_mode == "BASIC":
+ build_config["username"]["show"] = True
+ build_config["username"]["advanced"] = False
+ build_config["app_credentials"]["show"] = True
+ build_config["app_credentials"]["advanced"] = False
+ build_config["app_credentials"]["display_name"] = "Password"
+ build_config["auth_status"]["value"] = "Enter Username and Password"
+
+ elif auth_mode == "OAUTH2":
+ build_config["auth_link"]["show"] = True
+ build_config["auth_link"]["advanced"] = False
+ auth_url = self._initiate_default_connection(entity, app_name)
+ build_config["auth_link"]["value"] = auth_url
+ build_config["auth_status"]["value"] = "Click link to authenticate"
+
+ else:
+ build_config["auth_status"]["value"] = "Unsupported auth mode"
+
+ # Update action names if connected
+ if build_config["auth_status"]["value"] == "✅":
+ all_action_names = [str(action).replace("Action.", "") for action in Action.all()]
+ app_action_names = [
+ action_name
+ for action_name in all_action_names
+ if action_name.lower().startswith(app_name.lower() + "_")
+ ]
+ if build_config["action_names"]["options"] != app_action_names:
+ build_config["action_names"]["options"] = app_action_names
+ build_config["action_names"]["value"] = [app_action_names[0]] if app_action_names else [""]
+
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Error checking auth status: {e}, app: {app_name}")
+ build_config["auth_status"]["value"] = f"Error: {e!s}"
+
+ return build_config
+
+ def build_tool(self) -> Sequence[Tool]:
+ """Build Composio tools based on selected actions.
+
+ Returns:
+ Sequence[Tool]: List of configured Composio tools.
+ """
+ composio_toolset = self._build_wrapper()
+ return composio_toolset.get_tools(actions=self.action_names)
+
+ def _build_wrapper(self) -> ComposioToolSet:
+ """Build the Composio toolset wrapper.
+
+ Returns:
+ ComposioToolSet: The initialized toolset.
+
+ Raises:
+ ValueError: If the API key is not found or invalid.
+ """
+ try:
+ if not self.api_key:
+ msg = "Composio API Key is required"
+ raise ValueError(msg)
+ return ComposioToolSet(api_key=self.api_key, entity_id=self.entity_id)
+ except ValueError as e:
+ logger.error(f"Error building Composio wrapper: {e}")
+ msg = "Please provide a valid Composio API Key in the component settings"
+ raise ValueError(msg) from e
diff --git a/langflow/src/backend/base/langflow/components/confluence/__init__.py b/langflow/src/backend/base/langflow/components/confluence/__init__.py
new file mode 100644
index 0000000..63ccc66
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/confluence/__init__.py
@@ -0,0 +1,3 @@
+from .confluence import ConfluenceComponent
+
+__all__ = ["ConfluenceComponent"]
diff --git a/langflow/src/backend/base/langflow/components/confluence/confluence.py b/langflow/src/backend/base/langflow/components/confluence/confluence.py
new file mode 100644
index 0000000..418bfe0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/confluence/confluence.py
@@ -0,0 +1,84 @@
+from langchain_community.document_loaders import ConfluenceLoader
+from langchain_community.document_loaders.confluence import ContentFormat
+
+from langflow.custom import Component
+from langflow.io import BoolInput, DropdownInput, IntInput, Output, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class ConfluenceComponent(Component):
+ display_name = "Confluence"
+ description = "Confluence wiki collaboration platform"
+ documentation = "https://python.langchain.com/v0.2/docs/integrations/document_loaders/confluence/"
+ trace_type = "tool"
+ icon = "Confluence"
+ name = "Confluence"
+
+ inputs = [
+ StrInput(
+ name="url",
+ display_name="Site URL",
+ required=True,
+ info="The base URL of the Confluence Space. Example: https://.atlassian.net/wiki.",
+ ),
+ StrInput(
+ name="username",
+ display_name="Username",
+ required=True,
+ info="Atlassian User E-mail. Example: email@example.com",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ info="Atlassian Key. Create at: https://id.atlassian.com/manage-profile/security/api-tokens",
+ ),
+ StrInput(name="space_key", display_name="Space Key", required=True),
+ BoolInput(name="cloud", display_name="Use Cloud?", required=True, value=True, advanced=True),
+ DropdownInput(
+ name="content_format",
+ display_name="Content Format",
+ options=[
+ ContentFormat.EDITOR.value,
+ ContentFormat.EXPORT_VIEW.value,
+ ContentFormat.ANONYMOUS_EXPORT_VIEW.value,
+ ContentFormat.STORAGE.value,
+ ContentFormat.VIEW.value,
+ ],
+ value=ContentFormat.STORAGE.value,
+ required=True,
+ advanced=True,
+ info="Specify content format, defaults to ContentFormat.STORAGE",
+ ),
+ IntInput(
+ name="max_pages",
+ display_name="Max Pages",
+ required=False,
+ value=1000,
+ advanced=True,
+ info="Maximum number of pages to retrieve in total, defaults 1000",
+ ),
+ ]
+
+ outputs = [
+ Output(name="data", display_name="Data", method="load_documents"),
+ ]
+
+ def build_confluence(self) -> ConfluenceLoader:
+ content_format = ContentFormat(self.content_format)
+ return ConfluenceLoader(
+ url=self.url,
+ username=self.username,
+ api_key=self.api_key,
+ cloud=self.cloud,
+ space_key=self.space_key,
+ content_format=content_format,
+ max_pages=self.max_pages,
+ )
+
+ def load_documents(self) -> list[Data]:
+ confluence = self.build_confluence()
+ documents = confluence.load()
+ data = [Data.from_document(doc) for doc in documents] # Using the from_document method of Data
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/crewai/__init__.py b/langflow/src/backend/base/langflow/components/crewai/__init__.py
new file mode 100644
index 0000000..df92744
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/__init__.py
@@ -0,0 +1,15 @@
+from .crewai import CrewAIAgentComponent
+from .hierarchical_crew import HierarchicalCrewComponent
+from .hierarchical_task import HierarchicalTaskComponent
+from .sequential_crew import SequentialCrewComponent
+from .sequential_task import SequentialTaskComponent
+from .sequential_task_agent import SequentialTaskAgentComponent
+
+__all__ = [
+ "CrewAIAgentComponent",
+ "HierarchicalCrewComponent",
+ "HierarchicalTaskComponent",
+ "SequentialCrewComponent",
+ "SequentialTaskAgentComponent",
+ "SequentialTaskComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/crewai/crewai.py b/langflow/src/backend/base/langflow/components/crewai/crewai.py
new file mode 100644
index 0000000..e942ab2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/crewai.py
@@ -0,0 +1,102 @@
+from crewai import Agent
+
+from langflow.base.agents.crewai.crew import convert_llm, convert_tools
+from langflow.custom import Component
+from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
+
+
+class CrewAIAgentComponent(Component):
+ """Component for creating a CrewAI agent.
+
+ This component allows you to create a CrewAI agent with the specified role, goal, backstory, tools,
+ and language model.
+
+ Args:
+ Component (Component): Base class for all components.
+
+ Returns:
+ Agent: CrewAI agent.
+ """
+
+ display_name = "CrewAI Agent"
+ description = "Represents an agent of CrewAI."
+ documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
+ icon = "CrewAI"
+
+ inputs = [
+ MultilineInput(name="role", display_name="Role", info="The role of the agent."),
+ MultilineInput(name="goal", display_name="Goal", info="The objective of the agent."),
+ MultilineInput(name="backstory", display_name="Backstory", info="The backstory of the agent."),
+ HandleInput(
+ name="tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ info="Tools at agents disposal",
+ value=[],
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ info="Language model that will run the agent.",
+ input_types=["LanguageModel"],
+ ),
+ BoolInput(
+ name="memory",
+ display_name="Memory",
+ info="Whether the agent should have memory or not",
+ advanced=True,
+ value=True,
+ ),
+ BoolInput(
+ name="verbose",
+ display_name="Verbose",
+ advanced=True,
+ value=False,
+ ),
+ BoolInput(
+ name="allow_delegation",
+ display_name="Allow Delegation",
+ info="Whether the agent is allowed to delegate tasks to other agents.",
+ value=True,
+ ),
+ BoolInput(
+ name="allow_code_execution",
+ display_name="Allow Code Execution",
+ info="Whether the agent is allowed to execute code.",
+ value=False,
+ advanced=True,
+ ),
+ DictInput(
+ name="kwargs",
+ display_name="kwargs",
+ info="kwargs of agent.",
+ is_list=True,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Agent", name="output", method="build_output"),
+ ]
+
+ def build_output(self) -> Agent:
+ kwargs = self.kwargs or {}
+
+ # Define the Agent
+ agent = Agent(
+ role=self.role,
+ goal=self.goal,
+ backstory=self.backstory,
+ llm=convert_llm(self.llm),
+ verbose=self.verbose,
+ memory=self.memory,
+ tools=convert_tools(self.tools),
+ allow_delegation=self.allow_delegation,
+ allow_code_execution=self.allow_code_execution,
+ **kwargs,
+ )
+
+ self.status = repr(agent)
+
+ return agent
diff --git a/langflow/src/backend/base/langflow/components/crewai/hierarchical_crew.py b/langflow/src/backend/base/langflow/components/crewai/hierarchical_crew.py
new file mode 100644
index 0000000..1bc6f3f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/hierarchical_crew.py
@@ -0,0 +1,41 @@
+from crewai import Crew, Process
+
+from langflow.base.agents.crewai.crew import BaseCrewComponent
+from langflow.io import HandleInput
+
+
+class HierarchicalCrewComponent(BaseCrewComponent):
+ display_name: str = "Hierarchical Crew"
+ description: str = (
+ "Represents a group of agents, defining how they should collaborate and the tasks they should perform."
+ )
+ documentation: str = "https://docs.crewai.com/how-to/Hierarchical/"
+ icon = "CrewAI"
+
+ inputs = [
+ *BaseCrewComponent._base_inputs,
+ HandleInput(name="agents", display_name="Agents", input_types=["Agent"], is_list=True),
+ HandleInput(name="tasks", display_name="Tasks", input_types=["HierarchicalTask"], is_list=True),
+ HandleInput(name="manager_llm", display_name="Manager LLM", input_types=["LanguageModel"], required=False),
+ HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False),
+ ]
+
+ def build_crew(self) -> Crew:
+ tasks, agents = self.get_tasks_and_agents()
+ manager_llm = self.get_manager_llm()
+
+ return Crew(
+ agents=agents,
+ tasks=tasks,
+ process=Process.hierarchical,
+ verbose=self.verbose,
+ memory=self.memory,
+ cache=self.use_cache,
+ max_rpm=self.max_rpm,
+ share_crew=self.share_crew,
+ function_calling_llm=self.function_calling_llm,
+ manager_agent=self.manager_agent,
+ manager_llm=manager_llm,
+ step_callback=self.get_step_callback(),
+ task_callback=self.get_task_callback(),
+ )
diff --git a/langflow/src/backend/base/langflow/components/crewai/hierarchical_task.py b/langflow/src/backend/base/langflow/components/crewai/hierarchical_task.py
new file mode 100644
index 0000000..e5a73e8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/hierarchical_task.py
@@ -0,0 +1,43 @@
+from langflow.base.agents.crewai.tasks import HierarchicalTask
+from langflow.custom import Component
+from langflow.io import HandleInput, MultilineInput, Output
+
+
+class HierarchicalTaskComponent(Component):
+ display_name: str = "Hierarchical Task"
+ description: str = "Each task must have a description, an expected output and an agent responsible for execution."
+ icon = "CrewAI"
+ inputs = [
+ MultilineInput(
+ name="task_description",
+ display_name="Description",
+ info="Descriptive text detailing task's purpose and execution.",
+ ),
+ MultilineInput(
+ name="expected_output",
+ display_name="Expected Output",
+ info="Clear definition of expected task outcome.",
+ ),
+ HandleInput(
+ name="tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ info="List of tools/resources limited for task execution. Uses the Agent tools by default.",
+ required=False,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Task", name="task_output", method="build_task"),
+ ]
+
+ def build_task(self) -> HierarchicalTask:
+ task = HierarchicalTask(
+ description=self.task_description,
+ expected_output=self.expected_output,
+ tools=self.tools or [],
+ )
+ self.status = task
+ return task
diff --git a/langflow/src/backend/base/langflow/components/crewai/sequential_crew.py b/langflow/src/backend/base/langflow/components/crewai/sequential_crew.py
new file mode 100644
index 0000000..f6d09f9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/sequential_crew.py
@@ -0,0 +1,47 @@
+from crewai import Agent, Crew, Process, Task
+
+from langflow.base.agents.crewai.crew import BaseCrewComponent
+from langflow.io import HandleInput
+from langflow.schema.message import Message
+
+
+class SequentialCrewComponent(BaseCrewComponent):
+ display_name: str = "Sequential Crew"
+ description: str = "Represents a group of agents with tasks that are executed sequentially."
+ documentation: str = "https://docs.crewai.com/how-to/Sequential/"
+ icon = "CrewAI"
+
+ inputs = [
+ *BaseCrewComponent._base_inputs,
+ HandleInput(name="tasks", display_name="Tasks", input_types=["SequentialTask"], is_list=True),
+ ]
+
+ @property
+ def agents(self: "SequentialCrewComponent") -> list[Agent]:
+ # Derive agents directly from linked tasks
+ return [task.agent for task in self.tasks if hasattr(task, "agent")]
+
+ def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
+ # Use the agents property to derive agents
+ if not agents_list:
+ existing_agents = self.agents
+ agents_list = existing_agents + (agents_list or [])
+
+ return super().get_tasks_and_agents(agents_list=agents_list)
+
+ def build_crew(self) -> Message:
+ tasks, agents = self.get_tasks_and_agents()
+
+ return Crew(
+ agents=agents,
+ tasks=tasks,
+ process=Process.sequential,
+ verbose=self.verbose,
+ memory=self.memory,
+ cache=self.use_cache,
+ max_rpm=self.max_rpm,
+ share_crew=self.share_crew,
+ function_calling_llm=self.function_calling_llm,
+ step_callback=self.get_step_callback(),
+ task_callback=self.get_task_callback(),
+ )
diff --git a/langflow/src/backend/base/langflow/components/crewai/sequential_task.py b/langflow/src/backend/base/langflow/components/crewai/sequential_task.py
new file mode 100644
index 0000000..81add12
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/sequential_task.py
@@ -0,0 +1,72 @@
+from langflow.base.agents.crewai.tasks import SequentialTask
+from langflow.custom import Component
+from langflow.io import BoolInput, HandleInput, MultilineInput, Output
+
+
+class SequentialTaskComponent(Component):
+ display_name: str = "Sequential Task"
+ description: str = "Each task must have a description, an expected output and an agent responsible for execution."
+ icon = "CrewAI"
+ inputs = [
+ MultilineInput(
+ name="task_description",
+ display_name="Description",
+ info="Descriptive text detailing task's purpose and execution.",
+ ),
+ MultilineInput(
+ name="expected_output",
+ display_name="Expected Output",
+ info="Clear definition of expected task outcome.",
+ ),
+ HandleInput(
+ name="tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ info="List of tools/resources limited for task execution. Uses the Agent tools by default.",
+ required=False,
+ advanced=True,
+ ),
+ HandleInput(
+ name="agent",
+ display_name="Agent",
+ input_types=["Agent"],
+ info="CrewAI Agent that will perform the task",
+ required=True,
+ ),
+ HandleInput(
+ name="task",
+ display_name="Task",
+ input_types=["SequentialTask"],
+ info="CrewAI Task that will perform the task",
+ ),
+ BoolInput(
+ name="async_execution",
+ display_name="Async Execution",
+ value=True,
+ advanced=True,
+ info="Boolean flag indicating asynchronous task execution.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Task", name="task_output", method="build_task"),
+ ]
+
+ def build_task(self) -> list[SequentialTask]:
+ tasks: list[SequentialTask] = []
+ task = SequentialTask(
+ description=self.task_description,
+ expected_output=self.expected_output,
+ tools=self.agent.tools,
+ async_execution=False,
+ agent=self.agent,
+ )
+ tasks.append(task)
+ self.status = task
+ if self.task:
+ if isinstance(self.task, list) and all(isinstance(task, SequentialTask) for task in self.task):
+ tasks = self.task + tasks
+ elif isinstance(self.task, SequentialTask):
+ tasks = [self.task, *tasks]
+ return tasks
diff --git a/langflow/src/backend/base/langflow/components/crewai/sequential_task_agent.py b/langflow/src/backend/base/langflow/components/crewai/sequential_task_agent.py
new file mode 100644
index 0000000..bb3e5c5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/crewai/sequential_task_agent.py
@@ -0,0 +1,138 @@
+from crewai import Agent, Task
+
+from langflow.base.agents.crewai.tasks import SequentialTask
+from langflow.custom import Component
+from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
+
+
+class SequentialTaskAgentComponent(Component):
+ display_name = "Sequential Task Agent"
+ description = "Creates a CrewAI Task and its associated Agent."
+ documentation = "https://docs.crewai.com/how-to/LLM-Connections/"
+ icon = "CrewAI"
+
+ inputs = [
+ # Agent inputs
+ MultilineInput(name="role", display_name="Role", info="The role of the agent."),
+ MultilineInput(name="goal", display_name="Goal", info="The objective of the agent."),
+ MultilineInput(
+ name="backstory",
+ display_name="Backstory",
+ info="The backstory of the agent.",
+ ),
+ HandleInput(
+ name="tools",
+ display_name="Tools",
+ input_types=["Tool"],
+ is_list=True,
+ info="Tools at agent's disposal",
+ value=[],
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ info="Language model that will run the agent.",
+ input_types=["LanguageModel"],
+ ),
+ BoolInput(
+ name="memory",
+ display_name="Memory",
+ info="Whether the agent should have memory or not",
+ advanced=True,
+ value=True,
+ ),
+ BoolInput(
+ name="verbose",
+ display_name="Verbose",
+ advanced=True,
+ value=True,
+ ),
+ BoolInput(
+ name="allow_delegation",
+ display_name="Allow Delegation",
+ info="Whether the agent is allowed to delegate tasks to other agents.",
+ value=False,
+ advanced=True,
+ ),
+ BoolInput(
+ name="allow_code_execution",
+ display_name="Allow Code Execution",
+ info="Whether the agent is allowed to execute code.",
+ value=False,
+ advanced=True,
+ ),
+ DictInput(
+ name="agent_kwargs",
+ display_name="Agent kwargs",
+ info="Additional kwargs for the agent.",
+ is_list=True,
+ advanced=True,
+ ),
+ # Task inputs
+ MultilineInput(
+ name="task_description",
+ display_name="Task Description",
+ info="Descriptive text detailing task's purpose and execution.",
+ ),
+ MultilineInput(
+ name="expected_output",
+ display_name="Expected Task Output",
+ info="Clear definition of expected task outcome.",
+ ),
+ BoolInput(
+ name="async_execution",
+ display_name="Async Execution",
+ value=False,
+ advanced=True,
+ info="Boolean flag indicating asynchronous task execution.",
+ ),
+ # Chaining input
+ HandleInput(
+ name="previous_task",
+ display_name="Previous Task",
+ input_types=["SequentialTask"],
+ info="The previous task in the sequence (for chaining).",
+ required=False,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Sequential Task",
+ name="task_output",
+ method="build_agent_and_task",
+ ),
+ ]
+
+ def build_agent_and_task(self) -> list[SequentialTask]:
+ # Build the agent
+ agent_kwargs = self.agent_kwargs or {}
+ agent = Agent(
+ role=self.role,
+ goal=self.goal,
+ backstory=self.backstory,
+ llm=self.llm,
+ verbose=self.verbose,
+ memory=self.memory,
+ tools=self.tools or [],
+ allow_delegation=self.allow_delegation,
+ allow_code_execution=self.allow_code_execution,
+ **agent_kwargs,
+ )
+
+ # Build the task
+ task = Task(
+ description=self.task_description,
+ expected_output=self.expected_output,
+ agent=agent,
+ async_execution=self.async_execution,
+ )
+
+ # If there's a previous task, create a list of tasks
+ if self.previous_task:
+ tasks = [*self.previous_task, task] if isinstance(self.previous_task, list) else [self.previous_task, task]
+ else:
+ tasks = [task]
+
+ self.status = f"Agent: {agent!r}\nTask: {task!r}"
+ return tasks
diff --git a/langflow/src/backend/base/langflow/components/custom_component/__init__.py b/langflow/src/backend/base/langflow/components/custom_component/__init__.py
new file mode 100644
index 0000000..fa37797
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/custom_component/__init__.py
@@ -0,0 +1,5 @@
+from .custom_component import CustomComponent
+
+__all__ = [
+ "CustomComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/custom_component/custom_component.py b/langflow/src/backend/base/langflow/components/custom_component/custom_component.py
new file mode 100644
index 0000000..305200d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/custom_component/custom_component.py
@@ -0,0 +1,31 @@
+# from langflow.field_typing import Data
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+
+
+class CustomComponent(Component):
+ display_name = "Custom Component"
+ description = "Use as a template to create your own component."
+ documentation: str = "https://docs.langflow.org/components-custom-components"
+ icon = "code"
+ name = "CustomComponent"
+
+ inputs = [
+ MessageTextInput(
+ name="input_value",
+ display_name="Input Value",
+ info="This is a custom component Input",
+ value="Hello, World!",
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+
+ def build_output(self) -> Data:
+ data = Data(value=self.input_value)
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/data/__init__.py b/langflow/src/backend/base/langflow/components/data/__init__.py
new file mode 100644
index 0000000..6db6793
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/__init__.py
@@ -0,0 +1,21 @@
+from .api_request import APIRequestComponent
+from .csv_to_data import CSVToDataComponent
+from .directory import DirectoryComponent
+from .file import FileComponent
+from .json_to_data import JSONToDataComponent
+from .s3_bucket_uploader import S3BucketUploaderComponent
+from .sql_executor import SQLExecutorComponent
+from .url import URLComponent
+from .webhook import WebhookComponent
+
+__all__ = [
+ "APIRequestComponent",
+ "CSVToDataComponent",
+ "DirectoryComponent",
+ "FileComponent",
+ "JSONToDataComponent",
+ "S3BucketUploaderComponent",
+ "SQLExecutorComponent",
+ "URLComponent",
+ "WebhookComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/data/api_request.py b/langflow/src/backend/base/langflow/components/data/api_request.py
new file mode 100644
index 0000000..56dcd13
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/api_request.py
@@ -0,0 +1,642 @@
+import asyncio
+import json
+import re
+import tempfile
+from datetime import datetime, timezone
+from pathlib import Path
+from typing import Any
+from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
+
+import aiofiles
+import aiofiles.os as aiofiles_os
+import httpx
+import validators
+
+from langflow.base.curl.parse import parse_context
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ DataInput,
+ DropdownInput,
+ FloatInput,
+ IntInput,
+ MessageTextInput,
+ MultilineInput,
+ Output,
+ StrInput,
+ TableInput,
+)
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+
+class APIRequestComponent(Component):
+ display_name = "API Request"
+ description = "Make HTTP requests using URLs or cURL commands."
+ icon = "Globe"
+ name = "APIRequest"
+
+ default_keys = ["urls", "method", "query_params"]
+
+ inputs = [
+ MessageTextInput(
+ name="urls",
+ display_name="URLs",
+ list=True,
+ info="Enter one or more URLs, separated by commas.",
+ advanced=False,
+ tool_mode=True,
+ ),
+ MultilineInput(
+ name="curl",
+ display_name="cURL",
+ info=(
+ "Paste a curl command to populate the fields. "
+ "This will fill in the dictionary fields for headers and body."
+ ),
+ advanced=True,
+ real_time_refresh=True,
+ tool_mode=True,
+ ),
+ DropdownInput(
+ name="method",
+ display_name="Method",
+ options=["GET", "POST", "PATCH", "PUT", "DELETE"],
+ info="The HTTP method to use.",
+ real_time_refresh=True,
+ ),
+ BoolInput(
+ name="use_curl",
+ display_name="Use cURL",
+ value=False,
+ info="Enable cURL mode to populate fields from a cURL command.",
+ real_time_refresh=True,
+ ),
+ DataInput(
+ name="query_params",
+ display_name="Query Parameters",
+ info="The query parameters to append to the URL.",
+ advanced=True,
+ ),
+ TableInput(
+ name="body",
+ display_name="Body",
+ info="The body to send with the request as a dictionary (for POST, PATCH, PUT).",
+ table_schema=[
+ {
+ "name": "key",
+ "display_name": "Key",
+ "type": "str",
+ "description": "Parameter name",
+ },
+ {
+ "name": "value",
+ "display_name": "Value",
+ "description": "Parameter value",
+ },
+ ],
+ value=[],
+ input_types=["Data"],
+ advanced=True,
+ ),
+ TableInput(
+ name="headers",
+ display_name="Headers",
+ info="The headers to send with the request as a dictionary.",
+ table_schema=[
+ {
+ "name": "key",
+ "display_name": "Header",
+ "type": "str",
+ "description": "Header name",
+ },
+ {
+ "name": "value",
+ "display_name": "Value",
+ "type": "str",
+ "description": "Header value",
+ },
+ ],
+ value=[],
+ advanced=True,
+ input_types=["Data"],
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ value=5,
+ info="The timeout to use for the request.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="follow_redirects",
+ display_name="Follow Redirects",
+ value=True,
+ info="Whether to follow http redirects.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="save_to_file",
+ display_name="Save to File",
+ value=False,
+ info="Save the API response to a temporary file",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_httpx_metadata",
+ display_name="Include HTTPx Metadata",
+ value=False,
+ info=(
+ "Include properties such as headers, status_code, response_headers, "
+ "and redirection_history in the output."
+ ),
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="make_requests"),
+ ]
+
+ def _parse_json_value(self, value: Any) -> Any:
+ """Parse a value that might be a JSON string."""
+ if not isinstance(value, str):
+ return value
+
+ try:
+ parsed = json.loads(value)
+ except json.JSONDecodeError:
+ return value
+ else:
+ return parsed
+
+ def _process_body(self, body: Any) -> dict:
+ """Process the body input into a valid dictionary.
+
+ Args:
+ body: The body to process, can be dict, str, or list
+ Returns:
+ Processed dictionary
+ """
+ if body is None:
+ return {}
+ if isinstance(body, dict):
+ return self._process_dict_body(body)
+ if isinstance(body, str):
+ return self._process_string_body(body)
+ if isinstance(body, list):
+ return self._process_list_body(body)
+
+ return {}
+
+ def _process_dict_body(self, body: dict) -> dict:
+ """Process dictionary body by parsing JSON values."""
+ return {k: self._parse_json_value(v) for k, v in body.items()}
+
+ def _process_string_body(self, body: str) -> dict:
+ """Process string body by attempting JSON parse."""
+ try:
+ return self._process_body(json.loads(body))
+ except json.JSONDecodeError:
+ return {"data": body}
+
+ def _process_list_body(self, body: list) -> dict:
+ """Process list body by converting to key-value dictionary."""
+ processed_dict = {}
+
+ try:
+ for item in body:
+ if not self._is_valid_key_value_item(item):
+ continue
+
+ key = item["key"]
+ value = self._parse_json_value(item["value"])
+ processed_dict[key] = value
+
+ except (KeyError, TypeError, ValueError) as e:
+ self.log(f"Failed to process body list: {e}")
+ return {} # Return empty dictionary instead of None
+
+ return processed_dict
+
+ def _is_valid_key_value_item(self, item: Any) -> bool:
+ """Check if an item is a valid key-value dictionary."""
+ return isinstance(item, dict) and "key" in item and "value" in item
+
+ def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:
+ """Parse a cURL command and update build configuration.
+
+ Args:
+ curl: The cURL command to parse
+ build_config: The build configuration to update
+ Returns:
+ Updated build configuration
+ """
+ try:
+ parsed = parse_context(curl)
+
+ # Update basic configuration
+ build_config["urls"]["value"] = [parsed.url]
+ build_config["method"]["value"] = parsed.method.upper()
+ build_config["headers"]["advanced"] = True
+ build_config["body"]["advanced"] = True
+
+ # Process headers
+ headers_list = [{"key": k, "value": v} for k, v in parsed.headers.items()]
+ build_config["headers"]["value"] = headers_list
+
+ if headers_list:
+ build_config["headers"]["advanced"] = False
+
+ # Process body data
+ if not parsed.data:
+ build_config["body"]["value"] = []
+ elif parsed.data:
+ try:
+ json_data = json.loads(parsed.data)
+ if isinstance(json_data, dict):
+ body_list = [
+ {"key": k, "value": json.dumps(v) if isinstance(v, dict | list) else str(v)}
+ for k, v in json_data.items()
+ ]
+ build_config["body"]["value"] = body_list
+ build_config["body"]["advanced"] = False
+ else:
+ build_config["body"]["value"] = [{"key": "data", "value": json.dumps(json_data)}]
+ build_config["body"]["advanced"] = False
+ except json.JSONDecodeError:
+ build_config["body"]["value"] = [{"key": "data", "value": parsed.data}]
+ build_config["body"]["advanced"] = False
+
+ except Exception as exc:
+ msg = f"Error parsing curl: {exc}"
+ self.log(msg)
+ raise ValueError(msg) from exc
+
+ return build_config
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:
+ if field_name == "use_curl":
+ build_config = self._update_curl_mode(build_config, use_curl=field_value)
+
+ # Fields that should not be reset
+ preserve_fields = {"timeout", "follow_redirects", "save_to_file", "include_httpx_metadata", "use_curl"}
+
+ # Mapping between input types and their reset values
+ type_reset_mapping = {
+ TableInput: [],
+ BoolInput: False,
+ IntInput: 0,
+ FloatInput: 0.0,
+ MessageTextInput: "",
+ StrInput: "",
+ MultilineInput: "",
+ DropdownInput: "GET",
+ DataInput: {},
+ }
+
+ for input_field in self.inputs:
+ # Only reset if field is not in preserve list
+ if input_field.name not in preserve_fields:
+ reset_value = type_reset_mapping.get(type(input_field), None)
+ build_config[input_field.name]["value"] = reset_value
+ self.log(f"Reset field {input_field.name} to {reset_value}")
+ elif field_name == "method" and not self.use_curl:
+ build_config = self._update_method_fields(build_config, field_value)
+ elif field_name == "curl" and self.use_curl and field_value:
+ build_config = self.parse_curl(field_value, build_config)
+ return build_config
+
+ def _update_curl_mode(self, build_config: dotdict, *, use_curl: bool) -> dotdict:
+ always_visible = ["method", "use_curl"]
+
+ for field in self.inputs:
+ field_name = field.name
+ field_config = build_config.get(field_name)
+ if isinstance(field_config, dict):
+ if field_name in always_visible:
+ field_config["advanced"] = False
+ elif field_name == "urls":
+ field_config["advanced"] = use_curl
+ elif field_name == "curl":
+ field_config["advanced"] = not use_curl
+ field_config["real_time_refresh"] = use_curl
+ elif field_name in {"body", "headers"}:
+ field_config["advanced"] = True # Always keep body and headers in advanced when use_curl is False
+ else:
+ field_config["advanced"] = use_curl
+ else:
+ self.log(f"Expected dict for build_config[{field_name}], got {type(field_config).__name__}")
+
+ if not use_curl:
+ current_method = build_config.get("method", {}).get("value", "GET")
+ build_config = self._update_method_fields(build_config, current_method)
+
+ return build_config
+
+ def _update_method_fields(self, build_config: dotdict, method: str) -> dotdict:
+ common_fields = [
+ "urls",
+ "method",
+ "use_curl",
+ ]
+
+ always_advanced_fields = [
+ "body",
+ "headers",
+ "timeout",
+ "follow_redirects",
+ "save_to_file",
+ "include_httpx_metadata",
+ ]
+
+ body_fields = ["body"]
+
+ for field in self.inputs:
+ field_name = field.name
+ field_config = build_config.get(field_name)
+ if isinstance(field_config, dict):
+ if field_name in common_fields:
+ field_config["advanced"] = False
+ elif field_name in body_fields:
+ field_config["advanced"] = method not in {"POST", "PUT", "PATCH"}
+ elif field_name in always_advanced_fields:
+ field_config["advanced"] = True
+ else:
+ field_config["advanced"] = True
+ else:
+ self.log(f"Expected dict for build_config[{field_name}], got {type(field_config).__name__}")
+
+ return build_config
+
+ async def make_request(
+ self,
+ client: httpx.AsyncClient,
+ method: str,
+ url: str,
+ headers: dict | None = None,
+ body: Any = None,
+ timeout: int = 5,
+ *,
+ follow_redirects: bool = True,
+ save_to_file: bool = False,
+ include_httpx_metadata: bool = False,
+ ) -> Data:
+ method = method.upper()
+ if method not in {"GET", "POST", "PATCH", "PUT", "DELETE"}:
+ msg = f"Unsupported method: {method}"
+ raise ValueError(msg)
+
+ # Process body using the new helper method
+ processed_body = self._process_body(body)
+
+ try:
+ response = await client.request(
+ method,
+ url,
+ headers=headers,
+ json=processed_body,
+ timeout=timeout,
+ follow_redirects=follow_redirects,
+ )
+
+ redirection_history = [
+ {
+ "url": redirect.headers.get("Location", str(redirect.url)),
+ "status_code": redirect.status_code,
+ }
+ for redirect in response.history
+ ]
+
+ is_binary, file_path = await self._response_info(response, with_file_path=save_to_file)
+ response_headers = self._headers_to_dict(response.headers)
+
+ metadata: dict[str, Any] = {
+ "source": url,
+ }
+
+ if save_to_file:
+ mode = "wb" if is_binary else "w"
+ encoding = response.encoding if mode == "w" else None
+ if file_path:
+ # Ensure parent directory exists
+ await aiofiles_os.makedirs(file_path.parent, exist_ok=True)
+ if is_binary:
+ async with aiofiles.open(file_path, "wb") as f:
+ await f.write(response.content)
+ await f.flush()
+ else:
+ async with aiofiles.open(file_path, "w", encoding=encoding) as f:
+ await f.write(response.text)
+ await f.flush()
+ metadata["file_path"] = str(file_path)
+
+ if include_httpx_metadata:
+ metadata.update(
+ {
+ "headers": headers,
+ "status_code": response.status_code,
+ "response_headers": response_headers,
+ **({"redirection_history": redirection_history} if redirection_history else {}),
+ }
+ )
+ return Data(data=metadata)
+
+ if is_binary:
+ result = response.content
+ else:
+ try:
+ result = response.json()
+ except json.JSONDecodeError:
+ self.log("Failed to decode JSON response")
+ result = response.text.encode("utf-8")
+
+ metadata.update({"result": result})
+
+ if include_httpx_metadata:
+ metadata.update(
+ {
+ "headers": headers,
+ "status_code": response.status_code,
+ "response_headers": response_headers,
+ **({"redirection_history": redirection_history} if redirection_history else {}),
+ }
+ )
+ return Data(data=metadata)
+ except httpx.TimeoutException:
+ return Data(
+ data={
+ "source": url,
+ "headers": headers,
+ "status_code": 408,
+ "error": "Request timed out",
+ },
+ )
+ except Exception as exc: # noqa: BLE001
+ self.log(f"Error making request to {url}")
+ return Data(
+ data={
+ "source": url,
+ "headers": headers,
+ "status_code": 500,
+ "error": str(exc),
+ **({"redirection_history": redirection_history} if redirection_history else {}),
+ },
+ )
+
+ def add_query_params(self, url: str, params: dict) -> str:
+ url_parts = list(urlparse(url))
+ query = dict(parse_qsl(url_parts[4]))
+ query.update(params)
+ url_parts[4] = urlencode(query)
+ return urlunparse(url_parts)
+
+ async def make_requests(self) -> list[Data]:
+ method = self.method
+ urls = [url.strip() for url in self.urls if url.strip()]
+ headers = self.headers or {}
+ body = self.body or {}
+ timeout = self.timeout
+ follow_redirects = self.follow_redirects
+ save_to_file = self.save_to_file
+ include_httpx_metadata = self.include_httpx_metadata
+
+ if self.use_curl and self.curl:
+ self._build_config = self.parse_curl(self.curl, dotdict())
+
+ invalid_urls = [url for url in urls if not validators.url(url)]
+ if invalid_urls:
+ msg = f"Invalid URLs provided: {invalid_urls}"
+ raise ValueError(msg)
+
+ if isinstance(self.query_params, str):
+ query_params = dict(parse_qsl(self.query_params))
+ else:
+ query_params = self.query_params.data if self.query_params else {}
+
+ # Process headers here
+ headers = self._process_headers(headers)
+
+ # Process body
+ body = self._process_body(body)
+
+ bodies = [body] * len(urls)
+
+ urls = [self.add_query_params(url, query_params) for url in urls]
+
+ async with httpx.AsyncClient() as client:
+ results = await asyncio.gather(
+ *[
+ self.make_request(
+ client,
+ method,
+ u,
+ headers,
+ rec,
+ timeout,
+ follow_redirects=follow_redirects,
+ save_to_file=save_to_file,
+ include_httpx_metadata=include_httpx_metadata,
+ )
+ for u, rec in zip(urls, bodies, strict=False)
+ ]
+ )
+ self.status = results
+ return results
+
+ async def _response_info(
+ self, response: httpx.Response, *, with_file_path: bool = False
+ ) -> tuple[bool, Path | None]:
+ """Determine the file path and whether the response content is binary.
+
+ Args:
+ response (Response): The HTTP response object.
+ with_file_path (bool): Whether to save the response content to a file.
+
+ Returns:
+ Tuple[bool, Path | None]:
+ A tuple containing a boolean indicating if the content is binary and the full file path (if applicable).
+ """
+ content_type = response.headers.get("Content-Type", "")
+ is_binary = "application/octet-stream" in content_type or "application/binary" in content_type
+
+ if not with_file_path:
+ return is_binary, None
+
+ component_temp_dir = Path(tempfile.gettempdir()) / self.__class__.__name__
+
+ # Create directory asynchronously
+ await aiofiles_os.makedirs(component_temp_dir, exist_ok=True)
+
+ filename = None
+ if "Content-Disposition" in response.headers:
+ content_disposition = response.headers["Content-Disposition"]
+ filename_match = re.search(r'filename="(.+?)"', content_disposition)
+ if filename_match:
+ extracted_filename = filename_match.group(1)
+ filename = extracted_filename
+
+ # Step 3: Infer file extension or use part of the request URL if no filename
+ if not filename:
+ # Extract the last segment of the URL path
+ url_path = urlparse(str(response.request.url) if response.request else "").path
+ base_name = Path(url_path).name # Get the last segment of the path
+ if not base_name: # If the path ends with a slash or is empty
+ base_name = "response"
+
+ # Infer file extension
+ content_type_to_extension = {
+ "text/plain": ".txt",
+ "application/json": ".json",
+ "image/jpeg": ".jpg",
+ "image/png": ".png",
+ "application/octet-stream": ".bin",
+ }
+ extension = content_type_to_extension.get(content_type, ".bin" if is_binary else ".txt")
+ filename = f"{base_name}{extension}"
+
+ # Step 4: Define the full file path
+ file_path = component_temp_dir / filename
+
+ # Step 5: Check if file exists asynchronously and handle accordingly
+ try:
+ # Try to create the file exclusively (x mode) to check existence
+ async with aiofiles.open(file_path, "x") as _:
+ pass # File created successfully, we can use this path
+ except FileExistsError:
+ # If file exists, append a timestamp to the filename
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
+ file_path = component_temp_dir / f"{timestamp}-{filename}"
+
+ return is_binary, file_path
+
+ def _headers_to_dict(self, headers: httpx.Headers) -> dict[str, str]:
+ """Convert HTTP headers to a dictionary with lowercased keys."""
+ return {k.lower(): v for k, v in headers.items()}
+
+ def _process_headers(self, headers: Any) -> dict:
+ """Process the headers input into a valid dictionary.
+
+ Args:
+ headers: The headers to process, can be dict, str, or list
+ Returns:
+ Processed dictionary
+ """
+ if headers is None:
+ return {}
+ if isinstance(headers, dict):
+ return headers
+ if isinstance(headers, list):
+ processed_headers = {}
+ try:
+ for item in headers:
+ if not self._is_valid_key_value_item(item):
+ continue
+ key = item["key"]
+ value = item["value"]
+ processed_headers[key] = value
+ except (KeyError, TypeError, ValueError) as e:
+ self.log(f"Failed to process headers list: {e}")
+ return {} # Return empty dictionary instead of None
+ return processed_headers
+ return {}
diff --git a/langflow/src/backend/base/langflow/components/data/csv_to_data.py b/langflow/src/backend/base/langflow/components/data/csv_to_data.py
new file mode 100644
index 0000000..866062a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/csv_to_data.py
@@ -0,0 +1,95 @@
+import csv
+import io
+from pathlib import Path
+
+from langflow.custom import Component
+from langflow.io import FileInput, MessageTextInput, MultilineInput, Output
+from langflow.schema import Data
+
+
+class CSVToDataComponent(Component):
+ display_name = "Load CSV"
+ description = "Load a CSV file, CSV from a file path, or a valid CSV string and convert it to a list of Data"
+ icon = "file-spreadsheet"
+ name = "CSVtoData"
+ legacy = True
+
+ inputs = [
+ FileInput(
+ name="csv_file",
+ display_name="CSV File",
+ file_types=["csv"],
+ info="Upload a CSV file to convert to a list of Data objects",
+ ),
+ MessageTextInput(
+ name="csv_path",
+ display_name="CSV File Path",
+ info="Provide the path to the CSV file as pure text",
+ ),
+ MultilineInput(
+ name="csv_string",
+ display_name="CSV String",
+ info="Paste a CSV string directly to convert to a list of Data objects",
+ ),
+ MessageTextInput(
+ name="text_key",
+ display_name="Text Key",
+ info="The key to use for the text column. Defaults to 'text'.",
+ value="text",
+ ),
+ ]
+
+ outputs = [
+ Output(name="data_list", display_name="Data List", method="load_csv_to_data"),
+ ]
+
+ def load_csv_to_data(self) -> list[Data]:
+ if sum(bool(field) for field in [self.csv_file, self.csv_path, self.csv_string]) != 1:
+ msg = "Please provide exactly one of: CSV file, file path, or CSV string."
+ raise ValueError(msg)
+
+ csv_data = None
+ try:
+ if self.csv_file:
+ resolved_path = self.resolve_path(self.csv_file)
+ file_path = Path(resolved_path)
+ if file_path.suffix.lower() != ".csv":
+ self.status = "The provided file must be a CSV file."
+ else:
+ with file_path.open(newline="", encoding="utf-8") as csvfile:
+ csv_data = csvfile.read()
+
+ elif self.csv_path:
+ file_path = Path(self.csv_path)
+ if file_path.suffix.lower() != ".csv":
+ self.status = "The provided file must be a CSV file."
+ else:
+ with file_path.open(newline="", encoding="utf-8") as csvfile:
+ csv_data = csvfile.read()
+
+ else:
+ csv_data = self.csv_string
+
+ if csv_data:
+ csv_reader = csv.DictReader(io.StringIO(csv_data))
+ result = [Data(data=row, text_key=self.text_key) for row in csv_reader]
+
+ if not result:
+ self.status = "The CSV data is empty."
+ return []
+
+ self.status = result
+ return result
+
+ except csv.Error as e:
+ error_message = f"CSV parsing error: {e}"
+ self.status = error_message
+ raise ValueError(error_message) from e
+
+ except Exception as e:
+ error_message = f"An error occurred: {e}"
+ self.status = error_message
+ raise ValueError(error_message) from e
+
+ # An error occurred
+ raise ValueError(self.status)
diff --git a/langflow/src/backend/base/langflow/components/data/directory.py b/langflow/src/backend/base/langflow/components/data/directory.py
new file mode 100644
index 0000000..f195d7c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/directory.py
@@ -0,0 +1,113 @@
+from langflow.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths
+from langflow.custom import Component
+from langflow.io import BoolInput, IntInput, MessageTextInput, MultiselectInput
+from langflow.schema import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.template import Output
+
+
+class DirectoryComponent(Component):
+ display_name = "Directory"
+ description = "Recursively load files from a directory."
+ icon = "folder"
+ name = "Directory"
+
+ inputs = [
+ MessageTextInput(
+ name="path",
+ display_name="Path",
+ info="Path to the directory to load files from. Defaults to current directory ('.')",
+ value=".",
+ tool_mode=True,
+ ),
+ MultiselectInput(
+ name="types",
+ display_name="File Types",
+ info="File types to load. Select one or more types or leave empty to load all supported types.",
+ options=TEXT_FILE_TYPES,
+ value=[],
+ ),
+ IntInput(
+ name="depth",
+ display_name="Depth",
+ info="Depth to search for files.",
+ value=0,
+ ),
+ IntInput(
+ name="max_concurrency",
+ display_name="Max Concurrency",
+ advanced=True,
+ info="Maximum concurrency for loading files.",
+ value=2,
+ ),
+ BoolInput(
+ name="load_hidden",
+ display_name="Load Hidden",
+ advanced=True,
+ info="If true, hidden files will be loaded.",
+ ),
+ BoolInput(
+ name="recursive",
+ display_name="Recursive",
+ advanced=True,
+ info="If true, the search will be recursive.",
+ ),
+ BoolInput(
+ name="silent_errors",
+ display_name="Silent Errors",
+ advanced=True,
+ info="If true, errors will not raise an exception.",
+ ),
+ BoolInput(
+ name="use_multithreading",
+ display_name="Use Multithreading",
+ advanced=True,
+ info="If true, multithreading will be used.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="load_directory"),
+ Output(display_name="DataFrame", name="dataframe", method="as_dataframe"),
+ ]
+
+ def load_directory(self) -> list[Data]:
+ path = self.path
+ types = self.types
+ depth = self.depth
+ max_concurrency = self.max_concurrency
+ load_hidden = self.load_hidden
+ recursive = self.recursive
+ silent_errors = self.silent_errors
+ use_multithreading = self.use_multithreading
+
+ resolved_path = self.resolve_path(path)
+
+ # If no types are specified, use all supported types
+ if not types:
+ types = TEXT_FILE_TYPES
+
+ # Check if all specified types are valid
+ invalid_types = [t for t in types if t not in TEXT_FILE_TYPES]
+ if invalid_types:
+ msg = f"Invalid file types specified: {invalid_types}. Valid types are: {TEXT_FILE_TYPES}"
+ raise ValueError(msg)
+
+ valid_types = types
+
+ file_paths = retrieve_file_paths(
+ resolved_path, load_hidden=load_hidden, recursive=recursive, depth=depth, types=valid_types
+ )
+
+ loaded_data = []
+ if use_multithreading:
+ loaded_data = parallel_load_data(file_paths, silent_errors=silent_errors, max_concurrency=max_concurrency)
+ else:
+ loaded_data = [parse_text_file_to_data(file_path, silent_errors=silent_errors) for file_path in file_paths]
+
+ valid_data = [x for x in loaded_data if x is not None and isinstance(x, Data)]
+ self.status = valid_data
+ return valid_data
+
+ def as_dataframe(self) -> DataFrame:
+ return DataFrame(self.load_directory())
diff --git a/langflow/src/backend/base/langflow/components/data/file.py b/langflow/src/backend/base/langflow/components/data/file.py
new file mode 100644
index 0000000..b797451
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/file.py
@@ -0,0 +1,93 @@
+from langflow.base.data import BaseFileComponent
+from langflow.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data
+from langflow.io import BoolInput, IntInput
+from langflow.schema import Data
+
+
+class FileComponent(BaseFileComponent):
+ """Handles loading and processing of individual or zipped text files.
+
+ This component supports processing multiple valid files within a zip archive,
+ resolving paths, validating file types, and optionally using multithreading for processing.
+ """
+
+ display_name = "File"
+ description = "Load a file to be used in your project."
+ icon = "file-text"
+ name = "File"
+
+ VALID_EXTENSIONS = TEXT_FILE_TYPES
+
+ inputs = [
+ *BaseFileComponent._base_inputs,
+ BoolInput(
+ name="use_multithreading",
+ display_name="[Deprecated] Use Multithreading",
+ advanced=True,
+ value=True,
+ info="Set 'Processing Concurrency' greater than 1 to enable multithreading.",
+ ),
+ IntInput(
+ name="concurrency_multithreading",
+ display_name="Processing Concurrency",
+ advanced=True,
+ info="When multiple files are being processed, the number of files to process concurrently.",
+ value=1,
+ ),
+ ]
+
+ outputs = [
+ *BaseFileComponent._base_outputs,
+ ]
+
+ def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:
+ """Processes files either sequentially or in parallel, depending on concurrency settings.
+
+ Args:
+ file_list (list[BaseFileComponent.BaseFile]): List of files to process.
+
+ Returns:
+ list[BaseFileComponent.BaseFile]: Updated list of files with merged data.
+ """
+
+ def process_file(file_path: str, *, silent_errors: bool = False) -> Data | None:
+ """Processes a single file and returns its Data object."""
+ try:
+ return parse_text_file_to_data(file_path, silent_errors=silent_errors)
+ except FileNotFoundError as e:
+ msg = f"File not found: {file_path}. Error: {e}"
+ self.log(msg)
+ if not silent_errors:
+ raise
+ return None
+ except Exception as e:
+ msg = f"Unexpected error processing {file_path}: {e}"
+ self.log(msg)
+ if not silent_errors:
+ raise
+ return None
+
+ if not file_list:
+ msg = "No files to process."
+ raise ValueError(msg)
+
+ concurrency = 1 if not self.use_multithreading else max(1, self.concurrency_multithreading)
+ file_count = len(file_list)
+
+ parallel_processing_threshold = 2
+ if concurrency < parallel_processing_threshold or file_count < parallel_processing_threshold:
+ if file_count > 1:
+ self.log(f"Processing {file_count} files sequentially.")
+ processed_data = [process_file(str(file.path), silent_errors=self.silent_errors) for file in file_list]
+ else:
+ self.log(f"Starting parallel processing of {file_count} files with concurrency: {concurrency}.")
+ file_paths = [str(file.path) for file in file_list]
+ processed_data = parallel_load_data(
+ file_paths,
+ silent_errors=self.silent_errors,
+ load_function=process_file,
+ max_concurrency=concurrency,
+ )
+
+ # Use rollup_basefile_data to merge processed data with BaseFile objects
+ return self.rollup_data(file_list, processed_data)
diff --git a/langflow/src/backend/base/langflow/components/data/json_to_data.py b/langflow/src/backend/base/langflow/components/data/json_to_data.py
new file mode 100644
index 0000000..9374cdd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/json_to_data.py
@@ -0,0 +1,98 @@
+import json
+from pathlib import Path
+
+from json_repair import repair_json
+
+from langflow.custom import Component
+from langflow.io import FileInput, MessageTextInput, MultilineInput, Output
+from langflow.schema import Data
+
+
+class JSONToDataComponent(Component):
+ display_name = "Load JSON"
+ description = (
+ "Convert a JSON file, JSON from a file path, or a JSON string to a Data object or a list of Data objects"
+ )
+ icon = "braces"
+ name = "JSONtoData"
+ legacy = True
+
+ inputs = [
+ FileInput(
+ name="json_file",
+ display_name="JSON File",
+ file_types=["json"],
+ info="Upload a JSON file to convert to a Data object or list of Data objects",
+ ),
+ MessageTextInput(
+ name="json_path",
+ display_name="JSON File Path",
+ info="Provide the path to the JSON file as pure text",
+ ),
+ MultilineInput(
+ name="json_string",
+ display_name="JSON String",
+ info="Enter a valid JSON string (object or array) to convert to a Data object or list of Data objects",
+ ),
+ ]
+
+ outputs = [
+ Output(name="data", display_name="Data", method="convert_json_to_data"),
+ ]
+
+ def convert_json_to_data(self) -> Data | list[Data]:
+ if sum(bool(field) for field in [self.json_file, self.json_path, self.json_string]) != 1:
+ msg = "Please provide exactly one of: JSON file, file path, or JSON string."
+ self.status = msg
+ raise ValueError(msg)
+
+ json_data = None
+
+ try:
+ if self.json_file:
+ resolved_path = self.resolve_path(self.json_file)
+ file_path = Path(resolved_path)
+ if file_path.suffix.lower() != ".json":
+ self.status = "The provided file must be a JSON file."
+ else:
+ json_data = file_path.read_text(encoding="utf-8")
+
+ elif self.json_path:
+ file_path = Path(self.json_path)
+ if file_path.suffix.lower() != ".json":
+ self.status = "The provided file must be a JSON file."
+ else:
+ json_data = file_path.read_text(encoding="utf-8")
+
+ else:
+ json_data = self.json_string
+
+ if json_data:
+ # Try to parse the JSON string
+ try:
+ parsed_data = json.loads(json_data)
+ except json.JSONDecodeError:
+ # If JSON parsing fails, try to repair the JSON string
+ repaired_json_string = repair_json(json_data)
+ parsed_data = json.loads(repaired_json_string)
+
+ # Check if the parsed data is a list
+ if isinstance(parsed_data, list):
+ result = [Data(data=item) for item in parsed_data]
+ else:
+ result = Data(data=parsed_data)
+ self.status = result
+ return result
+
+ except (json.JSONDecodeError, SyntaxError, ValueError) as e:
+ error_message = f"Invalid JSON or Python literal: {e}"
+ self.status = error_message
+ raise ValueError(error_message) from e
+
+ except Exception as e:
+ error_message = f"An error occurred: {e}"
+ self.status = error_message
+ raise ValueError(error_message) from e
+
+ # An error occurred
+ raise ValueError(self.status)
diff --git a/langflow/src/backend/base/langflow/components/data/s3_bucket_uploader.py b/langflow/src/backend/base/langflow/components/data/s3_bucket_uploader.py
new file mode 100644
index 0000000..c5299f7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/s3_bucket_uploader.py
@@ -0,0 +1,207 @@
+from pathlib import Path
+from typing import Any
+
+import boto3
+
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ HandleInput,
+ Output,
+ SecretStrInput,
+ StrInput,
+)
+
+
+class S3BucketUploaderComponent(Component):
+ """S3BucketUploaderComponent is a component responsible for uploading files to an S3 bucket.
+
+ It provides two strategies for file upload: "By Data" and "By File Name". The component
+ requires AWS credentials and bucket details as inputs and processes files accordingly.
+
+ Attributes:
+ display_name (str): The display name of the component.
+ description (str): A brief description of the components functionality.
+ icon (str): The icon representing the component.
+ name (str): The internal name of the component.
+ inputs (list): A list of input configurations required by the component.
+ outputs (list): A list of output configurations provided by the component.
+
+ Methods:
+ process_files() -> None:
+ Processes files based on the selected strategy. Calls the appropriate method
+ based on the strategy attribute.
+ process_files_by_data() -> None:
+ Processes and uploads files to an S3 bucket based on the data inputs. Iterates
+ over the data inputs, logs the file path and text content, and uploads each file
+ to the specified S3 bucket if both file path and text content are available.
+ process_files_by_name() -> None:
+ Processes and uploads files to an S3 bucket based on their names. Iterates through
+ the list of data inputs, retrieves the file path from each data item, and uploads
+ the file to the specified S3 bucket if the file path is available. Logs the file
+ path being uploaded.
+ _s3_client() -> Any:
+ Creates and returns an S3 client using the provided AWS access key ID and secret
+ access key.
+
+ Please note that this component requires the boto3 library to be installed. It is designed
+ to work with File and Director components as inputs
+ """
+
+ display_name = "S3 Bucket Uploader"
+ description = "Uploads files to S3 bucket."
+ icon = "Globe"
+ name = "s3bucketuploader"
+
+ inputs = [
+ SecretStrInput(
+ name="aws_access_key_id",
+ display_name="AWS Access Key ID",
+ required=True,
+ password=True,
+ info="AWS Access key ID.",
+ ),
+ SecretStrInput(
+ name="aws_secret_access_key",
+ display_name="AWS Secret Key",
+ required=True,
+ password=True,
+ info="AWS Secret Key.",
+ ),
+ StrInput(
+ name="bucket_name",
+ display_name="Bucket Name",
+ info="Enter the name of the bucket.",
+ advanced=False,
+ ),
+ DropdownInput(
+ name="strategy",
+ display_name="Strategy for file upload",
+ options=["Store Data", "Store Original File"],
+ value="By Data",
+ info=(
+ "Choose the strategy to upload the file. By Data means that the source file "
+ "is parsed and stored as LangFlow data. By File Name means that the source "
+ "file is uploaded as is."
+ ),
+ ),
+ HandleInput(
+ name="data_inputs",
+ display_name="Data Inputs",
+ info="The data to split.",
+ input_types=["Data"],
+ is_list=True,
+ required=True,
+ ),
+ StrInput(
+ name="s3_prefix",
+ display_name="S3 Prefix",
+ info="Prefix for all files.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="strip_path",
+ display_name="Strip Path",
+ info="Removes path from file path.",
+ required=True,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Writes to AWS Bucket", name="data", method="process_files"),
+ ]
+
+ def process_files(self) -> None:
+ """Process files based on the selected strategy.
+
+ This method uses a strategy pattern to process files. The strategy is determined
+ by the `self.strategy` attribute, which can be either "By Data" or "By File Name".
+ Depending on the strategy, the corresponding method (`process_files_by_data` or
+ `process_files_by_name`) is called. If an invalid strategy is provided, an error
+ is logged.
+
+ Returns:
+ None
+ """
+ strategy_methods = {
+ "Store Data": self.process_files_by_data,
+ "Store Original File": self.process_files_by_name,
+ }
+ strategy_methods.get(self.strategy, lambda: self.log("Invalid strategy"))()
+
+ def process_files_by_data(self) -> None:
+ """Processes and uploads files to an S3 bucket based on the data inputs.
+
+ This method iterates over the data inputs, logs the file path and text content,
+ and uploads each file to the specified S3 bucket if both file path and text content
+ are available.
+
+ Args:
+ None
+
+ Returns:
+ None
+ """
+ for data_item in self.data_inputs:
+ file_path = data_item.data.get("file_path")
+ text_content = data_item.data.get("text")
+
+ if file_path and text_content:
+ self._s3_client().put_object(
+ Bucket=self.bucket_name, Key=self._normalize_path(file_path), Body=text_content
+ )
+
+ def process_files_by_name(self) -> None:
+ """Processes and uploads files to an S3 bucket based on their names.
+
+ Iterates through the list of data inputs, retrieves the file path from each data item,
+ and uploads the file to the specified S3 bucket if the file path is available.
+ Logs the file path being uploaded.
+
+ Returns:
+ None
+ """
+ for data_item in self.data_inputs:
+ file_path = data_item.data.get("file_path")
+ self.log(f"Uploading file: {file_path}")
+ if file_path:
+ self._s3_client().upload_file(file_path, Bucket=self.bucket_name, Key=self._normalize_path(file_path))
+
+ def _s3_client(self) -> Any:
+ """Creates and returns an S3 client using the provided AWS access key ID and secret access key.
+
+ Returns:
+ Any: A boto3 S3 client instance.
+ """
+ return boto3.client(
+ "s3",
+ aws_access_key_id=self.aws_access_key_id,
+ aws_secret_access_key=self.aws_secret_access_key,
+ )
+
+ def _normalize_path(self, file_path) -> str:
+ """Process the file path based on the s3_prefix and path_as_prefix.
+
+ Args:
+ file_path (str): The original file path.
+ s3_prefix (str): The S3 prefix to use.
+ path_as_prefix (bool): Whether to use the file path as the S3 prefix.
+
+ Returns:
+ str: The processed file path.
+ """
+ prefix = self.s3_prefix
+ strip_path = self.strip_path
+ processed_path: str = file_path
+
+ if strip_path:
+ # Filename only
+ processed_path = Path(file_path).name
+
+ # Concatenate the s3_prefix if it exists
+ if prefix:
+ processed_path = str(Path(prefix) / processed_path)
+
+ return processed_path
diff --git a/langflow/src/backend/base/langflow/components/data/sql_executor.py b/langflow/src/backend/base/langflow/components/data/sql_executor.py
new file mode 100644
index 0000000..add27a8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/sql_executor.py
@@ -0,0 +1,74 @@
+from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool
+from langchain_community.utilities import SQLDatabase
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import Text
+
+
+class SQLExecutorComponent(CustomComponent):
+ display_name = "SQL Query"
+ description = "Execute SQL query."
+ name = "SQLExecutor"
+ beta: bool = True
+
+ def build_config(self):
+ return {
+ "database_url": {
+ "display_name": "Database URL",
+ "info": "The URL of the database.",
+ },
+ "include_columns": {
+ "display_name": "Include Columns",
+ "info": "Include columns in the result.",
+ },
+ "passthrough": {
+ "display_name": "Passthrough",
+ "info": "If an error occurs, return the query instead of raising an exception.",
+ },
+ "add_error": {
+ "display_name": "Add Error",
+ "info": "Add the error to the result.",
+ },
+ }
+
+ def clean_up_uri(self, uri: str) -> str:
+ if uri.startswith("postgresql://"):
+ uri = uri.replace("postgresql://", "postgres://")
+ return uri.strip()
+
+ def build(
+ self,
+ query: str,
+ database_url: str,
+ *,
+ include_columns: bool = False,
+ passthrough: bool = False,
+ add_error: bool = False,
+ **kwargs,
+ ) -> Text:
+ _ = kwargs
+ error = None
+ try:
+ database = SQLDatabase.from_uri(database_url)
+ except Exception as e:
+ msg = f"An error occurred while connecting to the database: {e}"
+ raise ValueError(msg) from e
+ try:
+ tool = QuerySQLDataBaseTool(db=database)
+ result = tool.run(query, include_columns=include_columns)
+ self.status = result
+ except Exception as e:
+ result = str(e)
+ self.status = result
+ if not passthrough:
+ raise
+ error = repr(e)
+
+ if add_error and error is not None:
+ result = f"{result}\n\nError: {error}\n\nQuery: {query}"
+ elif error is not None:
+ # Then we won't add the error to the result
+ # but since we are in passthrough mode, we will return the query
+ result = query
+
+ return result
diff --git a/langflow/src/backend/base/langflow/components/data/url.py b/langflow/src/backend/base/langflow/components/data/url.py
new file mode 100644
index 0000000..1adf6e0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/url.py
@@ -0,0 +1,96 @@
+import re
+
+from langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader
+
+from langflow.custom import Component
+from langflow.helpers.data import data_to_text
+from langflow.io import DropdownInput, MessageTextInput, Output
+from langflow.schema import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.message import Message
+
+
+class URLComponent(Component):
+ display_name = "URL"
+ description = "Load and retrive data from specified URLs."
+ icon = "layout-template"
+ name = "URL"
+
+ inputs = [
+ MessageTextInput(
+ name="urls",
+ display_name="URLs",
+ is_list=True,
+ tool_mode=True,
+ placeholder="Enter a URL...",
+ list_add_label="Add URL",
+ ),
+ DropdownInput(
+ name="format",
+ display_name="Output Format",
+ info="Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ options=["Text", "Raw HTML"],
+ value="Text",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Message", name="text", method="fetch_content_text"),
+ Output(display_name="DataFrame", name="dataframe", method="as_dataframe"),
+ ]
+
+ def ensure_url(self, string: str) -> str:
+ """Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.
+
+ Raises an error if the string is not a valid URL.
+
+ Parameters:
+ string (str): The string to be checked and possibly modified.
+
+ Returns:
+ str: The modified string that is ensured to be a URL.
+
+ Raises:
+ ValueError: If the string is not a valid URL.
+ """
+ if not string.startswith(("http://", "https://")):
+ string = "http://" + string
+
+ # Basic URL validation regex
+ url_regex = re.compile(
+ r"^(https?:\/\/)?" # optional protocol
+ r"(www\.)?" # optional www
+ r"([a-zA-Z0-9.-]+)" # domain
+ r"(\.[a-zA-Z]{2,})?" # top-level domain
+ r"(:\d+)?" # optional port
+ r"(\/[^\s]*)?$", # optional path
+ re.IGNORECASE,
+ )
+
+ if not url_regex.match(string):
+ msg = f"Invalid URL: {string}"
+ raise ValueError(msg)
+
+ return string
+
+ def fetch_content(self) -> list[Data]:
+ urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]
+ if self.format == "Raw HTML":
+ loader = AsyncHtmlLoader(web_path=urls, encoding="utf-8")
+ else:
+ loader = WebBaseLoader(web_paths=urls, encoding="utf-8")
+ docs = loader.load()
+ data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]
+ self.status = data
+ return data
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+
+ result_string = data_to_text("{text}", data)
+ self.status = result_string
+ return Message(text=result_string)
+
+ def as_dataframe(self) -> DataFrame:
+ return DataFrame(self.fetch_content())
diff --git a/langflow/src/backend/base/langflow/components/data/webhook.py b/langflow/src/backend/base/langflow/components/data/webhook.py
new file mode 100644
index 0000000..537836c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/data/webhook.py
@@ -0,0 +1,39 @@
+import json
+
+from langflow.custom import Component
+from langflow.io import MultilineInput, Output
+from langflow.schema import Data
+
+
+class WebhookComponent(Component):
+ display_name = "Webhook"
+ description = "Defines a webhook input for the flow."
+ name = "Webhook"
+ icon = "webhook"
+
+ inputs = [
+ MultilineInput(
+ name="data",
+ display_name="Payload",
+ info="Receives a payload from external systems via HTTP POST.",
+ )
+ ]
+ outputs = [
+ Output(display_name="Data", name="output_data", method="build_data"),
+ ]
+
+ def build_data(self) -> Data:
+ message: str | Data = ""
+ if not self.data:
+ self.status = "No data provided."
+ return Data(data={})
+ try:
+ body = json.loads(self.data or "{}")
+ except json.JSONDecodeError:
+ body = {"payload": self.data}
+ message = f"Invalid JSON payload. Please check the format.\n\n{self.data}"
+ data = Data(data=body)
+ if not message:
+ message = data
+ self.status = message
+ return data
diff --git a/langflow/src/backend/base/langflow/components/deactivated/__init__.py b/langflow/src/backend/base/langflow/components/deactivated/__init__.py
new file mode 100644
index 0000000..9cf7ac2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/__init__.py
@@ -0,0 +1,21 @@
+from .extract_key_from_data import ExtractKeyFromDataComponent
+from .list_flows import ListFlowsComponent
+from .merge_data import MergeDataComponent
+from .selective_passthrough import SelectivePassThroughComponent
+from .split_text import SplitTextComponent
+from .sub_flow import SubFlowComponent
+
+__all__ = [
+ "ExtractKeyFromDataComponent",
+ "FlowToolComponent",
+ "ListFlowsComponent",
+ "ListenComponent",
+ "MergeDataComponent",
+ "NotifyComponent",
+ "PythonFunctionComponent",
+ "RunFlowComponent",
+ "SQLExecutorComponent",
+ "SelectivePassThroughComponent",
+ "SplitTextComponent",
+ "SubFlowComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/deactivated/chat_litellm_model.py b/langflow/src/backend/base/langflow/components/deactivated/chat_litellm_model.py
new file mode 100644
index 0000000..10ac5b1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/chat_litellm_model.py
@@ -0,0 +1,158 @@
+from langchain_community.chat_models.litellm import ChatLiteLLM, ChatLiteLLMException
+
+from langflow.base.constants import STREAM_INFO_TEXT
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.io import (
+ BoolInput,
+ DictInput,
+ DropdownInput,
+ FloatInput,
+ IntInput,
+ MessageInput,
+ SecretStrInput,
+ StrInput,
+)
+
+
+class ChatLiteLLMModelComponent(LCModelComponent):
+ display_name = "LiteLLM"
+ description = "`LiteLLM` collection of large language models."
+ documentation = "https://python.langchain.com/docs/integrations/chat/litellm"
+ icon = "🚄"
+
+ inputs = [
+ MessageInput(name="input_value", display_name="Input"),
+ StrInput(
+ name="model",
+ display_name="Model name",
+ advanced=False,
+ required=True,
+ info="The name of the model to use. For example, `gpt-3.5-turbo`.",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ advanced=False,
+ required=False,
+ ),
+ DropdownInput(
+ name="provider",
+ display_name="Provider",
+ info="The provider of the API key.",
+ options=[
+ "OpenAI",
+ "Azure",
+ "Anthropic",
+ "Replicate",
+ "Cohere",
+ "OpenRouter",
+ ],
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Temperature",
+ advanced=False,
+ required=False,
+ value=0.7,
+ ),
+ DictInput(
+ name="kwargs",
+ display_name="Kwargs",
+ advanced=True,
+ required=False,
+ is_list=True,
+ value={},
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model kwargs",
+ advanced=True,
+ required=False,
+ is_list=True,
+ value={},
+ ),
+ FloatInput(name="top_p", display_name="Top p", advanced=True, required=False, value=0.5),
+ IntInput(name="top_k", display_name="Top k", advanced=True, required=False, value=35),
+ IntInput(
+ name="n",
+ display_name="N",
+ advanced=True,
+ required=False,
+ info="Number of chat completions to generate for each prompt. "
+ "Note that the API may not return the full n completions if duplicates are generated.",
+ value=1,
+ ),
+ IntInput(
+ name="max_tokens",
+ display_name="Max tokens",
+ advanced=False,
+ value=256,
+ info="The maximum number of tokens to generate for each chat completion.",
+ ),
+ IntInput(
+ name="max_retries",
+ display_name="Max retries",
+ advanced=True,
+ required=False,
+ value=6,
+ ),
+ BoolInput(
+ name="verbose",
+ display_name="Verbose",
+ advanced=True,
+ required=False,
+ value=False,
+ ),
+ BoolInput(
+ name="stream",
+ display_name="Stream",
+ info=STREAM_INFO_TEXT,
+ advanced=True,
+ ),
+ StrInput(
+ name="system_message",
+ display_name="System Message",
+ info="System message to pass to the model.",
+ advanced=True,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ import litellm
+
+ litellm.drop_params = True
+ litellm.set_verbose = self.verbose
+ except ImportError as e:
+ msg = "Could not import litellm python package. Please install it with `pip install litellm`"
+ raise ChatLiteLLMException(msg) from e
+ # Remove empty keys
+ if "" in self.kwargs:
+ del self.kwargs[""]
+ if "" in self.model_kwargs:
+ del self.model_kwargs[""]
+ # Report missing fields for Azure provider
+ if self.provider == "Azure":
+ if "api_base" not in self.kwargs:
+ msg = "Missing api_base on kwargs"
+ raise ValueError(msg)
+ if "api_version" not in self.model_kwargs:
+ msg = "Missing api_version on model_kwargs"
+ raise ValueError(msg)
+ output = ChatLiteLLM(
+ model=f"{self.provider.lower()}/{self.model}",
+ client=None,
+ streaming=self.stream,
+ temperature=self.temperature,
+ model_kwargs=self.model_kwargs if self.model_kwargs is not None else {},
+ top_p=self.top_p,
+ top_k=self.top_k,
+ n=self.n,
+ max_tokens=self.max_tokens,
+ max_retries=self.max_retries,
+ **self.kwargs,
+ )
+ output.client.api_key = self.api_key
+
+ return output
diff --git a/langflow/src/backend/base/langflow/components/deactivated/code_block_extractor.py b/langflow/src/backend/base/langflow/components/deactivated/code_block_extractor.py
new file mode 100644
index 0000000..f1383fc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/code_block_extractor.py
@@ -0,0 +1,26 @@
+import re
+
+from langflow.custom import Component
+from langflow.field_typing import Input, Output, Text
+
+
+class CodeBlockExtractor(Component):
+ display_name = "Code Block Extractor"
+ description = "Extracts code block from text."
+ name = "CodeBlockExtractor"
+
+ inputs = [Input(name="text", field_type=Text, description="Text to extract code blocks from.")]
+
+ outputs = [Output(name="code_block", display_name="Code Block", method="get_code_block")]
+
+ def get_code_block(self) -> Text:
+ text = self.text.strip()
+ # Extract code block
+ # It may start with ``` or ```language
+ # It may end with ```
+ pattern = r"^```(?:\w+)?\s*\n(.*?)(?=^```)```"
+ match = re.search(pattern, text, re.MULTILINE)
+ code_block = ""
+ if match:
+ code_block = match.group(1)
+ return code_block
diff --git a/langflow/src/backend/base/langflow/components/deactivated/documents_to_data.py b/langflow/src/backend/base/langflow/components/deactivated/documents_to_data.py
new file mode 100644
index 0000000..5eaf6b6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/documents_to_data.py
@@ -0,0 +1,22 @@
+from langchain_core.documents import Document
+
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+
+
+class DocumentsToDataComponent(CustomComponent):
+ display_name = "Documents ⇢ Data"
+ description = "Convert LangChain Documents into Data."
+ icon = "LangChain"
+ name = "DocumentsToData"
+
+ field_config = {
+ "documents": {"display_name": "Documents"},
+ }
+
+ def build(self, documents: list[Document]) -> list[Data]:
+ if isinstance(documents, Document):
+ documents = [documents]
+ data = [Data.from_document(document) for document in documents]
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/deactivated/embed.py b/langflow/src/backend/base/langflow/components/deactivated/embed.py
new file mode 100644
index 0000000..dee38e2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/embed.py
@@ -0,0 +1,16 @@
+from langflow.custom import CustomComponent
+from langflow.field_typing import Embeddings
+from langflow.schema import Data
+
+
+class EmbedComponent(CustomComponent):
+ display_name = "Embed Texts"
+ name = "Embed"
+
+ def build_config(self):
+ return {"texts": {"display_name": "Texts"}, "embbedings": {"display_name": "Embeddings"}}
+
+ def build(self, texts: list[str], embbedings: Embeddings) -> Data:
+ vectors = Data(vector=embbedings.embed_documents(texts))
+ self.status = vectors
+ return vectors
diff --git a/langflow/src/backend/base/langflow/components/deactivated/extract_key_from_data.py b/langflow/src/backend/base/langflow/components/deactivated/extract_key_from_data.py
new file mode 100644
index 0000000..3882d0d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/extract_key_from_data.py
@@ -0,0 +1,46 @@
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+
+
+class ExtractKeyFromDataComponent(CustomComponent):
+ display_name = "Extract Key From Data"
+ description = "Extracts a key from a data."
+ beta: bool = True
+ name = "ExtractKeyFromData"
+
+ field_config = {
+ "data": {"display_name": "Data"},
+ "keys": {
+ "display_name": "Keys",
+ "info": "The keys to extract from the data.",
+ "input_types": [],
+ },
+ "silent_error": {
+ "display_name": "Silent Errors",
+ "info": "If True, errors will not be raised.",
+ "advanced": True,
+ },
+ }
+
+ def build(self, data: Data, keys: list[str], *, silent_error: bool = True) -> Data:
+ """Extracts the keys from a data.
+
+ Args:
+ data (Data): The data from which to extract the keys.
+ keys (list[str]): The keys to extract from the data.
+ silent_error (bool): If True, errors will not be raised.
+
+ Returns:
+ dict: The extracted keys.
+ """
+ extracted_keys = {}
+ for key in keys:
+ try:
+ extracted_keys[key] = getattr(data, key)
+ except AttributeError as e:
+ if not silent_error:
+ msg = f"The key '{key}' does not exist in the data."
+ raise KeyError(msg) from e
+ return_data = Data(data=extracted_keys)
+ self.status = return_data
+ return return_data
diff --git a/langflow/src/backend/base/langflow/components/deactivated/list_flows.py b/langflow/src/backend/base/langflow/components/deactivated/list_flows.py
new file mode 100644
index 0000000..70d77d8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/list_flows.py
@@ -0,0 +1,20 @@
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+
+
+class ListFlowsComponent(CustomComponent):
+ display_name = "List Flows"
+ description = "A component to list all available flows."
+ icon = "ListFlows"
+ beta: bool = True
+ name = "ListFlows"
+
+ def build_config(self):
+ return {}
+
+ async def build(
+ self,
+ ) -> list[Data]:
+ flows = await self.alist_flows()
+ self.status = flows
+ return flows
diff --git a/langflow/src/backend/base/langflow/components/deactivated/merge_data.py b/langflow/src/backend/base/langflow/components/deactivated/merge_data.py
new file mode 100644
index 0000000..8c57a78
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/merge_data.py
@@ -0,0 +1,94 @@
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import DataInput, Output
+from langflow.schema import Data
+
+
+class MergeDataComponent(Component):
+ """MergeDataComponent is responsible for combining multiple Data objects into a unified list of Data objects.
+
+ It ensures that all keys across the input Data objects are present in each merged Data object.
+ Missing keys are filled with empty strings to maintain consistency.
+ """
+
+ display_name = "Merge Data"
+ description = (
+ "Combines multiple Data objects into a unified list, ensuring all keys are present in each Data object."
+ )
+ icon = "merge"
+
+ inputs = [
+ DataInput(
+ name="data_inputs",
+ display_name="Data Inputs",
+ is_list=True,
+ info="A list of Data inputs objects to be merged.",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Merged Data",
+ name="merged_data",
+ method="merge_data",
+ ),
+ ]
+
+ def merge_data(self) -> list[Data]:
+ """Merges multiple Data objects into a single list of Data objects.
+
+ Ensures that all keys from the input Data objects are present in each merged Data object.
+ Missing keys are filled with empty strings.
+
+ Returns:
+ List[Data]: A list of merged Data objects with consistent keys.
+ """
+ logger.info("Initiating the data merging process.")
+
+ data_inputs: list[Data] = self.data_inputs
+ logger.debug(f"Received {len(data_inputs)} data input(s) for merging.")
+
+ if not data_inputs:
+ logger.warning("No data inputs provided. Returning an empty list.")
+ return []
+
+ # Collect all unique keys from all Data objects
+ all_keys: set[str] = set()
+ for idx, data_input in enumerate(data_inputs):
+ if not isinstance(data_input, Data):
+ error_message = f"Data input at index {idx} is not of type Data."
+ logger.error(error_message)
+ type_error_message = (
+ f"All items in data_inputs must be of type Data. Item at index {idx} is {type(data_input)}"
+ )
+ raise TypeError(type_error_message)
+ all_keys.update(data_input.data.keys())
+ logger.debug(f"Collected {len(all_keys)} unique key(s) from input data.")
+
+ try:
+ # Create new list of Data objects with missing keys filled with empty strings
+ merged_data_list = []
+ for idx, data_input in enumerate(data_inputs):
+ merged_data_dict = {}
+
+ for key in all_keys:
+ # Use the existing value if the key exists, otherwise use an empty string
+ value = data_input.data.get(key, "")
+ if key not in data_input.data:
+ log_message = f"Key '{key}' missing in data input at index {idx}. Assigning empty string."
+ logger.debug(log_message)
+ merged_data_dict[key] = value
+
+ merged_data = Data(
+ text_key=data_input.text_key, data=merged_data_dict, default_value=data_input.default_value
+ )
+ merged_data_list.append(merged_data)
+ logger.debug(f"Merged Data object created for input at index {idx}.")
+
+ except Exception:
+ logger.exception("An error occurred during the data merging process.")
+ raise
+
+ logger.info("Data merging process completed successfully.")
+ return merged_data_list
diff --git a/langflow/src/backend/base/langflow/components/deactivated/message.py b/langflow/src/backend/base/langflow/components/deactivated/message.py
new file mode 100644
index 0000000..1ab0bda
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/message.py
@@ -0,0 +1,37 @@
+from langflow.custom import CustomComponent
+from langflow.schema.message import Message
+from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
+
+
+class MessageComponent(CustomComponent):
+ display_name = "Message"
+ description = "Creates a Message object given a Session ID."
+ name = "Message"
+
+ def build_config(self):
+ return {
+ "sender": {
+ "options": [MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],
+ "display_name": "Sender Type",
+ },
+ "sender_name": {"display_name": "Sender Name"},
+ "text": {"display_name": "Text"},
+ "session_id": {
+ "display_name": "Session ID",
+ "info": "Session ID of the chat history.",
+ "input_types": ["Message"],
+ },
+ }
+
+ def build(
+ self,
+ sender: str = MESSAGE_SENDER_USER,
+ sender_name: str | None = None,
+ session_id: str | None = None,
+ text: str = "",
+ ) -> Message:
+ flow_id = self.graph.flow_id if hasattr(self, "graph") else None
+ message = Message(text=text, sender=sender, sender_name=sender_name, flow_id=flow_id, session_id=session_id)
+
+ self.status = message
+ return message
diff --git a/langflow/src/backend/base/langflow/components/deactivated/selective_passthrough.py b/langflow/src/backend/base/langflow/components/deactivated/selective_passthrough.py
new file mode 100644
index 0000000..f828429
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/selective_passthrough.py
@@ -0,0 +1,77 @@
+from langflow.custom import Component
+from langflow.field_typing import Text
+from langflow.io import BoolInput, DropdownInput, MessageTextInput, Output
+
+
+class SelectivePassThroughComponent(Component):
+ display_name = "Selective Pass Through"
+ description = "Passes the specified value if a specified condition is met."
+ icon = "filter"
+ name = "SelectivePassThrough"
+
+ inputs = [
+ MessageTextInput(
+ name="input_value",
+ display_name="Input Value",
+ info="The primary input value to evaluate.",
+ ),
+ MessageTextInput(
+ name="comparison_value",
+ display_name="Comparison Value",
+ info="The value to compare against the input value.",
+ ),
+ DropdownInput(
+ name="operator",
+ display_name="Operator",
+ options=["equals", "not equals", "contains", "starts with", "ends with"],
+ info="Condition to evaluate the input value.",
+ ),
+ MessageTextInput(
+ name="value_to_pass",
+ display_name="Value to Pass",
+ info="The value to pass if the condition is met.",
+ ),
+ BoolInput(
+ name="case_sensitive",
+ display_name="Case Sensitive",
+ info="If true, the comparison will be case sensitive.",
+ value=False,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Passed Output", name="passed_output", method="pass_through"),
+ ]
+
+ def evaluate_condition(
+ self, input_value: str, comparison_value: str, operator: str, *, case_sensitive: bool
+ ) -> bool:
+ if not case_sensitive:
+ input_value = input_value.lower()
+ comparison_value = comparison_value.lower()
+
+ if operator == "equals":
+ return input_value == comparison_value
+ if operator == "not equals":
+ return input_value != comparison_value
+ if operator == "contains":
+ return comparison_value in input_value
+ if operator == "starts with":
+ return input_value.startswith(comparison_value)
+ if operator == "ends with":
+ return input_value.endswith(comparison_value)
+ return False
+
+ def pass_through(self) -> Text:
+ input_value = self.input_value
+ comparison_value = self.comparison_value
+ operator = self.operator
+ value_to_pass = self.value_to_pass
+ case_sensitive = self.case_sensitive
+
+ if self.evaluate_condition(input_value, comparison_value, operator, case_sensitive=case_sensitive):
+ self.status = value_to_pass
+ return value_to_pass
+ self.status = ""
+ return ""
diff --git a/langflow/src/backend/base/langflow/components/deactivated/should_run_next.py b/langflow/src/backend/base/langflow/components/deactivated/should_run_next.py
new file mode 100644
index 0000000..a65687e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/should_run_next.py
@@ -0,0 +1,40 @@
+from langchain_core.messages import BaseMessage
+from langchain_core.prompts import PromptTemplate
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import LanguageModel, Text
+
+
+class ShouldRunNextComponent(CustomComponent):
+ display_name = "Should Run Next"
+ description = "Determines if a vertex is runnable."
+ name = "ShouldRunNext"
+
+ def build(self, llm: LanguageModel, question: str, context: str, retries: int = 3) -> Text:
+ template = (
+ "Given the following question and the context below, answer with a yes or no.\n\n"
+ "{error_message}\n\n"
+ "Question: {question}\n\n" # noqa: RUF100, RUF027
+ "Context: {context}\n\n" # noqa: RUF100, RUF027
+ "Answer:"
+ )
+
+ prompt = PromptTemplate.from_template(template)
+ chain = prompt | llm
+ error_message = ""
+ for _i in range(retries):
+ result = chain.invoke(
+ {"question": question, "context": context, "error_message": error_message},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+ if isinstance(result, BaseMessage):
+ content = result.content
+ elif isinstance(result, str):
+ content = result
+ if isinstance(content, str) and content.lower().strip() in {"yes", "no"}:
+ break
+ condition = str(content).lower().strip() == "yes"
+ self.status = f"Should Run Next: {condition}"
+ if condition is False:
+ self.stop()
+ return context
diff --git a/langflow/src/backend/base/langflow/components/deactivated/split_text.py b/langflow/src/backend/base/langflow/components/deactivated/split_text.py
new file mode 100644
index 0000000..3615753
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/split_text.py
@@ -0,0 +1,63 @@
+from langchain_text_splitters import CharacterTextSplitter
+
+from langflow.custom import Component
+from langflow.io import HandleInput, IntInput, MessageTextInput, Output
+from langflow.schema import Data
+from langflow.utils.util import unescape_string
+
+
+class SplitTextComponent(Component):
+ display_name: str = "Split Text"
+ description: str = "Split text into chunks based on specified criteria."
+ icon = "scissors-line-dashed"
+ name = "SplitText"
+
+ inputs = [
+ HandleInput(
+ name="data_inputs",
+ display_name="Data Inputs",
+ info="The data to split.",
+ input_types=["Data"],
+ is_list=True,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="Number of characters to overlap between chunks.",
+ value=200,
+ ),
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum number of characters in each chunk.",
+ value=1000,
+ ),
+ MessageTextInput(
+ name="separator",
+ display_name="Separator",
+ info="The character to split on. Defaults to newline.",
+ value="\n",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Chunks", name="chunks", method="split_text"),
+ ]
+
+ def _docs_to_data(self, docs):
+ return [Data(text=doc.page_content, data=doc.metadata) for doc in docs]
+
+ def split_text(self) -> list[Data]:
+ separator = unescape_string(self.separator)
+
+ documents = [_input.to_lc_document() for _input in self.data_inputs if isinstance(_input, Data)]
+
+ splitter = CharacterTextSplitter(
+ chunk_overlap=self.chunk_overlap,
+ chunk_size=self.chunk_size,
+ separator=separator,
+ )
+ docs = splitter.split_documents(documents)
+ data = self._docs_to_data(docs)
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/deactivated/store_message.py b/langflow/src/backend/base/langflow/components/deactivated/store_message.py
new file mode 100644
index 0000000..0d68ece
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/store_message.py
@@ -0,0 +1,24 @@
+from langflow.custom import CustomComponent
+from langflow.memory import aget_messages, astore_message
+from langflow.schema.message import Message
+
+
+class StoreMessageComponent(CustomComponent):
+ display_name = "Store Message"
+ description = "Stores a chat message."
+ name = "StoreMessage"
+
+ def build_config(self):
+ return {
+ "message": {"display_name": "Message"},
+ }
+
+ async def build(
+ self,
+ message: Message,
+ ) -> Message:
+ flow_id = self.graph.flow_id if hasattr(self, "graph") else None
+ await astore_message(message, flow_id=flow_id)
+ self.status = await aget_messages()
+
+ return message
diff --git a/langflow/src/backend/base/langflow/components/deactivated/sub_flow.py b/langflow/src/backend/base/langflow/components/deactivated/sub_flow.py
new file mode 100644
index 0000000..a4291a7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/deactivated/sub_flow.py
@@ -0,0 +1,125 @@
+from typing import TYPE_CHECKING, Any
+
+from loguru import logger
+
+from langflow.base.flow_processing.utils import build_data_from_result_data
+from langflow.custom import CustomComponent
+from langflow.graph.graph.base import Graph
+from langflow.graph.vertex.base import Vertex
+from langflow.helpers.flow import get_flow_inputs
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+from langflow.template.field.base import Input
+
+if TYPE_CHECKING:
+ from langflow.graph.schema import RunOutputs
+
+
+class SubFlowComponent(CustomComponent):
+ display_name = "Sub Flow"
+ description = (
+ "Dynamically Generates a Component from a Flow. The output is a list of data with keys 'result' and 'message'."
+ )
+ beta: bool = True
+ field_order = ["flow_name"]
+ name = "SubFlow"
+
+ async def get_flow_names(self) -> list[str]:
+ flow_datas = await self.alist_flows()
+ return [flow_data.data["name"] for flow_data in flow_datas]
+
+ async def get_flow(self, flow_name: str) -> Data | None:
+ flow_datas = await self.alist_flows()
+ for flow_data in flow_datas:
+ if flow_data.data["name"] == flow_name:
+ return flow_data
+ return None
+
+ async def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ logger.debug(f"Updating build config with field value {field_value} and field name {field_name}")
+ if field_name == "flow_name":
+ build_config["flow_name"]["options"] = await self.get_flow_names()
+ # Clean up the build config
+ for key in list(build_config.keys()):
+ if key not in {*self.field_order, "code", "_type", "get_final_results_only"}:
+ del build_config[key]
+ if field_value is not None and field_name == "flow_name":
+ try:
+ flow_data = await self.get_flow(field_value)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error getting flow {field_value}")
+ else:
+ if not flow_data:
+ msg = f"Flow {field_value} not found."
+ logger.error(msg)
+ else:
+ try:
+ graph = Graph.from_payload(flow_data.data["data"])
+ # Get all inputs from the graph
+ inputs = get_flow_inputs(graph)
+ # Add inputs to the build config
+ build_config = self.add_inputs_to_build_config(inputs, build_config)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error building graph for flow {field_value}")
+
+ return build_config
+
+ def add_inputs_to_build_config(self, inputs: list[Vertex], build_config: dotdict):
+ new_fields: list[Input] = []
+ for vertex in inputs:
+ field = Input(
+ display_name=vertex.display_name,
+ name=vertex.id,
+ info=vertex.description,
+ field_type="str",
+ value=None,
+ )
+ new_fields.append(field)
+ logger.debug(new_fields)
+ for field in new_fields:
+ build_config[field.name] = field.to_dict()
+ return build_config
+
+ def build_config(self):
+ return {
+ "input_value": {
+ "display_name": "Input Value",
+ "multiline": True,
+ },
+ "flow_name": {
+ "display_name": "Flow Name",
+ "info": "The name of the flow to run.",
+ "options": [],
+ "real_time_refresh": True,
+ "refresh_button": True,
+ },
+ "tweaks": {
+ "display_name": "Tweaks",
+ "info": "Tweaks to apply to the flow.",
+ },
+ "get_final_results_only": {
+ "display_name": "Get Final Results Only",
+ "info": "If False, the output will contain all outputs from the flow.",
+ "advanced": True,
+ },
+ }
+
+ async def build(self, flow_name: str, **kwargs) -> list[Data]:
+ tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
+ run_outputs: list[RunOutputs | None] = await self.run_flow(
+ tweaks=tweaks,
+ flow_name=flow_name,
+ )
+ if not run_outputs:
+ return []
+ run_output = run_outputs[0]
+
+ data = []
+ if run_output is not None:
+ for output in run_output.outputs:
+ if output:
+ data.extend(build_data_from_result_data(output))
+
+ self.status = data
+ logger.debug(data)
+ return data
diff --git a/langflow/src/backend/base/langflow/components/documentloaders/__init__.py b/langflow/src/backend/base/langflow/components/documentloaders/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/embeddings/__init__.py b/langflow/src/backend/base/langflow/components/embeddings/__init__.py
new file mode 100644
index 0000000..e0ab27f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/__init__.py
@@ -0,0 +1,35 @@
+from .aiml import AIMLEmbeddingsComponent
+from .amazon_bedrock import AmazonBedrockEmbeddingsComponent
+from .astra_vectorize import AstraVectorizeComponent
+from .azure_openai import AzureOpenAIEmbeddingsComponent
+from .cloudflare import CloudflareWorkersAIEmbeddingsComponent
+from .cohere import CohereEmbeddingsComponent
+from .google_generative_ai import GoogleGenerativeAIEmbeddingsComponent
+from .huggingface_inference_api import HuggingFaceInferenceAPIEmbeddingsComponent
+from .lmstudioembeddings import LMStudioEmbeddingsComponent
+from .mistral import MistralAIEmbeddingsComponent
+from .nvidia import NVIDIAEmbeddingsComponent
+from .ollama import OllamaEmbeddingsComponent
+from .openai import OpenAIEmbeddingsComponent
+from .similarity import EmbeddingSimilarityComponent
+from .text_embedder import TextEmbedderComponent
+from .vertexai import VertexAIEmbeddingsComponent
+
+__all__ = [
+ "AIMLEmbeddingsComponent",
+ "AmazonBedrockEmbeddingsComponent",
+ "AstraVectorizeComponent",
+ "AzureOpenAIEmbeddingsComponent",
+ "CloudflareWorkersAIEmbeddingsComponent",
+ "CohereEmbeddingsComponent",
+ "EmbeddingSimilarityComponent",
+ "GoogleGenerativeAIEmbeddingsComponent",
+ "HuggingFaceInferenceAPIEmbeddingsComponent",
+ "LMStudioEmbeddingsComponent",
+ "MistralAIEmbeddingsComponent",
+ "NVIDIAEmbeddingsComponent",
+ "OllamaEmbeddingsComponent",
+ "OpenAIEmbeddingsComponent",
+ "TextEmbedderComponent",
+ "VertexAIEmbeddingsComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/embeddings/aiml.py b/langflow/src/backend/base/langflow/components/embeddings/aiml.py
new file mode 100644
index 0000000..e9ed2bd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/aiml.py
@@ -0,0 +1,37 @@
+from langflow.base.embeddings.aiml_embeddings import AIMLEmbeddingsImpl
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.field_typing import Embeddings
+from langflow.inputs.inputs import DropdownInput
+from langflow.io import SecretStrInput
+
+
+class AIMLEmbeddingsComponent(LCEmbeddingsModel):
+ display_name = "AI/ML Embeddings"
+ description = "Generate embeddings using the AI/ML API."
+ icon = "AI/ML"
+ name = "AIMLEmbeddings"
+
+ inputs = [
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ options=[
+ "text-embedding-3-small",
+ "text-embedding-3-large",
+ "text-embedding-ada-002",
+ ],
+ required=True,
+ ),
+ SecretStrInput(
+ name="aiml_api_key",
+ display_name="AI/ML API Key",
+ value="AIML_API_KEY",
+ required=True,
+ ),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ return AIMLEmbeddingsImpl(
+ api_key=self.aiml_api_key,
+ model=self.model_name,
+ )
diff --git a/langflow/src/backend/base/langflow/components/embeddings/amazon_bedrock.py b/langflow/src/backend/base/langflow/components/embeddings/amazon_bedrock.py
new file mode 100644
index 0000000..266f6b1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/amazon_bedrock.py
@@ -0,0 +1,109 @@
+from langflow.base.models.aws_constants import AWS_EMBEDDING_MODEL_IDS, AWS_REGIONS
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import Embeddings
+from langflow.inputs import SecretStrInput
+from langflow.io import DropdownInput, MessageTextInput, Output
+
+
+class AmazonBedrockEmbeddingsComponent(LCModelComponent):
+ display_name: str = "Amazon Bedrock Embeddings"
+ description: str = "Generate embeddings using Amazon Bedrock models."
+ icon = "Amazon"
+ name = "AmazonBedrockEmbeddings"
+
+ inputs = [
+ DropdownInput(
+ name="model_id",
+ display_name="Model Id",
+ options=AWS_EMBEDDING_MODEL_IDS,
+ value="amazon.titan-embed-text-v1",
+ ),
+ SecretStrInput(
+ name="aws_access_key_id",
+ display_name="AWS Access Key ID",
+ info="The access key for your AWS account."
+ "Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.",
+ value="AWS_ACCESS_KEY_ID",
+ required=True,
+ ),
+ SecretStrInput(
+ name="aws_secret_access_key",
+ display_name="AWS Secret Access Key",
+ info="The secret key for your AWS account. "
+ "Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.",
+ value="AWS_SECRET_ACCESS_KEY",
+ required=True,
+ ),
+ SecretStrInput(
+ name="aws_session_token",
+ display_name="AWS Session Token",
+ advanced=False,
+ info="The session key for your AWS account. "
+ "Only needed for temporary credentials. "
+ "Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.",
+ value="AWS_SESSION_TOKEN",
+ ),
+ SecretStrInput(
+ name="credentials_profile_name",
+ display_name="Credentials Profile Name",
+ advanced=True,
+ info="The name of the profile to use from your "
+ "~/.aws/credentials file. "
+ "If not provided, the default profile will be used.",
+ value="AWS_CREDENTIALS_PROFILE_NAME",
+ ),
+ DropdownInput(
+ name="region_name",
+ display_name="Region Name",
+ value="us-east-1",
+ options=AWS_REGIONS,
+ info="The AWS region where your Bedrock resources are located.",
+ ),
+ MessageTextInput(
+ name="endpoint_url",
+ display_name="Endpoint URL",
+ advanced=True,
+ info="The URL of the AWS Bedrock endpoint to use.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ from langchain_aws import BedrockEmbeddings
+ except ImportError as e:
+ msg = "langchain_aws is not installed. Please install it with `pip install langchain_aws`."
+ raise ImportError(msg) from e
+ try:
+ import boto3
+ except ImportError as e:
+ msg = "boto3 is not installed. Please install it with `pip install boto3`."
+ raise ImportError(msg) from e
+ if self.aws_access_key_id or self.aws_secret_access_key:
+ session = boto3.Session(
+ aws_access_key_id=self.aws_access_key_id,
+ aws_secret_access_key=self.aws_secret_access_key,
+ aws_session_token=self.aws_session_token,
+ )
+ elif self.credentials_profile_name:
+ session = boto3.Session(profile_name=self.credentials_profile_name)
+ else:
+ session = boto3.Session()
+
+ client_params = {}
+ if self.endpoint_url:
+ client_params["endpoint_url"] = self.endpoint_url
+ if self.region_name:
+ client_params["region_name"] = self.region_name
+
+ boto3_client = session.client("bedrock-runtime", **client_params)
+ return BedrockEmbeddings(
+ credentials_profile_name=self.credentials_profile_name,
+ client=boto3_client,
+ model_id=self.model_id,
+ endpoint_url=self.endpoint_url,
+ region_name=self.region_name,
+ )
diff --git a/langflow/src/backend/base/langflow/components/embeddings/astra_vectorize.py b/langflow/src/backend/base/langflow/components/embeddings/astra_vectorize.py
new file mode 100644
index 0000000..1e2880c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/astra_vectorize.py
@@ -0,0 +1,123 @@
+from typing import Any
+
+from langflow.custom import Component
+from langflow.inputs.inputs import DictInput, DropdownInput, MessageTextInput, SecretStrInput
+from langflow.template.field.base import Output
+
+
+class AstraVectorizeComponent(Component):
+ display_name: str = "Astra Vectorize [DEPRECATED]"
+ description: str = (
+ "Configuration options for Astra Vectorize server-side embeddings. "
+ "This component is deprecated. Please use the Astra DB Component directly."
+ )
+ documentation: str = "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html"
+ icon = "AstraDB"
+ name = "AstraVectorize"
+
+ VECTORIZE_PROVIDERS_MAPPING = {
+ "Azure OpenAI": ["azureOpenAI", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
+ "Hugging Face - Dedicated": ["huggingfaceDedicated", ["endpoint-defined-model"]],
+ "Hugging Face - Serverless": [
+ "huggingface",
+ [
+ "sentence-transformers/all-MiniLM-L6-v2",
+ "intfloat/multilingual-e5-large",
+ "intfloat/multilingual-e5-large-instruct",
+ "BAAI/bge-small-en-v1.5",
+ "BAAI/bge-base-en-v1.5",
+ "BAAI/bge-large-en-v1.5",
+ ],
+ ],
+ "Jina AI": [
+ "jinaAI",
+ [
+ "jina-embeddings-v2-base-en",
+ "jina-embeddings-v2-base-de",
+ "jina-embeddings-v2-base-es",
+ "jina-embeddings-v2-base-code",
+ "jina-embeddings-v2-base-zh",
+ ],
+ ],
+ "Mistral AI": ["mistral", ["mistral-embed"]],
+ "NVIDIA": ["nvidia", ["NV-Embed-QA"]],
+ "OpenAI": ["openai", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
+ "Upstage": ["upstageAI", ["solar-embedding-1-large"]],
+ "Voyage AI": [
+ "voyageAI",
+ ["voyage-large-2-instruct", "voyage-law-2", "voyage-code-2", "voyage-large-2", "voyage-2"],
+ ],
+ }
+ VECTORIZE_MODELS_STR = "\n\n".join(
+ [provider + ": " + (", ".join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()]
+ )
+
+ inputs = [
+ DropdownInput(
+ name="provider",
+ display_name="Provider",
+ options=VECTORIZE_PROVIDERS_MAPPING.keys(),
+ value="",
+ required=True,
+ ),
+ MessageTextInput(
+ name="model_name",
+ display_name="Model Name",
+ info="The embedding model to use for the selected provider. Each provider has a different set of models "
+ f"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n{VECTORIZE_MODELS_STR}",
+ required=True,
+ ),
+ MessageTextInput(
+ name="api_key_name",
+ display_name="API Key name",
+ info="The name of the embeddings provider API key stored on Astra. "
+ "If set, it will override the 'ProviderKey' in the authentication parameters.",
+ ),
+ DictInput(
+ name="authentication",
+ display_name="Authentication parameters",
+ is_list=True,
+ advanced=True,
+ ),
+ SecretStrInput(
+ name="provider_api_key",
+ display_name="Provider API Key",
+ info="An alternative to the Astra Authentication that passes an API key for the provider with each request "
+ "to Astra DB. "
+ "This may be used when Vectorize is configured for the collection, "
+ "but no corresponding provider secret is stored within Astra's key management system.",
+ advanced=True,
+ ),
+ DictInput(
+ name="authentication",
+ display_name="Authentication Parameters",
+ is_list=True,
+ advanced=True,
+ ),
+ DictInput(
+ name="model_parameters",
+ display_name="Model Parameters",
+ advanced=True,
+ is_list=True,
+ ),
+ ]
+ outputs = [
+ Output(display_name="Vectorize", name="config", method="build_options", types=["dict"]),
+ ]
+
+ def build_options(self) -> dict[str, Any]:
+ provider_value = self.VECTORIZE_PROVIDERS_MAPPING[self.provider][0]
+ authentication = {**(self.authentication or {})}
+ api_key_name = self.api_key_name
+ if api_key_name:
+ authentication["providerKey"] = api_key_name
+ return {
+ # must match astrapy.info.CollectionVectorServiceOptions
+ "collection_vector_service_options": {
+ "provider": provider_value,
+ "modelName": self.model_name,
+ "authentication": authentication,
+ "parameters": self.model_parameters or {},
+ },
+ "collection_embedding_api_key": self.provider_api_key,
+ }
diff --git a/langflow/src/backend/base/langflow/components/embeddings/azure_openai.py b/langflow/src/backend/base/langflow/components/embeddings/azure_openai.py
new file mode 100644
index 0000000..cf6fabd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/azure_openai.py
@@ -0,0 +1,83 @@
+from langchain_openai import AzureOpenAIEmbeddings
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES
+from langflow.field_typing import Embeddings
+from langflow.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
+
+
+class AzureOpenAIEmbeddingsComponent(LCModelComponent):
+ display_name: str = "Azure OpenAI Embeddings"
+ description: str = "Generate embeddings using Azure OpenAI models."
+ documentation: str = "https://python.langchain.com/docs/integrations/text_embedding/azureopenai"
+ icon = "Azure"
+ name = "AzureOpenAIEmbeddings"
+
+ API_VERSION_OPTIONS = [
+ "2022-12-01",
+ "2023-03-15-preview",
+ "2023-05-15",
+ "2023-06-01-preview",
+ "2023-07-01-preview",
+ "2023-08-01-preview",
+ ]
+
+ inputs = [
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ advanced=False,
+ options=OPENAI_EMBEDDING_MODEL_NAMES,
+ value=OPENAI_EMBEDDING_MODEL_NAMES[0],
+ ),
+ MessageTextInput(
+ name="azure_endpoint",
+ display_name="Azure Endpoint",
+ required=True,
+ info="Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`",
+ ),
+ MessageTextInput(
+ name="azure_deployment",
+ display_name="Deployment Name",
+ required=True,
+ ),
+ DropdownInput(
+ name="api_version",
+ display_name="API Version",
+ options=API_VERSION_OPTIONS,
+ value=API_VERSION_OPTIONS[-1],
+ advanced=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ ),
+ IntInput(
+ name="dimensions",
+ display_name="Dimensions",
+ info="The number of dimensions the resulting output embeddings should have. "
+ "Only supported by certain models.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ embeddings = AzureOpenAIEmbeddings(
+ model=self.model,
+ azure_endpoint=self.azure_endpoint,
+ azure_deployment=self.azure_deployment,
+ api_version=self.api_version,
+ api_key=self.api_key,
+ dimensions=self.dimensions or None,
+ )
+ except Exception as e:
+ msg = f"Could not connect to AzureOpenAIEmbeddings API: {e}"
+ raise ValueError(msg) from e
+
+ return embeddings
diff --git a/langflow/src/backend/base/langflow/components/embeddings/cloudflare.py b/langflow/src/backend/base/langflow/components/embeddings/cloudflare.py
new file mode 100644
index 0000000..0609cfa
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/cloudflare.py
@@ -0,0 +1,81 @@
+from langchain_community.embeddings.cloudflare_workersai import CloudflareWorkersAIEmbeddings
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import Embeddings
+from langflow.io import BoolInput, DictInput, IntInput, MessageTextInput, Output, SecretStrInput
+
+
+class CloudflareWorkersAIEmbeddingsComponent(LCModelComponent):
+ display_name: str = "Cloudflare Workers AI Embeddings"
+ description: str = "Generate embeddings using Cloudflare Workers AI models."
+ documentation: str = "https://python.langchain.com/docs/integrations/text_embedding/cloudflare_workersai/"
+ icon = "Cloudflare"
+ name = "CloudflareWorkersAIEmbeddings"
+
+ inputs = [
+ MessageTextInput(
+ name="account_id",
+ display_name="Cloudflare account ID",
+ info="Find your account ID https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/#find-account-id-workers-and-pages",
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_token",
+ display_name="Cloudflare API token",
+ info="Create an API token https://developers.cloudflare.com/fundamentals/api/get-started/create-token/",
+ required=True,
+ ),
+ MessageTextInput(
+ name="model_name",
+ display_name="Model Name",
+ info="List of supported models https://developers.cloudflare.com/workers-ai/models/#text-embeddings",
+ required=True,
+ value="@cf/baai/bge-base-en-v1.5",
+ ),
+ BoolInput(
+ name="strip_new_lines",
+ display_name="Strip New Lines",
+ advanced=True,
+ value=True,
+ ),
+ IntInput(
+ name="batch_size",
+ display_name="Batch Size",
+ advanced=True,
+ value=50,
+ ),
+ MessageTextInput(
+ name="api_base_url",
+ display_name="Cloudflare API base URL",
+ advanced=True,
+ value="https://api.cloudflare.com/client/v4/accounts",
+ ),
+ DictInput(
+ name="headers",
+ display_name="Headers",
+ info="Additional request headers",
+ is_list=True,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ embeddings = CloudflareWorkersAIEmbeddings(
+ account_id=self.account_id,
+ api_base_url=self.api_base_url,
+ api_token=self.api_token,
+ batch_size=self.batch_size,
+ headers=self.headers,
+ model_name=self.model_name,
+ strip_new_lines=self.strip_new_lines,
+ )
+ except Exception as e:
+ msg = f"Could not connect to CloudflareWorkersAIEmbeddings API: {e!s}"
+ raise ValueError(msg) from e
+
+ return embeddings
diff --git a/langflow/src/backend/base/langflow/components/embeddings/cohere.py b/langflow/src/backend/base/langflow/components/embeddings/cohere.py
new file mode 100644
index 0000000..9f9bb29
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/cohere.py
@@ -0,0 +1,81 @@
+from typing import Any
+
+import cohere
+from langchain_cohere import CohereEmbeddings
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import Embeddings
+from langflow.io import DropdownInput, FloatInput, IntInput, MessageTextInput, Output, SecretStrInput
+
+HTTP_STATUS_OK = 200
+
+
+class CohereEmbeddingsComponent(LCModelComponent):
+ display_name = "Cohere Embeddings"
+ description = "Generate embeddings using Cohere models."
+ icon = "Cohere"
+ name = "CohereEmbeddings"
+
+ inputs = [
+ SecretStrInput(name="api_key", display_name="Cohere API Key", required=True, real_time_refresh=True),
+ DropdownInput(
+ name="model_name",
+ display_name="Model",
+ advanced=False,
+ options=[
+ "embed-english-v2.0",
+ "embed-multilingual-v2.0",
+ "embed-english-light-v2.0",
+ "embed-multilingual-light-v2.0",
+ ],
+ value="embed-english-v2.0",
+ refresh_button=True,
+ combobox=True,
+ ),
+ MessageTextInput(name="truncate", display_name="Truncate", advanced=True),
+ IntInput(name="max_retries", display_name="Max Retries", value=3, advanced=True),
+ MessageTextInput(name="user_agent", display_name="User Agent", advanced=True, value="langchain"),
+ FloatInput(name="request_timeout", display_name="Request Timeout", advanced=True),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ data = None
+ try:
+ data = CohereEmbeddings(
+ cohere_api_key=self.api_key,
+ model=self.model_name,
+ truncate=self.truncate,
+ max_retries=self.max_retries,
+ user_agent=self.user_agent,
+ request_timeout=self.request_timeout or None,
+ )
+ except Exception as e:
+ msg = (
+ "Unable to create Cohere Embeddings. ",
+ "Please verify the API key and model parameters, and try again.",
+ )
+ raise ValueError(msg) from e
+ # added status if not the return data would be serialised to create the status
+ return data
+
+ def get_model(self):
+ try:
+ co = cohere.ClientV2(self.api_key)
+ response = co.models.list(endpoint="embed")
+ models = response.models
+ return [model.name for model in models]
+ except Exception as e:
+ msg = f"Failed to fetch Cohere models. Error: {e}"
+ raise ValueError(msg) from e
+
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
+ if field_name in {"model_name", "api_key"}:
+ if build_config.get("api_key", {}).get("value", None):
+ build_config["model_name"]["options"] = self.get_model()
+ else:
+ build_config["model_name"]["options"] = field_value
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/embeddings/google_generative_ai.py b/langflow/src/backend/base/langflow/components/embeddings/google_generative_ai.py
new file mode 100644
index 0000000..2284d40
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/google_generative_ai.py
@@ -0,0 +1,141 @@
+# from langflow.field_typing import Data
+
+# TODO: remove ignore once the google package is published with types
+from google.ai.generativelanguage_v1beta.types import BatchEmbedContentsRequest
+from langchain_core.embeddings import Embeddings
+from langchain_google_genai import GoogleGenerativeAIEmbeddings
+from langchain_google_genai._common import GoogleGenerativeAIError
+
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output, SecretStrInput
+
+MIN_DIMENSION_ERROR = "Output dimensionality must be at least 1"
+MAX_DIMENSION_ERROR = (
+ "Output dimensionality cannot exceed 768. Google's embedding models only support dimensions up to 768."
+)
+MAX_DIMENSION = 768
+MIN_DIMENSION = 1
+
+
+class GoogleGenerativeAIEmbeddingsComponent(Component):
+ display_name = "Google Generative AI Embeddings"
+ description = (
+ "Connect to Google's generative AI embeddings service using the GoogleGenerativeAIEmbeddings class, "
+ "found in the langchain-google-genai package."
+ )
+ documentation: str = "https://python.langchain.com/v0.2/docs/integrations/text_embedding/google_generative_ai/"
+ icon = "Google"
+ name = "Google Generative AI Embeddings"
+
+ inputs = [
+ SecretStrInput(name="api_key", display_name="API Key", required=True),
+ MessageTextInput(name="model_name", display_name="Model Name", value="models/text-embedding-004"),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ if not self.api_key:
+ msg = "API Key is required"
+ raise ValueError(msg)
+
+ class HotaGoogleGenerativeAIEmbeddings(GoogleGenerativeAIEmbeddings):
+ def __init__(self, *args, **kwargs) -> None:
+ super(GoogleGenerativeAIEmbeddings, self).__init__(*args, **kwargs)
+
+ def embed_documents(
+ self,
+ texts: list[str],
+ *,
+ batch_size: int = 100,
+ task_type: str | None = None,
+ titles: list[str] | None = None,
+ output_dimensionality: int | None = 768,
+ ) -> list[list[float]]:
+ """Embed a list of strings.
+
+ Google Generative AI currently sets a max batch size of 100 strings.
+
+ Args:
+ texts: List[str] The list of strings to embed.
+ batch_size: [int] The batch size of embeddings to send to the model
+ task_type: task_type (https://ai.google.dev/api/rest/v1/TaskType)
+ titles: An optional list of titles for texts provided.
+ Only applicable when TaskType is RETRIEVAL_DOCUMENT.
+ output_dimensionality: Optional reduced dimension for the output embedding.
+ https://ai.google.dev/api/rest/v1/models/batchEmbedContents#EmbedContentRequest
+ Returns:
+ List of embeddings, one for each text.
+ """
+ if output_dimensionality is not None and output_dimensionality < MIN_DIMENSION:
+ raise ValueError(MIN_DIMENSION_ERROR)
+ if output_dimensionality is not None and output_dimensionality > MAX_DIMENSION:
+ error_msg = MAX_DIMENSION_ERROR.format(output_dimensionality)
+ raise ValueError(error_msg)
+
+ embeddings: list[list[float]] = []
+ batch_start_index = 0
+ for batch in GoogleGenerativeAIEmbeddings._prepare_batches(texts, batch_size):
+ if titles:
+ titles_batch = titles[batch_start_index : batch_start_index + len(batch)]
+ batch_start_index += len(batch)
+ else:
+ titles_batch = [None] * len(batch) # type: ignore[list-item]
+
+ requests = [
+ self._prepare_request(
+ text=text,
+ task_type=task_type,
+ title=title,
+ output_dimensionality=output_dimensionality,
+ )
+ for text, title in zip(batch, titles_batch, strict=True)
+ ]
+
+ try:
+ result = self.client.batch_embed_contents(
+ BatchEmbedContentsRequest(requests=requests, model=self.model)
+ )
+ except Exception as e:
+ msg = f"Error embedding content: {e}"
+ raise GoogleGenerativeAIError(msg) from e
+ embeddings.extend([list(e.values) for e in result.embeddings])
+ return embeddings
+
+ def embed_query(
+ self,
+ text: str,
+ task_type: str | None = None,
+ title: str | None = None,
+ output_dimensionality: int | None = 768,
+ ) -> list[float]:
+ """Embed a text.
+
+ Args:
+ text: The text to embed.
+ task_type: task_type (https://ai.google.dev/api/rest/v1/TaskType)
+ title: An optional title for the text.
+ Only applicable when TaskType is RETRIEVAL_DOCUMENT.
+ output_dimensionality: Optional reduced dimension for the output embedding.
+ https://ai.google.dev/api/rest/v1/models/batchEmbedContents#EmbedContentRequest
+
+ Returns:
+ Embedding for the text.
+ """
+ if output_dimensionality is not None and output_dimensionality < MIN_DIMENSION:
+ raise ValueError(MIN_DIMENSION_ERROR)
+ if output_dimensionality is not None and output_dimensionality > MAX_DIMENSION:
+ error_msg = MAX_DIMENSION_ERROR.format(output_dimensionality)
+ raise ValueError(error_msg)
+
+ task_type = task_type or "RETRIEVAL_QUERY"
+ return self.embed_documents(
+ [text],
+ task_type=task_type,
+ titles=[title] if title else None,
+ output_dimensionality=output_dimensionality,
+ )[0]
+
+ return HotaGoogleGenerativeAIEmbeddings(model=self.model_name, google_api_key=self.api_key)
diff --git a/langflow/src/backend/base/langflow/components/embeddings/huggingface_inference_api.py b/langflow/src/backend/base/langflow/components/embeddings/huggingface_inference_api.py
new file mode 100644
index 0000000..3ccd9f5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/huggingface_inference_api.py
@@ -0,0 +1,106 @@
+from urllib.parse import urlparse
+
+import requests
+from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings
+
+# Next update: use langchain_huggingface
+from pydantic import SecretStr
+from tenacity import retry, stop_after_attempt, wait_fixed
+
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.field_typing import Embeddings
+from langflow.io import MessageTextInput, Output, SecretStrInput
+
+
+class HuggingFaceInferenceAPIEmbeddingsComponent(LCEmbeddingsModel):
+ display_name = "HuggingFace Embeddings Inference"
+ description = "Generate embeddings using HuggingFace Text Embeddings Inference (TEI)"
+ documentation = "https://huggingface.co/docs/text-embeddings-inference/index"
+ icon = "HuggingFace"
+ name = "HuggingFaceInferenceAPIEmbeddings"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ advanced=False,
+ info="Required for non-local inference endpoints. Local inference does not require an API Key.",
+ ),
+ MessageTextInput(
+ name="inference_endpoint",
+ display_name="Inference Endpoint",
+ required=True,
+ value="https://api-inference.huggingface.co/models/",
+ info="Custom inference endpoint URL.",
+ ),
+ MessageTextInput(
+ name="model_name",
+ display_name="Model Name",
+ value="BAAI/bge-large-en-v1.5",
+ info="The name of the model to use for text embeddings.",
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def validate_inference_endpoint(self, inference_endpoint: str) -> bool:
+ parsed_url = urlparse(inference_endpoint)
+ if not all([parsed_url.scheme, parsed_url.netloc]):
+ msg = (
+ f"Invalid inference endpoint format: '{self.inference_endpoint}'. "
+ "Please ensure the URL includes both a scheme (e.g., 'http://' or 'https://') and a domain name. "
+ "Example: 'http://localhost:8080' or 'https://api.example.com'"
+ )
+ raise ValueError(msg)
+
+ try:
+ response = requests.get(f"{inference_endpoint}/health", timeout=5)
+ except requests.RequestException as e:
+ msg = (
+ f"Inference endpoint '{inference_endpoint}' is not responding. "
+ "Please ensure the URL is correct and the service is running."
+ )
+ raise ValueError(msg) from e
+
+ if response.status_code != requests.codes.ok:
+ msg = f"HuggingFace health check failed: {response.status_code}"
+ raise ValueError(msg)
+ # returning True to solve linting error
+ return True
+
+ def get_api_url(self) -> str:
+ if "huggingface" in self.inference_endpoint.lower():
+ return f"{self.inference_endpoint}"
+ return self.inference_endpoint
+
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
+ def create_huggingface_embeddings(
+ self, api_key: SecretStr, api_url: str, model_name: str
+ ) -> HuggingFaceInferenceAPIEmbeddings:
+ return HuggingFaceInferenceAPIEmbeddings(api_key=api_key, api_url=api_url, model_name=model_name)
+
+ def build_embeddings(self) -> Embeddings:
+ api_url = self.get_api_url()
+
+ is_local_url = (
+ api_url.startswith(("http://localhost", "http://127.0.0.1", "http://0.0.0.0", "http://docker"))
+ or "huggingface.co" not in api_url.lower()
+ )
+
+ if not self.api_key and is_local_url:
+ self.validate_inference_endpoint(api_url)
+ api_key = SecretStr("APIKeyForLocalDeployment")
+ elif not self.api_key:
+ msg = "API Key is required for non-local inference endpoints"
+ raise ValueError(msg)
+ else:
+ api_key = SecretStr(self.api_key).get_secret_value()
+
+ try:
+ return self.create_huggingface_embeddings(api_key, api_url, self.model_name)
+ except Exception as e:
+ msg = "Could not connect to HuggingFace Inference API."
+ raise ValueError(msg) from e
diff --git a/langflow/src/backend/base/langflow/components/embeddings/lmstudioembeddings.py b/langflow/src/backend/base/langflow/components/embeddings/lmstudioembeddings.py
new file mode 100644
index 0000000..57f783f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/lmstudioembeddings.py
@@ -0,0 +1,91 @@
+from typing import Any
+from urllib.parse import urljoin
+
+import httpx
+from typing_extensions import override
+
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.field_typing import Embeddings
+from langflow.inputs.inputs import DropdownInput, SecretStrInput
+from langflow.io import FloatInput, MessageTextInput
+
+
+class LMStudioEmbeddingsComponent(LCEmbeddingsModel):
+ display_name: str = "LM Studio Embeddings"
+ description: str = "Generate embeddings using LM Studio."
+ icon = "LMStudio"
+
+ @override
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
+ if field_name == "model":
+ base_url_dict = build_config.get("base_url", {})
+ base_url_load_from_db = base_url_dict.get("load_from_db", False)
+ base_url_value = base_url_dict.get("value")
+ if base_url_load_from_db:
+ base_url_value = await self.get_variables(base_url_value, field_name)
+ elif not base_url_value:
+ base_url_value = "http://localhost:1234/v1"
+ build_config["model"]["options"] = await self.get_model(base_url_value)
+
+ return build_config
+
+ @staticmethod
+ async def get_model(base_url_value: str) -> list[str]:
+ try:
+ url = urljoin(base_url_value, "/v1/models")
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ data = response.json()
+
+ return [model["id"] for model in data.get("data", [])]
+ except Exception as e:
+ msg = "Could not retrieve models. Please, make sure the LM Studio server is running."
+ raise ValueError(msg) from e
+
+ inputs = [
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ advanced=False,
+ refresh_button=True,
+ required=True,
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="LM Studio Base URL",
+ refresh_button=True,
+ value="http://localhost:1234/v1",
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="LM Studio API Key",
+ advanced=True,
+ value="LMSTUDIO_API_KEY",
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Model Temperature",
+ value=0.1,
+ advanced=True,
+ ),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
+ except ImportError as e:
+ msg = "Please install langchain-nvidia-ai-endpoints to use LM Studio Embeddings."
+ raise ImportError(msg) from e
+ try:
+ output = NVIDIAEmbeddings(
+ model=self.model,
+ base_url=self.base_url,
+ temperature=self.temperature,
+ nvidia_api_key=self.api_key,
+ )
+ except Exception as e:
+ msg = f"Could not connect to LM Studio API. Error: {e}"
+ raise ValueError(msg) from e
+ return output
diff --git a/langflow/src/backend/base/langflow/components/embeddings/mistral.py b/langflow/src/backend/base/langflow/components/embeddings/mistral.py
new file mode 100644
index 0000000..e183d01
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/mistral.py
@@ -0,0 +1,58 @@
+from langchain_mistralai.embeddings import MistralAIEmbeddings
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import Embeddings
+from langflow.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
+
+
+class MistralAIEmbeddingsComponent(LCModelComponent):
+ display_name = "MistralAI Embeddings"
+ description = "Generate embeddings using MistralAI models."
+ icon = "MistralAI"
+ name = "MistalAIEmbeddings"
+
+ inputs = [
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ advanced=False,
+ options=["mistral-embed"],
+ value="mistral-embed",
+ ),
+ SecretStrInput(name="mistral_api_key", display_name="Mistral API Key", required=True),
+ IntInput(
+ name="max_concurrent_requests",
+ display_name="Max Concurrent Requests",
+ advanced=True,
+ value=64,
+ ),
+ IntInput(name="max_retries", display_name="Max Retries", advanced=True, value=5),
+ IntInput(name="timeout", display_name="Request Timeout", advanced=True, value=120),
+ MessageTextInput(
+ name="endpoint",
+ display_name="API Endpoint",
+ advanced=True,
+ value="https://api.mistral.ai/v1/",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ if not self.mistral_api_key:
+ msg = "Mistral API Key is required"
+ raise ValueError(msg)
+
+ api_key = SecretStr(self.mistral_api_key).get_secret_value()
+
+ return MistralAIEmbeddings(
+ api_key=api_key,
+ model=self.model,
+ endpoint=self.endpoint,
+ max_concurrent_requests=self.max_concurrent_requests,
+ max_retries=self.max_retries,
+ timeout=self.timeout,
+ )
diff --git a/langflow/src/backend/base/langflow/components/embeddings/nvidia.py b/langflow/src/backend/base/langflow/components/embeddings/nvidia.py
new file mode 100644
index 0000000..302fd83
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/nvidia.py
@@ -0,0 +1,77 @@
+from typing import Any
+
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.field_typing import Embeddings
+from langflow.inputs.inputs import DropdownInput, SecretStrInput
+from langflow.io import FloatInput, MessageTextInput
+from langflow.schema.dotdict import dotdict
+
+
+class NVIDIAEmbeddingsComponent(LCEmbeddingsModel):
+ display_name: str = "NVIDIA Embeddings"
+ description: str = "Generate embeddings using NVIDIA models."
+ icon = "NVIDIA"
+
+ inputs = [
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ options=[
+ "nvidia/nv-embed-v1",
+ "snowflake/arctic-embed-I",
+ ],
+ value="nvidia/nv-embed-v1",
+ required=True,
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="NVIDIA Base URL",
+ refresh_button=True,
+ value="https://integrate.api.nvidia.com/v1",
+ required=True,
+ ),
+ SecretStrInput(
+ name="nvidia_api_key",
+ display_name="NVIDIA API Key",
+ info="The NVIDIA API Key.",
+ advanced=False,
+ value="NVIDIA_API_KEY",
+ required=True,
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Model Temperature",
+ value=0.1,
+ advanced=True,
+ ),
+ ]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "base_url" and field_value:
+ try:
+ build_model = self.build_embeddings()
+ ids = [model.id for model in build_model.available_models]
+ build_config["model"]["options"] = ids
+ build_config["model"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
+ except ImportError as e:
+ msg = "Please install langchain-nvidia-ai-endpoints to use the Nvidia model."
+ raise ImportError(msg) from e
+ try:
+ output = NVIDIAEmbeddings(
+ model=self.model,
+ base_url=self.base_url,
+ temperature=self.temperature,
+ nvidia_api_key=self.nvidia_api_key,
+ )
+ except Exception as e:
+ msg = f"Could not connect to NVIDIA API. Error: {e}"
+ raise ValueError(msg) from e
+ return output
diff --git a/langflow/src/backend/base/langflow/components/embeddings/ollama.py b/langflow/src/backend/base/langflow/components/embeddings/ollama.py
new file mode 100644
index 0000000..0ffd239
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/ollama.py
@@ -0,0 +1,106 @@
+from typing import Any
+from urllib.parse import urljoin
+
+import httpx
+from langchain_ollama import OllamaEmbeddings
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, URL_LIST
+from langflow.field_typing import Embeddings
+from langflow.io import DropdownInput, MessageTextInput, Output
+
+HTTP_STATUS_OK = 200
+
+
+class OllamaEmbeddingsComponent(LCModelComponent):
+ display_name: str = "Ollama Embeddings"
+ description: str = "Generate embeddings using Ollama models."
+ documentation = "https://python.langchain.com/docs/integrations/text_embedding/ollama"
+ icon = "Ollama"
+ name = "OllamaEmbeddings"
+
+ inputs = [
+ DropdownInput(
+ name="model_name",
+ display_name="Ollama Model",
+ value="",
+ options=[],
+ real_time_refresh=True,
+ refresh_button=True,
+ combobox=True,
+ required=True,
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="Ollama Base URL",
+ value="",
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ output = OllamaEmbeddings(model=self.model_name, base_url=self.base_url)
+ except Exception as e:
+ msg = (
+ "Unable to connect to the Ollama API. ",
+ "Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.",
+ )
+ raise ValueError(msg) from e
+ return output
+
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
+ if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(field_value):
+ # Check if any URL in the list is valid
+ valid_url = ""
+ for url in URL_LIST:
+ if await self.is_valid_ollama_url(url):
+ valid_url = url
+ break
+ build_config["base_url"]["value"] = valid_url
+ if field_name in {"model_name", "base_url", "tool_model_enabled"}:
+ if await self.is_valid_ollama_url(self.base_url):
+ build_config["model_name"]["options"] = await self.get_model(self.base_url)
+ elif await self.is_valid_ollama_url(build_config["base_url"].get("value", "")):
+ build_config["model_name"]["options"] = await self.get_model(build_config["base_url"].get("value", ""))
+ else:
+ build_config["model_name"]["options"] = []
+
+ return build_config
+
+ async def get_model(self, base_url_value: str) -> list[str]:
+ """Get the model names from Ollama."""
+ model_ids = []
+ try:
+ url = urljoin(base_url_value, "/api/tags")
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ data = response.json()
+
+ model_ids = [model["name"] for model in data.get("models", [])]
+ # this to ensure that not embedding models are included.
+ # not even the base models since models can have 1b 2b etc
+ # handles cases when embeddings models have tags like :latest - etc.
+ model_ids = [
+ model
+ for model in model_ids
+ if any(model.startswith(f"{embedding_model}") for embedding_model in OLLAMA_EMBEDDING_MODELS)
+ ]
+
+ except (ImportError, ValueError, httpx.RequestError) as e:
+ msg = "Could not get model names from Ollama."
+ raise ValueError(msg) from e
+
+ return model_ids
+
+ async def is_valid_ollama_url(self, url: str) -> bool:
+ try:
+ async with httpx.AsyncClient() as client:
+ return (await client.get(f"{url}/api/tags")).status_code == HTTP_STATUS_OK
+ except httpx.RequestError:
+ return False
diff --git a/langflow/src/backend/base/langflow/components/embeddings/openai.py b/langflow/src/backend/base/langflow/components/embeddings/openai.py
new file mode 100644
index 0000000..e4ae4e6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/openai.py
@@ -0,0 +1,100 @@
+from langchain_openai import OpenAIEmbeddings
+
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES
+from langflow.field_typing import Embeddings
+from langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput
+
+
+class OpenAIEmbeddingsComponent(LCEmbeddingsModel):
+ display_name = "OpenAI Embeddings"
+ description = "Generate embeddings using OpenAI models."
+ icon = "OpenAI"
+ name = "OpenAIEmbeddings"
+
+ inputs = [
+ DictInput(
+ name="default_headers",
+ display_name="Default Headers",
+ advanced=True,
+ info="Default headers to use for the API request.",
+ ),
+ DictInput(
+ name="default_query",
+ display_name="Default Query",
+ advanced=True,
+ info="Default query parameters to use for the API request.",
+ ),
+ IntInput(name="chunk_size", display_name="Chunk Size", advanced=True, value=1000),
+ MessageTextInput(name="client", display_name="Client", advanced=True),
+ MessageTextInput(name="deployment", display_name="Deployment", advanced=True),
+ IntInput(name="embedding_ctx_length", display_name="Embedding Context Length", advanced=True, value=1536),
+ IntInput(name="max_retries", display_name="Max Retries", value=3, advanced=True),
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ advanced=False,
+ options=OPENAI_EMBEDDING_MODEL_NAMES,
+ value="text-embedding-3-small",
+ ),
+ DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
+ SecretStrInput(name="openai_api_key", display_name="OpenAI API Key", value="OPENAI_API_KEY", required=True),
+ MessageTextInput(name="openai_api_base", display_name="OpenAI API Base", advanced=True),
+ MessageTextInput(name="openai_api_type", display_name="OpenAI API Type", advanced=True),
+ MessageTextInput(name="openai_api_version", display_name="OpenAI API Version", advanced=True),
+ MessageTextInput(
+ name="openai_organization",
+ display_name="OpenAI Organization",
+ advanced=True,
+ ),
+ MessageTextInput(name="openai_proxy", display_name="OpenAI Proxy", advanced=True),
+ FloatInput(name="request_timeout", display_name="Request Timeout", advanced=True),
+ BoolInput(name="show_progress_bar", display_name="Show Progress Bar", advanced=True),
+ BoolInput(name="skip_empty", display_name="Skip Empty", advanced=True),
+ MessageTextInput(
+ name="tiktoken_model_name",
+ display_name="TikToken Model Name",
+ advanced=True,
+ ),
+ BoolInput(
+ name="tiktoken_enable",
+ display_name="TikToken Enable",
+ advanced=True,
+ value=True,
+ info="If False, you must have transformers installed.",
+ ),
+ IntInput(
+ name="dimensions",
+ display_name="Dimensions",
+ info="The number of dimensions the resulting output embeddings should have. "
+ "Only supported by certain models.",
+ advanced=True,
+ ),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ return OpenAIEmbeddings(
+ client=self.client or None,
+ model=self.model,
+ dimensions=self.dimensions or None,
+ deployment=self.deployment or None,
+ api_version=self.openai_api_version or None,
+ base_url=self.openai_api_base or None,
+ openai_api_type=self.openai_api_type or None,
+ openai_proxy=self.openai_proxy or None,
+ embedding_ctx_length=self.embedding_ctx_length,
+ api_key=self.openai_api_key or None,
+ organization=self.openai_organization or None,
+ allowed_special="all",
+ disallowed_special="all",
+ chunk_size=self.chunk_size,
+ max_retries=self.max_retries,
+ timeout=self.request_timeout or None,
+ tiktoken_enabled=self.tiktoken_enable,
+ tiktoken_model_name=self.tiktoken_model_name or None,
+ show_progress_bar=self.show_progress_bar,
+ model_kwargs=self.model_kwargs,
+ skip_empty=self.skip_empty,
+ default_headers=self.default_headers or None,
+ default_query=self.default_query or None,
+ )
diff --git a/langflow/src/backend/base/langflow/components/embeddings/similarity.py b/langflow/src/backend/base/langflow/components/embeddings/similarity.py
new file mode 100644
index 0000000..7913ab3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/similarity.py
@@ -0,0 +1,73 @@
+import numpy as np
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, Output
+from langflow.schema import Data
+
+
+class EmbeddingSimilarityComponent(Component):
+ display_name: str = "Embedding Similarity"
+ description: str = "Compute selected form of similarity between two embedding vectors."
+ icon = "equal"
+
+ inputs = [
+ DataInput(
+ name="embedding_vectors",
+ display_name="Embedding Vectors",
+ info="A list containing exactly two data objects with embedding vectors to compare.",
+ is_list=True,
+ required=True,
+ ),
+ DropdownInput(
+ name="similarity_metric",
+ display_name="Similarity Metric",
+ info="Select the similarity metric to use.",
+ options=["Cosine Similarity", "Euclidean Distance", "Manhattan Distance"],
+ value="Cosine Similarity",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Similarity Data", name="similarity_data", method="compute_similarity"),
+ ]
+
+ def compute_similarity(self) -> Data:
+ embedding_vectors: list[Data] = self.embedding_vectors
+
+ # Assert that the list contains exactly two Data objects
+ if len(embedding_vectors) != 2: # noqa: PLR2004
+ msg = "Exactly two embedding vectors are required."
+ raise ValueError(msg)
+
+ embedding_1 = np.array(embedding_vectors[0].data["embeddings"])
+ embedding_2 = np.array(embedding_vectors[1].data["embeddings"])
+
+ if embedding_1.shape != embedding_2.shape:
+ similarity_score = {"error": "Embeddings must have the same dimensions."}
+ else:
+ similarity_metric = self.similarity_metric
+
+ if similarity_metric == "Cosine Similarity":
+ score = np.dot(embedding_1, embedding_2) / (np.linalg.norm(embedding_1) * np.linalg.norm(embedding_2))
+ similarity_score = {"cosine_similarity": score}
+
+ elif similarity_metric == "Euclidean Distance":
+ score = np.linalg.norm(embedding_1 - embedding_2)
+ similarity_score = {"euclidean_distance": score}
+
+ elif similarity_metric == "Manhattan Distance":
+ score = np.sum(np.abs(embedding_1 - embedding_2))
+ similarity_score = {"manhattan_distance": score}
+
+ # Create a Data object to encapsulate the similarity score and additional information
+ similarity_data = Data(
+ data={
+ "embedding_1": embedding_vectors[0].data["embeddings"],
+ "embedding_2": embedding_vectors[1].data["embeddings"],
+ "similarity_score": similarity_score,
+ },
+ text_key="similarity_score",
+ )
+
+ self.status = similarity_data
+ return similarity_data
diff --git a/langflow/src/backend/base/langflow/components/embeddings/text_embedder.py b/langflow/src/backend/base/langflow/components/embeddings/text_embedder.py
new file mode 100644
index 0000000..a9efb1f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/text_embedder.py
@@ -0,0 +1,63 @@
+import logging
+from typing import TYPE_CHECKING
+
+from langflow.custom import Component
+from langflow.io import HandleInput, MessageInput, Output
+from langflow.schema import Data
+
+if TYPE_CHECKING:
+ from langflow.field_typing import Embeddings
+ from langflow.schema.message import Message
+
+
+class TextEmbedderComponent(Component):
+ display_name: str = "Text Embedder"
+ description: str = "Generate embeddings for a given message using the specified embedding model."
+ icon = "binary"
+ inputs = [
+ HandleInput(
+ name="embedding_model",
+ display_name="Embedding Model",
+ info="The embedding model to use for generating embeddings.",
+ input_types=["Embeddings"],
+ required=True,
+ ),
+ MessageInput(
+ name="message",
+ display_name="Message",
+ info="The message to generate embeddings for.",
+ required=True,
+ ),
+ ]
+ outputs = [
+ Output(display_name="Embedding Data", name="embeddings", method="generate_embeddings"),
+ ]
+
+ def generate_embeddings(self) -> Data:
+ try:
+ embedding_model: Embeddings = self.embedding_model
+ message: Message = self.message
+
+ # Combine validation checks to reduce nesting
+ if not embedding_model or not hasattr(embedding_model, "embed_documents"):
+ msg = "Invalid or incompatible embedding model"
+ raise ValueError(msg)
+
+ text_content = message.text if message and message.text else ""
+ if not text_content:
+ msg = "No text content found in message"
+ raise ValueError(msg)
+
+ embeddings = embedding_model.embed_documents([text_content])
+ if not embeddings or not isinstance(embeddings, list):
+ msg = "Invalid embeddings generated"
+ raise ValueError(msg)
+
+ embedding_vector = embeddings[0]
+ self.status = {"text": text_content, "embeddings": embedding_vector}
+ return Data(data={"text": text_content, "embeddings": embedding_vector})
+ except Exception as e:
+ logging.exception("Error generating embeddings")
+ error_data = Data(data={"text": "", "embeddings": [], "error": str(e)})
+ self.status = {"error": str(e)}
+ return error_data
diff --git a/langflow/src/backend/base/langflow/components/embeddings/vertexai.py b/langflow/src/backend/base/langflow/components/embeddings/vertexai.py
new file mode 100644
index 0000000..026dd5d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/embeddings/vertexai.py
@@ -0,0 +1,67 @@
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import Embeddings
+from langflow.io import BoolInput, FileInput, FloatInput, IntInput, MessageTextInput, Output
+
+
+class VertexAIEmbeddingsComponent(LCModelComponent):
+ display_name = "VertexAI Embeddings"
+ description = "Generate embeddings using Google Cloud VertexAI models."
+ icon = "VertexAI"
+ name = "VertexAIEmbeddings"
+
+ inputs = [
+ FileInput(
+ name="credentials",
+ display_name="Credentials",
+ info="JSON credentials file. Leave empty to fallback to environment variables",
+ value="",
+ file_types=["json"],
+ required=True,
+ ),
+ MessageTextInput(name="location", display_name="Location", value="us-central1", advanced=True),
+ MessageTextInput(name="project", display_name="Project", info="The project ID.", advanced=True),
+ IntInput(name="max_output_tokens", display_name="Max Output Tokens", advanced=True),
+ IntInput(name="max_retries", display_name="Max Retries", value=1, advanced=True),
+ MessageTextInput(name="model_name", display_name="Model Name", value="textembedding-gecko", required=True),
+ IntInput(name="n", display_name="N", value=1, advanced=True),
+ IntInput(name="request_parallelism", value=5, display_name="Request Parallelism", advanced=True),
+ MessageTextInput(name="stop_sequences", display_name="Stop", advanced=True, is_list=True),
+ BoolInput(name="streaming", display_name="Streaming", value=False, advanced=True),
+ FloatInput(name="temperature", value=0.0, display_name="Temperature"),
+ IntInput(name="top_k", display_name="Top K", advanced=True),
+ FloatInput(name="top_p", display_name="Top P", value=0.95, advanced=True),
+ ]
+
+ outputs = [
+ Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ try:
+ from langchain_google_vertexai import VertexAIEmbeddings
+ except ImportError as e:
+ msg = "Please install the langchain-google-vertexai package to use the VertexAIEmbeddings component."
+ raise ImportError(msg) from e
+
+ from google.oauth2 import service_account
+
+ if self.credentials:
+ gcloud_credentials = service_account.Credentials.from_service_account_file(self.credentials)
+ else:
+ # will fallback to environment variable or inferred from gcloud CLI
+ gcloud_credentials = None
+ return VertexAIEmbeddings(
+ credentials=gcloud_credentials,
+ location=self.location,
+ max_output_tokens=self.max_output_tokens or None,
+ max_retries=self.max_retries,
+ model_name=self.model_name,
+ n=self.n,
+ project=self.project,
+ request_parallelism=self.request_parallelism,
+ stop=self.stop_sequences or None,
+ streaming=self.streaming,
+ temperature=self.temperature,
+ top_k=self.top_k or None,
+ top_p=self.top_p,
+ )
diff --git a/langflow/src/backend/base/langflow/components/firecrawl/__init__.py b/langflow/src/backend/base/langflow/components/firecrawl/__init__.py
new file mode 100644
index 0000000..1f718f0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/firecrawl/__init__.py
@@ -0,0 +1,6 @@
+from .firecrawl_crawl_api import FirecrawlCrawlApi
+from .firecrawl_extract_api import FirecrawlExtractApi
+from .firecrawl_map_api import FirecrawlMapApi
+from .firecrawl_scrape_api import FirecrawlScrapeApi
+
+__all__ = ["FirecrawlCrawlApi", "FirecrawlExtractApi", "FirecrawlMapApi", "FirecrawlScrapeApi"]
diff --git a/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_crawl_api.py b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_crawl_api.py
new file mode 100644
index 0000000..28e22d7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_crawl_api.py
@@ -0,0 +1,88 @@
+import uuid
+
+from langflow.custom import Component
+from langflow.io import DataInput, IntInput, MultilineInput, Output, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class FirecrawlCrawlApi(Component):
+ display_name: str = "FirecrawlCrawlApi"
+ description: str = "Firecrawl Crawl API."
+ name = "FirecrawlCrawlApi"
+
+ documentation: str = "https://docs.firecrawl.dev/v1/api-reference/endpoint/crawl-post"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ password=True,
+ info="The API key to use Firecrawl API.",
+ ),
+ MultilineInput(
+ name="url",
+ display_name="URL",
+ required=True,
+ info="The URL to scrape.",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ info="Timeout in milliseconds for the request.",
+ ),
+ StrInput(
+ name="idempotency_key",
+ display_name="Idempotency Key",
+ info="Optional idempotency key to ensure unique requests.",
+ ),
+ DataInput(
+ name="crawlerOptions",
+ display_name="Crawler Options",
+ info="The crawler options to send with the request.",
+ ),
+ DataInput(
+ name="scrapeOptions",
+ display_name="Scrape Options",
+ info="The page options to send with the request.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="crawl"),
+ ]
+ idempotency_key: str | None = None
+
+ def crawl(self) -> Data:
+ try:
+ from firecrawl import FirecrawlApp
+ except ImportError as e:
+ msg = "Could not import firecrawl integration package. Please install it with `pip install firecrawl-py`."
+ raise ImportError(msg) from e
+
+ params = self.crawlerOptions.__dict__["data"] if self.crawlerOptions else {}
+ scrape_options_dict = self.scrapeOptions.__dict__["data"] if self.scrapeOptions else {}
+ if scrape_options_dict:
+ params["scrapeOptions"] = scrape_options_dict
+
+ # Set default values for new parameters in v1
+ params.setdefault("maxDepth", 2)
+ params.setdefault("limit", 10000)
+ params.setdefault("allowExternalLinks", False)
+ params.setdefault("allowBackwardLinks", False)
+ params.setdefault("ignoreSitemap", False)
+ params.setdefault("ignoreQueryParameters", False)
+
+ # Ensure onlyMainContent is explicitly set if not provided
+ if "scrapeOptions" in params:
+ params["scrapeOptions"].setdefault("onlyMainContent", True)
+ else:
+ params["scrapeOptions"] = {"onlyMainContent": True}
+
+ if not self.idempotency_key:
+ self.idempotency_key = str(uuid.uuid4())
+
+ app = FirecrawlApp(api_key=self.api_key)
+ crawl_result = app.crawl_url(self.url, params=params, idempotency_key=self.idempotency_key)
+ return Data(data={"results": crawl_result})
diff --git a/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_extract_api.py b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_extract_api.py
new file mode 100644
index 0000000..e1aa3bb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_extract_api.py
@@ -0,0 +1,143 @@
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ DataInput,
+ MultilineInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class FirecrawlExtractApi(Component):
+ display_name: str = "FirecrawlExtractApi"
+ description: str = "Firecrawl Extract API."
+ name = "FirecrawlExtractApi"
+
+ documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/extract"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ password=True,
+ info="The API key to use Firecrawl API.",
+ ),
+ MultilineInput(
+ name="urls",
+ display_name="URLs",
+ required=True,
+ info="List of URLs to extract data from (separated by commas or new lines).",
+ tool_mode=True,
+ ),
+ MultilineInput(
+ name="prompt",
+ display_name="Prompt",
+ required=True,
+ info="Prompt to guide the extraction process.",
+ tool_mode=True,
+ ),
+ DataInput(
+ name="schema",
+ display_name="Schema",
+ required=False,
+ info="Schema to define the structure of the extracted data.",
+ ),
+ BoolInput(
+ name="enable_web_search",
+ display_name="Enable Web Search",
+ info="When true, the extraction will use web search to find additional data.",
+ ),
+ # # Optional: Not essential for basic extraction
+ # BoolInput(
+ # name="ignore_sitemap",
+ # display_name="Ignore Sitemap",
+ # info="When true, sitemap.xml files will be ignored during website scanning.",
+ # ),
+ # # Optional: Not essential for basic extraction
+ # BoolInput(
+ # name="include_subdomains",
+ # display_name="Include Subdomains",
+ # info="When true, subdomains of the provided URLs will also be scanned.",
+ # ),
+ # # Optional: Not essential for basic extraction
+ # BoolInput(
+ # name="show_sources",
+ # display_name="Show Sources",
+ # info="When true, the sources used to extract the data will be included in the response.",
+ # ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="extract"),
+ ]
+
+ def extract(self) -> Data:
+ try:
+ from firecrawl import FirecrawlApp
+ except ImportError as e:
+ msg = "Could not import firecrawl integration package. Please install it with `pip install firecrawl-py`."
+ raise ImportError(msg) from e
+
+ # Validate API key
+ if not self.api_key:
+ msg = "API key is required"
+ raise ValueError(msg)
+
+ # Validate URLs
+ if not self.urls:
+ msg = "URLs are required"
+ raise ValueError(msg)
+
+ # Split and validate URLs (handle both commas and newlines)
+ urls = [url.strip() for url in self.urls.replace("\n", ",").split(",") if url.strip()]
+ if not urls:
+ msg = "No valid URLs provided"
+ raise ValueError(msg)
+
+ # Validate and process prompt
+ if not self.prompt:
+ msg = "Prompt is required"
+ raise ValueError(msg)
+
+ # Get the prompt text (handling both string and multiline input)
+ prompt_text = self.prompt.strip()
+
+ # Enhance the prompt to encourage comprehensive extraction
+ enhanced_prompt = prompt_text
+ if "schema" not in prompt_text.lower():
+ enhanced_prompt = f"{prompt_text}. Please extract all instances in a comprehensive, structured format."
+
+ params = {
+ "prompt": enhanced_prompt,
+ "enableWebSearch": self.enable_web_search,
+ # Optional parameters - not essential for basic extraction
+ "ignoreSitemap": self.ignore_sitemap,
+ "includeSubdomains": self.include_subdomains,
+ "showSources": self.show_sources,
+ "timeout": 300,
+ }
+
+ # Only add schema to params if it's provided and is a valid schema structure
+ if self.schema:
+ try:
+ if isinstance(self.schema, dict) and "type" in self.schema:
+ params["schema"] = self.schema
+ elif hasattr(self.schema, "dict") and "type" in self.schema.dict():
+ params["schema"] = self.schema.dict()
+ else:
+ # Skip invalid schema without raising an error
+ pass
+ except Exception as e: # noqa: BLE001
+ logger.error(f"Invalid schema: {e!s}")
+
+ try:
+ app = FirecrawlApp(api_key=self.api_key)
+ extract_result = app.extract(urls, params=params)
+ return Data(data=extract_result)
+ except Exception as e:
+ msg = f"Error during extraction: {e!s}"
+ raise ValueError(msg) from e
diff --git a/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_map_api.py b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_map_api.py
new file mode 100644
index 0000000..73dd55a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_map_api.py
@@ -0,0 +1,89 @@
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ MultilineInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class FirecrawlMapApi(Component):
+ display_name: str = "FirecrawlMapApi"
+ description: str = "Firecrawl Map API."
+ name = "FirecrawlMapApi"
+
+ documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/map"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ password=True,
+ info="The API key to use Firecrawl API.",
+ ),
+ MultilineInput(
+ name="urls",
+ display_name="URLs",
+ required=True,
+ info="List of URLs to create maps from (separated by commas or new lines).",
+ tool_mode=True,
+ ),
+ BoolInput(
+ name="ignore_sitemap",
+ display_name="Ignore Sitemap",
+ info="When true, the sitemap.xml file will be ignored during crawling.",
+ ),
+ BoolInput(
+ name="sitemap_only",
+ display_name="Sitemap Only",
+ info="When true, only links found in the sitemap will be returned.",
+ ),
+ BoolInput(
+ name="include_subdomains",
+ display_name="Include Subdomains",
+ info="When true, subdomains of the provided URL will also be scanned.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="map"),
+ ]
+
+ def map(self) -> Data:
+ try:
+ from firecrawl import FirecrawlApp
+ except ImportError as e:
+ msg = "Could not import firecrawl integration package. Please install it with `pip install firecrawl-py`."
+ raise ImportError(msg) from e
+
+ # Validate URLs
+ if not self.urls:
+ msg = "URLs are required"
+ raise ValueError(msg)
+
+ # Split and validate URLs (handle both commas and newlines)
+ urls = [url.strip() for url in self.urls.replace("\n", ",").split(",") if url.strip()]
+ if not urls:
+ msg = "No valid URLs provided"
+ raise ValueError(msg)
+
+ params = {
+ "ignoreSitemap": self.ignore_sitemap,
+ "sitemapOnly": self.sitemap_only,
+ "includeSubdomains": self.include_subdomains,
+ }
+
+ app = FirecrawlApp(api_key=self.api_key)
+
+ # Map all provided URLs and combine results
+ combined_links = []
+ for url in urls:
+ result = app.map_url(url, params=params)
+ if isinstance(result, dict) and "links" in result:
+ combined_links.extend(result["links"])
+
+ map_result = {"success": True, "links": combined_links}
+
+ return Data(data=map_result)
diff --git a/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_scrape_api.py b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_scrape_api.py
new file mode 100644
index 0000000..d998944
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/firecrawl/firecrawl_scrape_api.py
@@ -0,0 +1,73 @@
+from langflow.custom import Component
+from langflow.io import (
+ DataInput,
+ IntInput,
+ MultilineInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class FirecrawlScrapeApi(Component):
+ display_name: str = "FirecrawlScrapeApi"
+ description: str = "Firecrawl Scrape API."
+ name = "FirecrawlScrapeApi"
+
+ documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/scrape"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ password=True,
+ info="The API key to use Firecrawl API.",
+ ),
+ MultilineInput(
+ name="url",
+ display_name="URL",
+ required=True,
+ info="The URL to scrape.",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ info="Timeout in milliseconds for the request.",
+ ),
+ DataInput(
+ name="scrapeOptions",
+ display_name="Scrape Options",
+ info="The page options to send with the request.",
+ ),
+ DataInput(
+ name="extractorOptions",
+ display_name="Extractor Options",
+ info="The extractor options to send with the request.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="scrape"),
+ ]
+
+ def scrape(self) -> Data:
+ try:
+ from firecrawl import FirecrawlApp
+ except ImportError as e:
+ msg = "Could not import firecrawl integration package. Please install it with `pip install firecrawl-py`."
+ raise ImportError(msg) from e
+
+ params = self.scrapeOptions.__dict__.get("data", {}) if self.scrapeOptions else {}
+ extractor_options_dict = self.extractorOptions.__dict__.get("data", {}) if self.extractorOptions else {}
+ if extractor_options_dict:
+ params["extract"] = extractor_options_dict
+
+ # Set default values for parameters
+ params.setdefault("formats", ["markdown"]) # Default output format
+ params.setdefault("onlyMainContent", True) # Default to only main content
+
+ app = FirecrawlApp(api_key=self.api_key)
+ results = app.scrape_url(self.url, params=params)
+ return Data(data=results)
diff --git a/langflow/src/backend/base/langflow/components/git/__init__.py b/langflow/src/backend/base/langflow/components/git/__init__.py
new file mode 100644
index 0000000..a103821
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/git/__init__.py
@@ -0,0 +1,4 @@
+from .git import GitLoaderComponent
+from .gitextractor import GitExtractorComponent
+
+__all__ = ["GitExtractorComponent", "GitLoaderComponent"]
diff --git a/langflow/src/backend/base/langflow/components/git/git.py b/langflow/src/backend/base/langflow/components/git/git.py
new file mode 100644
index 0000000..368062a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/git/git.py
@@ -0,0 +1,262 @@
+import re
+import tempfile
+from contextlib import asynccontextmanager
+from fnmatch import fnmatch
+from pathlib import Path
+
+import anyio
+from langchain_community.document_loaders.git import GitLoader
+
+from langflow.custom import Component
+from langflow.io import DropdownInput, MessageTextInput, Output
+from langflow.schema import Data
+
+
+class GitLoaderComponent(Component):
+ display_name = "Git"
+ description = (
+ "Load and filter documents from a local or remote Git repository. "
+ "Use a local repo path or clone from a remote URL."
+ )
+ trace_type = "tool"
+ icon = "GitLoader"
+
+ inputs = [
+ DropdownInput(
+ name="repo_source",
+ display_name="Repository Source",
+ options=["Local", "Remote"],
+ required=True,
+ info="Select whether to use a local repo path or clone from a remote URL.",
+ real_time_refresh=True,
+ ),
+ MessageTextInput(
+ name="repo_path",
+ display_name="Local Repository Path",
+ required=False,
+ info="The local path to the existing Git repository (used if 'Local' is selected).",
+ dynamic=True,
+ show=False,
+ ),
+ MessageTextInput(
+ name="clone_url",
+ display_name="Clone URL",
+ required=False,
+ info="The URL of the Git repository to clone (used if 'Clone' is selected).",
+ dynamic=True,
+ show=False,
+ ),
+ MessageTextInput(
+ name="branch",
+ display_name="Branch",
+ required=False,
+ value="main",
+ info="The branch to load files from. Defaults to 'main'.",
+ ),
+ MessageTextInput(
+ name="file_filter",
+ display_name="File Filter",
+ required=False,
+ advanced=True,
+ info=(
+ "Patterns to filter files. For example:\n"
+ "Include only .py files: '*.py'\n"
+ "Exclude .py files: '!*.py'\n"
+ "Multiple patterns can be separated by commas."
+ ),
+ ),
+ MessageTextInput(
+ name="content_filter",
+ display_name="Content Filter",
+ required=False,
+ advanced=True,
+ info="A regex pattern to filter files based on their content.",
+ ),
+ ]
+
+ outputs = [
+ Output(name="data", display_name="Data", method="load_documents"),
+ ]
+
+ @staticmethod
+ def is_binary(file_path: str | Path) -> bool:
+ """Check if a file is binary by looking for null bytes."""
+ try:
+ with Path(file_path).open("rb") as file:
+ content = file.read(1024)
+ return b"\x00" in content
+ except Exception: # noqa: BLE001
+ return True
+
+ @staticmethod
+ def check_file_patterns(file_path: str | Path, patterns: str) -> bool:
+ """Check if a file matches the given patterns.
+
+ Args:
+ file_path: Path to the file to check
+ patterns: Comma-separated list of glob patterns
+
+ Returns:
+ bool: True if file should be included, False if excluded
+ """
+ # Handle empty or whitespace-only patterns
+ if not patterns or patterns.isspace():
+ return True
+
+ path_str = str(file_path)
+ file_name = Path(path_str).name
+ pattern_list: list[str] = [pattern.strip() for pattern in patterns.split(",") if pattern.strip()]
+
+ # If no valid patterns after stripping, treat as include all
+ if not pattern_list:
+ return True
+
+ # Process exclusion patterns first
+ for pattern in pattern_list:
+ if pattern.startswith("!"):
+ # For exclusions, match against both full path and filename
+ exclude_pattern = pattern[1:]
+ if fnmatch(path_str, exclude_pattern) or fnmatch(file_name, exclude_pattern):
+ return False
+
+ # Then check inclusion patterns
+ include_patterns = [p for p in pattern_list if not p.startswith("!")]
+ # If no include patterns, treat as include all
+ if not include_patterns:
+ return True
+
+ # For inclusions, match against both full path and filename
+ return any(fnmatch(path_str, pattern) or fnmatch(file_name, pattern) for pattern in include_patterns)
+
+ @staticmethod
+ def check_content_pattern(file_path: str | Path, pattern: str) -> bool:
+ """Check if file content matches the given regex pattern.
+
+ Args:
+ file_path: Path to the file to check
+ pattern: Regex pattern to match against content
+
+ Returns:
+ bool: True if content matches, False otherwise
+ """
+ try:
+ # Check if file is binary
+ with Path(file_path).open("rb") as file:
+ content = file.read(1024)
+ if b"\x00" in content:
+ return False
+
+ # Try to compile the regex pattern first
+ try:
+ # Use the MULTILINE flag to better handle text content
+ content_regex = re.compile(pattern, re.MULTILINE)
+ # Test the pattern with a simple string to catch syntax errors
+ test_str = "test\nstring"
+ if not content_regex.search(test_str):
+ # Pattern is valid but doesn't match test string
+ pass
+ except (re.error, TypeError, ValueError):
+ return False
+
+ # If not binary and regex is valid, check content
+ with Path(file_path).open(encoding="utf-8") as file:
+ file_content = file.read()
+ return bool(content_regex.search(file_content))
+ except (OSError, UnicodeDecodeError):
+ return False
+
+ def build_combined_filter(self, file_filter_patterns: str | None = None, content_filter_pattern: str | None = None):
+ """Build a combined filter function from file and content patterns.
+
+ Args:
+ file_filter_patterns: Comma-separated glob patterns
+ content_filter_pattern: Regex pattern for content
+
+ Returns:
+ callable: Filter function that takes a file path and returns bool
+ """
+
+ def combined_filter(file_path: str) -> bool:
+ try:
+ path = Path(file_path)
+
+ # Check if file exists and is readable
+ if not path.exists():
+ return False
+
+ # Check if file is binary
+ if self.is_binary(path):
+ return False
+
+ # Apply file pattern filters
+ if file_filter_patterns and not self.check_file_patterns(path, file_filter_patterns):
+ return False
+
+ # Apply content filter
+ return not (content_filter_pattern and not self.check_content_pattern(path, content_filter_pattern))
+ except Exception: # noqa: BLE001
+ return False
+
+ return combined_filter
+
+ @asynccontextmanager
+ async def temp_clone_dir(self):
+ """Context manager for handling temporary clone directory."""
+ temp_dir = None
+ try:
+ temp_dir = tempfile.mkdtemp(prefix="langflow_clone_")
+ yield temp_dir
+ finally:
+ if temp_dir:
+ await anyio.Path(temp_dir).rmdir()
+
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
+ # Hide fields by default
+ build_config["repo_path"]["show"] = False
+ build_config["clone_url"]["show"] = False
+
+ if field_name == "repo_source":
+ if field_value == "Local":
+ build_config["repo_path"]["show"] = True
+ build_config["repo_path"]["required"] = True
+ build_config["clone_url"]["required"] = False
+ elif field_value == "Remote":
+ build_config["clone_url"]["show"] = True
+ build_config["clone_url"]["required"] = True
+ build_config["repo_path"]["required"] = False
+
+ return build_config
+
+ async def build_gitloader(self) -> GitLoader:
+ file_filter_patterns = getattr(self, "file_filter", None)
+ content_filter_pattern = getattr(self, "content_filter", None)
+
+ combined_filter = self.build_combined_filter(file_filter_patterns, content_filter_pattern)
+
+ repo_source = getattr(self, "repo_source", None)
+ if repo_source == "Local":
+ repo_path = self.repo_path
+ clone_url = None
+ else:
+ # Clone source
+ clone_url = self.clone_url
+ async with self.temp_clone_dir() as temp_dir:
+ repo_path = temp_dir
+
+ # Only pass branch if it's explicitly set
+ branch = getattr(self, "branch", None)
+ if not branch:
+ branch = None
+
+ return GitLoader(
+ repo_path=repo_path,
+ clone_url=clone_url if repo_source == "Remote" else None,
+ branch=branch,
+ file_filter=combined_filter,
+ )
+
+ async def load_documents(self) -> list[Data]:
+ gitloader = await self.build_gitloader()
+ data = [Data.from_document(doc) async for doc in gitloader.alazy_load()]
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/git/gitextractor.py b/langflow/src/backend/base/langflow/components/git/gitextractor.py
new file mode 100644
index 0000000..30584c7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/git/gitextractor.py
@@ -0,0 +1,196 @@
+import os
+import shutil
+import tempfile
+from contextlib import asynccontextmanager
+from pathlib import Path
+
+import aiofiles
+import git
+
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class GitExtractorComponent(Component):
+ display_name = "GitExtractor"
+ description = "Analyzes a Git repository and returns file contents and complete repository information"
+ icon = "GitLoader"
+
+ inputs = [
+ MessageTextInput(
+ name="repository_url",
+ display_name="Repository URL",
+ info="URL of the Git repository (e.g., https://github.com/username/repo)",
+ value="",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Text-Based File Contents",
+ name="text_based_file_contents",
+ method="get_text_based_file_contents",
+ ),
+ Output(display_name="Directory Structure", name="directory_structure", method="get_directory_structure"),
+ Output(display_name="Repository Info", name="repository_info", method="get_repository_info"),
+ Output(display_name="Statistics", name="statistics", method="get_statistics"),
+ Output(display_name="Files Content", name="files_content", method="get_files_content"),
+ ]
+
+ @asynccontextmanager
+ async def temp_git_repo(self):
+ """Async context manager for temporary git repository cloning."""
+ temp_dir = tempfile.mkdtemp()
+ try:
+ # Clone is still sync but wrapped in try/finally
+ git.Repo.clone_from(self.repository_url, temp_dir)
+ yield temp_dir
+ finally:
+ shutil.rmtree(temp_dir, ignore_errors=True)
+
+ async def get_repository_info(self) -> list[Data]:
+ try:
+ async with self.temp_git_repo() as temp_dir:
+ repo = git.Repo(temp_dir)
+ repo_info = {
+ "name": self.repository_url.split("/")[-1],
+ "url": self.repository_url,
+ "default_branch": repo.active_branch.name,
+ "remote_urls": [remote.url for remote in repo.remotes],
+ "last_commit": {
+ "hash": repo.head.commit.hexsha,
+ "author": str(repo.head.commit.author),
+ "message": repo.head.commit.message.strip(),
+ "date": str(repo.head.commit.committed_datetime),
+ },
+ "branches": [str(branch) for branch in repo.branches],
+ }
+ result = [Data(data=repo_info)]
+ self.status = result
+ return result
+ except git.GitError as e:
+ error_result = [Data(data={"error": f"Error getting repository info: {e!s}"})]
+ self.status = error_result
+ return error_result
+
+ async def get_statistics(self) -> list[Data]:
+ try:
+ async with self.temp_git_repo() as temp_dir:
+ total_files = 0
+ total_size = 0
+ total_lines = 0
+ binary_files = 0
+ directories = 0
+
+ for root, dirs, files in os.walk(temp_dir):
+ total_files += len(files)
+ directories += len(dirs)
+ for file in files:
+ file_path = Path(root) / file
+ total_size += file_path.stat().st_size
+ try:
+ async with aiofiles.open(file_path, encoding="utf-8") as f:
+ total_lines += sum(1 for _ in await f.readlines())
+ except UnicodeDecodeError:
+ binary_files += 1
+
+ statistics = {
+ "total_files": total_files,
+ "total_size_bytes": total_size,
+ "total_size_kb": round(total_size / 1024, 2),
+ "total_size_mb": round(total_size / (1024 * 1024), 2),
+ "total_lines": total_lines,
+ "binary_files": binary_files,
+ "directories": directories,
+ }
+ result = [Data(data=statistics)]
+ self.status = result
+ return result
+ except git.GitError as e:
+ error_result = [Data(data={"error": f"Error calculating statistics: {e!s}"})]
+ self.status = error_result
+ return error_result
+
+ async def get_directory_structure(self) -> Message:
+ try:
+ async with self.temp_git_repo() as temp_dir:
+ tree = ["Directory structure:"]
+ for root, _dirs, files in os.walk(temp_dir):
+ level = root.replace(temp_dir, "").count(os.sep)
+ indent = " " * level
+ if level == 0:
+ tree.append(f"└── {Path(root).name}")
+ else:
+ tree.append(f"{indent}├── {Path(root).name}")
+ subindent = " " * (level + 1)
+ tree.extend(f"{subindent}├── {f}" for f in files)
+ directory_structure = "\n".join(tree)
+ self.status = directory_structure
+ return Message(text=directory_structure)
+ except git.GitError as e:
+ error_message = f"Error getting directory structure: {e!s}"
+ self.status = error_message
+ return Message(text=error_message)
+
+ async def get_files_content(self) -> list[Data]:
+ try:
+ async with self.temp_git_repo() as temp_dir:
+ content_list = []
+ for root, _, files in os.walk(temp_dir):
+ for file in files:
+ file_path = Path(root) / file
+ relative_path = file_path.relative_to(temp_dir)
+ file_size = file_path.stat().st_size
+ try:
+ async with aiofiles.open(file_path, encoding="utf-8") as f:
+ file_content = await f.read()
+ except UnicodeDecodeError:
+ file_content = "[BINARY FILE]"
+ content_list.append(
+ Data(data={"path": str(relative_path), "size": file_size, "content": file_content})
+ )
+ self.status = content_list
+ return content_list
+ except git.GitError as e:
+ error_result = [Data(data={"error": f"Error getting files content: {e!s}"})]
+ self.status = error_result
+ return error_result
+
+ async def get_text_based_file_contents(self) -> Message:
+ try:
+ async with self.temp_git_repo() as temp_dir:
+ content_list = ["(Files content cropped to 300k characters, download full ingest to see more)"]
+ total_chars = 0
+ char_limit = 300000
+
+ for root, _, files in os.walk(temp_dir):
+ for file in files:
+ file_path = Path(root) / file
+ relative_path = file_path.relative_to(temp_dir)
+ content_list.extend(["=" * 50, f"File: /{relative_path}", "=" * 50])
+
+ try:
+ async with aiofiles.open(file_path, encoding="utf-8") as f:
+ file_content = await f.read()
+ if total_chars + len(file_content) > char_limit:
+ remaining_chars = char_limit - total_chars
+ file_content = file_content[:remaining_chars] + "\n... (content truncated)"
+ content_list.append(file_content)
+ total_chars += len(file_content)
+ except UnicodeDecodeError:
+ content_list.append("[BINARY FILE]")
+
+ content_list.append("")
+
+ if total_chars >= char_limit:
+ break
+
+ text_content = "\n".join(content_list)
+ self.status = text_content
+ return Message(text=text_content)
+ except git.GitError as e:
+ error_message = f"Error getting text-based file contents: {e!s}"
+ self.status = error_message
+ return Message(text=error_message)
diff --git a/langflow/src/backend/base/langflow/components/google/__init__.py b/langflow/src/backend/base/langflow/components/google/__init__.py
new file mode 100644
index 0000000..eda3a1e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/google/__init__.py
@@ -0,0 +1,11 @@
+from .gmail import GmailLoaderComponent
+from .google_drive import GoogleDriveComponent
+from .google_drive_search import GoogleDriveSearchComponent
+from .google_oauth_token import GoogleOAuthToken
+
+__all__ = [
+ "GmailLoaderComponent",
+ "GoogleDriveComponent",
+ "GoogleDriveSearchComponent",
+ "GoogleOAuthToken",
+]
diff --git a/langflow/src/backend/base/langflow/components/google/gmail.py b/langflow/src/backend/base/langflow/components/google/gmail.py
new file mode 100644
index 0000000..2f565f1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/google/gmail.py
@@ -0,0 +1,191 @@
+import base64
+import json
+import re
+from collections.abc import Iterator
+from json.decoder import JSONDecodeError
+from typing import Any
+
+from google.auth.exceptions import RefreshError
+from google.oauth2.credentials import Credentials
+from googleapiclient.discovery import build
+from langchain_core.chat_sessions import ChatSession
+from langchain_core.messages import HumanMessage
+from langchain_google_community.gmail.loader import GMailLoader
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.inputs import MessageTextInput
+from langflow.io import SecretStrInput
+from langflow.schema import Data
+from langflow.template import Output
+
+
+class GmailLoaderComponent(Component):
+ display_name = "Gmail Loader"
+ description = "Loads emails from Gmail using provided credentials."
+ icon = "Google"
+
+ inputs = [
+ SecretStrInput(
+ name="json_string",
+ display_name="JSON String of the Service Account Token",
+ info="JSON string containing OAuth 2.0 access token information for service account access",
+ required=True,
+ value="""{
+ "account": "",
+ "client_id": "",
+ "client_secret": "",
+ "expiry": "",
+ "refresh_token": "",
+ "scopes": [
+ "https://www.googleapis.com/auth/gmail.readonly",
+ ],
+ "token": "",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "universe_domain": "googleapis.com"
+ }""",
+ ),
+ MessageTextInput(
+ name="label_ids",
+ display_name="Label IDs",
+ info="Comma-separated list of label IDs to filter emails.",
+ required=True,
+ value="INBOX,SENT,UNREAD,IMPORTANT",
+ ),
+ MessageTextInput(
+ name="max_results",
+ display_name="Max Results",
+ info="Maximum number of emails to load.",
+ required=True,
+ value="10",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="load_emails"),
+ ]
+
+ def load_emails(self) -> Data:
+ class CustomGMailLoader(GMailLoader):
+ def __init__(
+ self, creds: Any, *, n: int = 100, label_ids: list[str] | None = None, raise_error: bool = False
+ ) -> None:
+ super().__init__(creds, n, raise_error)
+ self.label_ids = label_ids if label_ids is not None else ["SENT"]
+
+ def clean_message_content(self, message):
+ # Remove URLs
+ message = re.sub(r"http\S+|www\S+|https\S+", "", message, flags=re.MULTILINE)
+
+ # Remove email addresses
+ message = re.sub(r"\S+@\S+", "", message)
+
+ # Remove special characters and excessive whitespace
+ message = re.sub(r"[^A-Za-z0-9\s]+", " ", message)
+ message = re.sub(r"\s{2,}", " ", message)
+
+ # Trim leading and trailing whitespace
+ return message.strip()
+
+ def _extract_email_content(self, msg: Any) -> HumanMessage:
+ from_email = None
+ for values in msg["payload"]["headers"]:
+ name = values["name"]
+ if name == "From":
+ from_email = values["value"]
+ if from_email is None:
+ msg = "From email not found."
+ raise ValueError(msg)
+
+ parts = msg["payload"]["parts"] if "parts" in msg["payload"] else [msg["payload"]]
+
+ for part in parts:
+ if part["mimeType"] == "text/plain":
+ data = part["body"]["data"]
+ data = base64.urlsafe_b64decode(data).decode("utf-8")
+ pattern = re.compile(r"\r\nOn .+(\r\n)*wrote:\r\n")
+ newest_response = re.split(pattern, data)[0]
+ return HumanMessage(
+ content=self.clean_message_content(newest_response),
+ additional_kwargs={"sender": from_email},
+ )
+ msg = "No plain text part found in the email."
+ raise ValueError(msg)
+
+ def _get_message_data(self, service: Any, message: Any) -> ChatSession:
+ msg = service.users().messages().get(userId="me", id=message["id"]).execute()
+ message_content = self._extract_email_content(msg)
+
+ in_reply_to = None
+ email_data = msg["payload"]["headers"]
+ for values in email_data:
+ name = values["name"]
+ if name == "In-Reply-To":
+ in_reply_to = values["value"]
+
+ thread_id = msg["threadId"]
+
+ if in_reply_to:
+ thread = service.users().threads().get(userId="me", id=thread_id).execute()
+ messages = thread["messages"]
+
+ response_email = None
+ for _message in messages:
+ email_data = _message["payload"]["headers"]
+ for values in email_data:
+ if values["name"] == "Message-ID":
+ message_id = values["value"]
+ if message_id == in_reply_to:
+ response_email = _message
+ if response_email is None:
+ msg = "Response email not found in the thread."
+ raise ValueError(msg)
+ starter_content = self._extract_email_content(response_email)
+ return ChatSession(messages=[starter_content, message_content])
+ return ChatSession(messages=[message_content])
+
+ def lazy_load(self) -> Iterator[ChatSession]:
+ service = build("gmail", "v1", credentials=self.creds)
+ results = (
+ service.users().messages().list(userId="me", labelIds=self.label_ids, maxResults=self.n).execute()
+ )
+ messages = results.get("messages", [])
+ if not messages:
+ logger.warning("No messages found with the specified labels.")
+ for message in messages:
+ try:
+ yield self._get_message_data(service, message)
+ except Exception:
+ if self.raise_error:
+ raise
+ else:
+ logger.exception(f"Error processing message {message['id']}")
+
+ json_string = self.json_string
+ label_ids = self.label_ids.split(",") if self.label_ids else ["INBOX"]
+ max_results = int(self.max_results) if self.max_results else 100
+
+ # Load the token information from the JSON string
+ try:
+ token_info = json.loads(json_string)
+ except JSONDecodeError as e:
+ msg = "Invalid JSON string"
+ raise ValueError(msg) from e
+
+ creds = Credentials.from_authorized_user_info(token_info)
+
+ # Initialize the custom loader with the provided credentials
+ loader = CustomGMailLoader(creds=creds, n=max_results, label_ids=label_ids)
+
+ try:
+ docs = loader.load()
+ except RefreshError as e:
+ msg = "Authentication error: Unable to refresh authentication token. Please try to reauthenticate."
+ raise ValueError(msg) from e
+ except Exception as e:
+ msg = f"Error loading documents: {e}"
+ raise ValueError(msg) from e
+
+ # Return the loaded documents
+ self.status = docs
+ return Data(data={"text": docs})
diff --git a/langflow/src/backend/base/langflow/components/google/google_drive.py b/langflow/src/backend/base/langflow/components/google/google_drive.py
new file mode 100644
index 0000000..6ba03d7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/google/google_drive.py
@@ -0,0 +1,91 @@
+import json
+from json.decoder import JSONDecodeError
+
+from google.auth.exceptions import RefreshError
+from google.oauth2.credentials import Credentials
+from langchain_google_community import GoogleDriveLoader
+
+from langflow.custom import Component
+from langflow.helpers.data import docs_to_data
+from langflow.inputs import MessageTextInput
+from langflow.io import SecretStrInput
+from langflow.schema import Data
+from langflow.template import Output
+
+
+class GoogleDriveComponent(Component):
+ display_name = "Google Drive Loader"
+ description = "Loads documents from Google Drive using provided credentials."
+ icon = "Google"
+
+ inputs = [
+ SecretStrInput(
+ name="json_string",
+ display_name="JSON String of the Service Account Token",
+ info="JSON string containing OAuth 2.0 access token information for service account access",
+ required=True,
+ ),
+ MessageTextInput(
+ name="document_id", display_name="Document ID", info="Single Google Drive document ID", required=True
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Loaded Documents", name="docs", method="load_documents"),
+ ]
+
+ def load_documents(self) -> Data:
+ class CustomGoogleDriveLoader(GoogleDriveLoader):
+ creds: Credentials | None = None
+ """Credentials object to be passed directly."""
+
+ def _load_credentials(self):
+ """Load credentials from the provided creds attribute or fallback to the original method."""
+ if self.creds:
+ return self.creds
+ msg = "No credentials provided."
+ raise ValueError(msg)
+
+ class Config:
+ arbitrary_types_allowed = True
+
+ json_string = self.json_string
+
+ document_ids = [self.document_id]
+ if len(document_ids) != 1:
+ msg = "Expected a single document ID"
+ raise ValueError(msg)
+
+ # TODO: Add validation to check if the document ID is valid
+
+ # Load the token information from the JSON string
+ try:
+ token_info = json.loads(json_string)
+ except JSONDecodeError as e:
+ msg = "Invalid JSON string"
+ raise ValueError(msg) from e
+
+ # Initialize the custom loader with the provided credentials and document IDs
+ loader = CustomGoogleDriveLoader(
+ creds=Credentials.from_authorized_user_info(token_info), document_ids=document_ids
+ )
+
+ # Load the documents
+ try:
+ docs = loader.load()
+ # catch google.auth.exceptions.RefreshError
+ except RefreshError as e:
+ msg = "Authentication error: Unable to refresh authentication token. Please try to reauthenticate."
+ raise ValueError(msg) from e
+ except Exception as e:
+ msg = f"Error loading documents: {e}"
+ raise ValueError(msg) from e
+
+ if len(docs) != 1:
+ msg = "Expected a single document to be loaded."
+ raise ValueError(msg)
+
+ data = docs_to_data(docs)
+ # Return the loaded documents
+ self.status = data
+ return Data(data={"text": data})
diff --git a/langflow/src/backend/base/langflow/components/google/google_drive_search.py b/langflow/src/backend/base/langflow/components/google/google_drive_search.py
new file mode 100644
index 0000000..824c68c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/google/google_drive_search.py
@@ -0,0 +1,151 @@
+import json
+
+from google.oauth2.credentials import Credentials
+from googleapiclient.discovery import build
+
+from langflow.custom import Component
+from langflow.inputs import DropdownInput, MessageTextInput
+from langflow.io import SecretStrInput
+from langflow.schema import Data
+from langflow.template import Output
+
+
+class GoogleDriveSearchComponent(Component):
+ display_name = "Google Drive Search"
+ description = "Searches Google Drive files using provided credentials and query parameters."
+ icon = "Google"
+
+ inputs = [
+ SecretStrInput(
+ name="token_string",
+ display_name="Token String",
+ info="JSON string containing OAuth 2.0 access token information for service account access",
+ required=True,
+ ),
+ DropdownInput(
+ name="query_item",
+ display_name="Query Item",
+ options=[
+ "name",
+ "fullText",
+ "mimeType",
+ "modifiedTime",
+ "viewedByMeTime",
+ "trashed",
+ "starred",
+ "parents",
+ "owners",
+ "writers",
+ "readers",
+ "sharedWithMe",
+ "createdTime",
+ "properties",
+ "appProperties",
+ "visibility",
+ "shortcutDetails.targetId",
+ ],
+ info="The field to query.",
+ required=True,
+ ),
+ DropdownInput(
+ name="valid_operator",
+ display_name="Valid Operator",
+ options=["contains", "=", "!=", "<=", "<", ">", ">=", "in", "has"],
+ info="Operator to use in the query.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="search_term",
+ display_name="Search Term",
+ info="The value to search for in the specified query item.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="query_string",
+ display_name="Query String",
+ info="The query string used for searching. You can edit this manually.",
+ value="", # This will be updated with the generated query string
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Document URLs", name="doc_urls", method="search_doc_urls"),
+ Output(display_name="Document IDs", name="doc_ids", method="search_doc_ids"),
+ Output(display_name="Document Titles", name="doc_titles", method="search_doc_titles"),
+ Output(display_name="Data", name="Data", method="search_data"),
+ ]
+
+ def generate_query_string(self) -> str:
+ query_item = self.query_item
+ valid_operator = self.valid_operator
+ search_term = self.search_term
+
+ # Construct the query string
+ query = f"{query_item} {valid_operator} '{search_term}'"
+
+ # Update the editable query string input with the generated query
+ self.query_string = query
+
+ return query
+
+ def on_inputs_changed(self) -> None:
+ # Automatically regenerate the query string when inputs change
+ self.generate_query_string()
+
+ def generate_file_url(self, file_id: str, mime_type: str) -> str:
+ """Generates the appropriate Google Drive URL for a file based on its MIME type."""
+ return {
+ "application/vnd.google-apps.document": f"https://docs.google.com/document/d/{file_id}/edit",
+ "application/vnd.google-apps.spreadsheet": f"https://docs.google.com/spreadsheets/d/{file_id}/edit",
+ "application/vnd.google-apps.presentation": f"https://docs.google.com/presentation/d/{file_id}/edit",
+ "application/vnd.google-apps.drawing": f"https://docs.google.com/drawings/d/{file_id}/edit",
+ "application/pdf": f"https://drive.google.com/file/d/{file_id}/view?usp=drivesdk",
+ }.get(mime_type, f"https://drive.google.com/file/d/{file_id}/view?usp=drivesdk")
+
+ def search_files(self) -> dict:
+ # Load the token information from the JSON string
+ token_info = json.loads(self.token_string)
+ creds = Credentials.from_authorized_user_info(token_info)
+
+ # Use the query string from the input (which might have been edited by the user)
+ query = self.query_string or self.generate_query_string()
+
+ # Initialize the Google Drive API service
+ service = build("drive", "v3", credentials=creds)
+
+ # Perform the search
+ results = service.files().list(q=query, pageSize=5, fields="nextPageToken, files(id, name, mimeType)").execute()
+ items = results.get("files", [])
+
+ doc_urls = []
+ doc_ids = []
+ doc_titles_urls = []
+ doc_titles = []
+
+ if items:
+ for item in items:
+ # Directly use the file ID, title, and MIME type to generate the URL
+ file_id = item["id"]
+ file_title = item["name"]
+ mime_type = item["mimeType"]
+ file_url = self.generate_file_url(file_id, mime_type)
+
+ # Store the URL, ID, and title+URL in their respective lists
+ doc_urls.append(file_url)
+ doc_ids.append(file_id)
+ doc_titles.append(file_title)
+ doc_titles_urls.append({"title": file_title, "url": file_url})
+
+ return {"doc_urls": doc_urls, "doc_ids": doc_ids, "doc_titles_urls": doc_titles_urls, "doc_titles": doc_titles}
+
+ def search_doc_ids(self) -> list[str]:
+ return self.search_files()["doc_ids"]
+
+ def search_doc_urls(self) -> list[str]:
+ return self.search_files()["doc_urls"]
+
+ def search_doc_titles(self) -> list[str]:
+ return self.search_files()["doc_titles"]
+
+ def search_data(self) -> Data:
+ return Data(data={"text": self.search_files()["doc_titles_urls"]})
diff --git a/langflow/src/backend/base/langflow/components/google/google_oauth_token.py b/langflow/src/backend/base/langflow/components/google/google_oauth_token.py
new file mode 100644
index 0000000..b7d43a8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/google/google_oauth_token.py
@@ -0,0 +1,89 @@
+import json
+import re
+from pathlib import Path
+
+from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+
+from langflow.custom import Component
+from langflow.io import FileInput, MultilineInput, Output
+from langflow.schema import Data
+
+
+class GoogleOAuthToken(Component):
+ display_name = "Google OAuth Token"
+ description = "Generates a JSON string with your Google OAuth token."
+ documentation: str = "https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1"
+ icon = "Google"
+ name = "GoogleOAuthToken"
+
+ inputs = [
+ MultilineInput(
+ name="scopes",
+ display_name="Scopes",
+ info="Input scopes for your application.",
+ required=True,
+ ),
+ FileInput(
+ name="oauth_credentials",
+ display_name="Credentials File",
+ info="Input OAuth Credentials file (e.g. credentials.json).",
+ file_types=["json"],
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+
+ def validate_scopes(self, scopes):
+ pattern = (
+ r"^(https://www\.googleapis\.com/auth/[\w\.\-]+"
+ r"|mail\.google\.com/"
+ r"|www\.google\.com/calendar/feeds"
+ r"|www\.google\.com/m8/feeds)"
+ r"(,\s*https://www\.googleapis\.com/auth/[\w\.\-]+"
+ r"|mail\.google\.com/"
+ r"|www\.google\.com/calendar/feeds"
+ r"|www\.google\.com/m8/feeds)*$"
+ )
+ if not re.match(pattern, scopes):
+ error_message = "Invalid scope format."
+ raise ValueError(error_message)
+
+ def build_output(self) -> Data:
+ self.validate_scopes(self.scopes)
+
+ user_scopes = [scope.strip() for scope in self.scopes.split(",")]
+ if self.scopes:
+ scopes = user_scopes
+ else:
+ error_message = "Incorrect scope, check the scopes field."
+ raise ValueError(error_message)
+
+ creds = None
+ token_path = Path("token.json")
+
+ if token_path.exists():
+ creds = Credentials.from_authorized_user_file(str(token_path), scopes)
+
+ if not creds or not creds.valid:
+ if creds and creds.expired and creds.refresh_token:
+ creds.refresh(Request())
+ else:
+ if self.oauth_credentials:
+ client_secret_file = self.oauth_credentials
+ else:
+ error_message = "OAuth 2.0 Credentials file not provided."
+ raise ValueError(error_message)
+
+ flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, scopes)
+ creds = flow.run_local_server(port=0)
+
+ token_path.write_text(creds.to_json(), encoding="utf-8")
+
+ creds_json = json.loads(creds.to_json())
+
+ return Data(data=creds_json)
diff --git a/langflow/src/backend/base/langflow/components/helpers/__init__.py b/langflow/src/backend/base/langflow/components/helpers/__init__.py
new file mode 100644
index 0000000..917b2c1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/__init__.py
@@ -0,0 +1,18 @@
+from .create_list import CreateListComponent
+from .current_date import CurrentDateComponent
+from .id_generator import IDGeneratorComponent
+from .memory import MemoryComponent
+from .output_parser import OutputParserComponent
+from .store_message import MessageStoreComponent
+from .structured_output import StructuredOutputComponent
+
+__all__ = [
+ "BatchRunComponent",
+ "CreateListComponent",
+ "CurrentDateComponent",
+ "IDGeneratorComponent",
+ "MemoryComponent",
+ "MessageStoreComponent",
+ "OutputParserComponent",
+ "StructuredOutputComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/helpers/batch_run.py b/langflow/src/backend/base/langflow/components/helpers/batch_run.py
new file mode 100644
index 0000000..7e6468f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/batch_run.py
@@ -0,0 +1,195 @@
+from __future__ import annotations
+
+import operator
+from typing import TYPE_CHECKING, Any
+
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ DataFrameInput,
+ HandleInput,
+ MessageTextInput,
+ MultilineInput,
+ Output,
+)
+from langflow.schema import DataFrame
+
+if TYPE_CHECKING:
+ from langchain_core.runnables import Runnable
+
+
+class BatchRunComponent(Component):
+ display_name = "Batch Run"
+ description = (
+ "Runs a language model over each row of a DataFrame's text column and returns a new "
+ "DataFrame with three columns: '**text_input**' (the original text), "
+ "'**model_response**' (the model's response),and '**batch_index**' (the processing order)."
+ )
+ icon = "List"
+ beta = True
+
+ inputs = [
+ HandleInput(
+ name="model",
+ display_name="Language Model",
+ info="Connect the 'Language Model' output from your LLM component here.",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ MultilineInput(
+ name="system_message",
+ display_name="System Message",
+ info="Multi-line system instruction for all rows in the DataFrame.",
+ required=False,
+ ),
+ DataFrameInput(
+ name="df",
+ display_name="DataFrame",
+ info="The DataFrame whose column (specified by 'column_name') we'll treat as text messages.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="column_name",
+ display_name="Column Name",
+ info="The name of the DataFrame column to treat as text messages. Default='text'.",
+ value="text",
+ required=True,
+ advanced=True,
+ ),
+ BoolInput(
+ name="enable_metadata",
+ display_name="Enable Metadata",
+ info="If True, add metadata to the output DataFrame.",
+ value=True,
+ required=False,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Batch Results",
+ name="batch_results",
+ method="run_batch",
+ info="A DataFrame with columns: 'text_input', 'model_response', 'batch_index', and 'metadata'.",
+ ),
+ ]
+
+ def _create_base_row(self, text_input: str = "", model_response: str = "", batch_index: int = -1) -> dict[str, Any]:
+ """Create a base row with optional metadata."""
+ return {
+ "text_input": text_input,
+ "model_response": model_response,
+ "batch_index": batch_index,
+ }
+
+ def _add_metadata(
+ self, row: dict[str, Any], *, success: bool = True, system_msg: str = "", error: str | None = None
+ ) -> None:
+ """Add metadata to a row if enabled."""
+ if not self.enable_metadata:
+ return
+
+ if success:
+ row["metadata"] = {
+ "has_system_message": bool(system_msg),
+ "input_length": len(row["text_input"]),
+ "response_length": len(row["model_response"]),
+ "processing_status": "success",
+ }
+ else:
+ row["metadata"] = {
+ "error": error,
+ "processing_status": "failed",
+ }
+
+ async def run_batch(self) -> DataFrame:
+ """Process each row in df[column_name] with the language model asynchronously.
+
+ Returns:
+ DataFrame: A new DataFrame containing:
+ - text_input: The original input text
+ - model_response: The model's response
+ - batch_index: The processing order
+ - metadata: Additional processing information
+
+ Raises:
+ ValueError: If the specified column is not found in the DataFrame
+ TypeError: If the model is not compatible or input types are wrong
+ """
+ model: Runnable = self.model
+ system_msg = self.system_message or ""
+ df: DataFrame = self.df
+ col_name = self.column_name or "text"
+
+ # Validate inputs first
+ if not isinstance(df, DataFrame):
+ msg = f"Expected DataFrame input, got {type(df)}"
+ raise TypeError(msg)
+
+ if col_name not in df.columns:
+ msg = f"Column '{col_name}' not found in the DataFrame. Available columns: {', '.join(df.columns)}"
+ raise ValueError(msg)
+
+ try:
+ # Convert the specified column to a list of strings
+ user_texts = df[col_name].astype(str).tolist()
+ total_rows = len(user_texts)
+
+ logger.info(f"Processing {total_rows} rows with batch run")
+
+ # Prepare the batch of conversations
+ conversations = [
+ [{"role": "system", "content": system_msg}, {"role": "user", "content": text}]
+ if system_msg
+ else [{"role": "user", "content": text}]
+ for text in user_texts
+ ]
+
+ # Configure the model with project info and callbacks
+ model = model.with_config(
+ {
+ "run_name": self.display_name,
+ "project_name": self.get_project_name(),
+ "callbacks": self.get_langchain_callbacks(),
+ }
+ )
+
+ # Process batches and track progress
+ responses_with_idx = [
+ (idx, response)
+ for idx, response in zip(
+ range(len(conversations)), await model.abatch(list(conversations)), strict=True
+ )
+ ]
+
+ # Sort by index to maintain order
+ responses_with_idx.sort(key=operator.itemgetter(0))
+
+ # Build the final data with enhanced metadata
+ rows: list[dict[str, Any]] = []
+ for idx, response in responses_with_idx:
+ resp_text = response.content if hasattr(response, "content") else str(response)
+ row = self._create_base_row(
+ text_input=user_texts[idx],
+ model_response=resp_text,
+ batch_index=idx,
+ )
+ self._add_metadata(row, success=True, system_msg=system_msg)
+ rows.append(row)
+
+ # Log progress
+ if (idx + 1) % max(1, total_rows // 10) == 0:
+ logger.info(f"Processed {idx + 1}/{total_rows} rows")
+
+ logger.info("Batch processing completed successfully")
+ return DataFrame(rows)
+
+ except (KeyError, AttributeError) as e:
+ # Handle data structure and attribute access errors
+ logger.error(f"Data processing error: {e!s}")
+ error_row = self._create_base_row()
+ self._add_metadata(error_row, success=False, error=str(e))
+ return DataFrame([error_row])
diff --git a/langflow/src/backend/base/langflow/components/helpers/create_list.py b/langflow/src/backend/base/langflow/components/helpers/create_list.py
new file mode 100644
index 0000000..a978ffe
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/create_list.py
@@ -0,0 +1,30 @@
+from langflow.custom import Component
+from langflow.inputs import StrInput
+from langflow.schema import Data
+from langflow.template import Output
+
+
+class CreateListComponent(Component):
+ display_name = "Create List"
+ description = "Creates a list of texts."
+ icon = "list"
+ name = "CreateList"
+ legacy = True
+
+ inputs = [
+ StrInput(
+ name="texts",
+ display_name="Texts",
+ info="Enter one or more texts.",
+ is_list=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data List", name="list", method="create_list"),
+ ]
+
+ def create_list(self) -> list[Data]:
+ data = [Data(text=text) for text in self.texts]
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/helpers/current_date.py b/langflow/src/backend/base/langflow/components/helpers/current_date.py
new file mode 100644
index 0000000..951176d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/current_date.py
@@ -0,0 +1,42 @@
+from datetime import datetime
+from zoneinfo import ZoneInfo, available_timezones
+
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import DropdownInput, Output
+from langflow.schema.message import Message
+
+
+class CurrentDateComponent(Component):
+ display_name = "Current Date"
+ description = "Returns the current date and time in the selected timezone."
+ icon = "clock"
+ name = "CurrentDate"
+
+ inputs = [
+ DropdownInput(
+ name="timezone",
+ display_name="Timezone",
+ options=list(available_timezones()),
+ value="UTC",
+ info="Select the timezone for the current date and time.",
+ tool_mode=True,
+ ),
+ ]
+ outputs = [
+ Output(display_name="Current Date", name="current_date", method="get_current_date"),
+ ]
+
+ def get_current_date(self) -> Message:
+ try:
+ tz = ZoneInfo(self.timezone)
+ current_date = datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S %Z")
+ result = f"Current date and time in {self.timezone}: {current_date}"
+ self.status = result
+ return Message(text=result)
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error getting current date")
+ error_message = f"Error: {e}"
+ self.status = error_message
+ return Message(text=error_message)
diff --git a/langflow/src/backend/base/langflow/components/helpers/id_generator.py b/langflow/src/backend/base/langflow/components/helpers/id_generator.py
new file mode 100644
index 0000000..ebfbcb2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/id_generator.py
@@ -0,0 +1,41 @@
+import uuid
+from typing import Any
+
+from typing_extensions import override
+
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import dotdict
+from langflow.schema.message import Message
+
+
+class IDGeneratorComponent(Component):
+ display_name = "ID Generator"
+ description = "Generates a unique ID."
+ icon = "fingerprint"
+ name = "IDGenerator"
+
+ inputs = [
+ MessageTextInput(
+ name="unique_id",
+ display_name="Value",
+ info="The generated unique ID.",
+ refresh_button=True,
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="ID", name="id", method="generate_id"),
+ ]
+
+ @override
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "unique_id":
+ build_config[field_name]["value"] = str(uuid.uuid4())
+ return build_config
+
+ def generate_id(self) -> Message:
+ unique_id = self.unique_id or str(uuid.uuid4())
+ self.status = f"Generated ID: {unique_id}"
+ return Message(text=unique_id)
diff --git a/langflow/src/backend/base/langflow/components/helpers/memory.py b/langflow/src/backend/base/langflow/components/helpers/memory.py
new file mode 100644
index 0000000..07b16b4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/memory.py
@@ -0,0 +1,118 @@
+from langflow.custom import Component
+from langflow.helpers.data import data_to_text
+from langflow.inputs import HandleInput
+from langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output
+from langflow.memory import aget_messages
+from langflow.schema import Data
+from langflow.schema.message import Message
+from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
+
+
+class MemoryComponent(Component):
+ display_name = "Message History"
+ description = "Retrieves stored chat messages from Langflow tables or an external memory."
+ icon = "message-square-more"
+ name = "Memory"
+
+ inputs = [
+ HandleInput(
+ name="memory",
+ display_name="External Memory",
+ input_types=["Memory"],
+ info="Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ ),
+ DropdownInput(
+ name="sender",
+ display_name="Sender Type",
+ options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, "Machine and User"],
+ value="Machine and User",
+ info="Filter by sender type.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="sender_name",
+ display_name="Sender Name",
+ info="Filter by sender name.",
+ advanced=True,
+ ),
+ IntInput(
+ name="n_messages",
+ display_name="Number of Messages",
+ value=100,
+ info="Number of messages to retrieve.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="order",
+ display_name="Order",
+ options=["Ascending", "Descending"],
+ value="Ascending",
+ info="Order of the messages.",
+ advanced=True,
+ tool_mode=True,
+ ),
+ MultilineInput(
+ name="template",
+ display_name="Template",
+ info="The template to use for formatting the data. "
+ "It can contain the keys {text}, {sender} or any other key in the message data.",
+ value="{sender_name}: {text}",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="messages", method="retrieve_messages"),
+ Output(display_name="Message", name="messages_text", method="retrieve_messages_as_text"),
+ ]
+
+ async def retrieve_messages(self) -> Data:
+ sender = self.sender
+ sender_name = self.sender_name
+ session_id = self.session_id
+ n_messages = self.n_messages
+ order = "DESC" if self.order == "Descending" else "ASC"
+
+ if sender == "Machine and User":
+ sender = None
+
+ if self.memory and not hasattr(self.memory, "aget_messages"):
+ memory_name = type(self.memory).__name__
+ err_msg = f"External Memory object ({memory_name}) must have 'aget_messages' method."
+ raise AttributeError(err_msg)
+
+ if self.memory:
+ # override session_id
+ self.memory.session_id = session_id
+
+ stored = await self.memory.aget_messages()
+ # langchain memories are supposed to return messages in ascending order
+ if order == "DESC":
+ stored = stored[::-1]
+ if n_messages:
+ stored = stored[:n_messages]
+ stored = [Message.from_lc_message(m) for m in stored]
+ if sender:
+ expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER
+ stored = [m for m in stored if m.type == expected_type]
+ else:
+ stored = await aget_messages(
+ sender=sender,
+ sender_name=sender_name,
+ session_id=session_id,
+ limit=n_messages,
+ order=order,
+ )
+ self.status = stored
+ return stored
+
+ async def retrieve_messages_as_text(self) -> Message:
+ stored_text = data_to_text(self.template, await self.retrieve_messages())
+ self.status = stored_text
+ return Message(text=stored_text)
diff --git a/langflow/src/backend/base/langflow/components/helpers/output_parser.py b/langflow/src/backend/base/langflow/components/helpers/output_parser.py
new file mode 100644
index 0000000..7fa3f54
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/output_parser.py
@@ -0,0 +1,45 @@
+from langchain_core.output_parsers import CommaSeparatedListOutputParser
+
+from langflow.custom.custom_component.component import Component
+from langflow.field_typing.constants import OutputParser
+from langflow.io import DropdownInput, Output
+from langflow.schema.message import Message
+
+
+class OutputParserComponent(Component):
+ display_name = "Output Parser"
+ description = "Transforms the output of an LLM into a specified format."
+ icon = "type"
+ name = "OutputParser"
+ legacy = True
+
+ inputs = [
+ DropdownInput(
+ name="parser_type",
+ display_name="Parser",
+ options=["CSV"],
+ value="CSV",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Format Instructions",
+ name="format_instructions",
+ info="Pass to a prompt template to include formatting instructions for LLM responses.",
+ method="format_instructions",
+ ),
+ Output(display_name="Output Parser", name="output_parser", method="build_parser"),
+ ]
+
+ def build_parser(self) -> OutputParser:
+ if self.parser_type == "CSV":
+ return CommaSeparatedListOutputParser()
+ msg = "Unsupported or missing parser"
+ raise ValueError(msg)
+
+ def format_instructions(self) -> Message:
+ if self.parser_type == "CSV":
+ return Message(text=CommaSeparatedListOutputParser().get_format_instructions())
+ msg = "Unsupported or missing parser"
+ raise ValueError(msg)
diff --git a/langflow/src/backend/base/langflow/components/helpers/store_message.py b/langflow/src/backend/base/langflow/components/helpers/store_message.py
new file mode 100644
index 0000000..6870932
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/store_message.py
@@ -0,0 +1,87 @@
+from langflow.custom import Component
+from langflow.inputs import HandleInput
+from langflow.inputs.inputs import MessageTextInput
+from langflow.memory import aget_messages, astore_message
+from langflow.schema.message import Message
+from langflow.template import Output
+from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI
+
+
+class MessageStoreComponent(Component):
+ display_name = "Message Store"
+ description = "Stores a chat message or text into Langflow tables or an external memory."
+ icon = "message-square-text"
+ name = "StoreMessage"
+
+ inputs = [
+ MessageTextInput(
+ name="message", display_name="Message", info="The chat message to be stored.", required=True, tool_mode=True
+ ),
+ HandleInput(
+ name="memory",
+ display_name="External Memory",
+ input_types=["Memory"],
+ info="The external memory to store the message. If empty, it will use the Langflow tables.",
+ ),
+ MessageTextInput(
+ name="sender",
+ display_name="Sender",
+ info="The sender of the message. Might be Machine or User. "
+ "If empty, the current sender parameter will be used.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="sender_name",
+ display_name="Sender Name",
+ info="The name of the sender. Might be AI or User. If empty, the current sender parameter will be used.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
+ value="",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Stored Messages", name="stored_messages", method="store_message", hidden=True),
+ ]
+
+ async def store_message(self) -> Message:
+ message = Message(text=self.message) if isinstance(self.message, str) else self.message
+
+ message.session_id = self.session_id or message.session_id
+ message.sender = self.sender or message.sender or MESSAGE_SENDER_AI
+ message.sender_name = self.sender_name or message.sender_name or MESSAGE_SENDER_NAME_AI
+
+ stored_messages: list[Message] = []
+
+ if self.memory:
+ self.memory.session_id = message.session_id
+ lc_message = message.to_lc_message()
+ await self.memory.aadd_messages([lc_message])
+
+ stored_messages = await self.memory.aget_messages() or []
+
+ stored_messages = [Message.from_lc_message(m) for m in stored_messages] if stored_messages else []
+
+ if message.sender:
+ stored_messages = [m for m in stored_messages if m.sender == message.sender]
+ else:
+ await astore_message(message, flow_id=self.graph.flow_id)
+ stored_messages = (
+ await aget_messages(
+ session_id=message.session_id, sender_name=message.sender_name, sender=message.sender
+ )
+ or []
+ )
+
+ if not stored_messages:
+ msg = "No messages were stored. Please ensure that the session ID and sender are properly set."
+ raise ValueError(msg)
+
+ stored_message = stored_messages[0]
+ self.status = stored_message
+ return stored_message
diff --git a/langflow/src/backend/base/langflow/components/helpers/structured_output.py b/langflow/src/backend/base/langflow/components/helpers/structured_output.py
new file mode 100644
index 0000000..36301d1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/helpers/structured_output.py
@@ -0,0 +1,194 @@
+from typing import TYPE_CHECKING, cast
+
+from pydantic import BaseModel, Field, create_model
+
+from langflow.base.models.chat_result import get_chat_result
+from langflow.custom import Component
+from langflow.helpers.base_model import build_model_from_schema
+from langflow.io import (
+ BoolInput,
+ HandleInput,
+ MessageTextInput,
+ MultilineInput,
+ Output,
+ TableInput,
+)
+from langflow.schema.data import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.table import EditMode
+
+if TYPE_CHECKING:
+ from langflow.field_typing.constants import LanguageModel
+
+
+class StructuredOutputComponent(Component):
+ display_name = "Structured Output"
+ description = (
+ "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information "
+ "or creating consistent outputs."
+ )
+ name = "StructuredOutput"
+ icon = "braces"
+
+ inputs = [
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ info="The language model to use to generate the structured output.",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="input_value",
+ display_name="Input Message",
+ info="The input message to the language model.",
+ tool_mode=True,
+ required=True,
+ ),
+ MultilineInput(
+ name="system_prompt",
+ display_name="Format Instructions",
+ info="The instructions to the language model for formatting the output.",
+ value=(
+ "You are an AI system designed to extract structured information from unstructured text."
+ "Given the input_text, return a JSON object with predefined keys based on the expected structure."
+ "Extract values accurately and format them according to the specified type "
+ "(e.g., string, integer, float, date)."
+ "If a value is missing or cannot be determined, return a default "
+ "(e.g., null, 0, or 'N/A')."
+ "If multiple instances of the expected structure exist within the input_text, "
+ "stream each as a separate JSON object."
+ ),
+ required=True,
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="schema_name",
+ display_name="Schema Name",
+ info="Provide a name for the output data schema.",
+ advanced=True,
+ ),
+ TableInput(
+ name="output_schema",
+ display_name="Output Schema",
+ info="Define the structure and data types for the model's output.",
+ required=True,
+ # TODO: remove deault value
+ table_schema=[
+ {
+ "name": "name",
+ "display_name": "Name",
+ "type": "str",
+ "description": "Specify the name of the output field.",
+ "default": "field",
+ "edit_mode": EditMode.INLINE,
+ },
+ {
+ "name": "description",
+ "display_name": "Description",
+ "type": "str",
+ "description": "Describe the purpose of the output field.",
+ "default": "description of field",
+ "edit_mode": EditMode.POPOVER,
+ },
+ {
+ "name": "type",
+ "display_name": "Type",
+ "type": "str",
+ "edit_mode": EditMode.INLINE,
+ "description": (
+ "Indicate the data type of the output field (e.g., str, int, float, bool, list, dict)."
+ ),
+ "options": ["str", "int", "float", "bool", "list", "dict"],
+ "default": "str",
+ },
+ {
+ "name": "multiple",
+ "display_name": "Multiple",
+ "type": "boolean",
+ "description": "Set to True if this output field should be a list of the specified type.",
+ "default": "False",
+ "edit_mode": EditMode.INLINE,
+ },
+ ],
+ value=[
+ {
+ "name": "field",
+ "description": "description of field",
+ "type": "str",
+ "multiple": "False",
+ }
+ ],
+ ),
+ BoolInput(
+ name="multiple",
+ advanced=True,
+ display_name="Generate Multiple",
+ info="[Deplrecated] Always set to True",
+ value=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ name="structured_output",
+ display_name="Structured Output",
+ method="build_structured_output",
+ ),
+ Output(
+ name="structured_output_dataframe",
+ display_name="DataFrame",
+ method="as_dataframe",
+ ),
+ ]
+
+ def build_structured_output_base(self) -> Data:
+ schema_name = self.schema_name or "OutputModel"
+
+ if not hasattr(self.llm, "with_structured_output"):
+ msg = "Language model does not support structured output."
+ raise TypeError(msg)
+ if not self.output_schema:
+ msg = "Output schema cannot be empty"
+ raise ValueError(msg)
+
+ output_model_ = build_model_from_schema(self.output_schema)
+
+ output_model = create_model(
+ schema_name,
+ objects=(list[output_model_], Field(description=f"A list of {schema_name}.")), # type: ignore[valid-type]
+ )
+
+ try:
+ llm_with_structured_output = cast("LanguageModel", self.llm).with_structured_output(schema=output_model) # type: ignore[valid-type, attr-defined]
+
+ except NotImplementedError as exc:
+ msg = f"{self.llm.__class__.__name__} does not support structured output."
+ raise TypeError(msg) from exc
+ config_dict = {
+ "run_name": self.display_name,
+ "project_name": self.get_project_name(),
+ "callbacks": self.get_langchain_callbacks(),
+ }
+ result = get_chat_result(
+ runnable=llm_with_structured_output,
+ system_message=self.system_prompt,
+ input_value=self.input_value,
+ config=config_dict,
+ )
+ if isinstance(result, BaseModel):
+ result = result.model_dump()
+ if "objects" in result:
+ return result["objects"]
+ return result
+
+ def build_structured_output(self) -> Data:
+ output = self.build_structured_output_base()
+
+ return Data(results=output)
+
+ def as_dataframe(self) -> DataFrame:
+ output = self.build_structured_output_base()
+ if isinstance(output, list):
+ return DataFrame(data=output)
+ return DataFrame(data=[output])
diff --git a/langflow/src/backend/base/langflow/components/icosacomputing/__init__.py b/langflow/src/backend/base/langflow/components/icosacomputing/__init__.py
new file mode 100644
index 0000000..c161af6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/icosacomputing/__init__.py
@@ -0,0 +1,5 @@
+from .combinatorial_reasoner import CombinatorialReasonerComponent
+
+__all__ = [
+ "CombinatorialReasonerComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/icosacomputing/combinatorial_reasoner.py b/langflow/src/backend/base/langflow/components/icosacomputing/combinatorial_reasoner.py
new file mode 100644
index 0000000..22b2e78
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/icosacomputing/combinatorial_reasoner.py
@@ -0,0 +1,84 @@
+import requests
+from requests.auth import HTTPBasicAuth
+
+from langflow.base.models.openai_constants import OPENAI_MODEL_NAMES
+from langflow.custom import Component
+from langflow.inputs import DropdownInput, SecretStrInput, StrInput
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class CombinatorialReasonerComponent(Component):
+ display_name = "Combinatorial Reasoner"
+ description = "Uses Combinatorial Optimization to construct an optimal prompt with embedded reasons. Sign up here:\nhttps://forms.gle/oWNv2NKjBNaqqvCx6"
+ icon = "Icosa"
+ name = "Combinatorial Reasoner"
+
+ inputs = [
+ MessageTextInput(name="prompt", display_name="Prompt", required=True),
+ SecretStrInput(
+ name="openai_api_key",
+ display_name="OpenAI API Key",
+ info="The OpenAI API Key to use for the OpenAI model.",
+ advanced=False,
+ value="OPENAI_API_KEY",
+ required=True,
+ ),
+ StrInput(
+ name="username",
+ display_name="Username",
+ info="Username to authenticate access to Icosa CR API",
+ advanced=False,
+ required=True,
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="Password",
+ info="Password to authenticate access to Icosa CR API.",
+ advanced=False,
+ required=True,
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=OPENAI_MODEL_NAMES,
+ value=OPENAI_MODEL_NAMES[0],
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Optimized Prompt",
+ name="optimized_prompt",
+ method="build_prompt",
+ ),
+ Output(display_name="Selected Reasons", name="reasons", method="build_reasons"),
+ ]
+
+ def build_prompt(self) -> Message:
+ params = {
+ "prompt": self.prompt,
+ "apiKey": self.openai_api_key,
+ "model": self.model_name,
+ }
+
+ creds = HTTPBasicAuth(self.username, password=self.password)
+ response = requests.post(
+ "https://cr-api.icosacomputing.com/cr/langflow",
+ json=params,
+ auth=creds,
+ timeout=100,
+ )
+ response.raise_for_status()
+
+ prompt = response.json()["prompt"]
+
+ self.reasons = response.json()["finalReasons"]
+ return prompt
+
+ def build_reasons(self) -> Data:
+ # list of selected reasons
+ final_reasons = [reason[0] for reason in self.reasons]
+ return Data(value=final_reasons)
diff --git a/langflow/src/backend/base/langflow/components/inputs/__init__.py b/langflow/src/backend/base/langflow/components/inputs/__init__.py
new file mode 100644
index 0000000..311e23f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/inputs/__init__.py
@@ -0,0 +1,4 @@
+from .chat import ChatInput
+from .text import TextInputComponent
+
+__all__ = ["ChatInput", "TextInputComponent"]
diff --git a/langflow/src/backend/base/langflow/components/inputs/chat.py b/langflow/src/backend/base/langflow/components/inputs/chat.py
new file mode 100644
index 0000000..3f9124e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/inputs/chat.py
@@ -0,0 +1,118 @@
+from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES
+from langflow.base.io.chat import ChatComponent
+from langflow.inputs import BoolInput
+from langflow.io import (
+ DropdownInput,
+ FileInput,
+ MessageTextInput,
+ MultilineInput,
+ Output,
+)
+from langflow.schema.message import Message
+from langflow.utils.constants import (
+ MESSAGE_SENDER_AI,
+ MESSAGE_SENDER_NAME_USER,
+ MESSAGE_SENDER_USER,
+)
+
+
+class ChatInput(ChatComponent):
+ display_name = "Chat Input"
+ description = "Get chat inputs from the Playground."
+ icon = "MessagesSquare"
+ name = "ChatInput"
+ minimized = True
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Text",
+ value="",
+ info="Message to be passed as input.",
+ input_types=[],
+ ),
+ BoolInput(
+ name="should_store_message",
+ display_name="Store Messages",
+ info="Store the message in the history.",
+ value=True,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="sender",
+ display_name="Sender Type",
+ options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],
+ value=MESSAGE_SENDER_USER,
+ info="Type of sender.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="sender_name",
+ display_name="Sender Name",
+ info="Name of the sender.",
+ value=MESSAGE_SENDER_NAME_USER,
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
+ advanced=True,
+ ),
+ FileInput(
+ name="files",
+ display_name="Files",
+ file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,
+ info="Files to be sent with the message.",
+ advanced=True,
+ is_list=True,
+ ),
+ MessageTextInput(
+ name="background_color",
+ display_name="Background Color",
+ info="The background color of the icon.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="chat_icon",
+ display_name="Icon",
+ info="The icon of the message.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="text_color",
+ display_name="Text Color",
+ info="The text color of the name",
+ advanced=True,
+ ),
+ ]
+ outputs = [
+ Output(display_name="Message", name="message", method="message_response"),
+ ]
+
+ async def message_response(self) -> Message:
+ background_color = self.background_color
+ text_color = self.text_color
+ icon = self.chat_icon
+
+ message = await Message.create(
+ text=self.input_value,
+ sender=self.sender,
+ sender_name=self.sender_name,
+ session_id=self.session_id,
+ files=self.files,
+ properties={
+ "background_color": background_color,
+ "text_color": text_color,
+ "icon": icon,
+ },
+ )
+ if self.session_id and isinstance(message, Message) and self.should_store_message:
+ stored_message = await self.send_message(
+ message,
+ )
+ self.message.value = stored_message
+ message = stored_message
+
+ self.status = message
+ return message
diff --git a/langflow/src/backend/base/langflow/components/inputs/text.py b/langflow/src/backend/base/langflow/components/inputs/text.py
new file mode 100644
index 0000000..9d585a8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/inputs/text.py
@@ -0,0 +1,26 @@
+from langflow.base.io.text import TextComponent
+from langflow.io import MultilineInput, Output
+from langflow.schema.message import Message
+
+
+class TextInputComponent(TextComponent):
+ display_name = "Text Input"
+ description = "Get text inputs from the Playground."
+ icon = "type"
+ name = "TextInput"
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Text",
+ info="Text to be passed as input.",
+ ),
+ ]
+ outputs = [
+ Output(display_name="Message", name="text", method="text_response"),
+ ]
+
+ def text_response(self) -> Message:
+ return Message(
+ text=self.input_value,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/__init__.py b/langflow/src/backend/base/langflow/components/langchain_utilities/__init__.py
new file mode 100644
index 0000000..9fd9bb3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/__init__.py
@@ -0,0 +1,61 @@
+from .character import CharacterTextSplitterComponent
+from .conversation import ConversationChainComponent
+from .csv_agent import CSVAgentComponent
+from .fake_embeddings import FakeEmbeddingsComponent
+from .html_link_extractor import HtmlLinkExtractorComponent
+from .json_agent import JsonAgentComponent
+from .json_document_builder import JSONDocumentBuilder
+from .langchain_hub import LangChainHubPromptComponent
+from .language_recursive import LanguageRecursiveTextSplitterComponent
+from .language_semantic import SemanticTextSplitterComponent
+from .llm_checker import LLMCheckerChainComponent
+from .llm_math import LLMMathChainComponent
+from .natural_language import NaturalLanguageTextSplitterComponent
+from .openai_tools import OpenAIToolsAgentComponent
+from .openapi import OpenAPIAgentComponent
+from .recursive_character import RecursiveCharacterTextSplitterComponent
+from .retrieval_qa import RetrievalQAComponent
+from .retriever import RetrieverToolComponent
+from .runnable_executor import RunnableExecComponent
+from .self_query import SelfQueryRetrieverComponent
+from .spider import SpiderTool
+from .sql import SQLAgentComponent
+from .sql_database import SQLDatabaseComponent
+from .sql_generator import SQLGeneratorComponent
+from .tool_calling import ToolCallingAgentComponent
+from .vector_store import VectoStoreRetrieverComponent
+from .vector_store_info import VectorStoreInfoComponent
+from .vector_store_router import VectorStoreRouterAgentComponent
+from .xml_agent import XMLAgentComponent
+
+__all__ = [
+ "CSVAgentComponent",
+ "CharacterTextSplitterComponent",
+ "ConversationChainComponent",
+ "FakeEmbeddingsComponent",
+ "HtmlLinkExtractorComponent",
+ "JSONDocumentBuilder",
+ "JsonAgentComponent",
+ "LLMCheckerChainComponent",
+ "LLMMathChainComponent",
+ "LangChainHubPromptComponent",
+ "LanguageRecursiveTextSplitterComponent",
+ "NaturalLanguageTextSplitterComponent",
+ "OpenAIToolsAgentComponent",
+ "OpenAPIAgentComponent",
+ "RecursiveCharacterTextSplitterComponent",
+ "RetrievalQAComponent",
+ "RetrieverToolComponent",
+ "RunnableExecComponent",
+ "SQLAgentComponent",
+ "SQLDatabaseComponent",
+ "SQLGeneratorComponent",
+ "SelfQueryRetrieverComponent",
+ "SemanticTextSplitterComponent",
+ "SpiderTool",
+ "ToolCallingAgentComponent",
+ "VectoStoreRetrieverComponent",
+ "VectorStoreInfoComponent",
+ "VectorStoreRouterAgentComponent",
+ "XMLAgentComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/character.py b/langflow/src/backend/base/langflow/components/langchain_utilities/character.py
new file mode 100644
index 0000000..92dacd5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/character.py
@@ -0,0 +1,53 @@
+from typing import Any
+
+from langchain_text_splitters import CharacterTextSplitter, TextSplitter
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.inputs import DataInput, IntInput, MessageTextInput
+from langflow.utils.util import unescape_string
+
+
+class CharacterTextSplitterComponent(LCTextSplitterComponent):
+ display_name = "CharacterTextSplitter"
+ description = "Split text by number of characters."
+ documentation = "https://docs.langflow.org/components/text-splitters#charactertextsplitter"
+ name = "CharacterTextSplitter"
+ icon = "LangChain"
+
+ inputs = [
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum length of each chunk.",
+ value=1000,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="The amount of overlap between chunks.",
+ value=200,
+ ),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The texts to split.",
+ input_types=["Document", "Data"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="separator",
+ display_name="Separator",
+ info='The characters to split on.\nIf left empty defaults to "\\n\\n".',
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_text_splitter(self) -> TextSplitter:
+ separator = unescape_string(self.separator) if self.separator else "\n\n"
+ return CharacterTextSplitter(
+ chunk_overlap=self.chunk_overlap,
+ chunk_size=self.chunk_size,
+ separator=separator,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/conversation.py b/langflow/src/backend/base/langflow/components/langchain_utilities/conversation.py
new file mode 100644
index 0000000..6e9cbaa
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/conversation.py
@@ -0,0 +1,52 @@
+from langchain.chains import ConversationChain
+
+from langflow.base.chains.model import LCChainComponent
+from langflow.field_typing import Message
+from langflow.inputs import HandleInput, MultilineInput
+
+
+class ConversationChainComponent(LCChainComponent):
+ display_name = "ConversationChain"
+ description = "Chain to have a conversation and load context from memory."
+ name = "ConversationChain"
+ legacy: bool = True
+ icon = "LangChain"
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ info="The input value to pass to the chain.",
+ required=True,
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ HandleInput(
+ name="memory",
+ display_name="Memory",
+ input_types=["BaseChatMemory"],
+ ),
+ ]
+
+ def invoke_chain(self) -> Message:
+ if not self.memory:
+ chain = ConversationChain(llm=self.llm)
+ else:
+ chain = ConversationChain(llm=self.llm, memory=self.memory)
+
+ result = chain.invoke(
+ {"input": self.input_value},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+ if isinstance(result, dict):
+ result = result.get(chain.output_key, "")
+
+ elif not isinstance(result, str):
+ result = result.get("response")
+ result = str(result)
+ self.status = result
+ return Message(text=result)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/csv_agent.py b/langflow/src/backend/base/langflow/components/langchain_utilities/csv_agent.py
new file mode 100644
index 0000000..b85a66c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/csv_agent.py
@@ -0,0 +1,102 @@
+from langchain_experimental.agents.agent_toolkits.csv.base import create_csv_agent
+
+from langflow.base.agents.agent import LCAgentComponent
+from langflow.field_typing import AgentExecutor
+from langflow.inputs import DropdownInput, FileInput, HandleInput
+from langflow.inputs.inputs import DictInput, MessageTextInput
+from langflow.schema.message import Message
+from langflow.template.field.base import Output
+
+
+class CSVAgentComponent(LCAgentComponent):
+ display_name = "CSVAgent"
+ description = "Construct a CSV agent from a CSV and tools."
+ documentation = "https://python.langchain.com/docs/modules/agents/toolkits/csv"
+ name = "CSVAgent"
+ icon = "LangChain"
+
+ inputs = [
+ *LCAgentComponent._base_inputs,
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ info="An LLM Model Object (It can be found in any LLM Component).",
+ ),
+ FileInput(
+ name="path",
+ display_name="File Path",
+ file_types=["csv"],
+ input_types=["str", "Message"],
+ required=True,
+ info="A CSV File or File Path.",
+ ),
+ DropdownInput(
+ name="agent_type",
+ display_name="Agent Type",
+ advanced=True,
+ options=["zero-shot-react-description", "openai-functions", "openai-tools"],
+ value="openai-tools",
+ ),
+ MessageTextInput(
+ name="input_value",
+ display_name="Text",
+ info="Text to be passed as input and extract info from the CSV File.",
+ required=True,
+ ),
+ DictInput(
+ name="pandas_kwargs",
+ display_name="Pandas Kwargs",
+ info="Pandas Kwargs to be passed to the agent.",
+ advanced=True,
+ is_list=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Response", name="response", method="build_agent_response"),
+ Output(display_name="Agent", name="agent", method="build_agent", hidden=True, tool_mode=False),
+ ]
+
+ def _path(self) -> str:
+ if isinstance(self.path, Message) and isinstance(self.path.text, str):
+ return self.path.text
+ return self.path
+
+ def build_agent_response(self) -> Message:
+ agent_kwargs = {
+ "verbose": self.verbose,
+ "allow_dangerous_code": True,
+ }
+
+ agent_csv = create_csv_agent(
+ llm=self.llm,
+ path=self._path(),
+ agent_type=self.agent_type,
+ handle_parsing_errors=self.handle_parsing_errors,
+ pandas_kwargs=self.pandas_kwargs,
+ **agent_kwargs,
+ )
+
+ result = agent_csv.invoke({"input": self.input_value})
+ return Message(text=str(result["output"]))
+
+ def build_agent(self) -> AgentExecutor:
+ agent_kwargs = {
+ "verbose": self.verbose,
+ "allow_dangerous_code": True,
+ }
+
+ agent_csv = create_csv_agent(
+ llm=self.llm,
+ path=self._path(),
+ agent_type=self.agent_type,
+ handle_parsing_errors=self.handle_parsing_errors,
+ pandas_kwargs=self.pandas_kwargs,
+ **agent_kwargs,
+ )
+
+ self.status = Message(text=str(agent_csv))
+
+ return agent_csv
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/fake_embeddings.py b/langflow/src/backend/base/langflow/components/langchain_utilities/fake_embeddings.py
new file mode 100644
index 0000000..bb0e1ff
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/fake_embeddings.py
@@ -0,0 +1,26 @@
+from langchain_community.embeddings import FakeEmbeddings
+
+from langflow.base.embeddings.model import LCEmbeddingsModel
+from langflow.field_typing import Embeddings
+from langflow.io import IntInput
+
+
+class FakeEmbeddingsComponent(LCEmbeddingsModel):
+ display_name = "Fake Embeddings"
+ description = "Generate fake embeddings, useful for initial testing and connecting components."
+ icon = "LangChain"
+ name = "LangChainFakeEmbeddings"
+
+ inputs = [
+ IntInput(
+ name="dimensions",
+ display_name="Dimensions",
+ info="The number of dimensions the resulting output embeddings should have.",
+ value=5,
+ ),
+ ]
+
+ def build_embeddings(self) -> Embeddings:
+ return FakeEmbeddings(
+ size=self.dimensions or 5,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/html_link_extractor.py b/langflow/src/backend/base/langflow/components/langchain_utilities/html_link_extractor.py
new file mode 100644
index 0000000..0d3e654
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/html_link_extractor.py
@@ -0,0 +1,35 @@
+from typing import Any
+
+from langchain_community.graph_vectorstores.extractors import HtmlLinkExtractor, LinkExtractorTransformer
+from langchain_core.documents import BaseDocumentTransformer
+
+from langflow.base.document_transformers.model import LCDocumentTransformerComponent
+from langflow.inputs import BoolInput, DataInput, StrInput
+
+
+class HtmlLinkExtractorComponent(LCDocumentTransformerComponent):
+ display_name = "HTML Link Extractor"
+ description = "Extract hyperlinks from HTML content."
+ documentation = "https://python.langchain.com/v0.2/api_reference/community/graph_vectorstores/langchain_community.graph_vectorstores.extractors.html_link_extractor.HtmlLinkExtractor.html"
+ name = "HtmlLinkExtractor"
+ icon = "LangChain"
+
+ inputs = [
+ StrInput(name="kind", display_name="Kind of edge", value="hyperlink", required=False),
+ BoolInput(name="drop_fragments", display_name="Drop URL fragments", value=True, required=False),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The texts from which to extract links.",
+ input_types=["Document", "Data"],
+ required=True,
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_document_transformer(self) -> BaseDocumentTransformer:
+ return LinkExtractorTransformer(
+ [HtmlLinkExtractor(kind=self.kind, drop_fragments=self.drop_fragments).as_document_extractor()]
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/json_agent.py b/langflow/src/backend/base/langflow/components/langchain_utilities/json_agent.py
new file mode 100644
index 0000000..e241ac5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/json_agent.py
@@ -0,0 +1,45 @@
+from pathlib import Path
+
+import yaml
+from langchain.agents import AgentExecutor
+from langchain_community.agent_toolkits import create_json_agent
+from langchain_community.agent_toolkits.json.toolkit import JsonToolkit
+from langchain_community.tools.json.tool import JsonSpec
+
+from langflow.base.agents.agent import LCAgentComponent
+from langflow.inputs import FileInput, HandleInput
+
+
+class JsonAgentComponent(LCAgentComponent):
+ display_name = "JsonAgent"
+ description = "Construct a json agent from an LLM and tools."
+ name = "JsonAgent"
+ legacy: bool = True
+
+ inputs = [
+ *LCAgentComponent._base_inputs,
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ FileInput(
+ name="path",
+ display_name="File Path",
+ file_types=["json", "yaml", "yml"],
+ required=True,
+ ),
+ ]
+
+ def build_agent(self) -> AgentExecutor:
+ path = Path(self.path)
+ if path.suffix in {"yaml", "yml"}:
+ with path.open(encoding="utf-8") as file:
+ yaml_dict = yaml.safe_load(file)
+ spec = JsonSpec(dict_=yaml_dict)
+ else:
+ spec = JsonSpec.from_file(path)
+ toolkit = JsonToolkit(spec=spec)
+
+ return create_json_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs())
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/json_document_builder.py b/langflow/src/backend/base/langflow/components/langchain_utilities/json_document_builder.py
new file mode 100644
index 0000000..402a651
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/json_document_builder.py
@@ -0,0 +1,50 @@
+# 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 langchain_core.documents import Document
+
+from langflow.custom import CustomComponent
+from langflow.services.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."
+ name = "JSONDocumentBuilder"
+ legacy: bool = True
+
+ output_types: list[str] = ["Document"]
+ 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:
+ msg = f"Expected Document or list of Documents, got {type(document)}"
+ raise TypeError(msg)
+ self.repr_value = documents
+ return documents
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/langchain_hub.py b/langflow/src/backend/base/langflow/components/langchain_utilities/langchain_hub.py
new file mode 100644
index 0000000..593afd3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/langchain_hub.py
@@ -0,0 +1,126 @@
+import re
+
+from langchain_core.prompts import HumanMessagePromptTemplate
+
+from langflow.custom import Component
+from langflow.inputs import DefaultPromptField, SecretStrInput, StrInput
+from langflow.io import Output
+from langflow.schema.message import Message
+
+
+class LangChainHubPromptComponent(Component):
+ display_name: str = "Prompt Hub"
+ description: str = "Prompt Component that uses LangChain Hub prompts"
+ beta = True
+ icon = "LangChain"
+ trace_type = "prompt"
+ name = "LangChain Hub Prompt"
+
+ inputs = [
+ SecretStrInput(
+ name="langchain_api_key",
+ display_name="Your LangChain API Key",
+ info="The LangChain API Key to use.",
+ required=True,
+ ),
+ StrInput(
+ name="langchain_hub_prompt",
+ display_name="LangChain Hub Prompt",
+ info="The LangChain Hub prompt to use, i.e., 'efriis/my-first-prompt'",
+ refresh_button=True,
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Build Prompt", name="prompt", method="build_prompt"),
+ ]
+
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ # If the field is not langchain_hub_prompt or the value is empty, return the build config as is
+ if field_name != "langchain_hub_prompt" or not field_value:
+ return build_config
+
+ # Fetch the template
+ template = self._fetch_langchain_hub_template()
+
+ # Get the template's messages
+ if hasattr(template, "messages"):
+ template_messages = template.messages
+ else:
+ template_messages = [HumanMessagePromptTemplate(prompt=template)]
+
+ # Extract the messages from the prompt data
+ prompt_template = [message_data.prompt for message_data in template_messages]
+
+ # Regular expression to find all instances of {}
+ pattern = r"\{(.*?)\}"
+
+ # Get all the custom fields
+ custom_fields: list[str] = []
+ full_template = ""
+ for message in prompt_template:
+ # Find all matches
+ matches = re.findall(pattern, message.template)
+ custom_fields += matches
+
+ # Create a string version of the full template
+ full_template = full_template + "\n" + message.template
+
+ # No need to reprocess if we have them already
+ if all("param_" + custom_field in build_config for custom_field in custom_fields):
+ return build_config
+
+ # Easter egg: Show template in info popup
+ build_config["langchain_hub_prompt"]["info"] = full_template
+
+ # Remove old parameter inputs if any
+ for key in build_config.copy():
+ if key.startswith("param_"):
+ del build_config[key]
+
+ # Now create inputs for each
+ for custom_field in custom_fields:
+ new_parameter = DefaultPromptField(
+ name=f"param_{custom_field}",
+ display_name=custom_field,
+ info="Fill in the value for {" + custom_field + "}",
+ ).to_dict()
+
+ # Add the new parameter to the build config
+ build_config[f"param_{custom_field}"] = new_parameter
+
+ return build_config
+
+ async def build_prompt(
+ self,
+ ) -> Message:
+ # Fetch the template
+ template = self._fetch_langchain_hub_template()
+
+ # Get the parameters from the attributes
+ params_dict = {param: getattr(self, "param_" + param, f"{{{param}}}") for param in template.input_variables}
+ original_params = {k: v.text if hasattr(v, "text") else v for k, v in params_dict.items() if v is not None}
+ prompt_value = template.invoke(original_params)
+
+ # Update the template with the new value
+ original_params["template"] = prompt_value.to_string()
+
+ # Now pass the filtered attributes to the function
+ prompt = Message.from_template(**original_params)
+
+ self.status = prompt.text
+
+ return prompt
+
+ def _fetch_langchain_hub_template(self):
+ import langchain.hub
+
+ # Check if the api key is provided
+ if not self.langchain_api_key:
+ msg = "Please provide a LangChain API Key"
+
+ raise ValueError(msg)
+
+ # Pull the prompt from LangChain Hub
+ return langchain.hub.pull(self.langchain_hub_prompt, api_key=self.langchain_api_key)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/language_recursive.py b/langflow/src/backend/base/langflow/components/langchain_utilities/language_recursive.py
new file mode 100644
index 0000000..66f909e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/language_recursive.py
@@ -0,0 +1,49 @@
+from typing import Any
+
+from langchain_text_splitters import Language, RecursiveCharacterTextSplitter, TextSplitter
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.inputs import DataInput, DropdownInput, IntInput
+
+
+class LanguageRecursiveTextSplitterComponent(LCTextSplitterComponent):
+ 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"
+ name = "LanguageRecursiveTextSplitter"
+ icon = "LangChain"
+
+ inputs = [
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum length of each chunk.",
+ value=1000,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="The amount of overlap between chunks.",
+ value=200,
+ ),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The texts to split.",
+ input_types=["Document", "Data"],
+ required=True,
+ ),
+ DropdownInput(
+ name="code_language", display_name="Code Language", options=[x.value for x in Language], value="python"
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_text_splitter(self) -> TextSplitter:
+ return RecursiveCharacterTextSplitter.from_language(
+ language=Language(self.code_language),
+ chunk_size=self.chunk_size,
+ chunk_overlap=self.chunk_overlap,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/language_semantic.py b/langflow/src/backend/base/langflow/components/langchain_utilities/language_semantic.py
new file mode 100644
index 0000000..6edad7d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/language_semantic.py
@@ -0,0 +1,138 @@
+from langchain.docstore.document import Document
+from langchain_experimental.text_splitter import SemanticChunker
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.io import (
+ DropdownInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ MessageTextInput,
+ Output,
+)
+from langflow.schema import Data
+
+
+class SemanticTextSplitterComponent(LCTextSplitterComponent):
+ """Split text into semantically meaningful chunks using semantic similarity."""
+
+ display_name: str = "Semantic Text Splitter"
+ name: str = "SemanticTextSplitter"
+ description: str = "Split text into semantically meaningful chunks using semantic similarity."
+ documentation = "https://python.langchain.com/docs/how_to/semantic-chunker/"
+ beta = True # this component is beta because it is imported from langchain_experimental
+ icon = "LangChain"
+
+ inputs = [
+ HandleInput(
+ name="data_inputs",
+ display_name="Data Inputs",
+ info="List of Data objects containing text and metadata to split.",
+ input_types=["Data"],
+ is_list=True,
+ required=True,
+ ),
+ HandleInput(
+ name="embeddings",
+ display_name="Embeddings",
+ info="Embeddings model to use for semantic similarity. Required.",
+ input_types=["Embeddings"],
+ is_list=False,
+ required=True,
+ ),
+ DropdownInput(
+ name="breakpoint_threshold_type",
+ display_name="Breakpoint Threshold Type",
+ info=(
+ "Method to determine breakpoints. Options: 'percentile', "
+ "'standard_deviation', 'interquartile'. Defaults to 'percentile'."
+ ),
+ value="percentile",
+ options=["percentile", "standard_deviation", "interquartile"],
+ ),
+ FloatInput(
+ name="breakpoint_threshold_amount",
+ display_name="Breakpoint Threshold Amount",
+ info="Numerical amount for the breakpoint threshold.",
+ value=0.5,
+ ),
+ IntInput(
+ name="number_of_chunks",
+ display_name="Number of Chunks",
+ info="Number of chunks to split the text into.",
+ value=5,
+ ),
+ MessageTextInput(
+ name="sentence_split_regex",
+ display_name="Sentence Split Regex",
+ info="Regular expression to split sentences. Optional.",
+ value="",
+ advanced=True,
+ ),
+ IntInput(
+ name="buffer_size",
+ display_name="Buffer Size",
+ info="Size of the buffer.",
+ value=0,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Chunks", name="chunks", method="split_text"),
+ ]
+
+ def _docs_to_data(self, docs: list[Document]) -> list[Data]:
+ """Convert a list of Document objects to Data objects."""
+ return [Data(text=doc.page_content, data=doc.metadata) for doc in docs]
+
+ def split_text(self) -> list[Data]:
+ """Split the input data into semantically meaningful chunks."""
+ try:
+ embeddings = getattr(self, "embeddings", None)
+ if embeddings is None:
+ error_msg = "An embeddings model is required for SemanticTextSplitter."
+ raise ValueError(error_msg)
+
+ if not self.data_inputs:
+ error_msg = "Data inputs cannot be empty."
+ raise ValueError(error_msg)
+
+ documents = []
+ for _input in self.data_inputs:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ error_msg = f"Invalid data input type: {_input}"
+ raise TypeError(error_msg)
+
+ if not documents:
+ error_msg = "No valid Data objects found in data_inputs."
+ raise ValueError(error_msg)
+
+ texts = [doc.page_content for doc in documents]
+ metadatas = [doc.metadata for doc in documents]
+
+ splitter_params = {
+ "embeddings": embeddings,
+ "breakpoint_threshold_type": self.breakpoint_threshold_type or "percentile",
+ "breakpoint_threshold_amount": self.breakpoint_threshold_amount,
+ "number_of_chunks": self.number_of_chunks,
+ "buffer_size": self.buffer_size,
+ }
+
+ if self.sentence_split_regex:
+ splitter_params["sentence_split_regex"] = self.sentence_split_regex
+
+ splitter = SemanticChunker(**splitter_params)
+ docs = splitter.create_documents(texts, metadatas=metadatas)
+
+ data = self._docs_to_data(docs)
+ self.status = data
+
+ except Exception as e:
+ error_msg = f"An error occurred during semantic splitting: {e}"
+ raise RuntimeError(error_msg) from e
+
+ else:
+ return data
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/llm_checker.py b/langflow/src/backend/base/langflow/components/langchain_utilities/llm_checker.py
new file mode 100644
index 0000000..b9ba6a9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/llm_checker.py
@@ -0,0 +1,39 @@
+from langchain.chains import LLMCheckerChain
+
+from langflow.base.chains.model import LCChainComponent
+from langflow.field_typing import Message
+from langflow.inputs import HandleInput, MultilineInput
+
+
+class LLMCheckerChainComponent(LCChainComponent):
+ display_name = "LLMCheckerChain"
+ description = "Chain for question-answering with self-verification."
+ documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
+ name = "LLMCheckerChain"
+ legacy: bool = True
+ icon = "LangChain"
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ info="The input value to pass to the chain.",
+ required=True,
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ ]
+
+ def invoke_chain(self) -> Message:
+ chain = LLMCheckerChain.from_llm(llm=self.llm)
+ response = chain.invoke(
+ {chain.input_key: self.input_value},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+ result = response.get(chain.output_key, "")
+ result = str(result)
+ self.status = result
+ return Message(text=result)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/llm_math.py b/langflow/src/backend/base/langflow/components/langchain_utilities/llm_math.py
new file mode 100644
index 0000000..824cf68
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/llm_math.py
@@ -0,0 +1,42 @@
+from langchain.chains import LLMMathChain
+
+from langflow.base.chains.model import LCChainComponent
+from langflow.field_typing import Message
+from langflow.inputs import HandleInput, MultilineInput
+from langflow.template import Output
+
+
+class LLMMathChainComponent(LCChainComponent):
+ display_name = "LLMMathChain"
+ description = "Chain that interprets a prompt and executes python code to do math."
+ documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_math"
+ name = "LLMMathChain"
+ legacy: bool = True
+ icon = "LangChain"
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ info="The input value to pass to the chain.",
+ required=True,
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ ]
+
+ outputs = [Output(display_name="Message", name="text", method="invoke_chain")]
+
+ def invoke_chain(self) -> Message:
+ chain = LLMMathChain.from_llm(llm=self.llm)
+ response = chain.invoke(
+ {chain.input_key: self.input_value},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+ result = response.get(chain.output_key, "")
+ result = str(result)
+ self.status = result
+ return Message(text=result)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/natural_language.py b/langflow/src/backend/base/langflow/components/langchain_utilities/natural_language.py
new file mode 100644
index 0000000..f6e558d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/natural_language.py
@@ -0,0 +1,61 @@
+from typing import Any
+
+from langchain_text_splitters import NLTKTextSplitter, TextSplitter
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.inputs import DataInput, IntInput, MessageTextInput
+from langflow.utils.util import unescape_string
+
+
+class NaturalLanguageTextSplitterComponent(LCTextSplitterComponent):
+ display_name = "Natural Language Text Splitter"
+ description = "Split text based on natural language boundaries, optimized for a specified language."
+ documentation = (
+ "https://python.langchain.com/v0.1/docs/modules/data_connection/document_transformers/split_by_token/#nltk"
+ )
+ name = "NaturalLanguageTextSplitter"
+ icon = "LangChain"
+ inputs = [
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum number of characters in each chunk after splitting.",
+ value=1000,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="The number of characters that overlap between consecutive chunks.",
+ value=200,
+ ),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The text data to be split.",
+ input_types=["Document", "Data"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="separator",
+ display_name="Separator",
+ info='The character(s) to use as a delimiter when splitting text.\nDefaults to "\\n\\n" if left empty.',
+ ),
+ MessageTextInput(
+ name="language",
+ display_name="Language",
+ info='The language of the text. Default is "English". '
+ "Supports multiple languages for better text boundary recognition.",
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_text_splitter(self) -> TextSplitter:
+ separator = unescape_string(self.separator) if self.separator else "\n\n"
+ return NLTKTextSplitter(
+ language=self.language.lower() if self.language else "english",
+ separator=separator,
+ chunk_size=self.chunk_size,
+ chunk_overlap=self.chunk_overlap,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/openai_tools.py b/langflow/src/backend/base/langflow/components/langchain_utilities/openai_tools.py
new file mode 100644
index 0000000..c529330
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/openai_tools.py
@@ -0,0 +1,50 @@
+from langchain.agents import create_openai_tools_agent
+from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, PromptTemplate
+
+from langflow.base.agents.agent import LCToolsAgentComponent
+from langflow.inputs import MultilineInput
+from langflow.inputs.inputs import DataInput, HandleInput
+from langflow.schema import Data
+
+
+class OpenAIToolsAgentComponent(LCToolsAgentComponent):
+ display_name: str = "OpenAI Tools Agent"
+ description: str = "Agent that uses tools via openai-tools."
+ icon = "LangChain"
+ name = "OpenAIToolsAgent"
+
+ inputs = [
+ *LCToolsAgentComponent._base_inputs,
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel", "ToolEnabledLanguageModel"],
+ required=True,
+ ),
+ MultilineInput(
+ name="system_prompt",
+ display_name="System Prompt",
+ info="System prompt for the agent.",
+ value="You are a helpful assistant",
+ ),
+ MultilineInput(
+ name="user_prompt", display_name="Prompt", info="This prompt must contain 'input' key.", value="{input}"
+ ),
+ DataInput(name="chat_history", display_name="Chat History", is_list=True, advanced=True),
+ ]
+
+ def get_chat_history_data(self) -> list[Data] | None:
+ return self.chat_history
+
+ def create_agent_runnable(self):
+ if "input" not in self.user_prompt:
+ msg = "Prompt must contain 'input' key."
+ raise ValueError(msg)
+ messages = [
+ ("system", self.system_prompt),
+ ("placeholder", "{chat_history}"),
+ HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)),
+ ("placeholder", "{agent_scratchpad}"),
+ ]
+ prompt = ChatPromptTemplate.from_messages(messages)
+ return create_openai_tools_agent(self.llm, self.tools, prompt)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/openapi.py b/langflow/src/backend/base/langflow/components/langchain_utilities/openapi.py
new file mode 100644
index 0000000..3e662e7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/openapi.py
@@ -0,0 +1,48 @@
+from pathlib import Path
+
+import yaml
+from langchain.agents import AgentExecutor
+from langchain_community.agent_toolkits import create_openapi_agent
+from langchain_community.agent_toolkits.openapi.toolkit import OpenAPIToolkit
+from langchain_community.tools.json.tool import JsonSpec
+from langchain_community.utilities.requests import TextRequestsWrapper
+
+from langflow.base.agents.agent import LCAgentComponent
+from langflow.inputs import BoolInput, FileInput, HandleInput
+
+
+class OpenAPIAgentComponent(LCAgentComponent):
+ display_name = "OpenAPI Agent"
+ description = "Agent to interact with OpenAPI API."
+ name = "OpenAPIAgent"
+ icon = "LangChain"
+ inputs = [
+ *LCAgentComponent._base_inputs,
+ HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
+ FileInput(name="path", display_name="File Path", file_types=["json", "yaml", "yml"], required=True),
+ BoolInput(name="allow_dangerous_requests", display_name="Allow Dangerous Requests", value=False, required=True),
+ ]
+
+ def build_agent(self) -> AgentExecutor:
+ path = Path(self.path)
+ if path.suffix in {"yaml", "yml"}:
+ with path.open(encoding="utf-8") as file:
+ yaml_dict = yaml.safe_load(file)
+ spec = JsonSpec(dict_=yaml_dict)
+ else:
+ spec = JsonSpec.from_file(path)
+ requests_wrapper = TextRequestsWrapper()
+ toolkit = OpenAPIToolkit.from_llm(
+ llm=self.llm,
+ json_spec=spec,
+ requests_wrapper=requests_wrapper,
+ allow_dangerous_requests=self.allow_dangerous_requests,
+ )
+
+ agent_args = self.get_agent_kwargs()
+
+ # This is bit weird - generally other create_*_agent functions have max_iterations in the
+ # `agent_executor_kwargs`, but openai has this parameter passed directly.
+ agent_args["max_iterations"] = agent_args["agent_executor_kwargs"]["max_iterations"]
+ del agent_args["agent_executor_kwargs"]["max_iterations"]
+ return create_openapi_agent(llm=self.llm, toolkit=toolkit, **agent_args)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/recursive_character.py b/langflow/src/backend/base/langflow/components/langchain_utilities/recursive_character.py
new file mode 100644
index 0000000..86d7288
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/recursive_character.py
@@ -0,0 +1,60 @@
+from typing import Any
+
+from langchain_text_splitters import RecursiveCharacterTextSplitter, TextSplitter
+
+from langflow.base.textsplitters.model import LCTextSplitterComponent
+from langflow.inputs.inputs import DataInput, IntInput, MessageTextInput
+from langflow.utils.util import unescape_string
+
+
+class RecursiveCharacterTextSplitterComponent(LCTextSplitterComponent):
+ display_name: str = "Recursive Character Text Splitter"
+ description: str = "Split text trying to keep all related text together."
+ documentation: str = "https://docs.langflow.org/components-processing"
+ name = "RecursiveCharacterTextSplitter"
+ icon = "LangChain"
+
+ inputs = [
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum length of each chunk.",
+ value=1000,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="The amount of overlap between chunks.",
+ value=200,
+ ),
+ DataInput(
+ name="data_input",
+ display_name="Input",
+ info="The texts to split.",
+ input_types=["Document", "Data"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="separators",
+ display_name="Separators",
+ info='The characters to split on.\nIf left empty defaults to ["\\n\\n", "\\n", " ", ""].',
+ is_list=True,
+ ),
+ ]
+
+ def get_data_input(self) -> Any:
+ return self.data_input
+
+ def build_text_splitter(self) -> TextSplitter:
+ if not self.separators:
+ separators: list[str] | None = None
+ else:
+ # check if the separators list has escaped characters
+ # if there are escaped characters, unescape them
+ separators = [unescape_string(x) for x in self.separators]
+
+ return RecursiveCharacterTextSplitter(
+ separators=separators,
+ chunk_size=self.chunk_size,
+ chunk_overlap=self.chunk_overlap,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/retrieval_qa.py b/langflow/src/backend/base/langflow/components/langchain_utilities/retrieval_qa.py
new file mode 100644
index 0000000..deebd7b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/retrieval_qa.py
@@ -0,0 +1,81 @@
+from langchain.chains import RetrievalQA
+
+from langflow.base.chains.model import LCChainComponent
+from langflow.field_typing import Message
+from langflow.inputs import BoolInput, DropdownInput, HandleInput, MultilineInput
+
+
+class RetrievalQAComponent(LCChainComponent):
+ display_name = "Retrieval QA"
+ description = "Chain for question-answering querying sources from a retriever."
+ name = "RetrievalQA"
+ legacy: bool = True
+ icon = "LangChain"
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ info="The input value to pass to the chain.",
+ required=True,
+ ),
+ DropdownInput(
+ name="chain_type",
+ display_name="Chain Type",
+ info="Chain type to use.",
+ options=["Stuff", "Map Reduce", "Refine", "Map Rerank"],
+ value="Stuff",
+ advanced=True,
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ HandleInput(
+ name="retriever",
+ display_name="Retriever",
+ input_types=["Retriever"],
+ required=True,
+ ),
+ HandleInput(
+ name="memory",
+ display_name="Memory",
+ input_types=["BaseChatMemory"],
+ ),
+ BoolInput(
+ name="return_source_documents",
+ display_name="Return Source Documents",
+ value=False,
+ ),
+ ]
+
+ def invoke_chain(self) -> Message:
+ chain_type = self.chain_type.lower().replace(" ", "_")
+ if self.memory:
+ self.memory.input_key = "query"
+ self.memory.output_key = "result"
+
+ runnable = RetrievalQA.from_chain_type(
+ llm=self.llm,
+ chain_type=chain_type,
+ retriever=self.retriever,
+ memory=self.memory,
+ # always include to help debugging
+ #
+ return_source_documents=True,
+ )
+
+ result = runnable.invoke(
+ {"query": self.input_value},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+
+ source_docs = self.to_data(result.get("source_documents", keys=[]))
+ result_str = str(result.get("result", ""))
+ if self.return_source_documents and len(source_docs):
+ references_str = self.create_references_from_data(source_docs)
+ result_str = f"{result_str}\n{references_str}"
+ # put the entire result to debug history, query and content
+ self.status = {**result, "source_documents": source_docs, "output": result_str}
+ return result_str
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/retriever.py b/langflow/src/backend/base/langflow/components/langchain_utilities/retriever.py
new file mode 100644
index 0000000..c016ec0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/retriever.py
@@ -0,0 +1,32 @@
+from langchain_core.tools import create_retriever_tool
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import BaseRetriever, Tool
+
+
+class RetrieverToolComponent(CustomComponent):
+ display_name = "RetrieverTool"
+ description = "Tool for interacting with retriever"
+ name = "RetrieverTool"
+ legacy = True
+ icon = "LangChain"
+
+ def build_config(self):
+ return {
+ "retriever": {
+ "display_name": "Retriever",
+ "info": "Retriever to interact with",
+ "type": BaseRetriever,
+ "input_types": ["Retriever"],
+ },
+ "name": {"display_name": "Name", "info": "Name of the tool"},
+ "description": {"display_name": "Description", "info": "Description of the tool"},
+ }
+
+ def build(self, retriever: BaseRetriever, name: str, description: str, **kwargs) -> Tool:
+ _ = kwargs
+ return create_retriever_tool(
+ retriever=retriever,
+ name=name,
+ description=description,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/runnable_executor.py b/langflow/src/backend/base/langflow/components/langchain_utilities/runnable_executor.py
new file mode 100644
index 0000000..dccb03f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/runnable_executor.py
@@ -0,0 +1,137 @@
+from langchain.agents import AgentExecutor
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, HandleInput, MessageTextInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class RunnableExecComponent(Component):
+ description = "Execute a runnable. It will try to guess the input and output keys."
+ display_name = "Runnable Executor"
+ name = "RunnableExecutor"
+ beta: bool = True
+ icon = "LangChain"
+
+ inputs = [
+ MessageTextInput(name="input_value", display_name="Input", required=True),
+ HandleInput(
+ name="runnable",
+ display_name="Agent Executor",
+ input_types=["Chain", "AgentExecutor", "Agent", "Runnable"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="input_key",
+ display_name="Input Key",
+ value="input",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="output_key",
+ display_name="Output Key",
+ value="output",
+ advanced=True,
+ ),
+ BoolInput(
+ name="use_stream",
+ display_name="Stream",
+ value=False,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Message",
+ name="text",
+ method="build_executor",
+ ),
+ ]
+
+ def get_output(self, result, input_key, output_key):
+ """Retrieves the output value from the given result dictionary based on the specified input and output keys.
+
+ Args:
+ result (dict): The result dictionary containing the output value.
+ input_key (str): The key used to retrieve the input value from the result dictionary.
+ output_key (str): The key used to retrieve the output value from the result dictionary.
+
+ Returns:
+ tuple: A tuple containing the output value and the status message.
+
+ """
+ possible_output_keys = ["answer", "response", "output", "result", "text"]
+ status = ""
+ result_value = None
+
+ if output_key in result:
+ result_value = result.get(output_key)
+ elif len(result) == 2 and input_key in result: # noqa: PLR2004
+ # get the other key from the result dict
+ other_key = next(k for k in result if k != input_key)
+ if other_key == output_key:
+ result_value = result.get(output_key)
+ else:
+ status += f"Warning: The output key is not '{output_key}'. The output key is '{other_key}'."
+ result_value = result.get(other_key)
+ elif len(result) == 1:
+ result_value = next(iter(result.values()))
+ elif any(k in result for k in possible_output_keys):
+ for key in possible_output_keys:
+ if key in result:
+ result_value = result.get(key)
+ status += f"Output key: '{key}'."
+ break
+ if result_value is None:
+ result_value = result
+ status += f"Warning: The output key is not '{output_key}'."
+ else:
+ result_value = result
+ status += f"Warning: The output key is not '{output_key}'."
+
+ return result_value, status
+
+ def get_input_dict(self, runnable, input_key, input_value):
+ """Returns a dictionary containing the input key-value pair for the given runnable.
+
+ Args:
+ runnable: The runnable object.
+ input_key: The key for the input value.
+ input_value: The value for the input key.
+
+ Returns:
+ input_dict: A dictionary containing the input key-value pair.
+ status: A status message indicating if the input key is not in the runnable's input keys.
+ """
+ input_dict = {}
+ status = ""
+ if hasattr(runnable, "input_keys"):
+ # Check if input_key is in the runnable's input_keys
+ if input_key in runnable.input_keys:
+ input_dict[input_key] = input_value
+ else:
+ input_dict = dict.fromkeys(runnable.input_keys, input_value)
+ status = f"Warning: The input key is not '{input_key}'. The input key is '{runnable.input_keys}'."
+ return input_dict, status
+
+ async def build_executor(self) -> Message:
+ input_dict, status = self.get_input_dict(self.runnable, self.input_key, self.input_value)
+ if not isinstance(self.runnable, AgentExecutor):
+ msg = "The runnable must be an AgentExecutor"
+ raise TypeError(msg)
+
+ if self.use_stream:
+ return self.astream_events(input_dict)
+ result = await self.runnable.ainvoke(input_dict)
+ result_value, status_ = self.get_output(result, self.input_key, self.output_key)
+ status += status_
+ status += f"\n\nOutput: {result_value}\n\nRaw Output: {result}"
+ self.status = status
+ return result_value
+
+ async def astream_events(self, runnable_input):
+ async for event in self.runnable.astream_events(runnable_input, version="v1"):
+ if event.get("event") != "on_chat_model_stream":
+ continue
+
+ yield event.get("data").get("chunk")
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/self_query.py b/langflow/src/backend/base/langflow/components/langchain_utilities/self_query.py
new file mode 100644
index 0000000..0f3acee
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/self_query.py
@@ -0,0 +1,80 @@
+from langchain.chains.query_constructor.base import AttributeInfo
+from langchain.retrievers.self_query.base import SelfQueryRetriever
+
+from langflow.custom import Component
+from langflow.inputs import HandleInput, MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class SelfQueryRetrieverComponent(Component):
+ display_name = "Self Query Retriever"
+ description = "Retriever that uses a vector store and an LLM to generate the vector store queries."
+ name = "SelfQueryRetriever"
+ icon = "LangChain"
+ legacy: bool = True
+
+ inputs = [
+ HandleInput(
+ name="query",
+ display_name="Query",
+ info="Query to be passed as input.",
+ input_types=["Message"],
+ ),
+ HandleInput(
+ name="vectorstore",
+ display_name="Vector Store",
+ info="Vector Store to be passed as input.",
+ input_types=["VectorStore"],
+ ),
+ HandleInput(
+ name="attribute_infos",
+ display_name="Metadata Field Info",
+ info="Metadata Field Info to be passed as input.",
+ input_types=["Data"],
+ is_list=True,
+ ),
+ MessageTextInput(
+ name="document_content_description",
+ display_name="Document Content Description",
+ info="Document Content Description to be passed as input.",
+ ),
+ HandleInput(
+ name="llm",
+ display_name="LLM",
+ info="LLM to be passed as input.",
+ input_types=["LanguageModel"],
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Retrieved Documents",
+ name="documents",
+ method="retrieve_documents",
+ ),
+ ]
+
+ def retrieve_documents(self) -> list[Data]:
+ metadata_field_infos = [AttributeInfo(**value.data) for value in self.attribute_infos]
+ self_query_retriever = SelfQueryRetriever.from_llm(
+ llm=self.llm,
+ vectorstore=self.vectorstore,
+ document_contents=self.document_content_description,
+ metadata_field_info=metadata_field_infos,
+ enable_limit=True,
+ )
+
+ if isinstance(self.query, Message):
+ input_text = self.query.text
+ elif isinstance(self.query, str):
+ input_text = self.query
+ else:
+ msg = f"Query type {type(self.query)} not supported."
+ raise TypeError(msg)
+
+ documents = self_query_retriever.invoke(input=input_text, config={"callbacks": self.get_langchain_callbacks()})
+ data = [Data.from_document(document) for document in documents]
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/spider.py b/langflow/src/backend/base/langflow/components/langchain_utilities/spider.py
new file mode 100644
index 0000000..30c2aba
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/spider.py
@@ -0,0 +1,142 @@
+from spider.spider import Spider
+
+from langflow.base.langchain_utilities.spider_constants import MODES
+from langflow.custom import Component
+from langflow.io import (
+ BoolInput,
+ DictInput,
+ DropdownInput,
+ IntInput,
+ Output,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class SpiderTool(Component):
+ display_name: str = "Spider Web Crawler & Scraper"
+ description: str = "Spider API for web crawling and scraping."
+ output_types: list[str] = ["Document"]
+ documentation: str = "https://spider.cloud/docs/api"
+
+ inputs = [
+ SecretStrInput(
+ name="spider_api_key",
+ display_name="Spider API Key",
+ required=True,
+ password=True,
+ info="The Spider API Key, get it from https://spider.cloud",
+ ),
+ StrInput(
+ name="url",
+ display_name="URL",
+ required=True,
+ info="The URL to scrape or crawl",
+ ),
+ DropdownInput(
+ name="mode",
+ display_name="Mode",
+ required=True,
+ options=MODES,
+ value=MODES[0],
+ info="The mode of operation: scrape or crawl",
+ ),
+ IntInput(
+ name="limit",
+ display_name="Limit",
+ info="The maximum amount of pages allowed to crawl per website. Set to 0 to crawl all pages.",
+ advanced=True,
+ ),
+ IntInput(
+ name="depth",
+ display_name="Depth",
+ info="The crawl limit for maximum depth. If 0, no limit will be applied.",
+ advanced=True,
+ ),
+ StrInput(
+ name="blacklist",
+ display_name="Blacklist",
+ info="Blacklist paths that you do not want to crawl. Use Regex patterns.",
+ advanced=True,
+ ),
+ StrInput(
+ name="whitelist",
+ display_name="Whitelist",
+ info="Whitelist paths that you want to crawl, ignoring all other routes. Use Regex patterns.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="readability",
+ display_name="Use Readability",
+ info="Use readability to pre-process the content for reading.",
+ advanced=True,
+ ),
+ IntInput(
+ name="request_timeout",
+ display_name="Request Timeout",
+ info="Timeout for the request in seconds.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="metadata",
+ display_name="Metadata",
+ info="Include metadata in the response.",
+ advanced=True,
+ ),
+ DictInput(
+ name="params",
+ display_name="Additional Parameters",
+ info="Additional parameters to pass to the API. If provided, other inputs will be ignored.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Markdown", name="content", method="crawl"),
+ ]
+
+ def crawl(self) -> list[Data]:
+ if self.params:
+ parameters = self.params["data"]
+ else:
+ parameters = {
+ "limit": self.limit or None,
+ "depth": self.depth or None,
+ "blacklist": self.blacklist or None,
+ "whitelist": self.whitelist or None,
+ "readability": self.readability,
+ "request_timeout": self.request_timeout or None,
+ "metadata": self.metadata,
+ "return_format": "markdown",
+ }
+
+ app = Spider(api_key=self.spider_api_key)
+ if self.mode == "scrape":
+ parameters["limit"] = 1
+ result = app.scrape_url(self.url, parameters)
+ elif self.mode == "crawl":
+ result = app.crawl_url(self.url, parameters)
+ else:
+ msg = f"Invalid mode: {self.mode}. Must be 'scrape' or 'crawl'."
+ raise ValueError(msg)
+
+ records = []
+
+ for record in result:
+ if self.metadata:
+ records.append(
+ Data(
+ data={
+ "content": record["content"],
+ "url": record["url"],
+ "metadata": record["metadata"],
+ }
+ )
+ )
+ else:
+ records.append(Data(data={"content": record["content"], "url": record["url"]}))
+ return records
+
+
+class SpiderToolError(Exception):
+ """SpiderTool error."""
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/sql.py b/langflow/src/backend/base/langflow/components/langchain_utilities/sql.py
new file mode 100644
index 0000000..06916e2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/sql.py
@@ -0,0 +1,34 @@
+from langchain.agents import AgentExecutor
+from langchain_community.agent_toolkits import SQLDatabaseToolkit
+from langchain_community.agent_toolkits.sql.base import create_sql_agent
+from langchain_community.utilities import SQLDatabase
+
+from langflow.base.agents.agent import LCAgentComponent
+from langflow.inputs import HandleInput, MessageTextInput
+
+
+class SQLAgentComponent(LCAgentComponent):
+ display_name = "SQLAgent"
+ description = "Construct an SQL agent from an LLM and tools."
+ name = "SQLAgent"
+ icon = "LangChain"
+ inputs = [
+ *LCAgentComponent._base_inputs,
+ HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
+ MessageTextInput(name="database_uri", display_name="Database URI", required=True),
+ HandleInput(
+ name="extra_tools",
+ display_name="Extra Tools",
+ input_types=["Tool"],
+ is_list=True,
+ advanced=True,
+ ),
+ ]
+
+ def build_agent(self) -> AgentExecutor:
+ db = SQLDatabase.from_uri(self.database_uri)
+ toolkit = SQLDatabaseToolkit(db=db, llm=self.llm)
+ agent_args = self.get_agent_kwargs()
+ agent_args["max_iterations"] = agent_args["agent_executor_kwargs"]["max_iterations"]
+ del agent_args["agent_executor_kwargs"]["max_iterations"]
+ return create_sql_agent(llm=self.llm, toolkit=toolkit, extra_tools=self.extra_tools or [], **agent_args)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/sql_database.py b/langflow/src/backend/base/langflow/components/langchain_utilities/sql_database.py
new file mode 100644
index 0000000..9475581
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/sql_database.py
@@ -0,0 +1,35 @@
+from langchain_community.utilities.sql_database import SQLDatabase
+from sqlalchemy import create_engine
+from sqlalchemy.pool import StaticPool
+
+from langflow.custom import Component
+from langflow.io import (
+ Output,
+ StrInput,
+)
+
+
+class SQLDatabaseComponent(Component):
+ display_name = "SQLDatabase"
+ description = "SQL Database"
+ name = "SQLDatabase"
+ icon = "LangChain"
+
+ inputs = [
+ StrInput(name="uri", display_name="URI", info="URI to the database.", required=True),
+ ]
+
+ outputs = [
+ Output(display_name="SQLDatabase", name="SQLDatabase", method="build_sqldatabase"),
+ ]
+
+ def clean_up_uri(self, uri: str) -> str:
+ if uri.startswith("postgres://"):
+ uri = uri.replace("postgres://", "postgresql://")
+ return uri.strip()
+
+ def build_sqldatabase(self) -> SQLDatabase:
+ uri = self.clean_up_uri(self.uri)
+ # Create an engine using SQLAlchemy with StaticPool
+ engine = create_engine(uri, poolclass=StaticPool)
+ return SQLDatabase(engine)
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/sql_generator.py b/langflow/src/backend/base/langflow/components/langchain_utilities/sql_generator.py
new file mode 100644
index 0000000..eb0cc84
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/sql_generator.py
@@ -0,0 +1,78 @@
+from typing import TYPE_CHECKING
+
+from langchain.chains import create_sql_query_chain
+from langchain_core.prompts import PromptTemplate
+
+from langflow.base.chains.model import LCChainComponent
+from langflow.field_typing import Message
+from langflow.inputs import HandleInput, IntInput, MultilineInput
+from langflow.template import Output
+
+if TYPE_CHECKING:
+ from langchain_core.runnables import Runnable
+
+
+class SQLGeneratorComponent(LCChainComponent):
+ display_name = "Natural Language to SQL"
+ description = "Generate SQL from natural language."
+ name = "SQLGenerator"
+ legacy: bool = True
+ icon = "LangChain"
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ info="The input value to pass to the chain.",
+ required=True,
+ ),
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ HandleInput(
+ name="db",
+ display_name="SQLDatabase",
+ input_types=["SQLDatabase"],
+ required=True,
+ ),
+ IntInput(
+ name="top_k",
+ display_name="Top K",
+ info="The number of results per select statement to return.",
+ value=5,
+ ),
+ MultilineInput(
+ name="prompt",
+ display_name="Prompt",
+ info="The prompt must contain `{question}`.",
+ ),
+ ]
+
+ outputs = [Output(display_name="Message", name="text", method="invoke_chain")]
+
+ def invoke_chain(self) -> Message:
+ prompt_template = PromptTemplate.from_template(template=self.prompt) if self.prompt else None
+
+ if self.top_k < 1:
+ msg = "Top K must be greater than 0."
+ raise ValueError(msg)
+
+ if not prompt_template:
+ sql_query_chain = create_sql_query_chain(llm=self.llm, db=self.db, k=self.top_k)
+ else:
+ # Check if {question} is in the prompt
+ if "{question}" not in prompt_template.template or "question" not in prompt_template.input_variables:
+ msg = "Prompt must contain `{question}` to be used with Natural Language to SQL."
+ raise ValueError(msg)
+ sql_query_chain = create_sql_query_chain(llm=self.llm, db=self.db, prompt=prompt_template, k=self.top_k)
+ query_writer: Runnable = sql_query_chain | {"query": lambda x: x.replace("SQLQuery:", "").strip()}
+ response = query_writer.invoke(
+ {"question": self.input_value},
+ config={"callbacks": self.get_langchain_callbacks()},
+ )
+ query = response.get("query")
+ self.status = query
+ return query
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/tool_calling.py b/langflow/src/backend/base/langflow/components/langchain_utilities/tool_calling.py
new file mode 100644
index 0000000..60940c9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/tool_calling.py
@@ -0,0 +1,66 @@
+from langchain.agents import create_tool_calling_agent
+from langchain_core.prompts import ChatPromptTemplate
+
+from langflow.base.agents.agent import LCToolsAgentComponent
+from langflow.custom.custom_component.component import _get_component_toolkit
+from langflow.field_typing import Tool
+from langflow.inputs import MessageTextInput
+from langflow.inputs.inputs import DataInput, HandleInput
+from langflow.schema import Data
+
+
+class ToolCallingAgentComponent(LCToolsAgentComponent):
+ display_name: str = "Tool Calling Agent"
+ description: str = "An agent designed to utilize various tools seamlessly within workflows."
+ icon = "LangChain"
+ name = "ToolCallingAgent"
+
+ inputs = [
+ *LCToolsAgentComponent._base_inputs,
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ info="Language model that the agent utilizes to perform tasks effectively.",
+ ),
+ MessageTextInput(
+ name="system_prompt",
+ display_name="System Prompt",
+ info="System prompt to guide the agent's behavior.",
+ value="You are a helpful assistant that can use tools to answer questions and perform tasks.",
+ ),
+ DataInput(
+ name="chat_history",
+ display_name="Chat Memory",
+ is_list=True,
+ advanced=True,
+ info="This input stores the chat history, allowing the agent to remember previous conversations.",
+ ),
+ ]
+
+ def get_chat_history_data(self) -> list[Data] | None:
+ return self.chat_history
+
+ def create_agent_runnable(self):
+ messages = [
+ ("system", "{system_prompt}"),
+ ("placeholder", "{chat_history}"),
+ ("human", "{input}"),
+ ("placeholder", "{agent_scratchpad}"),
+ ]
+ prompt = ChatPromptTemplate.from_messages(messages)
+ self.validate_tool_names()
+ try:
+ return create_tool_calling_agent(self.llm, self.tools or [], prompt)
+ except NotImplementedError as e:
+ message = f"{self.display_name} does not support tool calling. Please try using a compatible model."
+ raise NotImplementedError(message) from e
+
+ async def to_toolkit(self) -> list[Tool]:
+ component_toolkit = _get_component_toolkit()
+ toolkit = component_toolkit(component=self)
+ tools = toolkit.get_tools(callbacks=self.get_langchain_callbacks())
+ if hasattr(self, "tools_metadata"):
+ tools = toolkit.update_tools_metadata(tools=tools)
+ return tools
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store.py b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store.py
new file mode 100644
index 0000000..dfd56de
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store.py
@@ -0,0 +1,20 @@
+from langchain_core.vectorstores import VectorStoreRetriever
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import VectorStore
+
+
+class VectoStoreRetrieverComponent(CustomComponent):
+ display_name = "VectorStore Retriever"
+ description = "A vector store retriever"
+ name = "VectorStoreRetriever"
+ legacy: bool = True
+ icon = "LangChain"
+
+ def build_config(self):
+ return {
+ "vectorstore": {"display_name": "Vector Store", "type": VectorStore},
+ }
+
+ def build(self, vectorstore: VectorStore) -> VectorStoreRetriever:
+ return vectorstore.as_retriever()
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_info.py b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_info.py
new file mode 100644
index 0000000..4658f05
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_info.py
@@ -0,0 +1,49 @@
+from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo
+
+from langflow.custom import Component
+from langflow.inputs import HandleInput, MessageTextInput, MultilineInput
+from langflow.template import Output
+
+
+class VectorStoreInfoComponent(Component):
+ display_name = "VectorStoreInfo"
+ description = "Information about a VectorStore"
+ name = "VectorStoreInfo"
+ legacy: bool = True
+ icon = "LangChain"
+
+ inputs = [
+ MessageTextInput(
+ name="vectorstore_name",
+ display_name="Name",
+ info="Name of the VectorStore",
+ required=True,
+ ),
+ MultilineInput(
+ name="vectorstore_description",
+ display_name="Description",
+ info="Description of the VectorStore",
+ required=True,
+ ),
+ HandleInput(
+ name="input_vectorstore",
+ display_name="Vector Store",
+ input_types=["VectorStore"],
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Vector Store Info", name="info", method="build_info"),
+ ]
+
+ def build_info(self) -> VectorStoreInfo:
+ self.status = {
+ "name": self.vectorstore_name,
+ "description": self.vectorstore_description,
+ }
+ return VectorStoreInfo(
+ vectorstore=self.input_vectorstore,
+ description=self.vectorstore_description,
+ name=self.vectorstore_name,
+ )
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_router.py b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_router.py
new file mode 100644
index 0000000..6c44c2d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/vector_store_router.py
@@ -0,0 +1,33 @@
+from langchain.agents import AgentExecutor, create_vectorstore_router_agent
+from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreRouterToolkit
+
+from langflow.base.agents.agent import LCAgentComponent
+from langflow.inputs import HandleInput
+
+
+class VectorStoreRouterAgentComponent(LCAgentComponent):
+ display_name = "VectorStoreRouterAgent"
+ description = "Construct an agent from a Vector Store Router."
+ name = "VectorStoreRouterAgent"
+ legacy: bool = True
+
+ inputs = [
+ *LCAgentComponent._base_inputs,
+ HandleInput(
+ name="llm",
+ display_name="Language Model",
+ input_types=["LanguageModel"],
+ required=True,
+ ),
+ HandleInput(
+ name="vectorstores",
+ display_name="Vector Stores",
+ input_types=["VectorStoreInfo"],
+ is_list=True,
+ required=True,
+ ),
+ ]
+
+ def build_agent(self) -> AgentExecutor:
+ toolkit = VectorStoreRouterToolkit(vectorstores=self.vectorstores, llm=self.llm)
+ return create_vectorstore_router_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs())
diff --git a/langflow/src/backend/base/langflow/components/langchain_utilities/xml_agent.py b/langflow/src/backend/base/langflow/components/langchain_utilities/xml_agent.py
new file mode 100644
index 0000000..5e31b4d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langchain_utilities/xml_agent.py
@@ -0,0 +1,68 @@
+from langchain.agents import create_xml_agent
+from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, PromptTemplate
+
+from langflow.base.agents.agent import LCToolsAgentComponent
+from langflow.inputs import MultilineInput
+from langflow.inputs.inputs import DataInput, HandleInput
+from langflow.schema import Data
+
+
+class XMLAgentComponent(LCToolsAgentComponent):
+ display_name: str = "XML Agent"
+ description: str = "Agent that uses tools formatting instructions as xml to the Language Model."
+ icon = "LangChain"
+ beta = True
+ name = "XMLAgent"
+ inputs = [
+ *LCToolsAgentComponent._base_inputs,
+ HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
+ DataInput(name="chat_history", display_name="Chat History", is_list=True, advanced=True),
+ MultilineInput(
+ name="system_prompt",
+ display_name="System Prompt",
+ info="System prompt for the agent.",
+ value="""You are a helpful assistant. Help the user answer any questions.
+
+You have access to the following tools:
+
+{tools}
+
+In order to use a tool, you can use and tags. You will then get back a response in the form
+
+For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond:
+
+search weather in SF
+
+64 degrees
+
+When you are done, respond with a final answer between . For example:
+
+The weather in SF is 64 degrees
+
+Begin!
+
+Question: {input}
+
+{agent_scratchpad}
+ """, # noqa: E501
+ ),
+ MultilineInput(
+ name="user_prompt", display_name="Prompt", info="This prompt must contain 'input' key.", value="{input}"
+ ),
+ ]
+
+ def get_chat_history_data(self) -> list[Data] | None:
+ return self.chat_history
+
+ def create_agent_runnable(self):
+ if "input" not in self.user_prompt:
+ msg = "Prompt must contain 'input' key."
+ raise ValueError(msg)
+ messages = [
+ ("system", self.system_prompt),
+ ("placeholder", "{chat_history}"),
+ HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)),
+ ("ai", "{agent_scratchpad}"),
+ ]
+ prompt = ChatPromptTemplate.from_messages(messages)
+ return create_xml_agent(self.llm, self.tools, prompt)
diff --git a/langflow/src/backend/base/langflow/components/langwatch/__init__.py b/langflow/src/backend/base/langflow/components/langwatch/__init__.py
new file mode 100644
index 0000000..dc3149b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langwatch/__init__.py
@@ -0,0 +1,3 @@
+from .langwatch import LangWatchComponent
+
+__all__ = ["LangWatchComponent"]
diff --git a/langflow/src/backend/base/langflow/components/langwatch/langwatch.py b/langflow/src/backend/base/langflow/components/langwatch/langwatch.py
new file mode 100644
index 0000000..f35b126
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/langwatch/langwatch.py
@@ -0,0 +1,292 @@
+import json
+import logging
+import os
+from typing import Any
+
+import httpx
+
+from langflow.custom import Component
+from langflow.inputs.inputs import MultilineInput
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ FloatInput,
+ IntInput,
+ MessageTextInput,
+ NestedDictInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class LangWatchComponent(Component):
+ display_name: str = "LangWatch Evaluator"
+ description: str = "Evaluates various aspects of language models using LangWatch's evaluation endpoints."
+ documentation: str = "https://docs.langwatch.ai/langevals/documentation/introduction"
+ icon: str = "Langwatch"
+ name: str = "LangWatchEvaluator"
+
+ inputs = [
+ DropdownInput(
+ name="evaluator_name",
+ display_name="Evaluator Name",
+ options=[],
+ required=True,
+ info="Select an evaluator.",
+ refresh_button=True,
+ real_time_refresh=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="API Key",
+ required=True,
+ info="Enter your LangWatch API key.",
+ ),
+ MessageTextInput(
+ name="input",
+ display_name="Input",
+ required=False,
+ info="The input text for evaluation.",
+ ),
+ MessageTextInput(
+ name="output",
+ display_name="Output",
+ required=False,
+ info="The output text for evaluation.",
+ ),
+ MessageTextInput(
+ name="expected_output",
+ display_name="Expected Output",
+ required=False,
+ info="The expected output for evaluation.",
+ ),
+ MessageTextInput(
+ name="contexts",
+ display_name="Contexts",
+ required=False,
+ info="The contexts for evaluation (comma-separated).",
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ info="The maximum time (in seconds) allowed for the server to respond before timing out.",
+ value=30,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="evaluation_result", display_name="Evaluation Result", method="evaluate"),
+ ]
+
+ def __init__(self, **data):
+ super().__init__(**data)
+ self.evaluators = self.get_evaluators()
+ self.dynamic_inputs = {}
+ self._code = data.get("_code", "")
+ self.current_evaluator = None
+ if self.evaluators:
+ self.current_evaluator = next(iter(self.evaluators))
+
+ def get_evaluators(self) -> dict[str, Any]:
+ url = f"{os.getenv('LANGWATCH_ENDPOINT', 'https://app.langwatch.ai')}/api/evaluations/list"
+ try:
+ response = httpx.get(url, timeout=10)
+ response.raise_for_status()
+ data = response.json()
+ return data.get("evaluators", {})
+ except httpx.RequestError as e:
+ self.status = f"Error fetching evaluators: {e}"
+ return {}
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:
+ try:
+ logger.info("Updating build config. Field name: %s, Field value: %s", field_name, field_value)
+
+ if field_name is None or field_name == "evaluator_name":
+ self.evaluators = self.get_evaluators()
+ build_config["evaluator_name"]["options"] = list(self.evaluators.keys())
+
+ # Set a default evaluator if none is selected
+ if not self.current_evaluator and self.evaluators:
+ self.current_evaluator = next(iter(self.evaluators))
+ build_config["evaluator_name"]["value"] = self.current_evaluator
+
+ # Define default keys that should always be present
+ default_keys = ["code", "_type", "evaluator_name", "api_key", "input", "output", "timeout"]
+
+ if field_value and field_value in self.evaluators and self.current_evaluator != field_value:
+ self.current_evaluator = field_value
+ evaluator = self.evaluators[field_value]
+
+ # Clear previous dynamic inputs
+ keys_to_remove = [key for key in build_config if key not in default_keys]
+ for key in keys_to_remove:
+ del build_config[key]
+
+ # Clear component's dynamic attributes
+ for attr in list(self.__dict__.keys()):
+ if attr not in default_keys and attr not in {
+ "evaluators",
+ "dynamic_inputs",
+ "_code",
+ "current_evaluator",
+ }:
+ delattr(self, attr)
+
+ # Add new dynamic inputs
+ self.dynamic_inputs = self.get_dynamic_inputs(evaluator)
+ for name, input_config in self.dynamic_inputs.items():
+ build_config[name] = input_config.to_dict()
+
+ # Update required fields
+ required_fields = {"api_key", "evaluator_name"}.union(evaluator.get("requiredFields", []))
+ for key in build_config:
+ if isinstance(build_config[key], dict):
+ build_config[key]["required"] = key in required_fields
+
+ # Validate presence of default keys
+ missing_keys = [key for key in default_keys if key not in build_config]
+ if missing_keys:
+ logger.warning("Missing required keys in build_config: %s", missing_keys)
+ # Add missing keys with default values
+ for key in missing_keys:
+ build_config[key] = {"value": None, "type": "str"}
+
+ # Ensure the current_evaluator is always set in the build_config
+ build_config["evaluator_name"]["value"] = self.current_evaluator
+
+ logger.info("Current evaluator set to: %s", self.current_evaluator)
+ return build_config
+
+ except (KeyError, AttributeError, ValueError) as e:
+ self.status = f"Error updating component: {e!s}"
+ return build_config
+ else:
+ return build_config
+
+ def get_dynamic_inputs(self, evaluator: dict[str, Any]):
+ try:
+ dynamic_inputs = {}
+
+ input_fields = [
+ field
+ for field in evaluator.get("requiredFields", []) + evaluator.get("optionalFields", [])
+ if field not in {"input", "output"}
+ ]
+
+ for field in input_fields:
+ input_params = {
+ "name": field,
+ "display_name": field.replace("_", " ").title(),
+ "required": field in evaluator.get("requiredFields", []),
+ }
+ if field == "contexts":
+ dynamic_inputs[field] = MultilineInput(**input_params, multiline=True)
+ else:
+ dynamic_inputs[field] = MessageTextInput(**input_params)
+
+ settings = evaluator.get("settings", {})
+ for setting_name, setting_config in settings.items():
+ schema = evaluator.get("settings_json_schema", {}).get("properties", {}).get(setting_name, {})
+
+ input_params = {
+ "name": setting_name,
+ "display_name": setting_name.replace("_", " ").title(),
+ "info": setting_config.get("description", ""),
+ "required": False,
+ }
+
+ if schema.get("type") == "object":
+ input_type = NestedDictInput
+ input_params["value"] = schema.get("default", setting_config.get("default", {}))
+ elif schema.get("type") == "boolean":
+ input_type = BoolInput
+ input_params["value"] = schema.get("default", setting_config.get("default", False))
+ elif schema.get("type") == "number":
+ is_float = isinstance(schema.get("default", setting_config.get("default")), float)
+ input_type = FloatInput if is_float else IntInput
+ input_params["value"] = schema.get("default", setting_config.get("default", 0))
+ elif "enum" in schema:
+ input_type = DropdownInput
+ input_params["options"] = schema["enum"]
+ input_params["value"] = schema.get("default", setting_config.get("default"))
+ else:
+ input_type = MessageTextInput
+ default_value = schema.get("default", setting_config.get("default"))
+ input_params["value"] = str(default_value) if default_value is not None else ""
+
+ dynamic_inputs[setting_name] = input_type(**input_params)
+
+ except (KeyError, AttributeError, ValueError, TypeError) as e:
+ self.status = f"Error creating dynamic inputs: {e!s}"
+ return {}
+ return dynamic_inputs
+
+ async def evaluate(self) -> Data:
+ if not self.api_key:
+ return Data(data={"error": "API key is required"})
+
+ # Prioritize evaluator_name if it exists
+ evaluator_name = getattr(self, "evaluator_name", None) or self.current_evaluator
+
+ if not evaluator_name:
+ if self.evaluators:
+ evaluator_name = next(iter(self.evaluators))
+ logger.info("No evaluator was selected. Using default: %s", evaluator_name)
+ else:
+ return Data(
+ data={"error": "No evaluator selected and no evaluators available. Please choose an evaluator."}
+ )
+
+ try:
+ evaluator = self.evaluators.get(evaluator_name)
+ if not evaluator:
+ return Data(data={"error": f"Selected evaluator '{evaluator_name}' not found."})
+
+ logger.info("Evaluating with evaluator: %s", evaluator_name)
+
+ endpoint = f"/api/evaluations/{evaluator_name}/evaluate"
+ url = f"{os.getenv('LANGWATCH_ENDPOINT', 'https://app.langwatch.ai')}{endpoint}"
+
+ headers = {"Content-Type": "application/json", "X-Auth-Token": self.api_key}
+
+ payload = {
+ "data": {
+ "input": self.input,
+ "output": self.output,
+ "expected_output": self.expected_output,
+ "contexts": self.contexts.split(",") if self.contexts else [],
+ },
+ "settings": {},
+ }
+
+ if (
+ self._tracing_service
+ and self._tracing_service._tracers
+ and "langwatch" in self._tracing_service._tracers
+ ):
+ payload["trace_id"] = str(self._tracing_service._tracers["langwatch"].trace_id) # type: ignore[assignment]
+
+ for setting_name in self.dynamic_inputs:
+ payload["settings"][setting_name] = getattr(self, setting_name, None)
+
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
+ response = await client.post(url, json=payload, headers=headers)
+
+ response.raise_for_status()
+ result = response.json()
+
+ formatted_result = json.dumps(result, indent=2)
+ self.status = f"Evaluation completed successfully. Result:\n{formatted_result}"
+ return Data(data=result)
+
+ except (httpx.RequestError, KeyError, AttributeError, ValueError) as e:
+ error_message = f"Evaluation error: {e!s}"
+ self.status = error_message
+ return Data(data={"error": error_message})
diff --git a/langflow/src/backend/base/langflow/components/link_extractors/__init__.py b/langflow/src/backend/base/langflow/components/link_extractors/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/logic/__init__.py b/langflow/src/backend/base/langflow/components/logic/__init__.py
new file mode 100644
index 0000000..40e84cd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/__init__.py
@@ -0,0 +1,21 @@
+from .conditional_router import ConditionalRouterComponent
+from .data_conditional_router import DataConditionalRouterComponent
+from .flow_tool import FlowToolComponent
+from .listen import ListenComponent
+from .loop import LoopComponent
+from .notify import NotifyComponent
+from .pass_message import PassMessageComponent
+from .run_flow import RunFlowComponent
+from .sub_flow import SubFlowComponent
+
+__all__ = [
+ "ConditionalRouterComponent",
+ "DataConditionalRouterComponent",
+ "FlowToolComponent",
+ "ListenComponent",
+ "LoopComponent",
+ "NotifyComponent",
+ "PassMessageComponent",
+ "RunFlowComponent",
+ "SubFlowComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/logic/conditional_router.py b/langflow/src/backend/base/langflow/components/logic/conditional_router.py
new file mode 100644
index 0000000..7709319
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/conditional_router.py
@@ -0,0 +1,139 @@
+import re
+
+from langflow.custom import Component
+from langflow.io import BoolInput, DropdownInput, IntInput, MessageInput, MessageTextInput, Output
+from langflow.schema.message import Message
+
+
+class ConditionalRouterComponent(Component):
+ display_name = "If-Else"
+ description = "Routes an input message to a corresponding output based on text comparison."
+ icon = "split"
+ name = "ConditionalRouter"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.__iteration_updated = False
+
+ inputs = [
+ MessageTextInput(
+ name="input_text",
+ display_name="Text Input",
+ info="The primary text input for the operation.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="match_text",
+ display_name="Match Text",
+ info="The text input to compare against.",
+ required=True,
+ ),
+ DropdownInput(
+ name="operator",
+ display_name="Operator",
+ options=["equals", "not equals", "contains", "starts with", "ends with", "regex"],
+ info="The operator to apply for comparing the texts.",
+ value="equals",
+ real_time_refresh=True,
+ ),
+ BoolInput(
+ name="case_sensitive",
+ display_name="Case Sensitive",
+ info="If true, the comparison will be case sensitive.",
+ value=False,
+ ),
+ MessageInput(
+ name="message",
+ display_name="Message",
+ info="The message to pass through either route.",
+ ),
+ IntInput(
+ name="max_iterations",
+ display_name="Max Iterations",
+ info="The maximum number of iterations for the conditional router.",
+ value=10,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="default_route",
+ display_name="Default Route",
+ options=["true_result", "false_result"],
+ info="The default route to take when max iterations are reached.",
+ value="false_result",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="True", name="true_result", method="true_response"),
+ Output(display_name="False", name="false_result", method="false_response"),
+ ]
+
+ def _pre_run_setup(self):
+ self.__iteration_updated = False
+
+ def evaluate_condition(self, input_text: str, match_text: str, operator: str, *, case_sensitive: bool) -> bool:
+ if not case_sensitive and operator != "regex":
+ input_text = input_text.lower()
+ match_text = match_text.lower()
+
+ if operator == "equals":
+ return input_text == match_text
+ if operator == "not equals":
+ return input_text != match_text
+ if operator == "contains":
+ return match_text in input_text
+ if operator == "starts with":
+ return input_text.startswith(match_text)
+ if operator == "ends with":
+ return input_text.endswith(match_text)
+ if operator == "regex":
+ try:
+ return bool(re.match(match_text, input_text))
+ except re.error:
+ return False # Return False if the regex is invalid
+ return False
+
+ def iterate_and_stop_once(self, route_to_stop: str):
+ if not self.__iteration_updated:
+ self.update_ctx({f"{self._id}_iteration": self.ctx.get(f"{self._id}_iteration", 0) + 1})
+ self.__iteration_updated = True
+ if self.ctx.get(f"{self._id}_iteration", 0) >= self.max_iterations and route_to_stop == self.default_route:
+ route_to_stop = "true_result" if route_to_stop == "false_result" else "false_result"
+ self.stop(route_to_stop)
+
+ def true_response(self) -> Message:
+ result = self.evaluate_condition(
+ self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive
+ )
+ if result:
+ self.status = self.message
+ self.iterate_and_stop_once("false_result")
+ return self.message
+ self.iterate_and_stop_once("true_result")
+ return Message(content="")
+
+ def false_response(self) -> Message:
+ result = self.evaluate_condition(
+ self.input_text, self.match_text, self.operator, case_sensitive=self.case_sensitive
+ )
+ if not result:
+ self.status = self.message
+ self.iterate_and_stop_once("true_result")
+ return self.message
+ self.iterate_and_stop_once("false_result")
+ return Message(content="")
+
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
+ if field_name == "operator":
+ if field_value == "regex":
+ build_config.pop("case_sensitive", None)
+
+ # Ensure case_sensitive is present for all other operators
+ elif "case_sensitive" not in build_config:
+ case_sensitive_input = next(
+ (input_field for input_field in self.inputs if input_field.name == "case_sensitive"), None
+ )
+ if case_sensitive_input:
+ build_config["case_sensitive"] = case_sensitive_input.to_dict()
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/logic/data_conditional_router.py b/langflow/src/backend/base/langflow/components/logic/data_conditional_router.py
new file mode 100644
index 0000000..a107458
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/data_conditional_router.py
@@ -0,0 +1,124 @@
+from typing import Any
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, MessageTextInput, Output
+from langflow.schema import Data, dotdict
+
+
+class DataConditionalRouterComponent(Component):
+ display_name = "Condition"
+ description = "Route Data object(s) based on a condition applied to a specified key, including boolean validation."
+ icon = "split"
+ name = "DataConditionalRouter"
+ legacy = True
+
+ inputs = [
+ DataInput(
+ name="data_input",
+ display_name="Data Input",
+ info="The Data object or list of Data objects to process",
+ is_list=True,
+ ),
+ MessageTextInput(
+ name="key_name",
+ display_name="Key Name",
+ info="The name of the key in the Data object(s) to check",
+ ),
+ DropdownInput(
+ name="operator",
+ display_name="Operator",
+ options=["equals", "not equals", "contains", "starts with", "ends with", "boolean validator"],
+ info="The operator to apply for comparing the values. 'boolean validator' treats the value as a boolean.",
+ value="equals",
+ ),
+ MessageTextInput(
+ name="compare_value",
+ display_name="Match Text",
+ info="The value to compare against (not used for boolean validator)",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="True Output", name="true_output", method="process_data"),
+ Output(display_name="False Output", name="false_output", method="process_data"),
+ ]
+
+ def compare_values(self, item_value: str, compare_value: str, operator: str) -> bool:
+ if operator == "equals":
+ return item_value == compare_value
+ if operator == "not equals":
+ return item_value != compare_value
+ if operator == "contains":
+ return compare_value in item_value
+ if operator == "starts with":
+ return item_value.startswith(compare_value)
+ if operator == "ends with":
+ return item_value.endswith(compare_value)
+ if operator == "boolean validator":
+ return self.parse_boolean(item_value)
+ return False
+
+ def parse_boolean(self, value):
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, str):
+ return value.lower() in {"true", "1", "yes", "y", "on"}
+ return bool(value)
+
+ def validate_input(self, data_item: Data) -> bool:
+ if not isinstance(data_item, Data):
+ self.status = "Input is not a Data object"
+ return False
+ if self.key_name not in data_item.data:
+ self.status = f"Key '{self.key_name}' not found in Data"
+ return False
+ return True
+
+ def process_data(self) -> Data | list[Data]:
+ if isinstance(self.data_input, list):
+ true_output = []
+ false_output = []
+ for item in self.data_input:
+ if self.validate_input(item):
+ result = self.process_single_data(item)
+ if result:
+ true_output.append(item)
+ else:
+ false_output.append(item)
+ self.stop("false_output" if true_output else "true_output")
+ return true_output or false_output
+ if not self.validate_input(self.data_input):
+ return Data(data={"error": self.status})
+ result = self.process_single_data(self.data_input)
+ self.stop("false_output" if result else "true_output")
+ return self.data_input
+
+ def process_single_data(self, data_item: Data) -> bool:
+ item_value = data_item.data[self.key_name]
+ operator = self.operator
+
+ if operator == "boolean validator":
+ condition_met = self.parse_boolean(item_value)
+ condition_description = f"Boolean validation of '{self.key_name}'"
+ else:
+ compare_value = self.compare_value
+ condition_met = self.compare_values(str(item_value), compare_value, operator)
+ condition_description = f"{self.key_name} {operator} {compare_value}"
+
+ if condition_met:
+ self.status = f"Condition met: {condition_description}"
+ return True
+ self.status = f"Condition not met: {condition_description}"
+ return False
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "operator":
+ if field_value == "boolean validator":
+ build_config["compare_value"]["show"] = False
+ build_config["compare_value"]["advanced"] = True
+ build_config["compare_value"]["value"] = None
+ else:
+ build_config["compare_value"]["show"] = True
+ build_config["compare_value"]["advanced"] = False
+
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/logic/flow_tool.py b/langflow/src/backend/base/langflow/components/logic/flow_tool.py
new file mode 100644
index 0000000..a7ed4d1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/flow_tool.py
@@ -0,0 +1,110 @@
+from typing import Any
+
+from loguru import logger
+from typing_extensions import override
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.base.tools.flow_tool import FlowTool
+from langflow.field_typing import Tool
+from langflow.graph.graph.base import Graph
+from langflow.helpers.flow import get_flow_inputs
+from langflow.io import BoolInput, DropdownInput, Output, StrInput
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+
+class FlowToolComponent(LCToolComponent):
+ display_name = "Flow as Tool [Deprecated]"
+ description = "Construct a Tool from a function that runs the loaded Flow."
+ field_order = ["flow_name", "name", "description", "return_direct"]
+ trace_type = "tool"
+ name = "FlowTool"
+ legacy: bool = True
+ icon = "hammer"
+
+ async def get_flow_names(self) -> list[str]:
+ flow_datas = await self.alist_flows()
+ return [flow_data.data["name"] for flow_data in flow_datas]
+
+ async def get_flow(self, flow_name: str) -> Data | None:
+ """Retrieves a flow by its name.
+
+ Args:
+ flow_name (str): The name of the flow to retrieve.
+
+ Returns:
+ Optional[Text]: The flow record if found, None otherwise.
+ """
+ flow_datas = await self.alist_flows()
+ for flow_data in flow_datas:
+ if flow_data.data["name"] == flow_name:
+ return flow_data
+ return None
+
+ @override
+ async def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "flow_name":
+ build_config["flow_name"]["options"] = self.get_flow_names()
+
+ return build_config
+
+ inputs = [
+ DropdownInput(
+ name="flow_name", display_name="Flow Name", info="The name of the flow to run.", refresh_button=True
+ ),
+ StrInput(
+ name="tool_name",
+ display_name="Name",
+ info="The name of the tool.",
+ ),
+ StrInput(
+ name="tool_description",
+ display_name="Description",
+ info="The description of the tool; defaults to the Flow's description.",
+ ),
+ BoolInput(
+ name="return_direct",
+ display_name="Return Direct",
+ info="Return the result directly from the Tool.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="api_build_tool", display_name="Tool", method="build_tool"),
+ ]
+
+ async def build_tool(self) -> Tool:
+ FlowTool.model_rebuild()
+ if "flow_name" not in self._attributes or not self._attributes["flow_name"]:
+ msg = "Flow name is required"
+ raise ValueError(msg)
+ flow_name = self._attributes["flow_name"]
+ flow_data = await self.get_flow(flow_name)
+ if not flow_data:
+ msg = "Flow not found."
+ raise ValueError(msg)
+ graph = Graph.from_payload(
+ flow_data.data["data"],
+ user_id=str(self.user_id),
+ )
+ try:
+ graph.set_run_id(self.graph.run_id)
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).warning("Failed to set run_id")
+ inputs = get_flow_inputs(graph)
+ tool_description = self.tool_description.strip() or flow_data.description
+ tool = FlowTool(
+ name=self.tool_name,
+ description=tool_description,
+ graph=graph,
+ return_direct=self.return_direct,
+ inputs=inputs,
+ flow_id=str(flow_data.id),
+ user_id=str(self.user_id),
+ session_id=self.graph.session_id if hasattr(self, "graph") else None,
+ )
+ description_repr = repr(tool.description).strip("'")
+ args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()])
+ self.status = f"{description_repr}\nArguments:\n{args_str}"
+ return tool
diff --git a/langflow/src/backend/base/langflow/components/logic/listen.py b/langflow/src/backend/base/langflow/components/logic/listen.py
new file mode 100644
index 0000000..e2e6afa
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/listen.py
@@ -0,0 +1,29 @@
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+
+
+class ListenComponent(CustomComponent):
+ display_name = "Listen"
+ description = "A component to listen for a notification."
+ name = "Listen"
+ beta: bool = True
+ icon = "Radio"
+
+ def build_config(self):
+ return {
+ "name": {
+ "display_name": "Name",
+ "info": "The name of the notification to listen for.",
+ },
+ }
+
+ def build(self, name: str) -> Data:
+ state = self.get_state(name)
+ self._set_successors_ids()
+ self.status = state
+ return state
+
+ def _set_successors_ids(self):
+ self._vertex.is_state = True
+ successors = self._vertex.graph.successor_map.get(self._vertex.id, [])
+ return successors + self._vertex.graph.activated_vertices
diff --git a/langflow/src/backend/base/langflow/components/logic/loop.py b/langflow/src/backend/base/langflow/components/logic/loop.py
new file mode 100644
index 0000000..781eb0e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/loop.py
@@ -0,0 +1,111 @@
+from langflow.custom import Component
+from langflow.io import DataInput, Output
+from langflow.schema import Data
+
+
+class LoopComponent(Component):
+ display_name = "Loop"
+ description = (
+ "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs."
+ )
+ icon = "infinity"
+
+ inputs = [
+ DataInput(
+ name="data",
+ display_name="Data",
+ info="The initial list of Data objects to iterate over.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Item", name="item", method="item_output", allows_loop=True),
+ Output(display_name="Done", name="done", method="done_output"),
+ ]
+
+ def initialize_data(self) -> None:
+ """Initialize the data list, context index, and aggregated list."""
+ if self.ctx.get(f"{self._id}_initialized", False):
+ return
+
+ # Ensure data is a list of Data objects
+ data_list = self._validate_data(self.data)
+
+ # Store the initial data and context variables
+ self.update_ctx(
+ {
+ f"{self._id}_data": data_list,
+ f"{self._id}_index": 0,
+ f"{self._id}_aggregated": [],
+ f"{self._id}_initialized": True,
+ }
+ )
+
+ def _validate_data(self, data):
+ """Validate and return a list of Data objects."""
+ if isinstance(data, Data):
+ return [data]
+ if isinstance(data, list) and all(isinstance(item, Data) for item in data):
+ return data
+ msg = "The 'data' input must be a list of Data objects or a single Data object."
+ raise TypeError(msg)
+
+ def evaluate_stop_loop(self) -> bool:
+ """Evaluate whether to stop item or done output."""
+ current_index = self.ctx.get(f"{self._id}_index", 0)
+ data_length = len(self.ctx.get(f"{self._id}_data", []))
+ return current_index > data_length
+
+ def item_output(self) -> Data:
+ """Output the next item in the list or stop if done."""
+ self.initialize_data()
+ current_item = Data(text="")
+
+ if self.evaluate_stop_loop():
+ self.stop("item")
+ return Data(text="")
+
+ # Get data list and current index
+ data_list, current_index = self.loop_variables()
+ if current_index < len(data_list):
+ # Output current item and increment index
+ try:
+ current_item = data_list[current_index]
+ except IndexError:
+ current_item = Data(text="")
+ self.aggregated_output()
+ self.update_ctx({f"{self._id}_index": current_index + 1})
+ return current_item
+
+ def done_output(self) -> Data:
+ """Trigger the done output when iteration is complete."""
+ self.initialize_data()
+
+ if self.evaluate_stop_loop():
+ self.stop("item")
+ self.start("done")
+
+ return self.ctx.get(f"{self._id}_aggregated", [])
+ self.stop("done")
+ return Data(text="")
+
+ def loop_variables(self):
+ """Retrieve loop variables from context."""
+ return (
+ self.ctx.get(f"{self._id}_data", []),
+ self.ctx.get(f"{self._id}_index", 0),
+ )
+
+ def aggregated_output(self) -> Data:
+ """Return the aggregated list once all items are processed."""
+ self.initialize_data()
+
+ # Get data list and aggregated list
+ data_list = self.ctx.get(f"{self._id}_data", [])
+ aggregated = self.ctx.get(f"{self._id}_aggregated", [])
+
+ # Check if loop input is provided and append to aggregated list
+ if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):
+ aggregated.append(self.item)
+ self.update_ctx({f"{self._id}_aggregated": aggregated})
+ return aggregated
diff --git a/langflow/src/backend/base/langflow/components/logic/notify.py b/langflow/src/backend/base/langflow/components/logic/notify.py
new file mode 100644
index 0000000..165b145
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/notify.py
@@ -0,0 +1,46 @@
+from langflow.custom import CustomComponent
+from langflow.schema import Data
+
+
+class NotifyComponent(CustomComponent):
+ display_name = "Notify"
+ description = "A component to generate a notification to Get Notified component."
+ icon = "Notify"
+ name = "Notify"
+ beta: bool = True
+
+ def build_config(self):
+ return {
+ "name": {"display_name": "Name", "info": "The name of the notification."},
+ "data": {"display_name": "Data", "info": "The data to store."},
+ "append": {
+ "display_name": "Append",
+ "info": "If True, the record will be appended to the notification.",
+ },
+ }
+
+ def build(self, name: str, *, data: Data | None = None, append: bool = False) -> Data:
+ if data and not isinstance(data, Data):
+ if isinstance(data, str):
+ data = Data(text=data)
+ elif isinstance(data, dict):
+ data = Data(data=data)
+ else:
+ data = Data(text=str(data))
+ elif not data:
+ data = Data(text="")
+ if data:
+ if append:
+ self.append_state(name, data)
+ else:
+ self.update_state(name, data)
+ else:
+ self.status = "No record provided."
+ self.status = data
+ self._set_successors_ids()
+ return data
+
+ def _set_successors_ids(self):
+ self._vertex.is_state = True
+ successors = self._vertex.graph.successor_map.get(self._vertex.id, [])
+ return successors + self._vertex.graph.activated_vertices
diff --git a/langflow/src/backend/base/langflow/components/logic/pass_message.py b/langflow/src/backend/base/langflow/components/logic/pass_message.py
new file mode 100644
index 0000000..9db6d80
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/pass_message.py
@@ -0,0 +1,34 @@
+from langflow.custom import Component
+from langflow.io import MessageInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class PassMessageComponent(Component):
+ display_name = "Pass"
+ description = "Forwards the input message, unchanged."
+ name = "Pass"
+ icon = "arrow-right"
+
+ inputs = [
+ MessageInput(
+ name="input_message",
+ display_name="Input Message",
+ info="The message to be passed forward.",
+ required=True,
+ ),
+ MessageInput(
+ name="ignored_message",
+ display_name="Ignored Message",
+ info="A second message to be ignored. Used as a workaround for continuity.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output Message", name="output_message", method="pass_message"),
+ ]
+
+ def pass_message(self) -> Message:
+ self.status = self.input_message
+ return self.input_message
diff --git a/langflow/src/backend/base/langflow/components/logic/run_flow.py b/langflow/src/backend/base/langflow/components/logic/run_flow.py
new file mode 100644
index 0000000..d96a4fc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/run_flow.py
@@ -0,0 +1,71 @@
+from typing import Any
+
+from loguru import logger
+
+from langflow.base.tools.run_flow import RunFlowBaseComponent
+from langflow.helpers.flow import run_flow
+from langflow.schema import dotdict
+
+
+class RunFlowComponent(RunFlowBaseComponent):
+ display_name = "Run Flow"
+ description = (
+ "Creates a tool component from a Flow that takes all its inputs and runs it. "
+ " \n **Select a Flow to use the tool mode**"
+ )
+ beta = True
+ name = "RunFlow"
+ icon = "Workflow"
+
+ inputs = RunFlowBaseComponent._base_inputs
+ outputs = RunFlowBaseComponent._base_outputs
+
+ async def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "flow_name_selected":
+ build_config["flow_name_selected"]["options"] = await self.get_flow_names()
+ missing_keys = [key for key in self.default_keys if key not in build_config]
+ if missing_keys:
+ msg = f"Missing required keys in build_config: {missing_keys}"
+ raise ValueError(msg)
+ if field_value is not None:
+ try:
+ graph = await self.get_graph(field_value)
+ build_config = self.update_build_config_from_graph(build_config, graph)
+ except Exception as e:
+ msg = f"Error building graph for flow {field_value}"
+ logger.exception(msg)
+ raise RuntimeError(msg) from e
+ return build_config
+
+ async def run_flow_with_tweaks(self):
+ tweaks: dict = {}
+
+ flow_name_selected = self._attributes.get("flow_name_selected")
+ parsed_flow_tweak_data = self._attributes.get("flow_tweak_data", {})
+ if not isinstance(parsed_flow_tweak_data, dict):
+ parsed_flow_tweak_data = parsed_flow_tweak_data.dict()
+
+ if parsed_flow_tweak_data != {}:
+ for field in parsed_flow_tweak_data:
+ if "~" in field:
+ [node, name] = field.split("~")
+ if node not in tweaks:
+ tweaks[node] = {}
+ tweaks[node][name] = parsed_flow_tweak_data[field]
+ else:
+ for field in self._attributes:
+ if field not in self.default_keys and "~" in field:
+ [node, name] = field.split("~")
+ if node not in tweaks:
+ tweaks[node] = {}
+ tweaks[node][name] = self._attributes[field]
+
+ return await run_flow(
+ inputs=None,
+ output_type="all",
+ flow_id=None,
+ flow_name=flow_name_selected,
+ tweaks=tweaks,
+ user_id=str(self.user_id),
+ session_id=self.graph.session_id or self.session_id,
+ )
diff --git a/langflow/src/backend/base/langflow/components/logic/sub_flow.py b/langflow/src/backend/base/langflow/components/logic/sub_flow.py
new file mode 100644
index 0000000..c6ddd79
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/logic/sub_flow.py
@@ -0,0 +1,114 @@
+from typing import Any
+
+from loguru import logger
+
+from langflow.base.flow_processing.utils import build_data_from_result_data
+from langflow.custom import Component
+from langflow.graph.graph.base import Graph
+from langflow.graph.vertex.base import Vertex
+from langflow.helpers.flow import get_flow_inputs
+from langflow.io import DropdownInput, Output
+from langflow.schema import Data, dotdict
+
+
+class SubFlowComponent(Component):
+ display_name = "Sub Flow [Deprecated]"
+ description = "Generates a Component from a Flow, with all of its inputs, and "
+ name = "SubFlow"
+ legacy: bool = True
+ icon = "Workflow"
+
+ async def get_flow_names(self) -> list[str]:
+ flow_data = await self.alist_flows()
+ return [flow_data.data["name"] for flow_data in flow_data]
+
+ async def get_flow(self, flow_name: str) -> Data | None:
+ flow_datas = await self.alist_flows()
+ for flow_data in flow_datas:
+ if flow_data.data["name"] == flow_name:
+ return flow_data
+ return None
+
+ async def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "flow_name":
+ build_config["flow_name"]["options"] = await self.get_flow_names()
+
+ for key in list(build_config.keys()):
+ if key not in [x.name for x in self.inputs] + ["code", "_type", "get_final_results_only"]:
+ del build_config[key]
+ if field_value is not None and field_name == "flow_name":
+ try:
+ flow_data = await self.get_flow(field_value)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error getting flow {field_value}")
+ else:
+ if not flow_data:
+ msg = f"Flow {field_value} not found."
+ logger.error(msg)
+ else:
+ try:
+ graph = Graph.from_payload(flow_data.data["data"])
+ # Get all inputs from the graph
+ inputs = get_flow_inputs(graph)
+ # Add inputs to the build config
+ build_config = self.add_inputs_to_build_config(inputs, build_config)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error building graph for flow {field_value}")
+
+ return build_config
+
+ def add_inputs_to_build_config(self, inputs_vertex: list[Vertex], build_config: dotdict):
+ new_fields: list[dotdict] = []
+
+ for vertex in inputs_vertex:
+ new_vertex_inputs = []
+ field_template = vertex.data["node"]["template"]
+ for inp in field_template:
+ if inp not in {"code", "_type"}:
+ field_template[inp]["display_name"] = (
+ vertex.display_name + " - " + field_template[inp]["display_name"]
+ )
+ field_template[inp]["name"] = vertex.id + "|" + inp
+ new_vertex_inputs.append(field_template[inp])
+ new_fields += new_vertex_inputs
+ for field in new_fields:
+ build_config[field["name"]] = field
+ return build_config
+
+ inputs = [
+ DropdownInput(
+ name="flow_name",
+ display_name="Flow Name",
+ info="The name of the flow to run.",
+ options=[],
+ refresh_button=True,
+ real_time_refresh=True,
+ ),
+ ]
+
+ outputs = [Output(name="flow_outputs", display_name="Flow Outputs", method="generate_results")]
+
+ async def generate_results(self) -> list[Data]:
+ tweaks: dict = {}
+ for field in self._attributes:
+ if field != "flow_name" and "|" in field:
+ [node, name] = field.split("|")
+ if node not in tweaks:
+ tweaks[node] = {}
+ tweaks[node][name] = self._attributes[field]
+ flow_name = self._attributes.get("flow_name")
+ run_outputs = await self.run_flow(
+ tweaks=tweaks,
+ flow_name=flow_name,
+ output_type="all",
+ )
+ data: list[Data] = []
+ if not run_outputs:
+ return data
+ run_output = run_outputs[0]
+
+ if run_output is not None:
+ for output in run_output.outputs:
+ if output:
+ data.extend(build_data_from_result_data(output))
+ return data
diff --git a/langflow/src/backend/base/langflow/components/memories/__init__.py b/langflow/src/backend/base/langflow/components/memories/__init__.py
new file mode 100644
index 0000000..f53bf0a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/__init__.py
@@ -0,0 +1,13 @@
+from .astra_db import AstraDBChatMemory
+from .cassandra import CassandraChatMemory
+from .mem0_chat_memory import Mem0MemoryComponent
+from .redis import RedisIndexChatMemory
+from .zep import ZepChatMemory
+
+__all__ = [
+ "AstraDBChatMemory",
+ "CassandraChatMemory",
+ "Mem0MemoryComponent",
+ "RedisIndexChatMemory",
+ "ZepChatMemory",
+]
diff --git a/langflow/src/backend/base/langflow/components/memories/astra_db.py b/langflow/src/backend/base/langflow/components/memories/astra_db.py
new file mode 100644
index 0000000..6792057
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/astra_db.py
@@ -0,0 +1,69 @@
+import os
+
+from astrapy.admin import parse_api_endpoint
+
+from langflow.base.memory.model import LCChatMemoryComponent
+from langflow.field_typing.constants import Memory
+from langflow.inputs import MessageTextInput, SecretStrInput, StrInput
+
+
+class AstraDBChatMemory(LCChatMemoryComponent):
+ display_name = "Astra DB Chat Memory"
+ description = "Retrieves and store chat messages from Astra DB."
+ name = "AstraDBChatMemory"
+ icon: str = "AstraDB"
+
+ inputs = [
+ SecretStrInput(
+ name="token",
+ display_name="Astra DB Application Token",
+ info="Authentication token for accessing Astra DB.",
+ value="ASTRA_DB_APPLICATION_TOKEN",
+ required=True,
+ advanced=os.getenv("ASTRA_ENHANCED", "false").lower() == "true",
+ ),
+ SecretStrInput(
+ name="api_endpoint",
+ display_name="API Endpoint",
+ info="API endpoint URL for the Astra DB service.",
+ value="ASTRA_DB_API_ENDPOINT",
+ required=True,
+ ),
+ StrInput(
+ name="collection_name",
+ display_name="Collection Name",
+ info="The name of the collection within Astra DB where the vectors will be stored.",
+ required=True,
+ ),
+ StrInput(
+ name="namespace",
+ display_name="Namespace",
+ info="Optional namespace within Astra DB to use for the collection.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
+ advanced=True,
+ ),
+ ]
+
+ def build_message_history(self) -> Memory:
+ try:
+ from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
+ except ImportError as e:
+ msg = (
+ "Could not import langchain Astra DB integration package. "
+ "Please install it with `pip install langchain-astradb`."
+ )
+ raise ImportError(msg) from e
+
+ return AstraDBChatMessageHistory(
+ session_id=self.session_id,
+ collection_name=self.collection_name,
+ token=self.token,
+ api_endpoint=self.api_endpoint,
+ namespace=self.namespace or None,
+ environment=parse_api_endpoint(self.api_endpoint).environment,
+ )
diff --git a/langflow/src/backend/base/langflow/components/memories/cassandra.py b/langflow/src/backend/base/langflow/components/memories/cassandra.py
new file mode 100644
index 0000000..0eb694d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/cassandra.py
@@ -0,0 +1,92 @@
+from langflow.base.memory.model import LCChatMemoryComponent
+from langflow.field_typing.constants import Memory
+from langflow.inputs import DictInput, MessageTextInput, SecretStrInput
+
+
+class CassandraChatMemory(LCChatMemoryComponent):
+ display_name = "Cassandra Chat Memory"
+ description = "Retrieves and store chat messages from Apache Cassandra."
+ name = "CassandraChatMemory"
+ icon = "Cassandra"
+
+ inputs = [
+ MessageTextInput(
+ name="database_ref",
+ display_name="Contact Points / Astra Database ID",
+ info="Contact points for the database (or AstraDB database ID)",
+ required=True,
+ ),
+ MessageTextInput(
+ name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
+ ),
+ SecretStrInput(
+ name="token",
+ display_name="Password / AstraDB Token",
+ info="User password for the database (or AstraDB token).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="keyspace",
+ display_name="Keyspace",
+ info="Table Keyspace (or AstraDB namespace).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="table_name",
+ display_name="Table Name",
+ info="The name of the table (or AstraDB collection) where vectors will be stored.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="session_id", display_name="Session ID", info="Session ID for the message.", advanced=True
+ ),
+ DictInput(
+ name="cluster_kwargs",
+ display_name="Cluster arguments",
+ info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
+ advanced=True,
+ is_list=True,
+ ),
+ ]
+
+ def build_message_history(self) -> Memory:
+ from langchain_community.chat_message_histories import CassandraChatMessageHistory
+
+ try:
+ import cassio
+ except ImportError as e:
+ msg = "Could not import cassio integration package. Please install it with `pip install cassio`."
+ raise ImportError(msg) from e
+
+ from uuid import UUID
+
+ database_ref = self.database_ref
+
+ try:
+ UUID(self.database_ref)
+ is_astra = True
+ except ValueError:
+ is_astra = False
+ if "," in self.database_ref:
+ # use a copy because we can't change the type of the parameter
+ database_ref = self.database_ref.split(",")
+
+ if is_astra:
+ cassio.init(
+ database_id=database_ref,
+ token=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+ else:
+ cassio.init(
+ contact_points=database_ref,
+ username=self.username,
+ password=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+
+ return CassandraChatMessageHistory(
+ session_id=self.session_id,
+ table_name=self.table_name,
+ keyspace=self.keyspace,
+ )
diff --git a/langflow/src/backend/base/langflow/components/memories/mem0_chat_memory.py b/langflow/src/backend/base/langflow/components/memories/mem0_chat_memory.py
new file mode 100644
index 0000000..6f5310c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/mem0_chat_memory.py
@@ -0,0 +1,144 @@
+import logging
+import os
+
+from mem0 import Memory, MemoryClient
+
+from langflow.base.memory.model import LCChatMemoryComponent
+from langflow.inputs import (
+ DictInput,
+ HandleInput,
+ MessageTextInput,
+ NestedDictInput,
+ SecretStrInput,
+)
+from langflow.io import Output
+from langflow.schema import Data
+
+logger = logging.getLogger(__name__)
+
+
+class Mem0MemoryComponent(LCChatMemoryComponent):
+ display_name = "Mem0 Chat Memory"
+ description = "Retrieves and stores chat messages using Mem0 memory storage."
+ name = "mem0_chat_memory"
+ icon: str = "Mem0"
+ inputs = [
+ NestedDictInput(
+ name="mem0_config",
+ display_name="Mem0 Configuration",
+ info="""Configuration dictionary for initializing Mem0 memory instance.
+ Example:
+ {
+ "graph_store": {
+ "provider": "neo4j",
+ "config": {
+ "url": "neo4j+s://your-neo4j-url",
+ "username": "neo4j",
+ "password": "your-password"
+ }
+ },
+ "version": "v1.1"
+ }""",
+ input_types=["Data"],
+ ),
+ MessageTextInput(
+ name="ingest_message",
+ display_name="Message to Ingest",
+ info="The message content to be ingested into Mem0 memory.",
+ ),
+ HandleInput(
+ name="existing_memory",
+ display_name="Existing Memory Instance",
+ input_types=["Memory"],
+ info="Optional existing Mem0 memory instance. If not provided, a new instance will be created.",
+ ),
+ MessageTextInput(
+ name="user_id", display_name="User ID", info="Identifier for the user associated with the messages."
+ ),
+ MessageTextInput(
+ name="search_query", display_name="Search Query", info="Input text for searching related memories in Mem0."
+ ),
+ SecretStrInput(
+ name="mem0_api_key",
+ display_name="Mem0 API Key",
+ info="API key for Mem0 platform. Leave empty to use the local version.",
+ ),
+ DictInput(
+ name="metadata",
+ display_name="Metadata",
+ info="Additional metadata to associate with the ingested message.",
+ advanced=True,
+ ),
+ SecretStrInput(
+ name="openai_api_key",
+ display_name="OpenAI API Key",
+ required=False,
+ info="API key for OpenAI. Required if using OpenAI Embeddings without a provided configuration.",
+ ),
+ ]
+
+ outputs = [
+ Output(name="memory", display_name="Mem0 Memory", method="ingest_data"),
+ Output(
+ name="search_results",
+ display_name="Search Results",
+ method="build_search_results",
+ ),
+ ]
+
+ def build_mem0(self) -> Memory:
+ """Initializes a Mem0 memory instance based on provided configuration and API keys."""
+ if self.openai_api_key:
+ os.environ["OPENAI_API_KEY"] = self.openai_api_key
+
+ try:
+ if not self.mem0_api_key:
+ return Memory.from_config(config_dict=dict(self.mem0_config)) if self.mem0_config else Memory()
+ if self.mem0_config:
+ return MemoryClient.from_config(api_key=self.mem0_api_key, config_dict=dict(self.mem0_config))
+ return MemoryClient(api_key=self.mem0_api_key)
+ except ImportError as e:
+ msg = "Mem0 is not properly installed. Please install it with 'pip install -U mem0ai'."
+ raise ImportError(msg) from e
+
+ def ingest_data(self) -> Memory:
+ """Ingests a new message into Mem0 memory and returns the updated memory instance."""
+ mem0_memory = self.existing_memory or self.build_mem0()
+
+ if not self.ingest_message or not self.user_id:
+ logger.warning("Missing 'ingest_message' or 'user_id'; cannot ingest data.")
+ return mem0_memory
+
+ metadata = self.metadata or {}
+
+ logger.info("Ingesting message for user_id: %s", self.user_id)
+
+ try:
+ mem0_memory.add(self.ingest_message, user_id=self.user_id, metadata=metadata)
+ except Exception:
+ logger.exception("Failed to add message to Mem0 memory.")
+ raise
+
+ return mem0_memory
+
+ def build_search_results(self) -> Data:
+ """Searches the Mem0 memory for related messages based on the search query and returns the results."""
+ mem0_memory = self.ingest_data()
+ search_query = self.search_query
+ user_id = self.user_id
+
+ logger.info("Search query: %s", search_query)
+
+ try:
+ if search_query:
+ logger.info("Performing search with query.")
+ related_memories = mem0_memory.search(query=search_query, user_id=user_id)
+ else:
+ logger.info("Retrieving all memories for user_id: %s", user_id)
+ related_memories = mem0_memory.get_all(user_id=user_id)
+ except Exception:
+ logger.exception("Failed to retrieve related memories from Mem0.")
+ raise
+
+ logger.info("Related memories retrieved: %s", related_memories)
+ return related_memories
diff --git a/langflow/src/backend/base/langflow/components/memories/redis.py b/langflow/src/backend/base/langflow/components/memories/redis.py
new file mode 100644
index 0000000..0b0e646
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/redis.py
@@ -0,0 +1,43 @@
+from urllib import parse
+
+from langchain_community.chat_message_histories.redis import RedisChatMessageHistory
+
+from langflow.base.memory.model import LCChatMemoryComponent
+from langflow.field_typing.constants import Memory
+from langflow.inputs import IntInput, MessageTextInput, SecretStrInput, StrInput
+
+
+class RedisIndexChatMemory(LCChatMemoryComponent):
+ display_name = "Redis Chat Memory"
+ description = "Retrieves and store chat messages from Redis."
+ name = "RedisChatMemory"
+ icon = "Redis"
+
+ inputs = [
+ StrInput(
+ name="host", display_name="hostname", required=True, value="localhost", info="IP address or hostname."
+ ),
+ IntInput(name="port", display_name="port", required=True, value=6379, info="Redis Port Number."),
+ StrInput(name="database", display_name="database", required=True, value="0", info="Redis database."),
+ MessageTextInput(
+ name="username", display_name="Username", value="", info="The Redis user name.", advanced=True
+ ),
+ SecretStrInput(
+ name="password", display_name="Password", value="", info="The password for username.", advanced=True
+ ),
+ StrInput(name="key_prefix", display_name="Key prefix", info="Key prefix.", advanced=True),
+ MessageTextInput(
+ name="session_id", display_name="Session ID", info="Session ID for the message.", advanced=True
+ ),
+ ]
+
+ def build_message_history(self) -> Memory:
+ kwargs = {}
+ password: str | None = self.password
+ if self.key_prefix:
+ kwargs["key_prefix"] = self.key_prefix
+ if password:
+ password = parse.quote_plus(password)
+
+ url = f"redis://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}"
+ return RedisChatMessageHistory(session_id=self.session_id, url=url, **kwargs)
diff --git a/langflow/src/backend/base/langflow/components/memories/zep.py b/langflow/src/backend/base/langflow/components/memories/zep.py
new file mode 100644
index 0000000..a051ee9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/memories/zep.py
@@ -0,0 +1,43 @@
+from langflow.base.memory.model import LCChatMemoryComponent
+from langflow.field_typing.constants import Memory
+from langflow.inputs import DropdownInput, MessageTextInput, SecretStrInput
+
+
+class ZepChatMemory(LCChatMemoryComponent):
+ display_name = "Zep Chat Memory"
+ description = "Retrieves and store chat messages from Zep."
+ name = "ZepChatMemory"
+ icon = "ZepMemory"
+
+ inputs = [
+ MessageTextInput(name="url", display_name="Zep URL", info="URL of the Zep instance."),
+ SecretStrInput(name="api_key", display_name="API Key", info="API Key for the Zep instance."),
+ DropdownInput(
+ name="api_base_path",
+ display_name="API Base Path",
+ options=["api/v1", "api/v2"],
+ value="api/v1",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id", display_name="Session ID", info="Session ID for the message.", advanced=True
+ ),
+ ]
+
+ def build_message_history(self) -> Memory:
+ try:
+ # Monkeypatch API_BASE_PATH to
+ # avoid 404
+ # This is a workaround for the local Zep instance
+ # cloud Zep works with v2
+ import zep_python.zep_client
+ from zep_python import ZepClient
+ from zep_python.langchain import ZepChatMessageHistory
+
+ zep_python.zep_client.API_BASE_PATH = self.api_base_path
+ except ImportError as e:
+ msg = "Could not import zep-python package. Please install it with `pip install zep-python`."
+ raise ImportError(msg) from e
+
+ zep_client = ZepClient(api_url=self.url, api_key=self.api_key)
+ return ZepChatMessageHistory(session_id=self.session_id, zep_client=zep_client)
diff --git a/langflow/src/backend/base/langflow/components/models/__init__.py b/langflow/src/backend/base/langflow/components/models/__init__.py
new file mode 100644
index 0000000..b986b6f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/__init__.py
@@ -0,0 +1,47 @@
+from .aiml import AIMLModelComponent
+from .amazon_bedrock import AmazonBedrockComponent
+from .anthropic import AnthropicModelComponent
+from .azure_openai import AzureChatOpenAIComponent
+from .baidu_qianfan_chat import QianfanChatEndpointComponent
+from .cohere import CohereComponent
+from .deepseek import DeepSeekModelComponent
+from .google_generative_ai import GoogleGenerativeAIComponent
+from .groq import GroqModel
+from .huggingface import HuggingFaceEndpointsComponent
+from .lmstudiomodel import LMStudioModelComponent
+from .maritalk import MaritalkModelComponent
+from .mistral import MistralAIModelComponent
+from .novita import NovitaModelComponent
+from .nvidia import NVIDIAModelComponent
+from .ollama import ChatOllamaComponent
+from .openai import OpenAIModelComponent
+from .openrouter import OpenRouterComponent
+from .perplexity import PerplexityComponent
+from .sambanova import SambaNovaComponent
+from .vertexai import ChatVertexAIComponent
+from .xai import XAIModelComponent
+
+__all__ = [
+ "AIMLModelComponent",
+ "AmazonBedrockComponent",
+ "AnthropicModelComponent",
+ "AzureChatOpenAIComponent",
+ "ChatOllamaComponent",
+ "ChatVertexAIComponent",
+ "CohereComponent",
+ "DeepSeekModelComponent",
+ "GoogleGenerativeAIComponent",
+ "GroqModel",
+ "HuggingFaceEndpointsComponent",
+ "LMStudioModelComponent",
+ "MaritalkModelComponent",
+ "MistralAIModelComponent",
+ "NVIDIAModelComponent",
+ "NovitaModelComponent",
+ "OpenAIModelComponent",
+ "OpenRouterComponent",
+ "PerplexityComponent",
+ "QianfanChatEndpointComponent",
+ "SambaNovaComponent",
+ "XAIModelComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/models/aiml.py b/langflow/src/backend/base/langflow/components/models/aiml.py
new file mode 100644
index 0000000..dd0613b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/aiml.py
@@ -0,0 +1,112 @@
+from langchain_openai import ChatOpenAI
+from pydantic.v1 import SecretStr
+from typing_extensions import override
+
+from langflow.base.models.aiml_constants import AimlModels
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import (
+ DictInput,
+ DropdownInput,
+ IntInput,
+ SecretStrInput,
+ SliderInput,
+ StrInput,
+)
+
+
+class AIMLModelComponent(LCModelComponent):
+ display_name = "AIML"
+ description = "Generates text using AIML LLMs."
+ icon = "AIML"
+ name = "AIMLModel"
+ documentation = "https://docs.aimlapi.com/api-reference"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=[],
+ refresh_button=True,
+ ),
+ StrInput(
+ name="aiml_api_base",
+ display_name="AIML API Base",
+ advanced=True,
+ info="The base URL of the OpenAI API. Defaults to https://api.aimlapi.com . "
+ "You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="AIML API Key",
+ info="The AIML API Key to use for the OpenAI model.",
+ advanced=False,
+ value="AIML_API_KEY",
+ required=True,
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=2, step=0.01)
+ ),
+ ]
+
+ @override
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ if field_name in {"api_key", "aiml_api_base", "model_name"}:
+ aiml = AimlModels()
+ aiml.get_aiml_models()
+ build_config["model_name"]["options"] = aiml.chat_models
+ return build_config
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ aiml_api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ model_kwargs = self.model_kwargs or {}
+ aiml_api_base = self.aiml_api_base or "https://api.aimlapi.com/v2"
+
+ openai_api_key = aiml_api_key.get_secret_value() if isinstance(aiml_api_key, SecretStr) else aiml_api_key
+
+ # TODO: Once OpenAI fixes their o1 models, this part will need to be removed
+ # to work correctly with o1 temperature settings.
+ if "o1" in model_name:
+ temperature = 1
+
+ return ChatOpenAI(
+ model=model_name,
+ temperature=temperature,
+ api_key=openai_api_key,
+ base_url=aiml_api_base,
+ max_tokens=max_tokens or None,
+ **model_kwargs,
+ )
+
+ def _get_exception_message(self, e: Exception):
+ """Get a message from an OpenAI exception.
+
+ Args:
+ e (Exception): The exception to get the message from.
+
+ Returns:
+ str: The message from the exception.
+ """
+ try:
+ from openai.error import BadRequestError
+ except ImportError:
+ return None
+ if isinstance(e, BadRequestError):
+ message = e.json_body.get("error", {}).get("message", "")
+ if message:
+ return message
+ return None
diff --git a/langflow/src/backend/base/langflow/components/models/amazon_bedrock.py b/langflow/src/backend/base/langflow/components/models/amazon_bedrock.py
new file mode 100644
index 0000000..fa294c9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/amazon_bedrock.py
@@ -0,0 +1,124 @@
+from langflow.base.models.aws_constants import AWS_REGIONS, AWS_MODEL_IDs
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.inputs import MessageTextInput, SecretStrInput
+from langflow.io import DictInput, DropdownInput
+
+
+class AmazonBedrockComponent(LCModelComponent):
+ display_name: str = "Amazon Bedrock"
+ description: str = "Generate text using Amazon Bedrock LLMs."
+ icon = "Amazon"
+ name = "AmazonBedrockModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ DropdownInput(
+ name="model_id",
+ display_name="Model ID",
+ options=AWS_MODEL_IDs,
+ value="anthropic.claude-3-haiku-20240307-v1:0",
+ info="List of available model IDs to choose from.",
+ ),
+ SecretStrInput(
+ name="aws_access_key_id",
+ display_name="AWS Access Key ID",
+ info="The access key for your AWS account."
+ "Usually set in Python code as the environment variable 'AWS_ACCESS_KEY_ID'.",
+ value="AWS_ACCESS_KEY_ID",
+ required=True,
+ ),
+ SecretStrInput(
+ name="aws_secret_access_key",
+ display_name="AWS Secret Access Key",
+ info="The secret key for your AWS account. "
+ "Usually set in Python code as the environment variable 'AWS_SECRET_ACCESS_KEY'.",
+ value="AWS_SECRET_ACCESS_KEY",
+ required=True,
+ ),
+ SecretStrInput(
+ name="aws_session_token",
+ display_name="AWS Session Token",
+ advanced=False,
+ info="The session key for your AWS account. "
+ "Only needed for temporary credentials. "
+ "Usually set in Python code as the environment variable 'AWS_SESSION_TOKEN'.",
+ load_from_db=False,
+ ),
+ SecretStrInput(
+ name="credentials_profile_name",
+ display_name="Credentials Profile Name",
+ advanced=True,
+ info="The name of the profile to use from your "
+ "~/.aws/credentials file. "
+ "If not provided, the default profile will be used.",
+ load_from_db=False,
+ ),
+ DropdownInput(
+ name="region_name",
+ display_name="Region Name",
+ value="us-east-1",
+ options=AWS_REGIONS,
+ info="The AWS region where your Bedrock resources are located.",
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model Kwargs",
+ advanced=True,
+ is_list=True,
+ info="Additional keyword arguments to pass to the model.",
+ ),
+ MessageTextInput(
+ name="endpoint_url",
+ display_name="Endpoint URL",
+ advanced=True,
+ info="The URL of the Bedrock endpoint to use.",
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ from langchain_aws import ChatBedrock
+ except ImportError as e:
+ msg = "langchain_aws is not installed. Please install it with `pip install langchain_aws`."
+ raise ImportError(msg) from e
+ try:
+ import boto3
+ except ImportError as e:
+ msg = "boto3 is not installed. Please install it with `pip install boto3`."
+ raise ImportError(msg) from e
+ if self.aws_access_key_id or self.aws_secret_access_key:
+ try:
+ session = boto3.Session(
+ aws_access_key_id=self.aws_access_key_id,
+ aws_secret_access_key=self.aws_secret_access_key,
+ aws_session_token=self.aws_session_token,
+ )
+ except Exception as e:
+ msg = "Could not create a boto3 session."
+ raise ValueError(msg) from e
+ elif self.credentials_profile_name:
+ session = boto3.Session(profile_name=self.credentials_profile_name)
+ else:
+ session = boto3.Session()
+
+ client_params = {}
+ if self.endpoint_url:
+ client_params["endpoint_url"] = self.endpoint_url
+ if self.region_name:
+ client_params["region_name"] = self.region_name
+
+ boto3_client = session.client("bedrock-runtime", **client_params)
+ try:
+ output = ChatBedrock(
+ client=boto3_client,
+ model_id=self.model_id,
+ region_name=self.region_name,
+ model_kwargs=self.model_kwargs,
+ endpoint_url=self.endpoint_url,
+ streaming=self.stream,
+ )
+ except Exception as e:
+ msg = "Could not connect to AmazonBedrock API."
+ raise ValueError(msg) from e
+ return output
diff --git a/langflow/src/backend/base/langflow/components/models/anthropic.py b/langflow/src/backend/base/langflow/components/models/anthropic.py
new file mode 100644
index 0000000..feb1514
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/anthropic.py
@@ -0,0 +1,156 @@
+from typing import Any
+
+import requests
+from loguru import logger
+
+from langflow.base.models.anthropic_constants import ANTHROPIC_MODELS
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
+from langflow.schema.dotdict import dotdict
+
+
+class AnthropicModelComponent(LCModelComponent):
+ display_name = "Anthropic"
+ description = "Generate text using Anthropic Chat&Completion LLMs with prefill support."
+ icon = "Anthropic"
+ name = "AnthropicModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ value=4096,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ options=ANTHROPIC_MODELS,
+ refresh_button=True,
+ value=ANTHROPIC_MODELS[0],
+ combobox=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Anthropic API Key",
+ info="Your Anthropic API key.",
+ value=None,
+ required=True,
+ real_time_refresh=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.1,
+ info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="Anthropic API URL",
+ info="Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
+ value="https://api.anthropic.com",
+ real_time_refresh=True,
+ ),
+ BoolInput(
+ name="tool_model_enabled",
+ display_name="Enable Tool Models",
+ info=(
+ "Select if you want to use models that can work with tools. If yes, only those models will be shown."
+ ),
+ advanced=False,
+ value=False,
+ real_time_refresh=True,
+ ),
+ MessageTextInput(
+ name="prefill", display_name="Prefill", info="Prefill text to guide the model's response.", advanced=True
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ from langchain_anthropic.chat_models import ChatAnthropic
+ except ImportError as e:
+ msg = "langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`."
+ raise ImportError(msg) from e
+ try:
+ output = ChatAnthropic(
+ model=self.model_name,
+ anthropic_api_key=self.api_key,
+ max_tokens_to_sample=self.max_tokens,
+ temperature=self.temperature,
+ anthropic_api_url=self.base_url,
+ streaming=self.stream,
+ )
+ except Exception as e:
+ msg = "Could not connect to Anthropic API."
+ raise ValueError(msg) from e
+
+ return output
+
+ def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
+ try:
+ import anthropic
+
+ client = anthropic.Anthropic(api_key=self.api_key)
+ models = client.models.list(limit=20).data
+ model_ids = [model.id for model in models]
+ except (ImportError, ValueError, requests.exceptions.RequestException) as e:
+ logger.exception(f"Error getting model names: {e}")
+ model_ids = ANTHROPIC_MODELS
+ if tool_model_enabled:
+ try:
+ from langchain_anthropic.chat_models import ChatAnthropic
+ except ImportError as e:
+ msg = "langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`."
+ raise ImportError(msg) from e
+ for model in model_ids:
+ model_with_tool = ChatAnthropic(
+ model=self.model_name,
+ anthropic_api_key=self.api_key,
+ anthropic_api_url=self.base_url,
+ )
+ if not self.supports_tool_calling(model_with_tool):
+ model_ids.remove(model)
+ return model_ids
+
+ def _get_exception_message(self, exception: Exception) -> str | None:
+ """Get a message from an Anthropic exception.
+
+ Args:
+ exception (Exception): The exception to get the message from.
+
+ Returns:
+ str: The message from the exception.
+ """
+ try:
+ from anthropic import BadRequestError
+ except ImportError:
+ return None
+ if isinstance(exception, BadRequestError):
+ message = exception.body.get("error", {}).get("message")
+ if message:
+ return message
+ return None
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
+ try:
+ if len(self.api_key) == 0:
+ ids = ANTHROPIC_MODELS
+ else:
+ try:
+ ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
+ except (ImportError, ValueError, requests.exceptions.RequestException) as e:
+ logger.exception(f"Error getting model names: {e}")
+ ids = ANTHROPIC_MODELS
+ build_config["model_name"]["options"] = ids
+ build_config["model_name"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/models/azure_openai.py b/langflow/src/backend/base/langflow/components/models/azure_openai.py
new file mode 100644
index 0000000..a6aa5b0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/azure_openai.py
@@ -0,0 +1,91 @@
+from langchain_openai import AzureChatOpenAI
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import MessageTextInput
+from langflow.io import DropdownInput, IntInput, SecretStrInput, SliderInput
+
+
+class AzureChatOpenAIComponent(LCModelComponent):
+ display_name: str = "Azure OpenAI"
+ description: str = "Generate text using Azure OpenAI LLMs."
+ documentation: str = "https://python.langchain.com/docs/integrations/llms/azure_openai"
+ beta = False
+ icon = "Azure"
+ name = "AzureOpenAIModel"
+
+ AZURE_OPENAI_API_VERSIONS = [
+ "2024-06-01",
+ "2024-07-01-preview",
+ "2024-08-01-preview",
+ "2024-09-01-preview",
+ "2024-10-01-preview",
+ "2023-05-15",
+ "2023-12-01-preview",
+ "2024-02-15-preview",
+ "2024-03-01-preview",
+ ]
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ MessageTextInput(
+ name="azure_endpoint",
+ display_name="Azure Endpoint",
+ info="Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`",
+ required=True,
+ ),
+ MessageTextInput(name="azure_deployment", display_name="Deployment Name", required=True),
+ SecretStrInput(name="api_key", display_name="API Key", required=True),
+ DropdownInput(
+ name="api_version",
+ display_name="API Version",
+ options=sorted(AZURE_OPENAI_API_VERSIONS, reverse=True),
+ value=next(
+ (
+ version
+ for version in sorted(AZURE_OPENAI_API_VERSIONS, reverse=True)
+ if not version.endswith("-preview")
+ ),
+ AZURE_OPENAI_API_VERSIONS[0],
+ ),
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.7,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
+ ),
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ azure_endpoint = self.azure_endpoint
+ azure_deployment = self.azure_deployment
+ api_version = self.api_version
+ api_key = self.api_key
+ temperature = self.temperature
+ max_tokens = self.max_tokens
+ stream = self.stream
+
+ try:
+ output = AzureChatOpenAI(
+ azure_endpoint=azure_endpoint,
+ azure_deployment=azure_deployment,
+ api_version=api_version,
+ api_key=api_key,
+ temperature=temperature,
+ max_tokens=max_tokens or None,
+ streaming=stream,
+ )
+ except Exception as e:
+ msg = f"Could not connect to AzureOpenAI API: {e}"
+ raise ValueError(msg) from e
+
+ return output
diff --git a/langflow/src/backend/base/langflow/components/models/baidu_qianfan_chat.py b/langflow/src/backend/base/langflow/components/models/baidu_qianfan_chat.py
new file mode 100644
index 0000000..2ad7e35
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/baidu_qianfan_chat.py
@@ -0,0 +1,113 @@
+from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing.constants import LanguageModel
+from langflow.io import DropdownInput, FloatInput, MessageTextInput, SecretStrInput
+
+
+class QianfanChatEndpointComponent(LCModelComponent):
+ display_name: str = "Qianfan"
+ description: str = "Generate text using Baidu Qianfan LLMs."
+ documentation: str = "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint"
+ icon = "BaiduQianfan"
+ name = "BaiduQianfanChatModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ DropdownInput(
+ name="model",
+ display_name="Model Name",
+ options=[
+ "EB-turbo-AppBuilder",
+ "Llama-2-70b-chat",
+ "ERNIE-Bot-turbo-AI",
+ "ERNIE-Lite-8K-0308",
+ "ERNIE-Speed",
+ "Qianfan-Chinese-Llama-2-13B",
+ "ERNIE-3.5-8K",
+ "BLOOMZ-7B",
+ "Qianfan-Chinese-Llama-2-7B",
+ "XuanYuan-70B-Chat-4bit",
+ "AquilaChat-7B",
+ "ERNIE-Bot-4",
+ "Llama-2-13b-chat",
+ "ChatGLM2-6B-32K",
+ "ERNIE-Bot",
+ "ERNIE-Speed-128k",
+ "ERNIE-4.0-8K",
+ "Qianfan-BLOOMZ-7B-compressed",
+ "ERNIE Speed",
+ "Llama-2-7b-chat",
+ "Mixtral-8x7B-Instruct",
+ "ERNIE 3.5",
+ "ERNIE Speed-AppBuilder",
+ "ERNIE-Speed-8K",
+ "Yi-34B-Chat",
+ ],
+ info="https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
+ value="ERNIE-4.0-8K",
+ ),
+ SecretStrInput(
+ name="qianfan_ak",
+ display_name="Qianfan Ak",
+ info="which you could get from https://cloud.baidu.com/product/wenxinworkshop",
+ ),
+ SecretStrInput(
+ name="qianfan_sk",
+ display_name="Qianfan Sk",
+ info="which you could get from https://cloud.baidu.com/product/wenxinworkshop",
+ ),
+ FloatInput(
+ name="top_p",
+ display_name="Top p",
+ info="Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
+ value=0.8,
+ advanced=True,
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Temperature",
+ info="Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
+ value=0.95,
+ ),
+ FloatInput(
+ name="penalty_score",
+ display_name="Penalty Score",
+ info="Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
+ value=1.0,
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="endpoint", display_name="Endpoint", info="Endpoint of the Qianfan LLM, required if custom model used."
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ model = self.model
+ qianfan_ak = self.qianfan_ak
+ qianfan_sk = self.qianfan_sk
+ top_p = self.top_p
+ temperature = self.temperature
+ penalty_score = self.penalty_score
+ endpoint = self.endpoint
+
+ try:
+ kwargs = {
+ "model": model,
+ "qianfan_ak": qianfan_ak or None,
+ "qianfan_sk": qianfan_sk or None,
+ "top_p": top_p,
+ "temperature": temperature,
+ "penalty_score": penalty_score,
+ }
+
+ if endpoint: # Only add endpoint if it has a value
+ kwargs["endpoint"] = endpoint
+
+ output = QianfanChatEndpoint(**kwargs)
+
+ except Exception as e:
+ msg = "Could not connect to Baidu Qianfan API."
+ raise ValueError(msg) from e
+
+ return output
diff --git a/langflow/src/backend/base/langflow/components/models/cohere.py b/langflow/src/backend/base/langflow/components/models/cohere.py
new file mode 100644
index 0000000..22c43b3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/cohere.py
@@ -0,0 +1,45 @@
+from langchain_cohere import ChatCohere
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import SecretStrInput, SliderInput
+
+
+class CohereComponent(LCModelComponent):
+ display_name = "Cohere"
+ description = "Generate text using Cohere LLMs."
+ documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
+ icon = "Cohere"
+ name = "CohereModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ SecretStrInput(
+ name="cohere_api_key",
+ display_name="Cohere API Key",
+ info="The Cohere API Key to use for the Cohere model.",
+ advanced=False,
+ value="COHERE_API_KEY",
+ required=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.75,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ cohere_api_key = self.cohere_api_key
+ temperature = self.temperature
+
+ api_key = SecretStr(cohere_api_key).get_secret_value() if cohere_api_key else None
+
+ return ChatCohere(
+ temperature=temperature or 0.75,
+ cohere_api_key=api_key,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/deepseek.py b/langflow/src/backend/base/langflow/components/models/deepseek.py
new file mode 100644
index 0000000..c01cf57
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/deepseek.py
@@ -0,0 +1,135 @@
+import requests
+from pydantic.v1 import SecretStr
+from typing_extensions import override
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput
+
+DEEPSEEK_MODELS = ["deepseek-chat"]
+
+
+class DeepSeekModelComponent(LCModelComponent):
+ display_name = "DeepSeek"
+ description = "Generate text using DeepSeek LLMs."
+ icon = "DeepSeek"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="Maximum number of tokens to generate. Set to 0 for unlimited.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model Kwargs",
+ advanced=True,
+ info="Additional keyword arguments to pass to the model.",
+ ),
+ BoolInput(
+ name="json_mode",
+ display_name="JSON Mode",
+ advanced=True,
+ info="If True, it will output JSON regardless of passing a schema.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ info="DeepSeek model to use",
+ options=DEEPSEEK_MODELS,
+ value="deepseek-chat",
+ refresh_button=True,
+ ),
+ StrInput(
+ name="api_base",
+ display_name="DeepSeek API Base",
+ advanced=True,
+ info="Base URL for API requests. Defaults to https://api.deepseek.com",
+ value="https://api.deepseek.com",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="DeepSeek API Key",
+ info="The DeepSeek API Key",
+ advanced=False,
+ required=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ info="Controls randomness in responses",
+ value=1.0,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ ),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ ]
+
+ def get_models(self) -> list[str]:
+ if not self.api_key:
+ return DEEPSEEK_MODELS
+
+ url = f"{self.api_base}/models"
+ headers = {"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"}
+
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+ model_list = response.json()
+ return [model["id"] for model in model_list.get("data", [])]
+ except requests.RequestException as e:
+ self.status = f"Error fetching models: {e}"
+ return DEEPSEEK_MODELS
+
+ @override
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ if field_name in {"api_key", "api_base", "model_name"}:
+ models = self.get_models()
+ build_config["model_name"]["options"] = models
+ return build_config
+
+ def build_model(self) -> LanguageModel:
+ try:
+ from langchain_openai import ChatOpenAI
+ except ImportError as e:
+ msg = "langchain-openai not installed. Please install with `pip install langchain-openai`"
+ raise ImportError(msg) from e
+
+ api_key = SecretStr(self.api_key).get_secret_value() if self.api_key else None
+ output = ChatOpenAI(
+ model=self.model_name,
+ temperature=self.temperature if self.temperature is not None else 0.1,
+ max_tokens=self.max_tokens or None,
+ model_kwargs=self.model_kwargs or {},
+ base_url=self.api_base,
+ api_key=api_key,
+ streaming=self.stream if hasattr(self, "stream") else False,
+ seed=self.seed,
+ )
+
+ if self.json_mode:
+ output = output.bind(response_format={"type": "json_object"})
+
+ return output
+
+ def _get_exception_message(self, e: Exception):
+ """Get message from DeepSeek API exception."""
+ try:
+ from openai import BadRequestError
+
+ if isinstance(e, BadRequestError):
+ message = e.body.get("message")
+ if message:
+ return message
+ except ImportError:
+ pass
+ return None
diff --git a/langflow/src/backend/base/langflow/components/models/google_generative_ai.py b/langflow/src/backend/base/langflow/components/models/google_generative_ai.py
new file mode 100644
index 0000000..cd8fa20
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/google_generative_ai.py
@@ -0,0 +1,147 @@
+from typing import Any
+
+import requests
+from loguru import logger
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import DropdownInput, FloatInput, IntInput, SecretStrInput, SliderInput
+from langflow.inputs.inputs import BoolInput
+from langflow.schema import dotdict
+
+
+class GoogleGenerativeAIComponent(LCModelComponent):
+ display_name = "Google Generative AI"
+ description = "Generate text using Google Generative AI."
+ icon = "GoogleGenerativeAI"
+ name = "GoogleGenerativeAIModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_output_tokens", display_name="Max Output Tokens", info="The maximum number of tokens to generate."
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model",
+ info="The name of the model to use.",
+ options=GOOGLE_GENERATIVE_AI_MODELS,
+ value="gemini-1.5-pro",
+ refresh_button=True,
+ combobox=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Google API Key",
+ info="The Google API Key to use for the Google Generative AI.",
+ required=True,
+ real_time_refresh=True,
+ ),
+ FloatInput(
+ name="top_p",
+ display_name="Top P",
+ info="The maximum cumulative probability of tokens to consider when sampling.",
+ advanced=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.1,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
+ ),
+ IntInput(
+ name="n",
+ display_name="N",
+ info="Number of chat completions to generate for each prompt. "
+ "Note that the API may not return the full n completions if duplicates are generated.",
+ advanced=True,
+ ),
+ IntInput(
+ name="top_k",
+ display_name="Top K",
+ info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="tool_model_enabled",
+ display_name="Tool Model Enabled",
+ info="Whether to use the tool model.",
+ value=False,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ from langchain_google_genai import ChatGoogleGenerativeAI
+ except ImportError as e:
+ msg = "The 'langchain_google_genai' package is required to use the Google Generative AI model."
+ raise ImportError(msg) from e
+
+ google_api_key = self.api_key
+ model = self.model_name
+ max_output_tokens = self.max_output_tokens
+ temperature = self.temperature
+ top_k = self.top_k
+ top_p = self.top_p
+ n = self.n
+
+ return ChatGoogleGenerativeAI(
+ model=model,
+ max_output_tokens=max_output_tokens or None,
+ temperature=temperature,
+ top_k=top_k or None,
+ top_p=top_p or None,
+ n=n or 1,
+ google_api_key=SecretStr(google_api_key).get_secret_value(),
+ )
+
+ def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
+ try:
+ import google.generativeai as genai
+
+ genai.configure(api_key=self.api_key)
+ model_ids = [
+ model.name.replace("models/", "")
+ for model in genai.list_models()
+ if "generateContent" in model.supported_generation_methods
+ ]
+ model_ids.sort(reverse=True)
+ except (ImportError, ValueError) as e:
+ logger.exception(f"Error getting model names: {e}")
+ model_ids = GOOGLE_GENERATIVE_AI_MODELS
+ if tool_model_enabled:
+ try:
+ from langchain_google_genai.chat_models import ChatGoogleGenerativeAI
+ except ImportError as e:
+ msg = "langchain_google_genai is not installed."
+ raise ImportError(msg) from e
+ for model in model_ids:
+ model_with_tool = ChatGoogleGenerativeAI(
+ model=self.model_name,
+ google_api_key=self.api_key,
+ )
+ if not self.supports_tool_calling(model_with_tool):
+ model_ids.remove(model)
+ return model_ids
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
+ try:
+ if len(self.api_key) == 0:
+ ids = GOOGLE_GENERATIVE_AI_MODELS
+ else:
+ try:
+ ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
+ except (ImportError, ValueError, requests.exceptions.RequestException) as e:
+ logger.exception(f"Error getting model names: {e}")
+ ids = GOOGLE_GENERATIVE_AI_MODELS
+ build_config["model_name"]["options"] = ids
+ build_config["model_name"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/models/groq.py b/langflow/src/backend/base/langflow/components/models/groq.py
new file mode 100644
index 0000000..5d94df0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/groq.py
@@ -0,0 +1,138 @@
+import requests
+from loguru import logger
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.groq_constants import GROQ_MODELS
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
+
+
+class GroqModel(LCModelComponent):
+ display_name: str = "Groq"
+ description: str = "Generate text using Groq."
+ icon = "Groq"
+ name = "GroqModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ SecretStrInput(
+ name="api_key", display_name="Groq API Key", info="API key for the Groq API.", real_time_refresh=True
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="Groq API Base",
+ info="Base URL path for API requests, leave blank if not using a proxy or service emulator.",
+ advanced=True,
+ value="https://api.groq.com",
+ real_time_refresh=True,
+ ),
+ IntInput(
+ name="max_tokens",
+ display_name="Max Output Tokens",
+ info="The maximum number of tokens to generate.",
+ advanced=True,
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Temperature",
+ info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ value=0.1,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.1,
+ info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
+ ),
+ IntInput(
+ name="n",
+ display_name="N",
+ info="Number of chat completions to generate for each prompt. "
+ "Note that the API may not return the full n completions if duplicates are generated.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model",
+ info="The name of the model to use.",
+ options=GROQ_MODELS,
+ value=GROQ_MODELS[0],
+ refresh_button=True,
+ combobox=True,
+ ),
+ BoolInput(
+ name="tool_model_enabled",
+ display_name="Enable Tool Models",
+ info=(
+ "Select if you want to use models that can work with tools. If yes, only those models will be shown."
+ ),
+ advanced=False,
+ value=False,
+ real_time_refresh=True,
+ ),
+ ]
+
+ def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
+ try:
+ url = f"{self.base_url}/openai/v1/models"
+ headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
+
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+ model_list = response.json()
+ model_ids = [model["id"] for model in model_list.get("data", [])]
+ except (ImportError, ValueError, requests.exceptions.RequestException) as e:
+ logger.exception(f"Error getting model names: {e}")
+ model_ids = GROQ_MODELS
+ if tool_model_enabled:
+ try:
+ from langchain_groq import ChatGroq
+ except ImportError as e:
+ msg = "langchain_groq is not installed. Please install it with `pip install langchain_groq`."
+ raise ImportError(msg) from e
+ for model in model_ids:
+ model_with_tool = ChatGroq(
+ model=model,
+ api_key=self.api_key,
+ base_url=self.base_url,
+ )
+ if not self.supports_tool_calling(model_with_tool):
+ model_ids.remove(model)
+ return model_ids
+ return model_ids
+
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
+ try:
+ if len(self.api_key) != 0:
+ try:
+ ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
+ except (ImportError, ValueError, requests.exceptions.RequestException) as e:
+ logger.exception(f"Error getting model names: {e}")
+ ids = GROQ_MODELS
+ build_config["model_name"]["options"] = ids
+ build_config["model_name"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ from langchain_groq import ChatGroq
+ except ImportError as e:
+ msg = "langchain-groq is not installed. Please install it with `pip install langchain-groq`."
+ raise ImportError(msg) from e
+
+ return ChatGroq(
+ model=self.model_name,
+ max_tokens=self.max_tokens or None,
+ temperature=self.temperature,
+ base_url=self.base_url,
+ n=self.n or 1,
+ api_key=SecretStr(self.api_key).get_secret_value(),
+ streaming=self.stream,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/huggingface.py b/langflow/src/backend/base/langflow/components/models/huggingface.py
new file mode 100644
index 0000000..88b8651
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/huggingface.py
@@ -0,0 +1,197 @@
+from typing import Any
+
+from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint
+from tenacity import retry, stop_after_attempt, wait_fixed
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import DictInput, DropdownInput, FloatInput, IntInput, SecretStrInput, SliderInput, StrInput
+
+# TODO: langchain_community.llms.huggingface_endpoint is depreciated.
+# Need to update to langchain_huggingface, but have dependency with langchain_core 0.3.0
+
+# Constants
+DEFAULT_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
+
+
+class HuggingFaceEndpointsComponent(LCModelComponent):
+ display_name: str = "HuggingFace"
+ description: str = "Generate text using Hugging Face Inference APIs."
+ icon = "HuggingFace"
+ name = "HuggingFaceModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ DropdownInput(
+ name="model_id",
+ display_name="Model ID",
+ info="Select a model from HuggingFace Hub",
+ options=[
+ DEFAULT_MODEL,
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
+ "mistralai/Mistral-7B-Instruct-v0.3",
+ "meta-llama/Llama-3.1-8B-Instruct",
+ "Qwen/Qwen2.5-Coder-32B-Instruct",
+ "Qwen/QwQ-32B-Preview",
+ "openai-community/gpt2",
+ "custom",
+ ],
+ value=DEFAULT_MODEL,
+ required=True,
+ real_time_refresh=True,
+ ),
+ StrInput(
+ name="custom_model",
+ display_name="Custom Model ID",
+ info="Enter a custom model ID from HuggingFace Hub",
+ value="",
+ show=False,
+ required=True,
+ ),
+ IntInput(
+ name="max_new_tokens", display_name="Max New Tokens", value=512, info="Maximum number of generated tokens"
+ ),
+ IntInput(
+ name="top_k",
+ display_name="Top K",
+ advanced=True,
+ info="The number of highest probability vocabulary tokens to keep for top-k-filtering",
+ ),
+ FloatInput(
+ name="top_p",
+ display_name="Top P",
+ value=0.95,
+ advanced=True,
+ info=(
+ "If set to < 1, only the smallest set of most probable tokens with "
+ "probabilities that add up to `top_p` or higher are kept for generation"
+ ),
+ ),
+ FloatInput(
+ name="typical_p",
+ display_name="Typical P",
+ value=0.95,
+ advanced=True,
+ info="Typical Decoding mass.",
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.8,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ info="The value used to module the logits distribution",
+ advanced=True,
+ ),
+ FloatInput(
+ name="repetition_penalty",
+ display_name="Repetition Penalty",
+ info="The parameter for repetition penalty. 1.0 means no penalty.",
+ advanced=True,
+ ),
+ StrInput(
+ name="inference_endpoint",
+ display_name="Inference Endpoint",
+ value="https://api-inference.huggingface.co/models/",
+ info="Custom inference endpoint URL.",
+ required=True,
+ ),
+ DropdownInput(
+ name="task",
+ display_name="Task",
+ options=["text2text-generation", "text-generation", "summarization", "translation"],
+ value="text-generation",
+ advanced=True,
+ info="The task to call the model with. Should be a task that returns `generated_text` or `summary_text`.",
+ ),
+ SecretStrInput(name="huggingfacehub_api_token", display_name="API Token", password=True, required=True),
+ DictInput(name="model_kwargs", display_name="Model Keyword Arguments", advanced=True),
+ IntInput(name="retry_attempts", display_name="Retry Attempts", value=1, advanced=True),
+ ]
+
+ def get_api_url(self) -> str:
+ if "huggingface" in self.inference_endpoint.lower():
+ if self.model_id == "custom":
+ if not self.custom_model:
+ error_msg = "Custom model ID is required when 'custom' is selected"
+ raise ValueError(error_msg)
+ return f"{self.inference_endpoint}{self.custom_model}"
+ return f"{self.inference_endpoint}{self.model_id}"
+ return self.inference_endpoint
+
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict:
+ """Update build configuration based on field updates."""
+ try:
+ if field_name is None or field_name == "model_id":
+ # If model_id is custom, show custom model field
+ if field_value == "custom":
+ build_config["custom_model"]["show"] = True
+ build_config["custom_model"]["required"] = True
+ else:
+ build_config["custom_model"]["show"] = False
+ build_config["custom_model"]["value"] = ""
+
+ except (KeyError, AttributeError) as e:
+ self.log(f"Error updating build config: {e!s}")
+ return build_config
+
+ def create_huggingface_endpoint(
+ self,
+ task: str | None,
+ huggingfacehub_api_token: str | None,
+ model_kwargs: dict[str, Any],
+ max_new_tokens: int,
+ top_k: int | None,
+ top_p: float,
+ typical_p: float | None,
+ temperature: float | None,
+ repetition_penalty: float | None,
+ ) -> HuggingFaceEndpoint:
+ retry_attempts = self.retry_attempts
+ endpoint_url = self.get_api_url()
+
+ @retry(stop=stop_after_attempt(retry_attempts), wait=wait_fixed(2))
+ def _attempt_create():
+ return HuggingFaceEndpoint(
+ endpoint_url=endpoint_url,
+ task=task,
+ huggingfacehub_api_token=huggingfacehub_api_token,
+ model_kwargs=model_kwargs,
+ max_new_tokens=max_new_tokens,
+ top_k=top_k,
+ top_p=top_p,
+ typical_p=typical_p,
+ temperature=temperature,
+ repetition_penalty=repetition_penalty,
+ )
+
+ return _attempt_create()
+
+ def build_model(self) -> LanguageModel:
+ task = self.task or None
+ huggingfacehub_api_token = self.huggingfacehub_api_token
+ model_kwargs = self.model_kwargs or {}
+ max_new_tokens = self.max_new_tokens
+ top_k = self.top_k or None
+ top_p = self.top_p
+ typical_p = self.typical_p or None
+ temperature = self.temperature or 0.8
+ repetition_penalty = self.repetition_penalty or None
+
+ try:
+ llm = self.create_huggingface_endpoint(
+ task=task,
+ huggingfacehub_api_token=huggingfacehub_api_token,
+ model_kwargs=model_kwargs,
+ max_new_tokens=max_new_tokens,
+ top_k=top_k,
+ top_p=top_p,
+ typical_p=typical_p,
+ temperature=temperature,
+ repetition_penalty=repetition_penalty,
+ )
+ except Exception as e:
+ msg = "Could not connect to HuggingFace Endpoints API."
+ raise ValueError(msg) from e
+
+ return llm
diff --git a/langflow/src/backend/base/langflow/components/models/lmstudiomodel.py b/langflow/src/backend/base/langflow/components/models/lmstudiomodel.py
new file mode 100644
index 0000000..6a503f3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/lmstudiomodel.py
@@ -0,0 +1,124 @@
+from typing import Any
+from urllib.parse import urljoin
+
+import httpx
+from langchain_openai import ChatOpenAI
+from typing_extensions import override
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import DictInput, DropdownInput, FloatInput, IntInput, SecretStrInput, StrInput
+
+
+class LMStudioModelComponent(LCModelComponent):
+ display_name = "LM Studio"
+ description = "Generate text using LM Studio Local LLMs."
+ icon = "LMStudio"
+ name = "LMStudioModel"
+
+ @override
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
+ if field_name == "model_name":
+ base_url_dict = build_config.get("base_url", {})
+ base_url_load_from_db = base_url_dict.get("load_from_db", False)
+ base_url_value = base_url_dict.get("value")
+ if base_url_load_from_db:
+ base_url_value = await self.get_variables(base_url_value, field_name)
+ elif not base_url_value:
+ base_url_value = "http://localhost:1234/v1"
+ build_config["model_name"]["options"] = await self.get_model(base_url_value)
+
+ return build_config
+
+ @staticmethod
+ async def get_model(base_url_value: str) -> list[str]:
+ try:
+ url = urljoin(base_url_value, "/v1/models")
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ data = response.json()
+
+ return [model["id"] for model in data.get("data", [])]
+ except Exception as e:
+ msg = "Could not retrieve models. Please, make sure the LM Studio server is running."
+ raise ValueError(msg) from e
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ refresh_button=True,
+ ),
+ StrInput(
+ name="base_url",
+ display_name="Base URL",
+ advanced=False,
+ info="Endpoint of the LM Studio API. Defaults to 'http://localhost:1234/v1' if not specified.",
+ value="http://localhost:1234/v1",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="LM Studio API Key",
+ info="The LM Studio API Key to use for LM Studio.",
+ advanced=True,
+ value="LMSTUDIO_API_KEY",
+ ),
+ FloatInput(name="temperature", display_name="Temperature", value=0.1),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ lmstudio_api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ model_kwargs = self.model_kwargs or {}
+ base_url = self.base_url or "http://localhost:1234/v1"
+ seed = self.seed
+
+ return ChatOpenAI(
+ max_tokens=max_tokens or None,
+ model_kwargs=model_kwargs,
+ model=model_name,
+ base_url=base_url,
+ api_key=lmstudio_api_key,
+ temperature=temperature if temperature is not None else 0.1,
+ seed=seed,
+ )
+
+ def _get_exception_message(self, e: Exception):
+ """Get a message from an LM Studio exception.
+
+ Args:
+ e (Exception): The exception to get the message from.
+
+ Returns:
+ str: The message from the exception.
+ """
+ try:
+ from openai import BadRequestError
+ except ImportError:
+ return None
+ if isinstance(e, BadRequestError):
+ message = e.body.get("message")
+ if message:
+ return message
+ return None
diff --git a/langflow/src/backend/base/langflow/components/models/maritalk.py b/langflow/src/backend/base/langflow/components/models/maritalk.py
new file mode 100644
index 0000000..b2f386e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/maritalk.py
@@ -0,0 +1,52 @@
+from langchain_community.chat_models import ChatMaritalk
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import DropdownInput, FloatInput, IntInput, SecretStrInput
+
+
+class MaritalkModelComponent(LCModelComponent):
+ display_name = "Maritalk"
+ description = "Generates text using Maritalk LLMs."
+ icon = "Maritalk"
+ name = "Maritalk"
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ value=512,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=["sabia-2-small", "sabia-2-medium"],
+ value=["sabia-2-small"],
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Maritalk API Key",
+ info="The Maritalk API Key to use for the OpenAI model.",
+ advanced=False,
+ ),
+ FloatInput(name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=1)),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ # self.output_schea is a list of dictionarie s
+ # let's convert it to a dictionary
+ api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+
+ return ChatMaritalk(
+ max_tokens=max_tokens,
+ model=model_name,
+ api_key=api_key,
+ temperature=temperature or 0.1,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/mistral.py b/langflow/src/backend/base/langflow/components/models/mistral.py
new file mode 100644
index 0000000..ed66ab5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/mistral.py
@@ -0,0 +1,114 @@
+from langchain_mistralai import ChatMistralAI
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, SecretStrInput, StrInput
+
+
+class MistralAIModelComponent(LCModelComponent):
+ display_name = "MistralAI"
+ description = "Generates text using MistralAI LLMs."
+ icon = "MistralAI"
+ name = "MistralModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=[
+ "open-mixtral-8x7b",
+ "open-mixtral-8x22b",
+ "mistral-small-latest",
+ "mistral-medium-latest",
+ "mistral-large-latest",
+ "codestral-latest",
+ ],
+ value="codestral-latest",
+ ),
+ StrInput(
+ name="mistral_api_base",
+ display_name="Mistral API Base",
+ advanced=True,
+ info="The base URL of the Mistral API. Defaults to https://api.mistral.ai/v1. "
+ "You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Mistral API Key",
+ info="The Mistral API Key to use for the Mistral model.",
+ advanced=False,
+ required=True,
+ value="MISTRAL_API_KEY",
+ ),
+ FloatInput(
+ name="temperature",
+ display_name="Temperature",
+ advanced=False,
+ value=0.5,
+ ),
+ IntInput(
+ name="max_retries",
+ display_name="Max Retries",
+ advanced=True,
+ value=5,
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ advanced=True,
+ value=60,
+ ),
+ IntInput(
+ name="max_concurrent_requests",
+ display_name="Max Concurrent Requests",
+ advanced=True,
+ value=3,
+ ),
+ FloatInput(
+ name="top_p",
+ display_name="Top P",
+ advanced=True,
+ value=1,
+ ),
+ IntInput(
+ name="random_seed",
+ display_name="Random Seed",
+ value=1,
+ advanced=True,
+ ),
+ BoolInput(
+ name="safe_mode",
+ display_name="Safe Mode",
+ advanced=True,
+ value=False,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ return ChatMistralAI(
+ model_name=self.model_name,
+ mistral_api_key=SecretStr(self.api_key).get_secret_value() if self.api_key else None,
+ endpoint=self.mistral_api_base or "https://api.mistral.ai/v1",
+ max_tokens=self.max_tokens or None,
+ temperature=self.temperature,
+ max_retries=self.max_retries,
+ timeout=self.timeout,
+ max_concurrent_requests=self.max_concurrent_requests,
+ top_p=self.top_p,
+ random_seed=self.random_seed,
+ safe_mode=self.safe_mode,
+ streaming=self.stream,
+ )
+ except Exception as e:
+ msg = "Could not connect to MistralAI API."
+ raise ValueError(msg) from e
diff --git a/langflow/src/backend/base/langflow/components/models/novita.py b/langflow/src/backend/base/langflow/components/models/novita.py
new file mode 100644
index 0000000..4dcd527
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/novita.py
@@ -0,0 +1,130 @@
+import requests
+from langchain_openai import ChatOpenAI
+from pydantic.v1 import SecretStr
+from typing_extensions import override
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.novita_constants import MODEL_NAMES
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import (
+ BoolInput,
+ DictInput,
+ DropdownInput,
+ IntInput,
+ SecretStrInput,
+ SliderInput,
+)
+from langflow.inputs.inputs import HandleInput
+
+
+class NovitaModelComponent(LCModelComponent):
+ display_name = "Novita AI"
+ description = "Generates text using Novita AI LLMs (OpenAI compatible)."
+ icon = "Novita"
+ name = "NovitaModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model Kwargs",
+ advanced=True,
+ info="Additional keyword arguments to pass to the model.",
+ ),
+ BoolInput(
+ name="json_mode",
+ display_name="JSON Mode",
+ advanced=True,
+ info="If True, it will output JSON regardless of passing a schema.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=MODEL_NAMES,
+ value=MODEL_NAMES[0],
+ refresh_button=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Novita API Key",
+ info="The Novita API Key to use for Novita AI models.",
+ advanced=False,
+ value="NOVITA_API_KEY",
+ real_time_refresh=True,
+ ),
+ SliderInput(name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=1)),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ HandleInput(
+ name="output_parser",
+ display_name="Output Parser",
+ info="The parser to use to parse the output of the model",
+ advanced=True,
+ input_types=["OutputParser"],
+ ),
+ ]
+
+ def get_models(self) -> list[str]:
+ base_url = "https://api.novita.ai/v3/openai"
+ url = f"{base_url}/models"
+
+ headers = {"Content-Type": "application/json"}
+
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+ model_list = response.json()
+ return [model["id"] for model in model_list.get("data", [])]
+ except requests.RequestException as e:
+ self.status = f"Error fetching models: {e}"
+ return MODEL_NAMES
+
+ @override
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ if field_name in {"api_key", "model_name"}:
+ models = self.get_models()
+ build_config["model_name"]["options"] = models
+ return build_config
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ model_kwargs = self.model_kwargs or {}
+ json_mode = self.json_mode
+ seed = self.seed
+
+ try:
+ output = ChatOpenAI(
+ model=model_name,
+ api_key=(SecretStr(api_key).get_secret_value() if api_key else None),
+ max_tokens=max_tokens or None,
+ temperature=temperature,
+ model_kwargs=model_kwargs,
+ streaming=self.stream,
+ seed=seed,
+ base_url="https://api.novita.ai/v3/openai",
+ )
+ except Exception as e:
+ msg = "Could not connect to Novita API."
+ raise ValueError(msg) from e
+
+ if json_mode:
+ output = output.bind(response_format={"type": "json_object"})
+
+ return output
diff --git a/langflow/src/backend/base/langflow/components/models/nvidia.py b/langflow/src/backend/base/langflow/components/models/nvidia.py
new file mode 100644
index 0000000..132a4df
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/nvidia.py
@@ -0,0 +1,109 @@
+from typing import Any
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
+from langflow.schema.dotdict import dotdict
+
+
+class NVIDIAModelComponent(LCModelComponent):
+ display_name = "NVIDIA"
+ description = "Generates text using NVIDIA LLMs."
+ icon = "NVIDIA"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=[],
+ refresh_button=True,
+ combobox=True,
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="NVIDIA Base URL",
+ value="https://integrate.api.nvidia.com/v1",
+ refresh_button=True,
+ info="The base URL of the NVIDIA API. Defaults to https://integrate.api.nvidia.com/v1.",
+ real_time_refresh=True,
+ ),
+ BoolInput(
+ name="tool_model_enabled",
+ display_name="Enable Tool Models",
+ info=(
+ "Select if you want to use models that can work with tools. If yes, only those models will be shown."
+ ),
+ advanced=False,
+ value=False,
+ real_time_refresh=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="NVIDIA API Key",
+ info="The NVIDIA API Key.",
+ advanced=False,
+ value="NVIDIA_API_KEY",
+ real_time_refresh=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.1,
+ info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
+ ),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ ]
+
+ def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
+ build_model = self.build_model()
+ if tool_model_enabled:
+ tool_models = [model for model in build_model.get_available_models() if model.supports_tools]
+ return [model.id for model in tool_models]
+ return [model.id for model in build_model.available_models]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
+ try:
+ ids = self.get_models(self.tool_model_enabled)
+ build_config["model_name"]["options"] = ids
+ build_config["model_name"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ try:
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA
+ except ImportError as e:
+ msg = "Please install langchain-nvidia-ai-endpoints to use the NVIDIA model."
+ raise ImportError(msg) from e
+ api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ seed = self.seed
+ return ChatNVIDIA(
+ max_tokens=max_tokens or None,
+ model=model_name,
+ base_url=self.base_url,
+ api_key=api_key,
+ temperature=temperature or 0.1,
+ seed=seed,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/ollama.py b/langflow/src/backend/base/langflow/components/models/ollama.py
new file mode 100644
index 0000000..b5b8f52
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/ollama.py
@@ -0,0 +1,278 @@
+from typing import Any
+from urllib.parse import urljoin
+
+import httpx
+from langchain_ollama import ChatOllama
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, OLLAMA_TOOL_MODELS_BASE, URL_LIST
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput
+
+HTTP_STATUS_OK = 200
+
+
+class ChatOllamaComponent(LCModelComponent):
+ display_name = "Ollama"
+ description = "Generate text using Ollama Local LLMs."
+ icon = "Ollama"
+ name = "OllamaModel"
+
+ inputs = [
+ MessageTextInput(
+ name="base_url",
+ display_name="Base URL",
+ info="Endpoint of the Ollama API.",
+ value="",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ options=[],
+ info="Refer to https://ollama.com/library for more models.",
+ refresh_button=True,
+ real_time_refresh=True,
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)
+ ),
+ MessageTextInput(
+ name="format", display_name="Format", info="Specify the format of the output (e.g., json).", advanced=True
+ ),
+ DictInput(name="metadata", display_name="Metadata", info="Metadata to add to the run trace.", advanced=True),
+ DropdownInput(
+ name="mirostat",
+ display_name="Mirostat",
+ options=["Disabled", "Mirostat", "Mirostat 2.0"],
+ info="Enable/disable Mirostat sampling for controlling perplexity.",
+ value="Disabled",
+ advanced=True,
+ real_time_refresh=True,
+ ),
+ FloatInput(
+ name="mirostat_eta",
+ display_name="Mirostat Eta",
+ info="Learning rate for Mirostat algorithm. (Default: 0.1)",
+ advanced=True,
+ ),
+ FloatInput(
+ name="mirostat_tau",
+ display_name="Mirostat Tau",
+ info="Controls the balance between coherence and diversity of the output. (Default: 5.0)",
+ advanced=True,
+ ),
+ IntInput(
+ name="num_ctx",
+ display_name="Context Window Size",
+ info="Size of the context window for generating tokens. (Default: 2048)",
+ advanced=True,
+ ),
+ IntInput(
+ name="num_gpu",
+ display_name="Number of GPUs",
+ info="Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)",
+ advanced=True,
+ ),
+ IntInput(
+ name="num_thread",
+ display_name="Number of Threads",
+ info="Number of threads to use during computation. (Default: detected for optimal performance)",
+ advanced=True,
+ ),
+ IntInput(
+ name="repeat_last_n",
+ display_name="Repeat Last N",
+ info="How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)",
+ advanced=True,
+ ),
+ FloatInput(
+ name="repeat_penalty",
+ display_name="Repeat Penalty",
+ info="Penalty for repetitions in generated text. (Default: 1.1)",
+ advanced=True,
+ ),
+ FloatInput(name="tfs_z", display_name="TFS Z", info="Tail free sampling value. (Default: 1)", advanced=True),
+ IntInput(name="timeout", display_name="Timeout", info="Timeout for the request stream.", advanced=True),
+ IntInput(
+ name="top_k", display_name="Top K", info="Limits token selection to top K. (Default: 40)", advanced=True
+ ),
+ FloatInput(name="top_p", display_name="Top P", info="Works together with top-k. (Default: 0.9)", advanced=True),
+ BoolInput(name="verbose", display_name="Verbose", info="Whether to print out response text.", advanced=True),
+ MessageTextInput(
+ name="tags",
+ display_name="Tags",
+ info="Comma-separated list of tags to add to the run trace.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="stop_tokens",
+ display_name="Stop Tokens",
+ info="Comma-separated list of tokens to signal the model to stop generating text.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="system", display_name="System", info="System to use for generating text.", advanced=True
+ ),
+ BoolInput(
+ name="tool_model_enabled",
+ display_name="Tool Model Enabled",
+ info="Whether to enable tool calling in the model.",
+ value=False,
+ real_time_refresh=True,
+ ),
+ MessageTextInput(
+ name="template", display_name="Template", info="Template to use for generating text.", advanced=True
+ ),
+ *LCModelComponent._base_inputs,
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ # Mapping mirostat settings to their corresponding values
+ mirostat_options = {"Mirostat": 1, "Mirostat 2.0": 2}
+
+ # Default to 0 for 'Disabled'
+ mirostat_value = mirostat_options.get(self.mirostat, 0)
+
+ # Set mirostat_eta and mirostat_tau to None if mirostat is disabled
+ if mirostat_value == 0:
+ mirostat_eta = None
+ mirostat_tau = None
+ else:
+ mirostat_eta = self.mirostat_eta
+ mirostat_tau = self.mirostat_tau
+
+ # Mapping system settings to their corresponding values
+ llm_params = {
+ "base_url": self.base_url,
+ "model": self.model_name,
+ "mirostat": mirostat_value,
+ "format": self.format,
+ "metadata": self.metadata,
+ "tags": self.tags.split(",") if self.tags else None,
+ "mirostat_eta": mirostat_eta,
+ "mirostat_tau": mirostat_tau,
+ "num_ctx": self.num_ctx or None,
+ "num_gpu": self.num_gpu or None,
+ "num_thread": self.num_thread or None,
+ "repeat_last_n": self.repeat_last_n or None,
+ "repeat_penalty": self.repeat_penalty or None,
+ "temperature": self.temperature or None,
+ "stop": self.stop_tokens.split(",") if self.stop_tokens else None,
+ "system": self.system,
+ "tfs_z": self.tfs_z or None,
+ "timeout": self.timeout or None,
+ "top_k": self.top_k or None,
+ "top_p": self.top_p or None,
+ "verbose": self.verbose,
+ "template": self.template,
+ }
+
+ # Remove parameters with None values
+ llm_params = {k: v for k, v in llm_params.items() if v is not None}
+
+ try:
+ output = ChatOllama(**llm_params)
+ except Exception as e:
+ msg = (
+ "Unable to connect to the Ollama API. ",
+ "Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.",
+ )
+ raise ValueError(msg) from e
+
+ return output
+
+ async def is_valid_ollama_url(self, url: str) -> bool:
+ try:
+ async with httpx.AsyncClient() as client:
+ return (await client.get(urljoin(url, "api/tags"))).status_code == HTTP_STATUS_OK
+ except httpx.RequestError:
+ return False
+
+ async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):
+ if field_name == "mirostat":
+ if field_value == "Disabled":
+ build_config["mirostat_eta"]["advanced"] = True
+ build_config["mirostat_tau"]["advanced"] = True
+ build_config["mirostat_eta"]["value"] = None
+ build_config["mirostat_tau"]["value"] = None
+
+ else:
+ build_config["mirostat_eta"]["advanced"] = False
+ build_config["mirostat_tau"]["advanced"] = False
+
+ if field_value == "Mirostat 2.0":
+ build_config["mirostat_eta"]["value"] = 0.2
+ build_config["mirostat_tau"]["value"] = 10
+ else:
+ build_config["mirostat_eta"]["value"] = 0.1
+ build_config["mirostat_tau"]["value"] = 5
+
+ if field_name in {"base_url", "model_name"} and not await self.is_valid_ollama_url(
+ build_config["base_url"].get("value", "")
+ ):
+ # Check if any URL in the list is valid
+ valid_url = ""
+ for url in URL_LIST:
+ if await self.is_valid_ollama_url(url):
+ valid_url = url
+ break
+ if valid_url != "":
+ build_config["base_url"]["value"] = valid_url
+ else:
+ msg = "No valid Ollama URL found."
+ raise ValueError(msg)
+ if field_name in {"model_name", "base_url", "tool_model_enabled"}:
+ if await self.is_valid_ollama_url(self.base_url):
+ tool_model_enabled = build_config["tool_model_enabled"].get("value", False) or self.tool_model_enabled
+ build_config["model_name"]["options"] = await self.get_model(self.base_url, tool_model_enabled)
+ elif await self.is_valid_ollama_url(build_config["base_url"].get("value", "")):
+ tool_model_enabled = build_config["tool_model_enabled"].get("value", False) or self.tool_model_enabled
+ build_config["model_name"]["options"] = await self.get_model(
+ build_config["base_url"].get("value", ""), tool_model_enabled
+ )
+ else:
+ build_config["model_name"]["options"] = []
+ if field_name == "keep_alive_flag":
+ if field_value == "Keep":
+ build_config["keep_alive"]["value"] = "-1"
+ build_config["keep_alive"]["advanced"] = True
+ elif field_value == "Immediately":
+ build_config["keep_alive"]["value"] = "0"
+ build_config["keep_alive"]["advanced"] = True
+ else:
+ build_config["keep_alive"]["advanced"] = False
+
+ return build_config
+
+ async def get_model(self, base_url_value: str, tool_model_enabled: bool | None = None) -> list[str]:
+ try:
+ url = urljoin(base_url_value, "api/tags")
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ data = response.json()
+
+ model_ids = [model["name"] for model in data.get("models", [])]
+ # this to ensure that not embedding models are included.
+ # not even the base models since models can have 1b 2b etc
+ # handles cases when embeddings models have tags like :latest - etc.
+ model_ids = [
+ model
+ for model in model_ids
+ if not any(
+ model == embedding_model or model.startswith(embedding_model.split("-")[0])
+ for embedding_model in OLLAMA_EMBEDDING_MODELS
+ )
+ ]
+
+ except (ImportError, ValueError, httpx.RequestError, Exception) as e:
+ msg = "Could not get model names from Ollama."
+ raise ValueError(msg) from e
+ return (
+ model_ids if not tool_model_enabled else [model for model in model_ids if self.supports_tool_calling(model)]
+ )
+
+ def supports_tool_calling(self, model: str) -> bool:
+ """Check if model name is in the base of any models example llama3.3 can have 1b and 2b."""
+ return any(model.startswith(f"{tool_model}") for tool_model in OLLAMA_TOOL_MODELS_BASE)
diff --git a/langflow/src/backend/base/langflow/components/models/openai.py b/langflow/src/backend/base/langflow/components/models/openai.py
new file mode 100644
index 0000000..0f68acf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/openai.py
@@ -0,0 +1,134 @@
+from langchain_openai import ChatOpenAI
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.openai_constants import OPENAI_MODEL_NAMES
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput
+
+
+class OpenAIModelComponent(LCModelComponent):
+ display_name = "OpenAI"
+ description = "Generates text using OpenAI LLMs."
+ icon = "OpenAI"
+ name = "OpenAIModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model Kwargs",
+ advanced=True,
+ info="Additional keyword arguments to pass to the model.",
+ ),
+ BoolInput(
+ name="json_mode",
+ display_name="JSON Mode",
+ advanced=True,
+ info="If True, it will output JSON regardless of passing a schema.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=OPENAI_MODEL_NAMES,
+ value=OPENAI_MODEL_NAMES[1],
+ combobox=True,
+ ),
+ StrInput(
+ name="openai_api_base",
+ display_name="OpenAI API Base",
+ advanced=True,
+ info="The base URL of the OpenAI API. "
+ "Defaults to https://api.openai.com/v1. "
+ "You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="OpenAI API Key",
+ info="The OpenAI API Key to use for the OpenAI model.",
+ advanced=False,
+ value="OPENAI_API_KEY",
+ required=True,
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)
+ ),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ IntInput(
+ name="max_retries",
+ display_name="Max Retries",
+ info="The maximum number of retries to make when generating.",
+ advanced=True,
+ value=5,
+ ),
+ IntInput(
+ name="timeout",
+ display_name="Timeout",
+ info="The timeout for requests to OpenAI completion API.",
+ advanced=True,
+ value=700,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ openai_api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ model_kwargs = self.model_kwargs or {}
+ openai_api_base = self.openai_api_base or "https://api.openai.com/v1"
+ json_mode = self.json_mode
+ seed = self.seed
+ max_retries = self.max_retries
+ timeout = self.timeout
+
+ api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None
+ output = ChatOpenAI(
+ max_tokens=max_tokens or None,
+ model_kwargs=model_kwargs,
+ model=model_name,
+ base_url=openai_api_base,
+ api_key=api_key,
+ temperature=temperature if temperature is not None else 0.1,
+ seed=seed,
+ max_retries=max_retries,
+ request_timeout=timeout,
+ )
+ if json_mode:
+ output = output.bind(response_format={"type": "json_object"})
+
+ return output
+
+ def _get_exception_message(self, e: Exception):
+ """Get a message from an OpenAI exception.
+
+ Args:
+ e (Exception): The exception to get the message from.
+
+ Returns:
+ str: The message from the exception.
+ """
+ try:
+ from openai import BadRequestError
+ except ImportError:
+ return None
+ if isinstance(e, BadRequestError):
+ message = e.body.get("message")
+ if message:
+ return message
+ return None
diff --git a/langflow/src/backend/base/langflow/components/models/openrouter.py b/langflow/src/backend/base/langflow/components/models/openrouter.py
new file mode 100644
index 0000000..dc7aa45
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/openrouter.py
@@ -0,0 +1,201 @@
+from collections import defaultdict
+from typing import Any
+
+import httpx
+from langchain_openai import ChatOpenAI
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import (
+ DropdownInput,
+ IntInput,
+ SecretStrInput,
+ SliderInput,
+ StrInput,
+)
+
+
+class OpenRouterComponent(LCModelComponent):
+ """OpenRouter API component for language models."""
+
+ display_name = "OpenRouter"
+ description = (
+ "OpenRouter provides unified access to multiple AI models from different providers through a single API."
+ )
+ icon = "OpenRouter"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ SecretStrInput(
+ name="api_key", display_name="OpenRouter API Key", required=True, info="Your OpenRouter API key"
+ ),
+ StrInput(
+ name="site_url",
+ display_name="Site URL",
+ info="Your site URL for OpenRouter rankings",
+ advanced=True,
+ ),
+ StrInput(
+ name="app_name",
+ display_name="App Name",
+ info="Your app name for OpenRouter rankings",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="provider",
+ display_name="Provider",
+ info="The AI model provider",
+ options=["Loading providers..."],
+ value="Loading providers...",
+ real_time_refresh=True,
+ required=True,
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model",
+ info="The model to use for chat completion",
+ options=["Select a provider first"],
+ value="Select a provider first",
+ real_time_refresh=True,
+ required=True,
+ ),
+ SliderInput(
+ name="temperature",
+ display_name="Temperature",
+ value=0.7,
+ range_spec=RangeSpec(min=0, max=2, step=0.01),
+ info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
+ ),
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ info="Maximum number of tokens to generate",
+ advanced=True,
+ ),
+ ]
+
+ def fetch_models(self) -> dict[str, list]:
+ """Fetch available models from OpenRouter API and organize them by provider."""
+ url = "https://openrouter.ai/api/v1/models"
+
+ try:
+ with httpx.Client() as client:
+ response = client.get(url)
+ response.raise_for_status()
+
+ models_data = response.json().get("data", [])
+ provider_models = defaultdict(list)
+
+ for model in models_data:
+ model_id = model.get("id", "")
+ if "/" in model_id:
+ provider = model_id.split("/")[0].title()
+ provider_models[provider].append(
+ {
+ "id": model_id,
+ "name": model.get("name", ""),
+ "description": model.get("description", ""),
+ "context_length": model.get("context_length", 0),
+ }
+ )
+
+ return dict(provider_models)
+
+ except httpx.HTTPError as e:
+ self.log(f"Error fetching models: {e!s}")
+ return {"Error": [{"id": "error", "name": f"Error fetching models: {e!s}"}]}
+
+ def build_model(self) -> LanguageModel:
+ """Build and return the OpenRouter language model."""
+ model_not_selected = "Please select a model"
+ api_key_required = "API key is required"
+
+ if not self.model_name or self.model_name == "Select a provider first":
+ raise ValueError(model_not_selected)
+
+ if not self.api_key:
+ raise ValueError(api_key_required)
+
+ api_key = SecretStr(self.api_key).get_secret_value()
+
+ # Build base configuration
+ kwargs: dict[str, Any] = {
+ "model": self.model_name,
+ "openai_api_key": api_key,
+ "openai_api_base": "https://openrouter.ai/api/v1",
+ "temperature": self.temperature if self.temperature is not None else 0.7,
+ }
+
+ # Add optional parameters
+ if self.max_tokens:
+ kwargs["max_tokens"] = self.max_tokens
+
+ headers = {}
+ if self.site_url:
+ headers["HTTP-Referer"] = self.site_url
+ if self.app_name:
+ headers["X-Title"] = self.app_name
+
+ if headers:
+ kwargs["default_headers"] = headers
+
+ try:
+ return ChatOpenAI(**kwargs)
+ except (ValueError, httpx.HTTPError) as err:
+ error_msg = f"Failed to build model: {err!s}"
+ self.log(error_msg)
+ raise ValueError(error_msg) from err
+
+ def _get_exception_message(self, e: Exception) -> str | None:
+ """Get a message from an OpenRouter exception.
+
+ Args:
+ e (Exception): The exception to get the message from.
+
+ Returns:
+ str | None: The message from the exception, or None if no specific message can be extracted.
+ """
+ try:
+ from openai import BadRequestError
+
+ if isinstance(e, BadRequestError):
+ message = e.body.get("message")
+ if message:
+ return message
+ except ImportError:
+ pass
+ return None
+
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
+ """Update build configuration based on field updates."""
+ try:
+ if field_name is None or field_name == "provider":
+ provider_models = self.fetch_models()
+ build_config["provider"]["options"] = sorted(provider_models.keys())
+ if build_config["provider"]["value"] not in provider_models:
+ build_config["provider"]["value"] = build_config["provider"]["options"][0]
+
+ if field_name == "provider" and field_value in self.fetch_models():
+ provider_models = self.fetch_models()
+ models = provider_models[field_value]
+
+ build_config["model_name"]["options"] = [model["id"] for model in models]
+ if models:
+ build_config["model_name"]["value"] = models[0]["id"]
+
+ tooltips = {
+ model["id"]: (f"{model['name']}\nContext Length: {model['context_length']}\n{model['description']}")
+ for model in models
+ }
+ build_config["model_name"]["tooltips"] = tooltips
+
+ except httpx.HTTPError as e:
+ self.log(f"Error updating build config: {e!s}")
+ build_config["provider"]["options"] = ["Error loading providers"]
+ build_config["provider"]["value"] = "Error loading providers"
+ build_config["model_name"]["options"] = ["Error loading models"]
+ build_config["model_name"]["value"] = "Error loading models"
+
+ return build_config
diff --git a/langflow/src/backend/base/langflow/components/models/perplexity.py b/langflow/src/backend/base/langflow/components/models/perplexity.py
new file mode 100644
index 0000000..fafd8fb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/perplexity.py
@@ -0,0 +1,85 @@
+from langchain_community.chat_models import ChatPerplexity
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import DropdownInput, FloatInput, IntInput, SecretStrInput, SliderInput
+
+
+class PerplexityComponent(LCModelComponent):
+ display_name = "Perplexity"
+ description = "Generate text using Perplexity LLMs."
+ documentation = "https://python.langchain.com/v0.2/docs/integrations/chat/perplexity/"
+ icon = "Perplexity"
+ name = "PerplexityModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=[
+ "llama-3.1-sonar-small-128k-online",
+ "llama-3.1-sonar-large-128k-online",
+ "llama-3.1-sonar-huge-128k-online",
+ "llama-3.1-sonar-small-128k-chat",
+ "llama-3.1-sonar-large-128k-chat",
+ "llama-3.1-8b-instruct",
+ "llama-3.1-70b-instruct",
+ ],
+ value="llama-3.1-sonar-small-128k-online",
+ ),
+ IntInput(
+ name="max_output_tokens", display_name="Max Output Tokens", info="The maximum number of tokens to generate."
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Perplexity API Key",
+ info="The Perplexity API Key to use for the Perplexity model.",
+ advanced=False,
+ required=True,
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.75, range_spec=RangeSpec(min=0, max=2, step=0.05)
+ ),
+ FloatInput(
+ name="top_p",
+ display_name="Top P",
+ info="The maximum cumulative probability of tokens to consider when sampling.",
+ advanced=True,
+ ),
+ IntInput(
+ name="n",
+ display_name="N",
+ info="Number of chat completions to generate for each prompt. "
+ "Note that the API may not return the full n completions if duplicates are generated.",
+ advanced=True,
+ ),
+ IntInput(
+ name="top_k",
+ display_name="Top K",
+ info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
+ advanced=True,
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ api_key = SecretStr(self.api_key).get_secret_value()
+ temperature = self.temperature
+ model = self.model_name
+ max_output_tokens = self.max_output_tokens
+ top_k = self.top_k
+ top_p = self.top_p
+ n = self.n
+
+ return ChatPerplexity(
+ model=model,
+ temperature=temperature or 0.75,
+ pplx_api_key=api_key,
+ top_k=top_k or None,
+ top_p=top_p or None,
+ n=n or 1,
+ max_output_tokens=max_output_tokens,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/sambanova.py b/langflow/src/backend/base/langflow/components/models/sambanova.py
new file mode 100644
index 0000000..b593330
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/sambanova.py
@@ -0,0 +1,80 @@
+from langchain_sambanova import ChatSambaNovaCloud
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.model import LCModelComponent
+from langflow.base.models.sambanova_constants import SAMBANOVA_MODEL_NAMES
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput
+
+
+class SambaNovaComponent(LCModelComponent):
+ display_name = "SambaNova"
+ description = "Generate text using Sambanova LLMs."
+ documentation = "https://cloud.sambanova.ai/"
+ icon = "SambaNova"
+ name = "SambaNovaModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ StrInput(
+ name="base_url",
+ display_name="SambaNova Cloud Base Url",
+ advanced=True,
+ info="The base URL of the Sambanova Cloud API. "
+ "Defaults to https://api.sambanova.ai/v1/chat/completions. "
+ "You can change this to use other urls like Sambastudio",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=SAMBANOVA_MODEL_NAMES,
+ value=SAMBANOVA_MODEL_NAMES[0],
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Sambanova API Key",
+ info="The Sambanova API Key to use for the Sambanova model.",
+ advanced=False,
+ value="SAMBANOVA_API_KEY",
+ required=True,
+ ),
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ value=2048,
+ info="The maximum number of tokens to generate.",
+ ),
+ SliderInput(
+ name="top_p",
+ display_name="top_p",
+ advanced=True,
+ value=1.0,
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
+ info="Model top_p",
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=2, step=0.01)
+ ),
+ ]
+
+ def build_model(self) -> LanguageModel: # type: ignore[type-var]
+ sambanova_url = self.base_url
+ sambanova_api_key = self.api_key
+ model_name = self.model_name
+ max_tokens = self.max_tokens
+ top_p = self.top_p
+ temperature = self.temperature
+
+ api_key = SecretStr(sambanova_api_key).get_secret_value() if sambanova_api_key else None
+
+ return ChatSambaNovaCloud(
+ model=model_name,
+ max_tokens=max_tokens or 1024,
+ temperature=temperature or 0.07,
+ top_p=top_p,
+ sambanova_url=sambanova_url,
+ sambanova_api_key=api_key,
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/vertexai.py b/langflow/src/backend/base/langflow/components/models/vertexai.py
new file mode 100644
index 0000000..20e6161
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/vertexai.py
@@ -0,0 +1,71 @@
+from typing import cast
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.inputs import MessageTextInput
+from langflow.io import BoolInput, FileInput, FloatInput, IntInput, StrInput
+
+
+class ChatVertexAIComponent(LCModelComponent):
+ display_name = "Vertex AI"
+ description = "Generate text using Vertex AI LLMs."
+ icon = "VertexAI"
+ name = "VertexAiModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ FileInput(
+ name="credentials",
+ display_name="Credentials",
+ info="JSON credentials file. Leave empty to fallback to environment variables",
+ file_types=["json"],
+ ),
+ MessageTextInput(name="model_name", display_name="Model Name", value="gemini-1.5-pro"),
+ StrInput(name="project", display_name="Project", info="The project ID.", advanced=True),
+ StrInput(name="location", display_name="Location", value="us-central1", advanced=True),
+ IntInput(name="max_output_tokens", display_name="Max Output Tokens", advanced=True),
+ IntInput(name="max_retries", display_name="Max Retries", value=1, advanced=True),
+ FloatInput(name="temperature", value=0.0, display_name="Temperature"),
+ IntInput(name="top_k", display_name="Top K", advanced=True),
+ FloatInput(name="top_p", display_name="Top P", value=0.95, advanced=True),
+ BoolInput(name="verbose", display_name="Verbose", value=False, advanced=True),
+ ]
+
+ def build_model(self) -> LanguageModel:
+ try:
+ from langchain_google_vertexai import ChatVertexAI
+ except ImportError as e:
+ msg = "Please install the langchain-google-vertexai package to use the VertexAIEmbeddings component."
+ raise ImportError(msg) from e
+ location = self.location or None
+ if self.credentials:
+ from google.cloud import aiplatform
+ from google.oauth2 import service_account
+
+ credentials = service_account.Credentials.from_service_account_file(self.credentials)
+ project = self.project or credentials.project_id
+ # ChatVertexAI sometimes skip manual credentials initialization
+ aiplatform.init(
+ project=project,
+ location=location,
+ credentials=credentials,
+ )
+ else:
+ project = self.project or None
+ credentials = None
+
+ return cast(
+ "LanguageModel",
+ ChatVertexAI(
+ credentials=credentials,
+ location=location,
+ project=project,
+ max_output_tokens=self.max_output_tokens or None,
+ max_retries=self.max_retries,
+ model_name=self.model_name,
+ temperature=self.temperature,
+ top_k=self.top_k or None,
+ top_p=self.top_p,
+ verbose=self.verbose,
+ ),
+ )
diff --git a/langflow/src/backend/base/langflow/components/models/xai.py b/langflow/src/backend/base/langflow/components/models/xai.py
new file mode 100644
index 0000000..bb6b04b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/models/xai.py
@@ -0,0 +1,155 @@
+import requests
+from langchain_openai import ChatOpenAI
+from pydantic.v1 import SecretStr
+from typing_extensions import override
+
+from langflow.base.models.model import LCModelComponent
+from langflow.field_typing import LanguageModel
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
+
+XAI_DEFAULT_MODELS = ["grok-2-latest"]
+
+
+class XAIModelComponent(LCModelComponent):
+ display_name = "xAI"
+ description = "Generates text using xAI models like Grok."
+ icon = "xAI"
+ name = "xAIModel"
+
+ inputs = [
+ *LCModelComponent._base_inputs,
+ IntInput(
+ name="max_tokens",
+ display_name="Max Tokens",
+ advanced=True,
+ info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ range_spec=RangeSpec(min=0, max=128000),
+ ),
+ DictInput(
+ name="model_kwargs",
+ display_name="Model Kwargs",
+ advanced=True,
+ info="Additional keyword arguments to pass to the model.",
+ ),
+ BoolInput(
+ name="json_mode",
+ display_name="JSON Mode",
+ advanced=True,
+ info="If True, it will output JSON regardless of passing a schema.",
+ ),
+ DropdownInput(
+ name="model_name",
+ display_name="Model Name",
+ advanced=False,
+ options=XAI_DEFAULT_MODELS,
+ value=XAI_DEFAULT_MODELS[0],
+ refresh_button=True,
+ combobox=True,
+ info="The xAI model to use",
+ ),
+ MessageTextInput(
+ name="base_url",
+ display_name="xAI API Base",
+ advanced=True,
+ info="The base URL of the xAI API. Defaults to https://api.x.ai/v1",
+ value="https://api.x.ai/v1",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="xAI API Key",
+ info="The xAI API Key to use for the model.",
+ advanced=False,
+ value="XAI_API_KEY",
+ required=True,
+ ),
+ SliderInput(
+ name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=2, step=0.01)
+ ),
+ IntInput(
+ name="seed",
+ display_name="Seed",
+ info="The seed controls the reproducibility of the job.",
+ advanced=True,
+ value=1,
+ ),
+ ]
+
+ def get_models(self) -> list[str]:
+ """Fetch available models from xAI API."""
+ if not self.api_key:
+ return XAI_DEFAULT_MODELS
+
+ base_url = self.base_url or "https://api.x.ai/v1"
+ url = f"{base_url}/language-models"
+ headers = {"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"}
+
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+ data = response.json()
+
+ # Extract model IDs and any aliases
+ models = set()
+ for model in data.get("models", []):
+ models.add(model["id"])
+ models.update(model.get("aliases", []))
+
+ return sorted(models) if models else XAI_DEFAULT_MODELS
+ except requests.RequestException as e:
+ self.status = f"Error fetching models: {e}"
+ return XAI_DEFAULT_MODELS
+
+ @override
+ def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ """Update build configuration with fresh model list when key fields change."""
+ if field_name in {"api_key", "base_url", "model_name"}:
+ models = self.get_models()
+ build_config["model_name"]["options"] = models
+ return build_config
+
+ def build_model(self) -> LanguageModel:
+ api_key = self.api_key
+ temperature = self.temperature
+ model_name: str = self.model_name
+ max_tokens = self.max_tokens
+ model_kwargs = self.model_kwargs or {}
+ base_url = self.base_url or "https://api.x.ai/v1"
+ json_mode = self.json_mode
+ seed = self.seed
+
+ api_key = SecretStr(api_key).get_secret_value() if api_key else None
+
+ output = ChatOpenAI(
+ max_tokens=max_tokens or None,
+ model_kwargs=model_kwargs,
+ model=model_name,
+ base_url=base_url,
+ api_key=api_key,
+ temperature=temperature if temperature is not None else 0.1,
+ seed=seed,
+ )
+
+ if json_mode:
+ output = output.bind(response_format={"type": "json_object"})
+
+ return output
+
+ def _get_exception_message(self, e: Exception):
+ """Get a message from an xAI exception.
+
+ Args:
+ e (Exception): The exception to get the message from.
+
+ Returns:
+ str: The message from the exception.
+ """
+ try:
+ from openai import BadRequestError
+ except ImportError:
+ return None
+ if isinstance(e, BadRequestError):
+ message = e.body.get("message")
+ if message:
+ return message
+ return None
diff --git a/langflow/src/backend/base/langflow/components/needle/__init__.py b/langflow/src/backend/base/langflow/components/needle/__init__.py
new file mode 100644
index 0000000..be67cca
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/needle/__init__.py
@@ -0,0 +1,3 @@
+from .needle import NeedleComponent
+
+__all__ = ["NeedleComponent"]
diff --git a/langflow/src/backend/base/langflow/components/needle/needle.py b/langflow/src/backend/base/langflow/components/needle/needle.py
new file mode 100644
index 0000000..11a6037
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/needle/needle.py
@@ -0,0 +1,129 @@
+from langchain.chains import ConversationalRetrievalChain
+from langchain_community.retrievers.needle import NeedleRetriever
+from langchain_openai import ChatOpenAI
+
+from langflow.custom.custom_component.component import Component
+from langflow.io import DropdownInput, Output, SecretStrInput, StrInput
+from langflow.schema.message import Message
+from langflow.utils.constants import MESSAGE_SENDER_AI
+
+
+class NeedleComponent(Component):
+ display_name = "Needle Retriever"
+ description = "A retriever that uses the Needle API to search collections and generates responses using OpenAI."
+ documentation = "https://docs.needle-ai.com"
+ icon = "Needle"
+ name = "needle"
+
+ inputs = [
+ SecretStrInput(
+ name="needle_api_key",
+ display_name="Needle API Key",
+ info="Your Needle API key.",
+ required=True,
+ ),
+ SecretStrInput(
+ name="openai_api_key",
+ display_name="OpenAI API Key",
+ info="Your OpenAI API key.",
+ required=True,
+ ),
+ StrInput(
+ name="collection_id",
+ display_name="Collection ID",
+ info="The ID of the Needle collection.",
+ required=True,
+ ),
+ StrInput(
+ name="query",
+ display_name="User Query",
+ info="Enter your question here.",
+ required=True,
+ ),
+ DropdownInput(
+ name="output_type",
+ display_name="Output Type",
+ info="Return either the answer or the chunks.",
+ options=["answer", "chunks"],
+ value="answer",
+ required=True,
+ ),
+ ]
+
+ outputs = [Output(display_name="Result", name="result", type_="Message", method="run")]
+
+ def run(self) -> Message:
+ needle_api_key = self.needle_api_key or ""
+ openai_api_key = self.openai_api_key or ""
+ collection_id = self.collection_id
+ query = self.query
+ output_type = self.output_type
+
+ # Define error messages
+ needle_api_key = "The Needle API key cannot be empty."
+ openai_api_key = "The OpenAI API key cannot be empty."
+ collection_id_error = "The Collection ID cannot be empty."
+ query_error = "The query cannot be empty."
+
+ # Validate inputs
+ if not needle_api_key.strip():
+ raise ValueError(needle_api_key)
+ if not openai_api_key.strip():
+ raise ValueError(openai_api_key)
+ if not collection_id.strip():
+ raise ValueError(collection_id_error)
+ if not query.strip():
+ raise ValueError(query_error)
+
+ # Handle output_type if it's somehow a list
+ if isinstance(output_type, list):
+ output_type = output_type[0]
+
+ try:
+ # Initialize the retriever
+ retriever = NeedleRetriever(
+ needle_api_key=needle_api_key,
+ collection_id=collection_id,
+ )
+
+ # Create the chain
+ llm = ChatOpenAI(
+ temperature=0.7,
+ api_key=openai_api_key,
+ )
+
+ qa_chain = ConversationalRetrievalChain.from_llm(
+ llm=llm,
+ retriever=retriever,
+ return_source_documents=True,
+ )
+
+ # Process the query
+ result = qa_chain({"question": query, "chat_history": []})
+
+ # Format content based on output type
+ if str(output_type).lower().strip() == "chunks":
+ # If chunks selected, include full context and answer
+ docs = result["source_documents"]
+ context = "\n\n".join([f"Document {i + 1}:\n{doc.page_content}" for i, doc in enumerate(docs)])
+ text_content = f"Question: {query}\n\nContext:\n{context}\n\nAnswer: {result['answer']}"
+ else:
+ # If answer selected, only include the answer
+ text_content = result["answer"]
+
+ # Create a Message object following chat.py pattern
+ return Message(
+ text=text_content,
+ type="assistant",
+ sender=MESSAGE_SENDER_AI,
+ additional_kwargs={
+ "source_documents": [
+ {"page_content": doc.page_content, "metadata": doc.metadata}
+ for doc in result["source_documents"]
+ ]
+ },
+ )
+
+ except Exception as e:
+ error_msg = f"Error processing query: {e!s}"
+ raise ValueError(error_msg) from e
diff --git a/langflow/src/backend/base/langflow/components/notdiamond/__init__.py b/langflow/src/backend/base/langflow/components/notdiamond/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/notdiamond/notdiamond.py b/langflow/src/backend/base/langflow/components/notdiamond/notdiamond.py
new file mode 100644
index 0000000..7f6b322
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/notdiamond/notdiamond.py
@@ -0,0 +1,228 @@
+import warnings
+
+import requests
+from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
+from pydantic.v1 import SecretStr
+
+from langflow.base.models.chat_result import get_chat_result
+from langflow.base.models.model_utils import get_model_name
+from langflow.custom.custom_component.component import Component
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ HandleInput,
+ MessageInput,
+ MessageTextInput,
+ Output,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema.message import Message
+
+ND_MODEL_MAPPING = {
+ "gpt-4o": {"provider": "openai", "model": "gpt-4o"},
+ "gpt-4o-mini": {"provider": "openai", "model": "gpt-4o-mini"},
+ "gpt-4-turbo": {"provider": "openai", "model": "gpt-4-turbo-2024-04-09"},
+ "claude-3-5-haiku-20241022": {"provider": "anthropic", "model": "claude-3-5-haiku-20241022"},
+ "claude-3-5-sonnet-20241022": {"provider": "anthropic", "model": "claude-3-5-sonnet-20241022"},
+ "anthropic.claude-3-5-sonnet-20241022-v2:0": {"provider": "anthropic", "model": "claude-3-5-sonnet-20241022"},
+ "anthropic.claude-3-5-haiku-20241022-v1:0": {"provider": "anthropic", "model": "claude-3-5-haiku-20241022"},
+ "gemini-1.5-pro": {"provider": "google", "model": "gemini-1.5-pro-latest"},
+ "gemini-1.5-flash": {"provider": "google", "model": "gemini-1.5-flash-latest"},
+ "llama-3.1-sonar-large-128k-online": {"provider": "perplexity", "model": "llama-3.1-sonar-large-128k-online"},
+ "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": {
+ "provider": "togetherai",
+ "model": "Meta-Llama-3.1-70B-Instruct-Turbo",
+ },
+ "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": {
+ "provider": "togetherai",
+ "model": "Meta-Llama-3.1-405B-Instruct-Turbo",
+ },
+ "mistral-large-latest": {"provider": "mistral", "model": "mistral-large-2407"},
+}
+
+
+class NotDiamondComponent(Component):
+ display_name = "Not Diamond Router"
+ description = "Call the right model at the right time with the world's most powerful AI model router."
+ documentation: str = "https://docs.notdiamond.ai/"
+ icon = "NotDiamond"
+ name = "NotDiamond"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._selected_model_name = None
+
+ inputs = [
+ MessageInput(name="input_value", display_name="Input", required=True),
+ MessageTextInput(
+ name="system_message",
+ display_name="System Message",
+ info="System message to pass to the model.",
+ advanced=False,
+ ),
+ HandleInput(
+ name="models",
+ display_name="Language Models",
+ input_types=["LanguageModel"],
+ required=True,
+ is_list=True,
+ info="Link the models you want to route between.",
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Not Diamond API Key",
+ info="The Not Diamond API Key to use for routing.",
+ advanced=False,
+ value="NOTDIAMOND_API_KEY",
+ required=True,
+ ),
+ StrInput(
+ name="preference_id",
+ display_name="Preference ID",
+ info="The ID of the router preference that was configured via the Dashboard.",
+ advanced=False,
+ ),
+ DropdownInput(
+ name="tradeoff",
+ display_name="Tradeoff",
+ info="The tradeoff between cost and latency for the router to determine the best LLM for a given query.",
+ advanced=False,
+ options=["quality", "cost", "latency"],
+ value="quality",
+ ),
+ BoolInput(
+ name="hash_content",
+ display_name="Hash Content",
+ info="Whether to hash the content before being sent to the NotDiamond API.",
+ advanced=False,
+ value=False,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="model_select"),
+ Output(
+ display_name="Selected Model",
+ name="selected_model",
+ method="get_selected_model",
+ required_inputs=["output"],
+ ),
+ ]
+
+ def get_selected_model(self) -> str:
+ return self._selected_model_name
+
+ def model_select(self) -> Message:
+ api_key = SecretStr(self.api_key).get_secret_value() if self.api_key else None
+ input_value = self.input_value
+ system_message = self.system_message
+ messages = self._format_input(input_value, system_message)
+
+ selected_models = []
+ mapped_selected_models = []
+ for model in self.models:
+ model_name = get_model_name(model)
+
+ if model_name in ND_MODEL_MAPPING:
+ selected_models.append(model)
+ mapped_selected_models.append(ND_MODEL_MAPPING[model_name])
+
+ payload = {
+ "messages": messages,
+ "llm_providers": mapped_selected_models,
+ "hash_content": self.hash_content,
+ }
+
+ if self.tradeoff != "quality":
+ payload["tradeoff"] = self.tradeoff
+
+ if self.preference_id and self.preference_id != "":
+ payload["preference_id"] = self.preference_id
+
+ header = {
+ "Authorization": f"Bearer {api_key}",
+ "accept": "application/json",
+ "content-type": "application/json",
+ }
+
+ response = requests.post(
+ "https://api.notdiamond.ai/v2/modelRouter/modelSelect",
+ json=payload,
+ headers=header,
+ timeout=10,
+ )
+
+ result = response.json()
+ chosen_model = self.models[0] # By default there is a fallback model
+ self._selected_model_name = get_model_name(chosen_model)
+
+ if "providers" not in result:
+ # No provider returned by NotDiamond API, likely failed. Fallback to first model.
+ return self._call_get_chat_result(chosen_model, input_value, system_message)
+
+ providers = result["providers"]
+
+ if len(providers) == 0:
+ # No provider returned by NotDiamond API, likely failed. Fallback to first model.
+ return self._call_get_chat_result(chosen_model, input_value, system_message)
+
+ nd_result = providers[0]
+
+ for nd_model, selected_model in zip(mapped_selected_models, selected_models, strict=False):
+ if nd_model["provider"] == nd_result["provider"] and nd_model["model"] == nd_result["model"]:
+ chosen_model = selected_model
+ self._selected_model_name = get_model_name(chosen_model)
+ break
+
+ return self._call_get_chat_result(chosen_model, input_value, system_message)
+
+ def _call_get_chat_result(self, chosen_model, input_value, system_message):
+ return get_chat_result(
+ runnable=chosen_model,
+ input_value=input_value,
+ system_message=system_message,
+ )
+
+ def _format_input(
+ self,
+ input_value: str | Message,
+ system_message: str | None = None,
+ ):
+ messages: list[BaseMessage] = []
+ if not input_value and not system_message:
+ msg = "The message you want to send to the router is empty."
+ raise ValueError(msg)
+ system_message_added = False
+ if input_value:
+ if isinstance(input_value, Message):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ if "prompt" in input_value:
+ prompt = input_value.load_lc_prompt()
+ if system_message:
+ prompt.messages = [
+ SystemMessage(content=system_message),
+ *prompt.messages, # type: ignore[has-type]
+ ]
+ system_message_added = True
+ messages.extend(prompt.messages)
+ else:
+ messages.append(input_value.to_lc_message())
+ else:
+ messages.append(HumanMessage(content=input_value))
+
+ if system_message and not system_message_added:
+ messages.insert(0, SystemMessage(content=system_message))
+
+ # Convert Langchain messages to OpenAI format
+ openai_messages = []
+ for msg in messages:
+ if isinstance(msg, HumanMessage):
+ openai_messages.append({"role": "user", "content": msg.content})
+ elif isinstance(msg, AIMessage):
+ openai_messages.append({"role": "assistant", "content": msg.content})
+ elif isinstance(msg, SystemMessage):
+ openai_messages.append({"role": "system", "content": msg.content})
+
+ return openai_messages
diff --git a/langflow/src/backend/base/langflow/components/nvidia/__init__.py b/langflow/src/backend/base/langflow/components/nvidia/__init__.py
new file mode 100644
index 0000000..fa36462
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/nvidia/__init__.py
@@ -0,0 +1,4 @@
+from .nvidia_ingest import NvidiaIngestComponent
+from .nvidia_rerank import NvidiaRerankComponent
+
+__all__ = ["NvidiaIngestComponent", "NvidiaRerankComponent"]
diff --git a/langflow/src/backend/base/langflow/components/nvidia/nvidia_ingest.py b/langflow/src/backend/base/langflow/components/nvidia/nvidia_ingest.py
new file mode 100644
index 0000000..9aa7c80
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/nvidia/nvidia_ingest.py
@@ -0,0 +1,232 @@
+from pathlib import Path
+from urllib.parse import urlparse
+
+from langflow.custom import Component
+from langflow.io import BoolInput, DropdownInput, FileInput, IntInput, MessageTextInput, Output
+from langflow.schema import Data
+
+
+class NvidiaIngestComponent(Component):
+ display_name = "NVIDIA Ingest"
+ description = "Process, transform, and store data."
+ documentation: str = "https://github.com/NVIDIA/nv-ingest/tree/main/docs"
+ icon = "NVIDIA"
+ name = "NVIDIAIngest"
+ beta = True
+
+ try:
+ from nv_ingest_client.util.file_processing.extract import EXTENSION_TO_DOCUMENT_TYPE
+
+ file_types = list(EXTENSION_TO_DOCUMENT_TYPE.keys())
+ supported_file_types_info = f"Supported file types: {', '.join(file_types)}"
+ except ImportError:
+ msg = (
+ "NVIDIA Ingest dependencies missing. "
+ "Please install them using your package manager. (e.g. uv pip install langflow[nv-ingest])"
+ )
+ file_types = [msg]
+ supported_file_types_info = msg
+
+ inputs = [
+ MessageTextInput(
+ name="base_url",
+ display_name="NVIDIA Ingestion URL",
+ info="The URL of the NVIDIA Ingestion API.",
+ ),
+ FileInput(
+ name="path",
+ display_name="Path",
+ file_types=file_types,
+ info=supported_file_types_info,
+ required=True,
+ ),
+ BoolInput(
+ name="extract_text",
+ display_name="Extract Text",
+ info="Extract text from documents",
+ value=True,
+ ),
+ BoolInput(
+ name="extract_charts",
+ display_name="Extract Charts",
+ info="Extract text from charts",
+ value=False,
+ ),
+ BoolInput(
+ name="extract_tables",
+ display_name="Extract Tables",
+ info="Extract text from tables",
+ value=True,
+ ),
+ DropdownInput(
+ name="text_depth",
+ display_name="Text Depth",
+ info=(
+ "Level at which text is extracted (applies before splitting). "
+ "Support for 'block', 'line', 'span' varies by document type."
+ ),
+ options=["document", "page", "block", "line", "span"],
+ value="document", # Default value
+ advanced=True,
+ ),
+ BoolInput(
+ name="split_text",
+ display_name="Split Text",
+ info="Split text into smaller chunks",
+ value=True,
+ ),
+ DropdownInput(
+ name="split_by",
+ display_name="Split By",
+ info="How to split into chunks ('size' splits by number of characters)",
+ options=["page", "sentence", "word", "size"],
+ value="word", # Default value
+ advanced=True,
+ ),
+ IntInput(
+ name="split_length",
+ display_name="Split Length",
+ info="The size of each chunk based on the 'split_by' method",
+ value=200,
+ advanced=True,
+ ),
+ IntInput(
+ name="split_overlap",
+ display_name="Split Overlap",
+ info="Number of segments (as determined by the 'split_by' method) to overlap from previous chunk",
+ value=20,
+ advanced=True,
+ ),
+ IntInput(
+ name="max_character_length",
+ display_name="Max Character Length",
+ info="Maximum number of characters in each chunk",
+ value=1000,
+ advanced=True,
+ ),
+ IntInput(
+ name="sentence_window_size",
+ display_name="Sentence Window Size",
+ info="Number of sentences to include from previous and following chunk (when split_by='sentence')",
+ value=0,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="load_file"),
+ ]
+
+ def load_file(self) -> list[Data]:
+ try:
+ from nv_ingest_client.client import Ingestor
+ except ImportError as e:
+ msg = (
+ "NVIDIA Ingest dependencies missing. "
+ "Please install them using your package manager. (e.g. uv pip install langflow[nv-ingest])"
+ )
+ raise ImportError(msg) from e
+
+ self.base_url: str | None = self.base_url.strip() if self.base_url else None
+
+ if not self.path:
+ err_msg = "Upload a file to use this component."
+ self.log(err_msg, name="NVIDIAIngestComponent")
+ raise ValueError(err_msg)
+
+ resolved_path = self.resolve_path(self.path)
+ extension = Path(resolved_path).suffix[1:].lower()
+ if extension not in self.file_types:
+ err_msg = f"Unsupported file type: {extension}"
+ self.log(err_msg, name="NVIDIAIngestComponent")
+ raise ValueError(err_msg)
+
+ try:
+ parsed_url = urlparse(self.base_url)
+ if not parsed_url.hostname or not parsed_url.port:
+ err_msg = "Invalid URL: Missing hostname or port."
+ self.log(err_msg, name="NVIDIAIngestComponent")
+ raise ValueError(err_msg)
+ except Exception as e:
+ self.log(f"Error parsing URL: {e}", name="NVIDIAIngestComponent")
+ raise
+
+ self.log(
+ f"Creating Ingestor for host: {parsed_url.hostname!r}, port: {parsed_url.port!r}",
+ name="NVIDIAIngestComponent",
+ )
+ try:
+ from nv_ingest_client.client import Ingestor
+
+ ingestor = (
+ Ingestor(message_client_hostname=parsed_url.hostname, message_client_port=parsed_url.port)
+ .files(resolved_path)
+ .extract(
+ extract_text=self.extract_text,
+ extract_tables=self.extract_tables,
+ extract_charts=self.extract_charts,
+ extract_images=False, # Currently not supported
+ text_depth=self.text_depth,
+ )
+ )
+ except Exception as e:
+ self.log(f"Error creating Ingestor: {e}", name="NVIDIAIngestComponent")
+ raise
+
+ if self.split_text:
+ ingestor = ingestor.split(
+ split_by=self.split_by,
+ split_length=self.split_length,
+ split_overlap=self.split_overlap,
+ max_character_length=self.max_character_length,
+ sentence_window_size=self.sentence_window_size,
+ )
+
+ try:
+ result = ingestor.ingest()
+ except Exception as e:
+ self.log(f"Error during ingestion: {e}", name="NVIDIAIngestComponent")
+ raise
+
+ self.log(f"Results: {result}", name="NVIDIAIngestComponent")
+
+ data = []
+ document_type_text = "text"
+ document_type_structured = "structured"
+
+ # Result is a list of segments as determined by the text_depth option (if "document" then only one segment)
+ # each segment is a list of elements (text, structured, image)
+ for segment in result:
+ for element in segment:
+ document_type = element.get("document_type")
+ metadata = element.get("metadata", {})
+ source_metadata = metadata.get("source_metadata", {})
+ content_metadata = metadata.get("content_metadata", {})
+
+ if document_type == document_type_text:
+ data.append(
+ Data(
+ text=metadata.get("content", ""),
+ file_path=source_metadata.get("source_name", ""),
+ document_type=document_type,
+ description=content_metadata.get("description", ""),
+ )
+ )
+ # Both charts and tables are returned as "structured" document type,
+ # with extracted text in "table_content"
+ elif document_type == document_type_structured:
+ table_metadata = metadata.get("table_metadata", {})
+ data.append(
+ Data(
+ text=table_metadata.get("table_content", ""),
+ file_path=source_metadata.get("source_name", ""),
+ document_type=document_type,
+ description=content_metadata.get("description", ""),
+ )
+ )
+ else:
+ # image is not yet supported; skip if encountered
+ self.log(f"Unsupported document type: {document_type}", name="NVIDIAIngestComponent")
+
+ self.status = data or "No data"
+ return data
diff --git a/langflow/src/backend/base/langflow/components/nvidia/nvidia_rerank.py b/langflow/src/backend/base/langflow/components/nvidia/nvidia_rerank.py
new file mode 100644
index 0000000..122e0ec
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/nvidia/nvidia_rerank.py
@@ -0,0 +1,63 @@
+from typing import Any
+
+from langflow.base.compressors.model import LCCompressorComponent
+from langflow.field_typing import BaseDocumentCompressor
+from langflow.inputs.inputs import SecretStrInput
+from langflow.io import DropdownInput, StrInput
+from langflow.schema.dotdict import dotdict
+from langflow.template.field.base import Output
+
+
+class NvidiaRerankComponent(LCCompressorComponent):
+ display_name = "NVIDIA Rerank"
+ description = "Rerank documents using the NVIDIA API."
+ icon = "NVIDIA"
+
+ inputs = [
+ *LCCompressorComponent.inputs,
+ SecretStrInput(
+ name="api_key",
+ display_name="NVIDIA API Key",
+ ),
+ StrInput(
+ name="base_url",
+ display_name="Base URL",
+ value="https://integrate.api.nvidia.com/v1",
+ refresh_button=True,
+ info="The base URL of the NVIDIA API. Defaults to https://integrate.api.nvidia.com/v1.",
+ ),
+ DropdownInput(
+ name="model",
+ display_name="Model",
+ options=["nv-rerank-qa-mistral-4b:1"],
+ value="nv-rerank-qa-mistral-4b:1",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Reranked Documents",
+ name="reranked_documents",
+ method="compress_documents",
+ ),
+ ]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "base_url" and field_value:
+ try:
+ build_model = self.build_compressor()
+ ids = [model.id for model in build_model.available_models]
+ build_config["model"]["options"] = ids
+ build_config["model"]["value"] = ids[0]
+ except Exception as e:
+ msg = f"Error getting model names: {e}"
+ raise ValueError(msg) from e
+ return build_config
+
+ def build_compressor(self) -> BaseDocumentCompressor:
+ try:
+ from langchain_nvidia_ai_endpoints import NVIDIARerank
+ except ImportError as e:
+ msg = "Please install langchain-nvidia-ai-endpoints to use the NVIDIA model."
+ raise ImportError(msg) from e
+ return NVIDIARerank(api_key=self.api_key, model=self.model, base_url=self.base_url, top_n=self.top_n)
diff --git a/langflow/src/backend/base/langflow/components/olivya/__init__.py b/langflow/src/backend/base/langflow/components/olivya/__init__.py
new file mode 100644
index 0000000..8aa987f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/olivya/__init__.py
@@ -0,0 +1,3 @@
+from .olivya import OlivyaComponent
+
+__all__ = ["OlivyaComponent"]
diff --git a/langflow/src/backend/base/langflow/components/olivya/olivya.py b/langflow/src/backend/base/langflow/components/olivya/olivya.py
new file mode 100644
index 0000000..1604df9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/olivya/olivya.py
@@ -0,0 +1,116 @@
+import json
+
+import httpx
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema import Data
+
+
+class OlivyaComponent(Component):
+ display_name = "Place Call"
+ description = "A component to create an outbound call request from Olivya's platform."
+ documentation: str = "http://docs.langflow.org/components/olivya"
+ icon = "Olivya"
+ name = "OlivyaComponent"
+
+ inputs = [
+ MessageTextInput(
+ name="api_key",
+ display_name="API Key",
+ info="Your API key for authentication",
+ value="",
+ required=True,
+ ),
+ MessageTextInput(
+ name="from_number",
+ display_name="From Number",
+ info="The Agent's phone number",
+ value="",
+ required=True,
+ ),
+ MessageTextInput(
+ name="to_number",
+ display_name="To Number",
+ info="The recipient's phone number",
+ value="",
+ required=True,
+ ),
+ MessageTextInput(
+ name="first_message",
+ display_name="First Message",
+ info="The Agent's introductory message",
+ value="",
+ required=False,
+ tool_mode=True,
+ ),
+ MessageTextInput(
+ name="system_prompt",
+ display_name="System Prompt",
+ info="The system prompt to guide the interaction",
+ value="",
+ required=False,
+ ),
+ MessageTextInput(
+ name="conversation_history",
+ display_name="Conversation History",
+ info="The summary of the conversation",
+ value="",
+ required=False,
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="build_output"),
+ ]
+
+ async def build_output(self) -> Data:
+ try:
+ payload = {
+ "variables": {
+ "first_message": self.first_message.strip() if self.first_message else None,
+ "system_prompt": self.system_prompt.strip() if self.system_prompt else None,
+ "conversation_history": self.conversation_history.strip() if self.conversation_history else None,
+ },
+ "from_number": self.from_number.strip(),
+ "to_number": self.to_number.strip(),
+ }
+
+ headers = {
+ "Authorization": self.api_key.strip(),
+ "Content-Type": "application/json",
+ }
+
+ logger.info("Sending POST request with payload: %s", payload)
+
+ # Send the POST request with a timeout
+ async with httpx.AsyncClient() as client:
+ response = await client.post(
+ "https://phone.olivya.io/create_zap_call",
+ headers=headers,
+ json=payload,
+ timeout=10.0,
+ )
+ response.raise_for_status()
+
+ # Parse and return the successful response
+ response_data = response.json()
+ logger.info("Request successful: %s", response_data)
+
+ except httpx.HTTPStatusError as http_err:
+ logger.exception("HTTP error occurred")
+ response_data = {"error": f"HTTP error occurred: {http_err}", "response_text": response.text}
+ except httpx.RequestError as req_err:
+ logger.exception("Request failed")
+ response_data = {"error": f"Request failed: {req_err}"}
+ except json.JSONDecodeError as json_err:
+ logger.exception("Response parsing failed")
+ response_data = {"error": f"Response parsing failed: {json_err}", "raw_response": response.text}
+ except Exception as e: # noqa: BLE001
+ logger.exception("An unexpected error occurred")
+ response_data = {"error": f"An unexpected error occurred: {e!s}"}
+
+ # Return the response as part of the output
+ return Data(value=response_data)
diff --git a/langflow/src/backend/base/langflow/components/output_parsers/__init__.py b/langflow/src/backend/base/langflow/components/output_parsers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/outputs/__init__.py b/langflow/src/backend/base/langflow/components/outputs/__init__.py
new file mode 100644
index 0000000..0a48af2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/outputs/__init__.py
@@ -0,0 +1,4 @@
+from .chat import ChatOutput
+from .text import TextOutputComponent
+
+__all__ = ["ChatOutput", "TextOutputComponent"]
diff --git a/langflow/src/backend/base/langflow/components/outputs/chat.py b/langflow/src/backend/base/langflow/components/outputs/chat.py
new file mode 100644
index 0000000..e97342d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/outputs/chat.py
@@ -0,0 +1,220 @@
+from collections.abc import Generator
+from typing import Any
+
+from langflow.base.io.chat import ChatComponent
+from langflow.inputs import BoolInput
+from langflow.inputs.inputs import HandleInput
+from langflow.io import DropdownInput, MessageTextInput, Output
+from langflow.schema.data import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.message import Message
+from langflow.schema.properties import Source
+from langflow.utils.constants import (
+ MESSAGE_SENDER_AI,
+ MESSAGE_SENDER_NAME_AI,
+ MESSAGE_SENDER_USER,
+)
+
+
+class ChatOutput(ChatComponent):
+ display_name = "Chat Output"
+ description = "Display a chat message in the Playground."
+ icon = "MessagesSquare"
+ name = "ChatOutput"
+ minimized = True
+
+ inputs = [
+ HandleInput(
+ name="input_value",
+ display_name="Text",
+ info="Message to be passed as output.",
+ input_types=["Data", "DataFrame", "Message"],
+ required=True,
+ ),
+ BoolInput(
+ name="should_store_message",
+ display_name="Store Messages",
+ info="Store the message in the history.",
+ value=True,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="sender",
+ display_name="Sender Type",
+ options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],
+ value=MESSAGE_SENDER_AI,
+ advanced=True,
+ info="Type of sender.",
+ ),
+ MessageTextInput(
+ name="sender_name",
+ display_name="Sender Name",
+ info="Name of the sender.",
+ value=MESSAGE_SENDER_NAME_AI,
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="session_id",
+ display_name="Session ID",
+ info="The session ID of the chat. If empty, the current session ID parameter will be used.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="data_template",
+ display_name="Data Template",
+ value="{text}",
+ advanced=True,
+ info="Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ ),
+ MessageTextInput(
+ name="background_color",
+ display_name="Background Color",
+ info="The background color of the icon.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="chat_icon",
+ display_name="Icon",
+ info="The icon of the message.",
+ advanced=True,
+ ),
+ MessageTextInput(
+ name="text_color",
+ display_name="Text Color",
+ info="The text color of the name",
+ advanced=True,
+ ),
+ BoolInput(
+ name="clean_data",
+ display_name="Basic Clean Data",
+ value=True,
+ info="Whether to clean the data",
+ advanced=True,
+ ),
+ ]
+ outputs = [
+ Output(
+ display_name="Message",
+ name="message",
+ method="message_response",
+ ),
+ ]
+
+ def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:
+ source_dict = {}
+ if id_:
+ source_dict["id"] = id_
+ if display_name:
+ source_dict["display_name"] = display_name
+ if source:
+ # Handle case where source is a ChatOpenAI object
+ if hasattr(source, "model_name"):
+ source_dict["source"] = source.model_name
+ elif hasattr(source, "model"):
+ source_dict["source"] = str(source.model)
+ else:
+ source_dict["source"] = str(source)
+ return Source(**source_dict)
+
+ async def message_response(self) -> Message:
+ # First convert the input to string if needed
+ text = self.convert_to_string()
+ # Get source properties
+ source, icon, display_name, source_id = self.get_properties_from_source_component()
+ background_color = self.background_color
+ text_color = self.text_color
+ if self.chat_icon:
+ icon = self.chat_icon
+
+ # Create or use existing Message object
+ if isinstance(self.input_value, Message):
+ message = self.input_value
+ # Update message properties
+ message.text = text
+ else:
+ message = Message(text=text)
+
+ # Set message properties
+ message.sender = self.sender
+ message.sender_name = self.sender_name
+ message.session_id = self.session_id
+ message.flow_id = self.graph.flow_id if hasattr(self, "graph") else None
+ message.properties.source = self._build_source(source_id, display_name, source)
+ message.properties.icon = icon
+ message.properties.background_color = background_color
+ message.properties.text_color = text_color
+
+ # Store message if needed
+ if self.session_id and self.should_store_message:
+ stored_message = await self.send_message(message)
+ self.message.value = stored_message
+ message = stored_message
+
+ self.status = message
+ return message
+
+ def _validate_input(self) -> None:
+ """Validate the input data and raise ValueError if invalid."""
+ if self.input_value is None:
+ msg = "Input data cannot be None"
+ raise ValueError(msg)
+ if isinstance(self.input_value, list) and not all(
+ isinstance(item, Message | Data | DataFrame | str) for item in self.input_value
+ ):
+ invalid_types = [
+ type(item).__name__
+ for item in self.input_value
+ if not isinstance(item, Message | Data | DataFrame | str)
+ ]
+ msg = f"Expected Data or DataFrame or Message or str, got {invalid_types}"
+ raise TypeError(msg)
+ if not isinstance(
+ self.input_value,
+ Message | Data | DataFrame | str | list | Generator | type(None),
+ ):
+ type_name = type(self.input_value).__name__
+ msg = f"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}"
+ raise TypeError(msg)
+
+ def _safe_convert(self, data: Any) -> str:
+ """Safely convert input data to string."""
+ try:
+ if isinstance(data, str):
+ return data
+ if isinstance(data, Message):
+ return data.get_text()
+ if isinstance(data, Data):
+ if data.get_text() is None:
+ msg = "Empty Data object"
+ raise ValueError(msg)
+ return data.get_text()
+ if isinstance(data, DataFrame):
+ if self.clean_data:
+ # Remove empty rows
+ data = data.dropna(how="all")
+ # Remove empty lines in each cell
+ data = data.replace(r"^\s*$", "", regex=True)
+ # Replace multiple newlines with a single newline
+ data = data.replace(r"\n+", "\n", regex=True)
+
+ # Replace pipe characters to avoid markdown table issues
+ processed_data = data.replace(r"\|", r"\\|", regex=True)
+
+ processed_data = processed_data.map(
+ lambda x: str(x).replace("\n", " ") if isinstance(x, str) else x
+ )
+
+ return processed_data.to_markdown(index=False)
+ return str(data)
+ except (ValueError, TypeError, AttributeError) as e:
+ msg = f"Error converting data: {e!s}"
+ raise ValueError(msg) from e
+
+ def convert_to_string(self) -> str | Generator[Any, None, None]:
+ """Convert input data to string with proper error handling."""
+ self._validate_input()
+ if isinstance(self.input_value, list):
+ return "\n".join([self._safe_convert(item) for item in self.input_value])
+ if isinstance(self.input_value, Generator):
+ return self.input_value
+ return self._safe_convert(self.input_value)
diff --git a/langflow/src/backend/base/langflow/components/outputs/text.py b/langflow/src/backend/base/langflow/components/outputs/text.py
new file mode 100644
index 0000000..7726cbc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/outputs/text.py
@@ -0,0 +1,28 @@
+from langflow.base.io.text import TextComponent
+from langflow.io import MultilineInput, Output
+from langflow.schema.message import Message
+
+
+class TextOutputComponent(TextComponent):
+ display_name = "Text Output"
+ description = "Display a text output in the Playground."
+ icon = "type"
+ name = "TextOutput"
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Text",
+ info="Text to be passed as output.",
+ ),
+ ]
+ outputs = [
+ Output(display_name="Message", name="text", method="text_response"),
+ ]
+
+ def text_response(self) -> Message:
+ message = Message(
+ text=self.input_value,
+ )
+ self.status = self.input_value
+ return message
diff --git a/langflow/src/backend/base/langflow/components/processing/__init__.py b/langflow/src/backend/base/langflow/components/processing/__init__.py
new file mode 100644
index 0000000..4895546
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/__init__.py
@@ -0,0 +1,32 @@
+from .alter_metadata import AlterMetadataComponent
+from .combine_text import CombineTextComponent
+from .create_data import CreateDataComponent
+from .extract_key import ExtractDataKeyComponent
+from .filter_data_values import DataFilterComponent
+from .json_cleaner import JSONCleaner
+from .llm_router import LLMRouterComponent
+from .merge_data import MergeDataComponent
+from .message_to_data import MessageToDataComponent
+from .parse_data import ParseDataComponent
+from .parse_json_data import ParseJSONDataComponent
+from .select_data import SelectDataComponent
+from .split_text import SplitTextComponent
+from .update_data import UpdateDataComponent
+
+__all__ = [
+ "AlterMetadataComponent",
+ "CombineTextComponent",
+ "CreateDataComponent",
+ "DataFilterComponent",
+ "ExtractDataKeyComponent",
+ "JSONCleaner",
+ "LLMRouterComponent",
+ "MergeDataComponent",
+ "MessageToDataComponent",
+ "ParseDataComponent",
+ "ParseDataFrameComponent",
+ "ParseJSONDataComponent",
+ "SelectDataComponent",
+ "SplitTextComponent",
+ "UpdateDataComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/processing/alter_metadata.py b/langflow/src/backend/base/langflow/components/processing/alter_metadata.py
new file mode 100644
index 0000000..118f410
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/alter_metadata.py
@@ -0,0 +1,90 @@
+from langflow.custom import Component
+from langflow.inputs import MessageTextInput
+from langflow.io import HandleInput, NestedDictInput, Output, StrInput
+from langflow.schema import Data
+
+
+class AlterMetadataComponent(Component):
+ display_name = "Alter Metadata"
+ description = "Adds/Removes Metadata Dictionary on inputs"
+ icon = "merge"
+ name = "AlterMetadata"
+
+ inputs = [
+ HandleInput(
+ name="input_value",
+ display_name="Input",
+ info="Object(s) to which Metadata should be added",
+ required=False,
+ input_types=["Message", "Data"],
+ is_list=True,
+ ),
+ StrInput(
+ name="text_in",
+ display_name="User Text",
+ info="Text input; value will be in 'text' attribute of Data object. Empty text entries are ignored.",
+ required=False,
+ ),
+ NestedDictInput(
+ name="metadata",
+ display_name="Metadata",
+ info="Metadata to add to each object",
+ input_types=["Data"],
+ required=True,
+ ),
+ MessageTextInput(
+ name="remove_fields",
+ display_name="Fields to Remove",
+ info="Metadata Fields to Remove",
+ required=False,
+ is_list=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ name="data",
+ display_name="Data",
+ info="List of Input objects each with added Metadata",
+ method="process_output",
+ ),
+ ]
+
+ def _as_clean_dict(self, obj):
+ """Convert a Data object or a standard dictionary to a standard dictionary."""
+ if isinstance(obj, dict):
+ as_dict = obj
+ elif isinstance(obj, Data):
+ as_dict = obj.data
+ else:
+ msg = f"Expected a Data object or a dictionary but got {type(obj)}."
+ raise TypeError(msg)
+
+ return {k: v for k, v in (as_dict or {}).items() if k and k.strip()}
+
+ def process_output(self) -> list[Data]:
+ # Ensure metadata is a dictionary, filtering out any empty keys
+ metadata = self._as_clean_dict(self.metadata)
+
+ # Convert text_in to a Data object if it exists, and initialize our list of Data objects
+ data_objects = [Data(text=self.text_in)] if self.text_in else []
+
+ # Append existing Data objects from input_value, if any
+ if self.input_value:
+ data_objects.extend(self.input_value)
+
+ # Update each Data object with the new metadata, preserving existing fields
+ for data in data_objects:
+ data.data.update(metadata)
+
+ # Handle removal of fields specified in remove_fields
+ if self.remove_fields:
+ fields_to_remove = {field.strip() for field in self.remove_fields if field.strip()}
+
+ # Remove specified fields from each Data object's metadata
+ for data in data_objects:
+ data.data = {k: v for k, v in data.data.items() if k not in fields_to_remove}
+
+ # Set the status for tracking/debugging purposes
+ self.status = data_objects
+ return data_objects
diff --git a/langflow/src/backend/base/langflow/components/processing/combine_text.py b/langflow/src/backend/base/langflow/components/processing/combine_text.py
new file mode 100644
index 0000000..251d337
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/combine_text.py
@@ -0,0 +1,38 @@
+from langflow.custom import Component
+from langflow.io import MessageTextInput, Output
+from langflow.schema.message import Message
+
+
+class CombineTextComponent(Component):
+ display_name = "Combine Text"
+ description = "Concatenate two text sources into a single text chunk using a specified delimiter."
+ icon = "merge"
+ name = "CombineText"
+
+ inputs = [
+ MessageTextInput(
+ name="text1",
+ display_name="First Text",
+ info="The first text input to concatenate.",
+ ),
+ MessageTextInput(
+ name="text2",
+ display_name="Second Text",
+ info="The second text input to concatenate.",
+ ),
+ MessageTextInput(
+ name="delimiter",
+ display_name="Delimiter",
+ info="A string used to separate the two text inputs. Defaults to a whitespace.",
+ value=" ",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Combined Text", name="combined_text", method="combine_texts"),
+ ]
+
+ def combine_texts(self) -> Message:
+ combined = self.delimiter.join([self.text1, self.text2])
+ self.status = combined
+ return Message(text=combined)
diff --git a/langflow/src/backend/base/langflow/components/processing/create_data.py b/langflow/src/backend/base/langflow/components/processing/create_data.py
new file mode 100644
index 0000000..ec528c0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/create_data.py
@@ -0,0 +1,110 @@
+from typing import Any
+
+from langflow.custom import Component
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs.inputs import BoolInput, DictInput, IntInput, MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+
+class CreateDataComponent(Component):
+ display_name: str = "Create Data"
+ description: str = "Dynamically create a Data with a specified number of fields."
+ name: str = "CreateData"
+ MAX_FIELDS = 15 # Define a constant for maximum number of fields
+ legacy = True
+ icon = "ListFilter"
+
+ inputs = [
+ IntInput(
+ name="number_of_fields",
+ display_name="Number of Fields",
+ info="Number of fields to be added to the record.",
+ real_time_refresh=True,
+ value=1,
+ range_spec=RangeSpec(min=1, max=MAX_FIELDS, step=1, step_type="int"),
+ ),
+ MessageTextInput(
+ name="text_key",
+ display_name="Text Key",
+ info="Key that identifies the field to be used as the text content.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="text_key_validator",
+ display_name="Text Key Validator",
+ advanced=True,
+ info="If enabled, checks if the given 'Text Key' is present in the given 'Data'.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="build_data"),
+ ]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ if field_name == "number_of_fields":
+ default_keys = ["code", "_type", "number_of_fields", "text_key", "text_key_validator"]
+ try:
+ field_value_int = int(field_value)
+ except ValueError:
+ return build_config
+ existing_fields = {}
+ if field_value_int > self.MAX_FIELDS:
+ build_config["number_of_fields"]["value"] = self.MAX_FIELDS
+ msg = (
+ f"Number of fields cannot exceed {self.MAX_FIELDS}. "
+ "Please adjust the number of fields to be within the allowed limit."
+ )
+ raise ValueError(msg)
+ if len(build_config) > len(default_keys):
+ # back up the existing template fields
+ for key in build_config.copy():
+ if key not in default_keys:
+ existing_fields[key] = build_config.pop(key)
+
+ for i in range(1, field_value_int + 1):
+ key = f"field_{i}_key"
+ if key in existing_fields:
+ field = existing_fields[key]
+ build_config[key] = field
+ else:
+ field = DictInput(
+ display_name=f"Field {i}",
+ name=key,
+ info=f"Key for field {i}.",
+ input_types=["Message", "Data"],
+ )
+ build_config[field.name] = field.to_dict()
+
+ build_config["number_of_fields"]["value"] = field_value_int
+ return build_config
+
+ async def build_data(self) -> Data:
+ data = self.get_data()
+ return_data = Data(data=data, text_key=self.text_key)
+ self.status = return_data
+ if self.text_key_validator:
+ self.validate_text_key()
+ return return_data
+
+ def get_data(self):
+ """Function to get the Data from the attributes."""
+ data = {}
+ for value_dict in self._attributes.values():
+ if isinstance(value_dict, dict):
+ # Check if the value of the value_dict is a Data
+ value_dict_ = {
+ key: value.get_text() if isinstance(value, Data) else value for key, value in value_dict.items()
+ }
+ data.update(value_dict_)
+ return data
+
+ def validate_text_key(self) -> None:
+ """This function validates that the Text Key is one of the keys in the Data."""
+ data_keys = self.get_data().keys()
+ if self.text_key not in data_keys and self.text_key != "":
+ formatted_data_keys = ", ".join(data_keys)
+ msg = f"Text Key: '{self.text_key}' not found in the Data keys: '{formatted_data_keys}'"
+ raise ValueError(msg)
diff --git a/langflow/src/backend/base/langflow/components/processing/data_to_dataframe.py b/langflow/src/backend/base/langflow/components/processing/data_to_dataframe.py
new file mode 100644
index 0000000..9cd8e47
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/data_to_dataframe.py
@@ -0,0 +1,68 @@
+from langflow.custom import Component
+from langflow.io import DataInput, Output
+from langflow.schema import Data, DataFrame
+
+
+class DataToDataFrameComponent(Component):
+ display_name = "Data → DataFrame"
+ description = (
+ "Converts one or multiple Data objects into a DataFrame. "
+ "Each Data object corresponds to one row. Fields from `.data` become columns, "
+ "and the `.text` (if present) is placed in a 'text' column."
+ )
+ icon = "table"
+ name = "DataToDataFrame"
+
+ inputs = [
+ DataInput(
+ name="data_list",
+ display_name="Data or Data List",
+ info="One or multiple Data objects to transform into a DataFrame.",
+ is_list=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="DataFrame",
+ name="dataframe",
+ method="build_dataframe",
+ info="A DataFrame built from each Data object's fields plus a 'text' column.",
+ ),
+ ]
+
+ def build_dataframe(self) -> DataFrame:
+ """Builds a DataFrame from Data objects by combining their fields.
+
+ For each Data object:
+ - Merge item.data (dictionary) as columns
+ - If item.text is present, add 'text' column
+
+ Returns a DataFrame with one row per Data object.
+ """
+ data_input = self.data_list
+
+ # If user passed a single Data, it might come in as a single object rather than a list
+ if not isinstance(data_input, list):
+ data_input = [data_input]
+
+ rows = []
+ for item in data_input:
+ if not isinstance(item, Data):
+ msg = f"Expected Data objects, got {type(item)} instead."
+ raise TypeError(msg)
+
+ # Start with a copy of item.data or an empty dict
+ row_dict = dict(item.data) if item.data else {}
+
+ # If the Data object has text, store it under 'text' col
+ text_val = item.get_text()
+ if text_val:
+ row_dict["text"] = text_val
+
+ rows.append(row_dict)
+
+ # Build a DataFrame from these row dictionaries
+ df_result = DataFrame(rows)
+ self.status = df_result # store in self.status for logs
+ return df_result
diff --git a/langflow/src/backend/base/langflow/components/processing/dataframe_operations.py b/langflow/src/backend/base/langflow/components/processing/dataframe_operations.py
new file mode 100644
index 0000000..f217d37
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/dataframe_operations.py
@@ -0,0 +1,212 @@
+from langflow.custom import Component
+from langflow.io import BoolInput, DataFrameInput, DropdownInput, IntInput, MessageTextInput, Output, StrInput
+from langflow.schema import DataFrame
+
+
+class DataFrameOperationsComponent(Component):
+ display_name = "DataFrame Operations"
+ description = "Perform various operations on a DataFrame."
+ icon = "table"
+
+ # Available operations
+ OPERATION_CHOICES = [
+ "Add Column",
+ "Drop Column",
+ "Filter",
+ "Head",
+ "Rename Column",
+ "Replace Value",
+ "Select Columns",
+ "Sort",
+ "Tail",
+ ]
+
+ inputs = [
+ DataFrameInput(
+ name="df",
+ display_name="DataFrame",
+ info="The input DataFrame to operate on.",
+ ),
+ DropdownInput(
+ name="operation",
+ display_name="Operation",
+ options=OPERATION_CHOICES,
+ info="Select the DataFrame operation to perform.",
+ real_time_refresh=True,
+ ),
+ StrInput(
+ name="column_name",
+ display_name="Column Name",
+ info="The column name to use for the operation.",
+ dynamic=True,
+ show=False,
+ ),
+ MessageTextInput(
+ name="filter_value",
+ display_name="Filter Value",
+ info="The value to filter rows by.",
+ dynamic=True,
+ show=False,
+ ),
+ BoolInput(
+ name="ascending",
+ display_name="Sort Ascending",
+ info="Whether to sort in ascending order.",
+ dynamic=True,
+ show=False,
+ value=True,
+ ),
+ StrInput(
+ name="new_column_name",
+ display_name="New Column Name",
+ info="The new column name when renaming or adding a column.",
+ dynamic=True,
+ show=False,
+ ),
+ MessageTextInput(
+ name="new_column_value",
+ display_name="New Column Value",
+ info="The value to populate the new column with.",
+ dynamic=True,
+ show=False,
+ ),
+ StrInput(
+ name="columns_to_select",
+ display_name="Columns to Select",
+ dynamic=True,
+ is_list=True,
+ show=False,
+ ),
+ IntInput(
+ name="num_rows",
+ display_name="Number of Rows",
+ info="Number of rows to return (for head/tail).",
+ dynamic=True,
+ show=False,
+ value=5,
+ ),
+ MessageTextInput(
+ name="replace_value",
+ display_name="Value to Replace",
+ info="The value to replace in the column.",
+ dynamic=True,
+ show=False,
+ ),
+ MessageTextInput(
+ name="replacement_value",
+ display_name="Replacement Value",
+ info="The value to replace with.",
+ dynamic=True,
+ show=False,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="DataFrame",
+ name="output",
+ method="perform_operation",
+ info="The resulting DataFrame after the operation.",
+ )
+ ]
+
+ def update_build_config(self, build_config, field_value, field_name=None):
+ # Hide all dynamic fields by default
+ dynamic_fields = [
+ "column_name",
+ "filter_value",
+ "ascending",
+ "new_column_name",
+ "new_column_value",
+ "columns_to_select",
+ "num_rows",
+ "replace_value",
+ "replacement_value",
+ ]
+ for field in dynamic_fields:
+ build_config[field]["show"] = False
+
+ # Show relevant fields based on the selected operation
+ if field_name == "operation":
+ if field_value == "Filter":
+ build_config["column_name"]["show"] = True
+ build_config["filter_value"]["show"] = True
+ elif field_value == "Sort":
+ build_config["column_name"]["show"] = True
+ build_config["ascending"]["show"] = True
+ elif field_value == "Drop Column":
+ build_config["column_name"]["show"] = True
+ elif field_value == "Rename Column":
+ build_config["column_name"]["show"] = True
+ build_config["new_column_name"]["show"] = True
+ elif field_value == "Add Column":
+ build_config["new_column_name"]["show"] = True
+ build_config["new_column_value"]["show"] = True
+ elif field_value == "Select Columns":
+ build_config["columns_to_select"]["show"] = True
+ elif field_value in {"Head", "Tail"}:
+ build_config["num_rows"]["show"] = True
+ elif field_value == "Replace Value":
+ build_config["column_name"]["show"] = True
+ build_config["replace_value"]["show"] = True
+ build_config["replacement_value"]["show"] = True
+
+ return build_config
+
+ def perform_operation(self) -> DataFrame:
+ dataframe_copy = self.df.copy()
+ operation = self.operation
+
+ if operation == "Filter":
+ return self.filter_rows_by_value(dataframe_copy)
+ if operation == "Sort":
+ return self.sort_by_column(dataframe_copy)
+ if operation == "Drop Column":
+ return self.drop_column(dataframe_copy)
+ if operation == "Rename Column":
+ return self.rename_column(dataframe_copy)
+ if operation == "Add Column":
+ return self.add_column(dataframe_copy)
+ if operation == "Select Columns":
+ return self.select_columns(dataframe_copy)
+ if operation == "Head":
+ return self.head(dataframe_copy)
+ if operation == "Tail":
+ return self.tail(dataframe_copy)
+ if operation == "Replace Value":
+ return self.replace_values(dataframe_copy)
+ msg = f"Unsupported operation: {operation}"
+
+ raise ValueError(msg)
+
+ # Existing methods
+ def filter_rows_by_value(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df[df[self.column_name] == self.filter_value])
+
+ def sort_by_column(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df.sort_values(by=self.column_name, ascending=self.ascending))
+
+ def drop_column(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df.drop(columns=[self.column_name]))
+
+ def rename_column(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df.rename(columns={self.column_name: self.new_column_name}))
+
+ def add_column(self, df: DataFrame) -> DataFrame:
+ df[self.new_column_name] = [self.new_column_value] * len(df)
+ return DataFrame(df)
+
+ def select_columns(self, df: DataFrame) -> DataFrame:
+ columns = [col.strip() for col in self.columns_to_select]
+ return DataFrame(df[columns])
+
+ # New methods
+ def head(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df.head(self.num_rows))
+
+ def tail(self, df: DataFrame) -> DataFrame:
+ return DataFrame(df.tail(self.num_rows))
+
+ def replace_values(self, df: DataFrame) -> DataFrame:
+ df[self.column_name] = df[self.column_name].replace(self.replace_value, self.replacement_value)
+ return DataFrame(df)
diff --git a/langflow/src/backend/base/langflow/components/processing/extract_key.py b/langflow/src/backend/base/langflow/components/processing/extract_key.py
new file mode 100644
index 0000000..eaeb82b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/extract_key.py
@@ -0,0 +1,53 @@
+from langflow.custom import Component
+from langflow.io import DataInput, Output, StrInput
+from langflow.schema import Data
+
+
+class ExtractDataKeyComponent(Component):
+ display_name = "Extract Key"
+ description = (
+ "Extract a specific key from a Data object or a list of "
+ "Data objects and return the extracted value(s) as Data object(s)."
+ )
+ icon = "key"
+ name = "ExtractaKey"
+ legacy = True
+
+ inputs = [
+ DataInput(
+ name="data_input",
+ display_name="Data Input",
+ info="The Data object or list of Data objects to extract the key from.",
+ ),
+ StrInput(
+ name="key",
+ display_name="Key to Extract",
+ info="The key in the Data object(s) to extract.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Extracted Data", name="extracted_data", method="extract_key"),
+ ]
+
+ def extract_key(self) -> Data | list[Data]:
+ key = self.key
+
+ if isinstance(self.data_input, list):
+ result = []
+ for item in self.data_input:
+ if isinstance(item, Data) and key in item.data:
+ extracted_value = item.data[key]
+ result.append(Data(data={key: extracted_value}))
+ self.status = result
+ return result
+ if isinstance(self.data_input, Data):
+ if key in self.data_input.data:
+ extracted_value = self.data_input.data[key]
+ result = Data(data={key: extracted_value})
+ self.status = result
+ return result
+ self.status = f"Key '{key}' not found in Data object."
+ return Data(data={"error": f"Key '{key}' not found in Data object."})
+ self.status = "Invalid input. Expected Data object or list of Data objects."
+ return Data(data={"error": "Invalid input. Expected Data object or list of Data objects."})
diff --git a/langflow/src/backend/base/langflow/components/processing/filter_data.py b/langflow/src/backend/base/langflow/components/processing/filter_data.py
new file mode 100644
index 0000000..bbdfe68
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/filter_data.py
@@ -0,0 +1,41 @@
+from langflow.custom import Component
+from langflow.io import DataInput, MessageTextInput, Output
+from langflow.schema import Data
+
+
+class FilterDataComponent(Component):
+ display_name = "Filter Data"
+ description = "Filters a Data object based on a list of keys."
+ icon = "filter"
+ beta = True
+ name = "FilterData"
+
+ inputs = [
+ DataInput(
+ name="data",
+ display_name="Data",
+ info="Data object to filter.",
+ ),
+ MessageTextInput(
+ name="filter_criteria",
+ display_name="Filter Criteria",
+ info="List of keys to filter by.",
+ is_list=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Filtered Data", name="filtered_data", method="filter_data"),
+ ]
+
+ def filter_data(self) -> Data:
+ filter_criteria: list[str] = self.filter_criteria
+ data = self.data.data if isinstance(self.data, Data) else {}
+
+ # Filter the data
+ filtered = {key: value for key, value in data.items() if key in filter_criteria}
+
+ # Create a new Data object with the filtered data
+ filtered_data = Data(data=filtered)
+ self.status = filtered_data
+ return filtered_data
diff --git a/langflow/src/backend/base/langflow/components/processing/filter_data_values.py b/langflow/src/backend/base/langflow/components/processing/filter_data_values.py
new file mode 100644
index 0000000..ff3afb1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/filter_data_values.py
@@ -0,0 +1,87 @@
+from typing import Any
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, MessageTextInput, Output
+from langflow.schema import Data
+
+
+class DataFilterComponent(Component):
+ display_name = "Filter Values"
+ description = (
+ "Filter a list of data items based on a specified key, filter value,"
+ " and comparison operator. Check advanced options to select match comparision."
+ )
+ icon = "filter"
+ beta = True
+ name = "FilterDataValues"
+
+ inputs = [
+ DataInput(name="input_data", display_name="Input Data", info="The list of data items to filter.", is_list=True),
+ MessageTextInput(
+ name="filter_key",
+ display_name="Filter Key",
+ info="The key to filter on (e.g., 'route').",
+ value="route",
+ input_types=["Data"],
+ ),
+ MessageTextInput(
+ name="filter_value",
+ display_name="Filter Value",
+ info="The value to filter by (e.g., 'CMIP').",
+ value="CMIP",
+ input_types=["Data"],
+ ),
+ DropdownInput(
+ name="operator",
+ display_name="Comparison Operator",
+ options=["equals", "not equals", "contains", "starts with", "ends with"],
+ info="The operator to apply for comparing the values.",
+ value="equals",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Filtered Data", name="filtered_data", method="filter_data"),
+ ]
+
+ def compare_values(self, item_value: Any, filter_value: str, operator: str) -> bool:
+ if operator == "equals":
+ return str(item_value) == filter_value
+ if operator == "not equals":
+ return str(item_value) != filter_value
+ if operator == "contains":
+ return filter_value in str(item_value)
+ if operator == "starts with":
+ return str(item_value).startswith(filter_value)
+ if operator == "ends with":
+ return str(item_value).endswith(filter_value)
+ return False
+
+ def filter_data(self) -> list[Data]:
+ # Extract inputs
+ input_data: list[Data] = self.input_data
+ filter_key: str = self.filter_key.text
+ filter_value: str = self.filter_value.text
+ operator: str = self.operator
+
+ # Validate inputs
+ if not input_data:
+ self.status = "Input data is empty."
+ return []
+
+ if not filter_key or not filter_value:
+ self.status = "Filter key or value is missing."
+ return input_data
+
+ # Filter the data
+ filtered_data = []
+ for item in input_data:
+ if isinstance(item.data, dict) and filter_key in item.data:
+ if self.compare_values(item.data[filter_key], filter_value, operator):
+ filtered_data.append(item)
+ else:
+ self.status = f"Warning: Some items don't have the key '{filter_key}' or are not dictionaries."
+
+ self.status = filtered_data
+ return filtered_data
diff --git a/langflow/src/backend/base/langflow/components/processing/json_cleaner.py b/langflow/src/backend/base/langflow/components/processing/json_cleaner.py
new file mode 100644
index 0000000..93c193a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/json_cleaner.py
@@ -0,0 +1,103 @@
+import json
+import unicodedata
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, MessageTextInput
+from langflow.schema.message import Message
+from langflow.template import Output
+
+
+class JSONCleaner(Component):
+ icon = "braces"
+ display_name = "JSON Cleaner"
+ description = (
+ "Cleans the messy and sometimes incorrect JSON strings produced by LLMs "
+ "so that they are fully compliant with the JSON spec."
+ )
+
+ inputs = [
+ MessageTextInput(
+ name="json_str", display_name="JSON String", info="The JSON string to be cleaned.", required=True
+ ),
+ BoolInput(
+ name="remove_control_chars",
+ display_name="Remove Control Characters",
+ info="Remove control characters from the JSON string.",
+ required=False,
+ ),
+ BoolInput(
+ name="normalize_unicode",
+ display_name="Normalize Unicode",
+ info="Normalize Unicode characters in the JSON string.",
+ required=False,
+ ),
+ BoolInput(
+ name="validate_json",
+ display_name="Validate JSON",
+ info="Validate the JSON string to ensure it is well-formed.",
+ required=False,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Cleaned JSON String", name="output", method="clean_json"),
+ ]
+
+ def clean_json(self) -> Message:
+ try:
+ from json_repair import repair_json
+ except ImportError as e:
+ msg = "Could not import the json_repair package. Please install it with `pip install json_repair`."
+ raise ImportError(msg) from e
+
+ """Clean the input JSON string based on provided options and return the cleaned JSON string."""
+ json_str = self.json_str
+ remove_control_chars = self.remove_control_chars
+ normalize_unicode = self.normalize_unicode
+ validate_json = self.validate_json
+
+ start = json_str.find("{")
+ end = json_str.rfind("}")
+ if start == -1 or end == -1:
+ msg = "Invalid JSON string: Missing '{' or '}'"
+ raise ValueError(msg)
+ try:
+ json_str = json_str[start : end + 1]
+
+ if remove_control_chars:
+ json_str = self._remove_control_characters(json_str)
+ if normalize_unicode:
+ json_str = self._normalize_unicode(json_str)
+ if validate_json:
+ json_str = self._validate_json(json_str)
+
+ cleaned_json_str = repair_json(json_str)
+ result = str(cleaned_json_str)
+
+ self.status = result
+ return Message(text=result)
+ except Exception as e:
+ msg = f"Error cleaning JSON string: {e}"
+ raise ValueError(msg) from e
+
+ def _remove_control_characters(self, s: str) -> str:
+ """Remove control characters from the string."""
+ return s.translate(self.translation_table)
+
+ def _normalize_unicode(self, s: str) -> str:
+ """Normalize Unicode characters in the string."""
+ return unicodedata.normalize("NFC", s)
+
+ def _validate_json(self, s: str) -> str:
+ """Validate the JSON string."""
+ try:
+ json.loads(s)
+ except json.JSONDecodeError as e:
+ msg = f"Invalid JSON string: {e}"
+ raise ValueError(msg) from e
+ return s
+
+ def __init__(self, *args, **kwargs):
+ # Create a translation table that maps control characters to None
+ super().__init__(*args, **kwargs)
+ self.translation_table = str.maketrans("", "", "".join(chr(i) for i in range(32)) + chr(127))
diff --git a/langflow/src/backend/base/langflow/components/processing/llm_router.py b/langflow/src/backend/base/langflow/components/processing/llm_router.py
new file mode 100644
index 0000000..a6150f6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/llm_router.py
@@ -0,0 +1,178 @@
+import json
+
+import requests
+
+from langflow.base.models.chat_result import get_chat_result
+from langflow.base.models.model_utils import get_model_name
+from langflow.custom import Component
+from langflow.io import DropdownInput, HandleInput, Output
+from langflow.schema.message import Message
+
+
+class LLMRouterComponent(Component):
+ display_name = "LLM Router"
+ description = "Routes the input to the most appropriate LLM based on OpenRouter model specifications"
+ icon = "git-branch"
+
+ inputs = [
+ HandleInput(
+ name="models",
+ display_name="Language Models",
+ input_types=["LanguageModel"],
+ required=True,
+ is_list=True,
+ info="List of LLMs to route between",
+ ),
+ HandleInput(
+ name="input_value",
+ display_name="Input",
+ input_types=["Message"],
+ info="The input message to be routed",
+ ),
+ HandleInput(
+ name="judge_llm",
+ display_name="Judge LLM",
+ input_types=["LanguageModel"],
+ info="LLM that will evaluate and select the most appropriate model",
+ ),
+ DropdownInput(
+ name="optimization",
+ display_name="Optimization",
+ options=["quality", "speed", "cost", "balanced"],
+ value="balanced",
+ info="Optimization preference for model selection",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Output", name="output", method="route_to_model"),
+ Output(
+ display_name="Selected Model",
+ name="selected_model",
+ method="get_selected_model",
+ required_inputs=["output"],
+ ),
+ ]
+
+ _selected_model_name: str | None = None
+
+ def get_selected_model(self) -> str:
+ return self._selected_model_name or ""
+
+ def _get_model_specs(self, model_name: str) -> str:
+ """Fetch specific model information from OpenRouter API."""
+ http_success = 200
+ base_info = f"Model: {model_name}\n"
+
+ # Remove any special characters and spaces, keep only alphanumeric
+ clean_name = "".join(c.lower() for c in model_name if c.isalnum())
+ url = f"https://openrouter.ai/api/v1/models/{clean_name}/endpoints"
+
+ try:
+ response = requests.get(url, timeout=10)
+ except requests.exceptions.RequestException as e:
+ return base_info + f"Error fetching specs: {e!s}"
+
+ if response.status_code != http_success:
+ return base_info + "No specifications available"
+
+ try:
+ data = response.json().get("data", {})
+ except (json.JSONDecodeError, requests.exceptions.JSONDecodeError):
+ return base_info + "Error parsing response data"
+
+ # Extract relevant information
+ context_length = data.get("context_length", "Unknown")
+ max_completion_tokens = data.get("max_completion_tokens", "Unknown")
+ architecture = data.get("architecture", {})
+ tokenizer = architecture.get("tokenizer", "Unknown")
+ instruct_type = architecture.get("instruct_type", "Unknown")
+
+ pricing = data.get("pricing", {})
+ prompt_price = pricing.get("prompt", "Unknown")
+ completion_price = pricing.get("completion", "Unknown")
+
+ description = data.get("description", "No description available")
+ created = data.get("created", "Unknown")
+
+ return f"""
+Model: {model_name}
+Description: {description}
+Context Length: {context_length} tokens
+Max Completion Tokens: {max_completion_tokens}
+Tokenizer: {tokenizer}
+Instruct Type: {instruct_type}
+Pricing: ${prompt_price}/1k tokens (prompt), ${completion_price}/1k tokens (completion)
+Created: {created}
+"""
+
+ MISSING_INPUTS_MSG = "Missing required inputs: models, input_value, or judge_llm"
+
+ async def route_to_model(self) -> Message:
+ if not self.models or not self.input_value or not self.judge_llm:
+ raise ValueError(self.MISSING_INPUTS_MSG)
+
+ system_prompt = {
+ "role": "system",
+ "content": (
+ "You are a model selection expert. Analyze the input and select the most "
+ "appropriate model based on:\n"
+ "1. Task complexity and requirements\n"
+ "2. Context length needed\n"
+ "3. Model capabilities\n"
+ "4. Cost considerations\n"
+ "5. Speed requirements\n\n"
+ "Consider the detailed model specifications provided and the user's "
+ "optimization preference. Return only the index number (0-based) of the best model."
+ ),
+ }
+
+ # Create list of available models with their detailed specs
+ models_info = []
+ for i, model in enumerate(self.models):
+ model_name = get_model_name(model)
+ model_specs = self._get_model_specs(model_name)
+ models_info.append(f"=== Model {i} ===\n{model_specs}")
+
+ models_str = "\n\n".join(models_info)
+
+ user_message = {
+ "role": "user",
+ "content": f"""Available Models with Specifications:\n{models_str}\n
+ Optimization Preference: {self.optimization}\n
+ Input Query: "{self.input_value.text}"\n
+ Based on the model specifications and optimization preference,
+ select the most appropriate model (return only the index number):""",
+ }
+
+ try:
+ # Get judge's decision
+ response = await self.judge_llm.ainvoke([system_prompt, user_message])
+
+ try:
+ selected_index = int(response.content.strip())
+ if 0 <= selected_index < len(self.models):
+ chosen_model = self.models[selected_index]
+ self._selected_model_name = get_model_name(chosen_model)
+ else:
+ chosen_model = self.models[0]
+ self._selected_model_name = get_model_name(chosen_model)
+ except ValueError:
+ chosen_model = self.models[0]
+ self._selected_model_name = get_model_name(chosen_model)
+
+ # Get response from chosen model
+ return get_chat_result(
+ runnable=chosen_model,
+ input_value=self.input_value,
+ )
+
+ except (RuntimeError, ValueError) as e:
+ self.status = f"Error: {e!s}"
+ # Fallback to first model
+ chosen_model = self.models[0]
+ self._selected_model_name = get_model_name(chosen_model)
+ return get_chat_result(
+ runnable=chosen_model,
+ input_value=self.input_value,
+ )
diff --git a/langflow/src/backend/base/langflow/components/processing/merge_data.py b/langflow/src/backend/base/langflow/components/processing/merge_data.py
new file mode 100644
index 0000000..cc6c346
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/merge_data.py
@@ -0,0 +1,90 @@
+from enum import Enum
+from typing import cast
+
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import DataInput, DropdownInput, Output
+from langflow.schema import DataFrame
+
+
+class DataOperation(str, Enum):
+ CONCATENATE = "Concatenate"
+ APPEND = "Append"
+ MERGE = "Merge"
+ JOIN = "Join"
+
+
+class MergeDataComponent(Component):
+ display_name = "Combine Data"
+ description = "Combines data using different operations"
+ icon = "merge"
+ MIN_INPUTS_REQUIRED = 2
+
+ inputs = [
+ DataInput(name="data_inputs", display_name="Data Inputs", info="Data to combine", is_list=True, required=True),
+ DropdownInput(
+ name="operation",
+ display_name="Operation Type",
+ options=[op.value for op in DataOperation],
+ value=DataOperation.CONCATENATE.value,
+ ),
+ ]
+ outputs = [Output(display_name="DataFrame", name="combined_data", method="combine_data")]
+
+ def combine_data(self) -> DataFrame:
+ if not self.data_inputs or len(self.data_inputs) < self.MIN_INPUTS_REQUIRED:
+ empty_dataframe = DataFrame()
+ self.status = empty_dataframe
+ return empty_dataframe
+
+ operation = DataOperation(self.operation)
+ try:
+ combined_dataframe = self._process_operation(operation)
+ self.status = combined_dataframe
+ except Exception as e:
+ logger.error(f"Error during operation {operation}: {e!s}")
+ raise
+ else:
+ return combined_dataframe
+
+ def _process_operation(self, operation: DataOperation) -> DataFrame:
+ if operation == DataOperation.CONCATENATE:
+ combined_data: dict[str, str | object] = {}
+ for data_input in self.data_inputs:
+ for key, value in data_input.data.items():
+ if key in combined_data:
+ if isinstance(combined_data[key], str) and isinstance(value, str):
+ combined_data[key] = f"{combined_data[key]}\n{value}"
+ else:
+ combined_data[key] = value
+ else:
+ combined_data[key] = value
+ return DataFrame([combined_data])
+
+ if operation == DataOperation.APPEND:
+ rows = [data_input.data for data_input in self.data_inputs]
+ return DataFrame(rows)
+
+ if operation == DataOperation.MERGE:
+ result_data: dict[str, str | list[str] | object] = {}
+ for data_input in self.data_inputs:
+ for key, value in data_input.data.items():
+ if key in result_data and isinstance(value, str):
+ if isinstance(result_data[key], list):
+ cast("list[str]", result_data[key]).append(value)
+ else:
+ result_data[key] = [result_data[key], value]
+ else:
+ result_data[key] = value
+ return DataFrame([result_data])
+
+ if operation == DataOperation.JOIN:
+ combined_data = {}
+ for idx, data_input in enumerate(self.data_inputs, 1):
+ for key, value in data_input.data.items():
+ new_key = f"{key}_doc{idx}" if idx > 1 else key
+ combined_data[new_key] = value
+ return DataFrame([combined_data])
+
+ return DataFrame()
diff --git a/langflow/src/backend/base/langflow/components/processing/message_to_data.py b/langflow/src/backend/base/langflow/components/processing/message_to_data.py
new file mode 100644
index 0000000..f5303de
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/message_to_data.py
@@ -0,0 +1,36 @@
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.io import MessageInput, Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class MessageToDataComponent(Component):
+ display_name = "Message to Data"
+ description = "Convert a Message object to a Data object"
+ icon = "message-square-share"
+ beta = True
+ name = "MessagetoData"
+
+ inputs = [
+ MessageInput(
+ name="message",
+ display_name="Message",
+ info="The Message object to convert to a Data object",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="convert_message_to_data"),
+ ]
+
+ def convert_message_to_data(self) -> Data:
+ if isinstance(self.message, Message):
+ # Convert Message to Data
+ return Data(data=self.message.data)
+
+ msg = "Error converting Message to Data: Input must be a Message object"
+ logger.opt(exception=True).debug(msg)
+ self.status = msg
+ return Data(data={"error": msg})
diff --git a/langflow/src/backend/base/langflow/components/processing/parse_data.py b/langflow/src/backend/base/langflow/components/processing/parse_data.py
new file mode 100644
index 0000000..f6d865e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/parse_data.py
@@ -0,0 +1,69 @@
+from langflow.custom import Component
+from langflow.helpers.data import data_to_text, data_to_text_list
+from langflow.io import DataInput, MultilineInput, Output, StrInput
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class ParseDataComponent(Component):
+ display_name = "Data to Message"
+ description = "Convert Data objects into Messages using any {field_name} from input data."
+ icon = "message-square"
+ name = "ParseData"
+ metadata = {
+ "legacy_name": "Parse Data",
+ }
+
+ inputs = [
+ DataInput(
+ name="data",
+ display_name="Data",
+ info="The data to convert to text.",
+ is_list=True,
+ required=True,
+ ),
+ MultilineInput(
+ name="template",
+ display_name="Template",
+ info="The template to use for formatting the data. "
+ "It can contain the keys {text}, {data} or any other key in the Data.",
+ value="{text}",
+ required=True,
+ ),
+ StrInput(name="sep", display_name="Separator", advanced=True, value="\n"),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Message",
+ name="text",
+ info="Data as a single Message, with each input Data separated by Separator",
+ method="parse_data",
+ ),
+ Output(
+ display_name="Data List",
+ name="data_list",
+ info="Data as a list of new Data, each having `text` formatted by Template",
+ method="parse_data_as_list",
+ ),
+ ]
+
+ def _clean_args(self) -> tuple[list[Data], str, str]:
+ data = self.data if isinstance(self.data, list) else [self.data]
+ template = self.template
+ sep = self.sep
+ return data, template, sep
+
+ def parse_data(self) -> Message:
+ data, template, sep = self._clean_args()
+ result_string = data_to_text(template, data, sep)
+ self.status = result_string
+ return Message(text=result_string)
+
+ def parse_data_as_list(self) -> list[Data]:
+ data, template, _ = self._clean_args()
+ text_list, data_list = data_to_text_list(template, data)
+ for item, text in zip(data_list, text_list, strict=True):
+ item.set_text(text)
+ self.status = data_list
+ return data_list
diff --git a/langflow/src/backend/base/langflow/components/processing/parse_dataframe.py b/langflow/src/backend/base/langflow/components/processing/parse_dataframe.py
new file mode 100644
index 0000000..f408459
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/parse_dataframe.py
@@ -0,0 +1,67 @@
+from langflow.custom import Component
+from langflow.io import DataFrameInput, MultilineInput, Output, StrInput
+from langflow.schema.message import Message
+
+
+class ParseDataFrameComponent(Component):
+ display_name = "Parse DataFrame"
+ description = (
+ "Convert a DataFrame into plain text following a specified template. "
+ "Each column in the DataFrame is treated as a possible template key, e.g. {col_name}."
+ )
+ icon = "braces"
+ name = "ParseDataFrame"
+
+ inputs = [
+ DataFrameInput(name="df", display_name="DataFrame", info="The DataFrame to convert to text rows."),
+ MultilineInput(
+ name="template",
+ display_name="Template",
+ info=(
+ "The template for formatting each row. "
+ "Use placeholders matching column names in the DataFrame, for example '{col1}', '{col2}'."
+ ),
+ value="{text}",
+ ),
+ StrInput(
+ name="sep",
+ display_name="Separator",
+ advanced=True,
+ value="\n",
+ info="String that joins all row texts when building the single Text output.",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Text",
+ name="text",
+ info="All rows combined into a single text, each row formatted by the template and separated by `sep`.",
+ method="parse_data",
+ ),
+ ]
+
+ def _clean_args(self):
+ dataframe = self.df
+ template = self.template or "{text}"
+ sep = self.sep or "\n"
+ return dataframe, template, sep
+
+ def parse_data(self) -> Message:
+ """Converts each row of the DataFrame into a formatted string using the template.
+
+ then joins them with `sep`. Returns a single combined string as a Message.
+ """
+ dataframe, template, sep = self._clean_args()
+
+ lines = []
+ # For each row in the DataFrame, build a dict and format
+ for _, row in dataframe.iterrows():
+ row_dict = row.to_dict()
+ text_line = template.format(**row_dict) # e.g. template="{text}", row_dict={"text": "Hello"}
+ lines.append(text_line)
+
+ # Join all lines with the provided separator
+ result_string = sep.join(lines)
+ self.status = result_string # store in self.status for UI logs
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/processing/parse_json_data.py b/langflow/src/backend/base/langflow/components/processing/parse_json_data.py
new file mode 100644
index 0000000..0881b0b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/parse_json_data.py
@@ -0,0 +1,90 @@
+import json
+from json import JSONDecodeError
+
+import jq
+from json_repair import repair_json
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.inputs import HandleInput, MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class ParseJSONDataComponent(Component):
+ display_name = "Parse JSON"
+ description = "Convert and extract JSON fields."
+ icon = "braces"
+ name = "ParseJSONData"
+ legacy: bool = True
+
+ inputs = [
+ HandleInput(
+ name="input_value",
+ display_name="Input",
+ info="Data object to filter.",
+ required=True,
+ input_types=["Message", "Data"],
+ ),
+ MessageTextInput(
+ name="query",
+ display_name="JQ Query",
+ info="JQ Query to filter the data. The input is always a JSON list.",
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Filtered Data", name="filtered_data", method="filter_data"),
+ ]
+
+ def _parse_data(self, input_value) -> str:
+ if isinstance(input_value, Message) and isinstance(input_value.text, str):
+ return input_value.text
+ if isinstance(input_value, Data):
+ return json.dumps(input_value.data)
+ return str(input_value)
+
+ def filter_data(self) -> list[Data]:
+ to_filter = self.input_value
+ if not to_filter:
+ return []
+ # Check if input is a list
+ if isinstance(to_filter, list):
+ to_filter = [self._parse_data(f) for f in to_filter]
+ else:
+ to_filter = self._parse_data(to_filter)
+
+ # If input is not a list, don't wrap it in a list
+ if not isinstance(to_filter, list):
+ to_filter = repair_json(to_filter)
+ try:
+ to_filter_as_dict = json.loads(to_filter)
+ except JSONDecodeError:
+ try:
+ to_filter_as_dict = json.loads(repair_json(to_filter))
+ except JSONDecodeError as e:
+ msg = f"Invalid JSON: {e}"
+ raise ValueError(msg) from e
+ else:
+ to_filter = [repair_json(f) for f in to_filter]
+ to_filter_as_dict = []
+ for f in to_filter:
+ try:
+ to_filter_as_dict.append(json.loads(f))
+ except JSONDecodeError:
+ try:
+ to_filter_as_dict.append(json.loads(repair_json(f)))
+ except JSONDecodeError as e:
+ msg = f"Invalid JSON: {e}"
+ raise ValueError(msg) from e
+ to_filter = to_filter_as_dict
+
+ full_filter_str = json.dumps(to_filter_as_dict)
+
+ logger.info("to_filter: ", to_filter)
+
+ results = jq.compile(self.query).input_text(full_filter_str).all()
+ logger.info("results: ", results)
+ return [Data(data=value) if isinstance(value, dict) else Data(text=str(value)) for value in results]
diff --git a/langflow/src/backend/base/langflow/components/processing/save_to_file.py b/langflow/src/backend/base/langflow/components/processing/save_to_file.py
new file mode 100644
index 0000000..d595d74
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/save_to_file.py
@@ -0,0 +1,172 @@
+import json
+from collections.abc import AsyncIterator, Iterator
+from pathlib import Path
+
+import pandas as pd
+
+from langflow.custom import Component
+from langflow.io import (
+ DataFrameInput,
+ DataInput,
+ DropdownInput,
+ MessageInput,
+ Output,
+ StrInput,
+)
+from langflow.schema import Data, DataFrame, Message
+
+
+class SaveToFileComponent(Component):
+ display_name = "Save to File"
+ description = "Save DataFrames, Data, or Messages to various file formats."
+ icon = "save"
+ name = "SaveToFile"
+
+ # File format options for different types
+ DATA_FORMAT_CHOICES = ["csv", "excel", "json", "markdown"]
+ MESSAGE_FORMAT_CHOICES = ["txt", "json", "markdown"]
+
+ inputs = [
+ DropdownInput(
+ name="input_type",
+ display_name="Input Type",
+ options=["DataFrame", "Data", "Message"],
+ info="Select the type of input to save.",
+ value="DataFrame",
+ real_time_refresh=True,
+ ),
+ DataFrameInput(
+ name="df",
+ display_name="DataFrame",
+ info="The DataFrame to save.",
+ dynamic=True,
+ show=True,
+ ),
+ DataInput(
+ name="data",
+ display_name="Data",
+ info="The Data object to save.",
+ dynamic=True,
+ show=False,
+ ),
+ MessageInput(
+ name="message",
+ display_name="Message",
+ info="The Message to save.",
+ dynamic=True,
+ show=False,
+ ),
+ DropdownInput(
+ name="file_format",
+ display_name="File Format",
+ options=DATA_FORMAT_CHOICES,
+ info="Select the file format to save the input.",
+ real_time_refresh=True,
+ ),
+ StrInput(
+ name="file_path",
+ display_name="File Path (including filename)",
+ info="The full file path (including filename and extension).",
+ value="./output",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ name="confirmation",
+ display_name="Confirmation",
+ method="save_to_file",
+ info="Confirmation message after saving the file.",
+ ),
+ ]
+
+ def update_build_config(self, build_config, field_value, field_name=None):
+ # Hide/show dynamic fields based on the selected input type
+ if field_name == "input_type":
+ build_config["df"]["show"] = field_value == "DataFrame"
+ build_config["data"]["show"] = field_value == "Data"
+ build_config["message"]["show"] = field_value == "Message"
+
+ if field_value in {"DataFrame", "Data"}:
+ build_config["file_format"]["options"] = self.DATA_FORMAT_CHOICES
+ elif field_value == "Message":
+ build_config["file_format"]["options"] = self.MESSAGE_FORMAT_CHOICES
+
+ return build_config
+
+ def save_to_file(self) -> str:
+ input_type = self.input_type
+ file_format = self.file_format
+ file_path = Path(self.file_path).expanduser()
+
+ # Ensure the directory exists
+ if not file_path.parent.exists():
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+
+ if input_type == "DataFrame":
+ dataframe = self.df
+ return self._save_dataframe(dataframe, file_path, file_format)
+ if input_type == "Data":
+ data = self.data
+ return self._save_data(data, file_path, file_format)
+ if input_type == "Message":
+ message = self.message
+ return self._save_message(message, file_path, file_format)
+
+ error_msg = f"Unsupported input type: {input_type}"
+ raise ValueError(error_msg)
+
+ def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:
+ if fmt == "csv":
+ dataframe.to_csv(path, index=False)
+ elif fmt == "excel":
+ dataframe.to_excel(path, index=False, engine="openpyxl")
+ elif fmt == "json":
+ dataframe.to_json(path, orient="records", indent=2)
+ elif fmt == "markdown":
+ path.write_text(dataframe.to_markdown(index=False), encoding="utf-8")
+ else:
+ error_msg = f"Unsupported DataFrame format: {fmt}"
+ raise ValueError(error_msg)
+
+ return f"DataFrame saved successfully as '{path}'"
+
+ def _save_data(self, data: Data, path: Path, fmt: str) -> str:
+ if fmt == "csv":
+ pd.DataFrame(data.data).to_csv(path, index=False)
+ elif fmt == "excel":
+ pd.DataFrame(data.data).to_excel(path, index=False, engine="openpyxl")
+ elif fmt == "json":
+ path.write_text(json.dumps(data.data, indent=2), encoding="utf-8")
+ elif fmt == "markdown":
+ path.write_text(pd.DataFrame(data.data).to_markdown(index=False), encoding="utf-8")
+ else:
+ error_msg = f"Unsupported Data format: {fmt}"
+ raise ValueError(error_msg)
+
+ return f"Data saved successfully as '{path}'"
+
+ def _save_message(self, message: Message, path: Path, fmt: str) -> str:
+ if message.text is None:
+ content = ""
+ elif isinstance(message.text, AsyncIterator):
+ # AsyncIterator needs to be handled differently
+ error_msg = "AsyncIterator not supported"
+ raise ValueError(error_msg)
+ elif isinstance(message.text, Iterator):
+ # Convert iterator to string
+ content = " ".join(str(item) for item in message.text)
+ else:
+ content = str(message.text)
+
+ if fmt == "txt":
+ path.write_text(content, encoding="utf-8")
+ elif fmt == "json":
+ path.write_text(json.dumps({"message": content}, indent=2), encoding="utf-8")
+ elif fmt == "markdown":
+ path.write_text(f"**Message:**\n\n{content}", encoding="utf-8")
+ else:
+ error_msg = f"Unsupported Message format: {fmt}"
+ raise ValueError(error_msg)
+
+ return f"Message saved successfully as '{path}'"
diff --git a/langflow/src/backend/base/langflow/components/processing/select_data.py b/langflow/src/backend/base/langflow/components/processing/select_data.py
new file mode 100644
index 0000000..577c1d4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/select_data.py
@@ -0,0 +1,48 @@
+from langflow.custom import Component
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs.inputs import DataInput, IntInput
+from langflow.io import Output
+from langflow.schema import Data
+
+
+class SelectDataComponent(Component):
+ display_name: str = "Select Data"
+ description: str = "Select a single data from a list of data."
+ name: str = "SelectData"
+ icon = "prototypes"
+ legacy = True
+
+ inputs = [
+ DataInput(
+ name="data_list",
+ display_name="Data List",
+ info="List of data to select from.",
+ is_list=True, # Specify that this input takes a list of Data objects
+ ),
+ IntInput(
+ name="data_index",
+ display_name="Data Index",
+ info="Index of the data to select.",
+ value=0, # Will be populated dynamically based on the length of data_list
+ range_spec=RangeSpec(min=0, max=15, step=1, step_type="int"),
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Selected Data", name="selected_data", method="select_data"),
+ ]
+
+ async def select_data(self) -> Data:
+ # Retrieve the selected index from the dropdown
+ selected_index = int(self.data_index)
+ # Get the data list
+
+ # Validate that the selected index is within bounds
+ if selected_index < 0 or selected_index >= len(self.data_list):
+ msg = f"Selected index {selected_index} is out of range."
+ raise ValueError(msg)
+
+ # Return the selected Data object
+ selected_data = self.data_list[selected_index]
+ self.status = selected_data # Update the component status to reflect the selected data
+ return selected_data
diff --git a/langflow/src/backend/base/langflow/components/processing/split_text.py b/langflow/src/backend/base/langflow/components/processing/split_text.py
new file mode 100644
index 0000000..e6118f1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/split_text.py
@@ -0,0 +1,108 @@
+from langchain_text_splitters import CharacterTextSplitter
+
+from langflow.custom import Component
+from langflow.io import HandleInput, IntInput, MessageTextInput, Output
+from langflow.schema import Data, DataFrame
+from langflow.utils.util import unescape_string
+
+
+class SplitTextComponent(Component):
+ display_name: str = "Split Text"
+ description: str = "Split text into chunks based on specified criteria."
+ icon = "scissors-line-dashed"
+ name = "SplitText"
+
+ inputs = [
+ HandleInput(
+ name="data_inputs",
+ display_name="Input Documents",
+ info="The data to split.",
+ input_types=["Data", "DataFrame"],
+ required=True,
+ ),
+ IntInput(
+ name="chunk_overlap",
+ display_name="Chunk Overlap",
+ info="Number of characters to overlap between chunks.",
+ value=200,
+ ),
+ IntInput(
+ name="chunk_size",
+ display_name="Chunk Size",
+ info="The maximum number of characters in each chunk.",
+ value=1000,
+ ),
+ MessageTextInput(
+ name="separator",
+ display_name="Separator",
+ info="The character to split on. Defaults to newline.",
+ value="\n",
+ ),
+ MessageTextInput(
+ name="text_key",
+ display_name="Text Key",
+ info="The key to use for the text column.",
+ value="text",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Chunks", name="chunks", method="split_text"),
+ Output(display_name="DataFrame", name="dataframe", method="as_dataframe"),
+ ]
+
+ def _docs_to_data(self, docs) -> list[Data]:
+ return [Data(text=doc.page_content, data=doc.metadata) for doc in docs]
+
+ def _docs_to_dataframe(self, docs):
+ data_dicts = [{self.text_key: doc.page_content, **doc.metadata} for doc in docs]
+ return DataFrame(data_dicts)
+
+ def split_text_base(self):
+ separator = unescape_string(self.separator)
+ if isinstance(self.data_inputs, DataFrame):
+ if not len(self.data_inputs):
+ msg = "DataFrame is empty"
+ raise TypeError(msg)
+
+ self.data_inputs.text_key = self.text_key
+ try:
+ documents = self.data_inputs.to_lc_documents()
+ except Exception as e:
+ msg = f"Error converting DataFrame to documents: {e}"
+ raise TypeError(msg) from e
+ else:
+ if not self.data_inputs:
+ msg = "No data inputs provided"
+ raise TypeError(msg)
+
+ documents = []
+ if isinstance(self.data_inputs, Data):
+ self.data_inputs.text_key = self.text_key
+ documents = [self.data_inputs.to_lc_document()]
+ else:
+ try:
+ documents = [input_.to_lc_document() for input_ in self.data_inputs if isinstance(input_, Data)]
+ if not documents:
+ msg = f"No valid Data inputs found in {type(self.data_inputs)}"
+ raise TypeError(msg)
+ except AttributeError as e:
+ msg = f"Invalid input type in collection: {e}"
+ raise TypeError(msg) from e
+ try:
+ splitter = CharacterTextSplitter(
+ chunk_overlap=self.chunk_overlap,
+ chunk_size=self.chunk_size,
+ separator=separator,
+ )
+ return splitter.split_documents(documents)
+ except Exception as e:
+ msg = f"Error splitting text: {e}"
+ raise TypeError(msg) from e
+
+ def split_text(self) -> list[Data]:
+ return self._docs_to_data(self.split_text_base())
+
+ def as_dataframe(self) -> DataFrame:
+ return self._docs_to_dataframe(self.split_text_base())
diff --git a/langflow/src/backend/base/langflow/components/processing/update_data.py b/langflow/src/backend/base/langflow/components/processing/update_data.py
new file mode 100644
index 0000000..11ced91
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/processing/update_data.py
@@ -0,0 +1,159 @@
+from typing import Any
+
+from langflow.custom import Component
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.inputs.inputs import (
+ BoolInput,
+ DataInput,
+ DictInput,
+ IntInput,
+ MessageTextInput,
+)
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+
+class UpdateDataComponent(Component):
+ display_name: str = "Update Data"
+ description: str = "Dynamically update or append data with the specified fields."
+ name: str = "UpdateData"
+ MAX_FIELDS = 15 # Define a constant for maximum number of fields
+ icon = "FolderSync"
+
+ inputs = [
+ DataInput(
+ name="old_data",
+ display_name="Data",
+ info="The record to update.",
+ is_list=True, # Changed to True to handle list of Data objects
+ required=True,
+ ),
+ IntInput(
+ name="number_of_fields",
+ display_name="Number of Fields",
+ info="Number of fields to be added to the record.",
+ real_time_refresh=True,
+ value=0,
+ range_spec=RangeSpec(min=1, max=MAX_FIELDS, step=1, step_type="int"),
+ ),
+ MessageTextInput(
+ name="text_key",
+ display_name="Text Key",
+ info="Key that identifies the field to be used as the text content.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="text_key_validator",
+ display_name="Text Key Validator",
+ advanced=True,
+ info="If enabled, checks if the given 'Text Key' is present in the given 'Data'.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="build_data"),
+ ]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
+ """Update the build configuration when the number of fields changes.
+
+ Args:
+ build_config (dotdict): The current build configuration.
+ field_value (Any): The new value for the field.
+ field_name (Optional[str]): The name of the field being updated.
+ """
+ if field_name == "number_of_fields":
+ default_keys = {
+ "code",
+ "_type",
+ "number_of_fields",
+ "text_key",
+ "old_data",
+ "text_key_validator",
+ }
+ try:
+ field_value_int = int(field_value)
+ except ValueError:
+ return build_config
+
+ if field_value_int > self.MAX_FIELDS:
+ build_config["number_of_fields"]["value"] = self.MAX_FIELDS
+ msg = f"Number of fields cannot exceed {self.MAX_FIELDS}. Try using a Component to combine two Data."
+ raise ValueError(msg)
+
+ existing_fields = {}
+ # Back up the existing template fields
+ for key in list(build_config.keys()):
+ if key not in default_keys:
+ existing_fields[key] = build_config.pop(key)
+
+ for i in range(1, field_value_int + 1):
+ key = f"field_{i}_key"
+ if key in existing_fields:
+ field = existing_fields[key]
+ build_config[key] = field
+ else:
+ field = DictInput(
+ display_name=f"Field {i}",
+ name=key,
+ info=f"Key for field {i}.",
+ input_types=["Message", "Data"],
+ )
+ build_config[field.name] = field.to_dict()
+
+ build_config["number_of_fields"]["value"] = field_value_int
+ return build_config
+
+ async def build_data(self) -> Data | list[Data]:
+ """Build the updated data by combining the old data with new fields."""
+ new_data = self.get_data()
+ if isinstance(self.old_data, list):
+ for data_item in self.old_data:
+ if not isinstance(data_item, Data):
+ continue # Skip invalid items
+ data_item.data.update(new_data)
+ if self.text_key:
+ data_item.text_key = self.text_key
+ self.validate_text_key(data_item)
+ self.status = self.old_data
+ return self.old_data # Returns List[Data]
+ if isinstance(self.old_data, Data):
+ self.old_data.data.update(new_data)
+ if self.text_key:
+ self.old_data.text_key = self.text_key
+ self.status = self.old_data
+ self.validate_text_key(self.old_data)
+ return self.old_data # Returns Data
+ msg = "old_data is not a Data object or list of Data objects."
+ raise ValueError(msg)
+
+ def get_data(self):
+ """Function to get the Data from the attributes."""
+ data = {}
+ default_keys = {
+ "code",
+ "_type",
+ "number_of_fields",
+ "text_key",
+ "old_data",
+ "text_key_validator",
+ }
+ for attr_name, attr_value in self._attributes.items():
+ if attr_name in default_keys:
+ continue # Skip default attributes
+ if isinstance(attr_value, dict):
+ for key, value in attr_value.items():
+ data[key] = value.get_text() if isinstance(value, Data) else value
+ elif isinstance(attr_value, Data):
+ data[attr_name] = attr_value.get_text()
+ else:
+ data[attr_name] = attr_value
+ return data
+
+ def validate_text_key(self, data: Data) -> None:
+ """This function validates that the Text Key is one of the keys in the Data."""
+ data_keys = data.data.keys()
+ if self.text_key and self.text_key not in data_keys:
+ msg = f"Text Key: '{self.text_key}' not found in the Data keys: {', '.join(data_keys)}"
+ raise ValueError(msg)
diff --git a/langflow/src/backend/base/langflow/components/prompts/__init__.py b/langflow/src/backend/base/langflow/components/prompts/__init__.py
new file mode 100644
index 0000000..89e6cec
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/prompts/__init__.py
@@ -0,0 +1,3 @@
+from .prompt import PromptComponent
+
+__all__ = ["PromptComponent"]
diff --git a/langflow/src/backend/base/langflow/components/prompts/prompt.py b/langflow/src/backend/base/langflow/components/prompts/prompt.py
new file mode 100644
index 0000000..3799c10
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/prompts/prompt.py
@@ -0,0 +1,65 @@
+from langflow.base.prompts.api_utils import process_prompt_template
+from langflow.custom import Component
+from langflow.inputs.inputs import DefaultPromptField
+from langflow.io import MessageTextInput, Output, PromptInput
+from langflow.schema.message import Message
+from langflow.template.utils import update_template_values
+
+
+class PromptComponent(Component):
+ display_name: str = "Prompt"
+ description: str = "Create a prompt template with dynamic variables."
+ icon = "prompts"
+ trace_type = "prompt"
+ name = "Prompt"
+
+ inputs = [
+ PromptInput(name="template", display_name="Template"),
+ MessageTextInput(
+ name="tool_placeholder",
+ display_name="Tool Placeholder",
+ tool_mode=True,
+ advanced=True,
+ info="A placeholder input for tool mode.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Prompt Message", name="prompt", method="build_prompt"),
+ ]
+
+ async def build_prompt(self) -> Message:
+ prompt = Message.from_template(**self._attributes)
+ self.status = prompt.text
+ return prompt
+
+ def _update_template(self, frontend_node: dict):
+ prompt_template = frontend_node["template"]["template"]["value"]
+ custom_fields = frontend_node["custom_fields"]
+ frontend_node_template = frontend_node["template"]
+ _ = process_prompt_template(
+ template=prompt_template,
+ name="template",
+ custom_fields=custom_fields,
+ frontend_node_template=frontend_node_template,
+ )
+ return frontend_node
+
+ async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):
+ """This function is called after the code validation is done."""
+ frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)
+ template = frontend_node["template"]["template"]["value"]
+ # Kept it duplicated for backwards compatibility
+ _ = process_prompt_template(
+ template=template,
+ name="template",
+ custom_fields=frontend_node["custom_fields"],
+ frontend_node_template=frontend_node["template"],
+ )
+ # Now that template is updated, we need to grab any values that were set in the current_frontend_node
+ # and update the frontend_node with those values
+ update_template_values(new_template=frontend_node, previous_template=current_frontend_node["template"])
+ return frontend_node
+
+ def _get_fallback_input(self, **kwargs):
+ return DefaultPromptField(**kwargs)
diff --git a/langflow/src/backend/base/langflow/components/prototypes/__init__.py b/langflow/src/backend/base/langflow/components/prototypes/__init__.py
new file mode 100644
index 0000000..8ad61e0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/prototypes/__init__.py
@@ -0,0 +1,5 @@
+from .python_function import PythonFunctionComponent
+
+__all__ = [
+ "PythonFunctionComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/prototypes/python_function.py b/langflow/src/backend/base/langflow/components/prototypes/python_function.py
new file mode 100644
index 0000000..cb41558
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/prototypes/python_function.py
@@ -0,0 +1,73 @@
+from collections.abc import Callable
+
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.custom.utils import get_function
+from langflow.io import CodeInput, Output
+from langflow.schema import Data, dotdict
+from langflow.schema.message import Message
+
+
+class PythonFunctionComponent(Component):
+ display_name = "Python Function"
+ description = "Define and execute a Python function that returns a Data object or a Message."
+ icon = "Python"
+ name = "PythonFunction"
+ legacy = True
+
+ inputs = [
+ CodeInput(
+ name="function_code",
+ display_name="Function Code",
+ info="The code for the function.",
+ ),
+ ]
+
+ outputs = [
+ Output(
+ name="function_output",
+ display_name="Function Callable",
+ method="get_function_callable",
+ ),
+ Output(
+ name="function_output_data",
+ display_name="Function Output (Data)",
+ method="execute_function_data",
+ ),
+ Output(
+ name="function_output_str",
+ display_name="Function Output (Message)",
+ method="execute_function_message",
+ ),
+ ]
+
+ def get_function_callable(self) -> Callable:
+ function_code = self.function_code
+ self.status = function_code
+ return get_function(function_code)
+
+ def execute_function(self) -> list[dotdict | str] | dotdict | str:
+ function_code = self.function_code
+
+ if not function_code:
+ return "No function code provided."
+
+ try:
+ func = get_function(function_code)
+ return func()
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error executing function")
+ return f"Error executing function: {e}"
+
+ def execute_function_data(self) -> list[Data]:
+ results = self.execute_function()
+ results = results if isinstance(results, list) else [results]
+ return [(Data(text=x) if isinstance(x, str) else Data(**x)) for x in results]
+
+ def execute_function_message(self) -> Message:
+ results = self.execute_function()
+ results = results if isinstance(results, list) else [results]
+ results_list = [str(x) for x in results]
+ results_str = "\n".join(results_list)
+ return Message(text=results_str)
diff --git a/langflow/src/backend/base/langflow/components/retrievers/__init__.py b/langflow/src/backend/base/langflow/components/retrievers/__init__.py
new file mode 100644
index 0000000..8afcd6a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/retrievers/__init__.py
@@ -0,0 +1,11 @@
+from .amazon_kendra import AmazonKendraRetrieverComponent
+from .metal import MetalRetrieverComponent
+from .multi_query import MultiQueryRetrieverComponent
+from .needle import NeedleRetriever
+
+__all__ = [
+ "AmazonKendraRetrieverComponent",
+ "MetalRetrieverComponent",
+ "MultiQueryRetrieverComponent",
+ "NeedleRetriever",
+]
diff --git a/langflow/src/backend/base/langflow/components/retrievers/amazon_kendra.py b/langflow/src/backend/base/langflow/components/retrievers/amazon_kendra.py
new file mode 100644
index 0000000..8fdd9cc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/retrievers/amazon_kendra.py
@@ -0,0 +1,54 @@
+from typing import cast
+
+from langchain_community.retrievers import AmazonKendraRetriever
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import Retriever
+
+
+class AmazonKendraRetrieverComponent(CustomComponent):
+ display_name: str = "Amazon Kendra Retriever"
+ description: str = "Retriever that uses the Amazon Kendra API."
+ name = "AmazonKendra"
+ icon = "Amazon"
+ legacy: bool = True
+
+ def build_config(self):
+ return {
+ "index_id": {"display_name": "Index ID"},
+ "region_name": {"display_name": "Region Name"},
+ "credentials_profile_name": {"display_name": "Credentials Profile Name"},
+ "attribute_filter": {
+ "display_name": "Attribute Filter",
+ "field_type": "code",
+ },
+ "top_k": {"display_name": "Top K", "field_type": "int"},
+ "user_context": {
+ "display_name": "User Context",
+ "field_type": "code",
+ },
+ "code": {"show": False},
+ }
+
+ def build(
+ self,
+ index_id: str,
+ top_k: int = 3,
+ region_name: str | None = None,
+ credentials_profile_name: str | None = None,
+ attribute_filter: dict | None = None,
+ user_context: dict | None = None,
+ ) -> Retriever: # type: ignore[type-var]
+ try:
+ output = AmazonKendraRetriever(
+ index_id=index_id,
+ top_k=top_k,
+ region_name=region_name,
+ credentials_profile_name=credentials_profile_name,
+ attribute_filter=attribute_filter,
+ user_context=user_context,
+ )
+ except Exception as e:
+ msg = "Could not connect to AmazonKendra API."
+ raise ValueError(msg) from e
+ return cast("Retriever", output)
diff --git a/langflow/src/backend/base/langflow/components/retrievers/metal.py b/langflow/src/backend/base/langflow/components/retrievers/metal.py
new file mode 100644
index 0000000..88c228f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/retrievers/metal.py
@@ -0,0 +1,31 @@
+from typing import cast
+
+from langchain_community.retrievers import MetalRetriever
+from metal_sdk.metal import Metal
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import Retriever
+
+
+class MetalRetrieverComponent(CustomComponent):
+ display_name: str = "Metal Retriever"
+ description: str = "Retriever that uses the Metal API."
+ name = "MetalRetriever"
+ legacy: bool = True
+
+ 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"},
+ "code": {"show": False},
+ }
+
+ def build(self, api_key: str, client_id: str, index_id: str, params: dict | None = None) -> Retriever: # type: ignore[type-var]
+ try:
+ metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
+ except Exception as e:
+ msg = "Could not connect to Metal API."
+ raise ValueError(msg) from e
+ return cast("Retriever", MetalRetriever(client=metal, params=params or {}))
diff --git a/langflow/src/backend/base/langflow/components/retrievers/multi_query.py b/langflow/src/backend/base/langflow/components/retrievers/multi_query.py
new file mode 100644
index 0000000..6d381af
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/retrievers/multi_query.py
@@ -0,0 +1,50 @@
+from langchain.retrievers import MultiQueryRetriever
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import BaseRetriever, LanguageModel, PromptTemplate, Text
+
+
+class MultiQueryRetrieverComponent(CustomComponent):
+ display_name = "MultiQueryRetriever"
+ description = "Initialize from llm using default template."
+ documentation = "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever"
+ name = "MultiQueryRetriever"
+ legacy: bool = True
+
+ def build_config(self):
+ return {
+ "llm": {"display_name": "LLM"},
+ "prompt": {
+ "display_name": "Prompt",
+ "default": {
+ "input_variables": ["question"],
+ "input_types": {},
+ "output_parser": None,
+ "partial_variables": {},
+ "template": "You are an AI language model assistant. Your task is \n"
+ "to generate 3 different versions of the given user \n"
+ "question to retrieve relevant documents from a vector database. \n"
+ "By generating multiple perspectives on the user question, \n"
+ "your goal is to help the user overcome some of the limitations \n"
+ "of distance-based similarity search. Provide these alternative \n"
+ "questions separated by newlines. Original question: {question}",
+ "template_format": "f-string",
+ "validate_template": False,
+ "_type": "prompt",
+ },
+ },
+ "retriever": {"display_name": "Retriever"},
+ "parser_key": {"display_name": "Parser Key", "default": "lines"},
+ }
+
+ def build(
+ self,
+ llm: LanguageModel,
+ retriever: BaseRetriever,
+ prompt: Text | None = None,
+ parser_key: str = "lines",
+ ) -> MultiQueryRetriever:
+ if not prompt:
+ return MultiQueryRetriever.from_llm(llm=llm, retriever=retriever, parser_key=parser_key)
+ prompt_template = PromptTemplate.from_template(prompt)
+ return MultiQueryRetriever.from_llm(llm=llm, retriever=retriever, prompt=prompt_template, parser_key=parser_key)
diff --git a/langflow/src/backend/base/langflow/components/scrapegraph/__init__.py b/langflow/src/backend/base/langflow/components/scrapegraph/__init__.py
new file mode 100644
index 0000000..f14fca5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/scrapegraph/__init__.py
@@ -0,0 +1,5 @@
+from .scrapegraph_markdownify_api import ScrapeGraphMarkdownifyApi
+from .scrapegraph_search_api import ScrapeGraphSearchApi
+from .scrapegraph_smart_scraper_api import ScrapeGraphSmartScraperApi
+
+__all__ = ["ScrapeGraphMarkdownifyApi", "ScrapeGraphSearchApi", "ScrapeGraphSmartScraperApi"]
diff --git a/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_markdownify_api.py b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_markdownify_api.py
new file mode 100644
index 0000000..79ffff8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_markdownify_api.py
@@ -0,0 +1,66 @@
+from langflow.custom import Component
+from langflow.io import (
+ MessageTextInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class ScrapeGraphMarkdownifyApi(Component):
+ display_name: str = "ScrapeGraphMarkdownifyApi"
+ description: str = """ScrapeGraph Markdownify API.
+ Given a URL, it will return the markdownified content of the website.
+ More info at https://docs.scrapegraphai.com/services/markdownify"""
+ name = "ScrapeGraphMarkdownifyApi"
+
+ output_types: list[str] = ["Document"]
+ documentation: str = "https://docs.scrapegraphai.com/introduction"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="ScrapeGraph API Key",
+ required=True,
+ password=True,
+ info="The API key to use ScrapeGraph API.",
+ ),
+ MessageTextInput(
+ name="url",
+ display_name="URL",
+ tool_mode=True,
+ info="The URL to markdownify.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="scrape"),
+ ]
+
+ def scrape(self) -> list[Data]:
+ try:
+ from scrapegraph_py import Client
+ from scrapegraph_py.logger import sgai_logger
+ except ImportError as e:
+ msg = "Could not import scrapegraph-py package. Please install it with `pip install scrapegraph-py`."
+ raise ImportError(msg) from e
+
+ # Set logging level
+ sgai_logger.set_logging(level="INFO")
+
+ # Initialize the client with API key
+ sgai_client = Client(api_key=self.api_key)
+
+ try:
+ # Markdownify request
+ response = sgai_client.markdownify(
+ website_url=self.url,
+ )
+
+ # Close the client
+ sgai_client.close()
+
+ return Data(data=response)
+ except Exception:
+ sgai_client.close()
+ raise
diff --git a/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_search_api.py b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_search_api.py
new file mode 100644
index 0000000..22778ce
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_search_api.py
@@ -0,0 +1,66 @@
+from langflow.custom import Component
+from langflow.io import (
+ MessageTextInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class ScrapeGraphSearchApi(Component):
+ display_name: str = "ScrapeGraphSearchApi"
+ description: str = """ScrapeGraph Search API.
+ Given a search prompt, it will return search results using ScrapeGraph's search functionality.
+ More info at https://docs.scrapegraphai.com/services/searchscraper"""
+ name = "ScrapeGraphSearchApi"
+
+ documentation: str = "https://docs.scrapegraphai.com/introduction"
+ icon = "ScrapeGraph"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="ScrapeGraph API Key",
+ required=True,
+ password=True,
+ info="The API key to use ScrapeGraph API.",
+ ),
+ MessageTextInput(
+ name="user_prompt",
+ display_name="Search Prompt",
+ tool_mode=True,
+ info="The search prompt to use.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="search"),
+ ]
+
+ def search(self) -> list[Data]:
+ try:
+ from scrapegraph_py import Client
+ from scrapegraph_py.logger import sgai_logger
+ except ImportError as e:
+ msg = "Could not import scrapegraph-py package. Please install it with `pip install scrapegraph-py`."
+ raise ImportError(msg) from e
+
+ # Set logging level
+ sgai_logger.set_logging(level="INFO")
+
+ # Initialize the client with API key
+ sgai_client = Client(api_key=self.api_key)
+
+ try:
+ # SearchScraper request
+ response = sgai_client.searchscraper(
+ user_prompt=self.user_prompt,
+ )
+
+ # Close the client
+ sgai_client.close()
+
+ return Data(data=response)
+ except Exception:
+ sgai_client.close()
+ raise
diff --git a/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_smart_scraper_api.py b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_smart_scraper_api.py
new file mode 100644
index 0000000..8a33f34
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/scrapegraph/scrapegraph_smart_scraper_api.py
@@ -0,0 +1,73 @@
+from langflow.custom import Component
+from langflow.io import (
+ MessageTextInput,
+ Output,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class ScrapeGraphSmartScraperApi(Component):
+ display_name: str = "ScrapeGraphSmartScraperApi"
+ description: str = """ScrapeGraph Smart Scraper API.
+ Given a URL, it will return the structured data of the website.
+ More info at https://docs.scrapegraphai.com/services/smartscraper"""
+ name = "ScrapeGraphSmartScraperApi"
+
+ output_types: list[str] = ["Document"]
+ documentation: str = "https://docs.scrapegraphai.com/introduction"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="ScrapeGraph API Key",
+ required=True,
+ password=True,
+ info="The API key to use ScrapeGraph API.",
+ ),
+ MessageTextInput(
+ name="url",
+ display_name="URL",
+ tool_mode=True,
+ info="The URL to scrape.",
+ ),
+ MessageTextInput(
+ name="prompt",
+ display_name="Prompt",
+ tool_mode=True,
+ info="The prompt to use for the smart scraper.",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="scrape"),
+ ]
+
+ def scrape(self) -> list[Data]:
+ try:
+ from scrapegraph_py import Client
+ from scrapegraph_py.logger import sgai_logger
+ except ImportError as e:
+ msg = "Could not import scrapegraph-py package. Please install it with `pip install scrapegraph-py`."
+ raise ImportError(msg) from e
+
+ # Set logging level
+ sgai_logger.set_logging(level="INFO")
+
+ # Initialize the client with API key
+ sgai_client = Client(api_key=self.api_key)
+
+ try:
+ # SmartScraper request
+ response = sgai_client.smartscraper(
+ website_url=self.url,
+ user_prompt=self.prompt,
+ )
+
+ # Close the client
+ sgai_client.close()
+
+ return Data(data=response)
+ except Exception:
+ sgai_client.close()
+ raise
diff --git a/langflow/src/backend/base/langflow/components/textsplitters/__init__.py b/langflow/src/backend/base/langflow/components/textsplitters/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/toolkits/__init__.py b/langflow/src/backend/base/langflow/components/toolkits/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/tools/__init__.py b/langflow/src/backend/base/langflow/components/tools/__init__.py
new file mode 100644
index 0000000..78820ed
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/__init__.py
@@ -0,0 +1,72 @@
+import warnings
+
+from langchain_core._api.deprecation import LangChainDeprecationWarning
+
+from .arxiv import ArXivComponent
+from .bing_search_api import BingSearchAPIComponent
+from .calculator import CalculatorToolComponent
+from .calculator_core import CalculatorComponent
+from .duck_duck_go_search_run import DuckDuckGoSearchComponent
+from .exa_search import ExaSearchToolkit
+from .glean_search_api import GleanSearchAPIComponent
+from .google_search_api import GoogleSearchAPIComponent
+from .google_search_api_core import GoogleSearchAPICore
+from .google_serper_api import GoogleSerperAPIComponent
+from .google_serper_api_core import GoogleSerperAPICore
+from .mcp_stdio import MCPStdio
+from .python_code_structured_tool import PythonCodeStructuredTool
+from .python_repl import PythonREPLToolComponent
+from .python_repl_core import PythonREPLComponent
+from .search import SearchComponent
+from .search_api import SearchAPIComponent
+from .searxng import SearXNGToolComponent
+from .serp import SerpComponent
+from .serp_api import SerpAPIComponent
+from .tavily import TavilySearchComponent
+from .tavily_search import TavilySearchToolComponent
+from .wikidata import WikidataComponent
+from .wikidata_api import WikidataAPIComponent
+from .wikipedia import WikipediaComponent
+from .wikipedia_api import WikipediaAPIComponent
+from .wolfram_alpha_api import WolframAlphaAPIComponent
+from .yahoo import YfinanceComponent
+from .yahoo_finance import YfinanceToolComponent
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", LangChainDeprecationWarning)
+ from .astradb import AstraDBToolComponent
+ from .astradb_cql import AstraDBCQLToolComponent
+
+__all__ = [
+ "ArXivComponent",
+ "AstraDBCQLToolComponent",
+ "AstraDBToolComponent",
+ "BingSearchAPIComponent",
+ "CalculatorComponent",
+ "CalculatorToolComponent",
+ "DuckDuckGoSearchComponent",
+ "ExaSearchToolkit",
+ "GleanSearchAPIComponent",
+ "GoogleSearchAPIComponent",
+ "GoogleSearchAPICore",
+ "GoogleSerperAPIComponent",
+ "GoogleSerperAPICore",
+ "MCPStdio",
+ "PythonCodeStructuredTool",
+ "PythonREPLComponent",
+ "PythonREPLToolComponent",
+ "SearXNGToolComponent",
+ "SearchAPIComponent",
+ "SearchComponent",
+ "SerpAPIComponent",
+ "SerpComponent",
+ "TavilySearchComponent",
+ "TavilySearchToolComponent",
+ "WikidataAPIComponent",
+ "WikidataComponent",
+ "WikipediaAPIComponent",
+ "WikipediaComponent",
+ "WolframAlphaAPIComponent",
+ "YfinanceComponent",
+ "YfinanceToolComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/tools/arxiv.py b/langflow/src/backend/base/langflow/components/tools/arxiv.py
new file mode 100644
index 0000000..dde27e4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/arxiv.py
@@ -0,0 +1,150 @@
+import urllib.request
+from urllib.parse import urlparse
+from xml.etree.ElementTree import Element
+
+from defusedxml.ElementTree import fromstring
+
+from langflow.custom import Component
+from langflow.io import DropdownInput, IntInput, MessageTextInput, Output
+from langflow.schema import Data
+
+
+class ArXivComponent(Component):
+ display_name = "arXiv"
+ description = "Search and retrieve papers from arXiv.org"
+ icon = "arXiv"
+
+ inputs = [
+ MessageTextInput(
+ name="search_query",
+ display_name="Search Query",
+ info="The search query for arXiv papers (e.g., 'quantum computing')",
+ tool_mode=True,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Field",
+ info="The field to search in",
+ options=["all", "title", "abstract", "author", "cat"], # cat is for category
+ value="all",
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ info="Maximum number of results to return",
+ value=10,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Papers", name="papers", method="search_papers"),
+ ]
+
+ def build_query_url(self) -> str:
+ """Build the arXiv API query URL."""
+ base_url = "http://export.arxiv.org/api/query?"
+
+ # Build the search query
+ search_query = f"{self.search_type}:{self.search_query}"
+
+ # URL parameters
+ params = {
+ "search_query": search_query,
+ "max_results": str(self.max_results),
+ }
+
+ # Convert params to URL query string
+ query_string = "&".join([f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items()])
+
+ return base_url + query_string
+
+ def parse_atom_response(self, response_text: str) -> list[dict]:
+ """Parse the Atom XML response from arXiv."""
+ # Parse XML safely using defusedxml
+ root = fromstring(response_text)
+
+ # Define namespace dictionary for XML parsing
+ ns = {"atom": "http://www.w3.org/2005/Atom", "arxiv": "http://arxiv.org/schemas/atom"}
+
+ papers = []
+ # Process each entry (paper)
+ for entry in root.findall("atom:entry", ns):
+ paper = {
+ "id": self._get_text(entry, "atom:id", ns),
+ "title": self._get_text(entry, "atom:title", ns),
+ "summary": self._get_text(entry, "atom:summary", ns),
+ "published": self._get_text(entry, "atom:published", ns),
+ "updated": self._get_text(entry, "atom:updated", ns),
+ "authors": [author.find("atom:name", ns).text for author in entry.findall("atom:author", ns)],
+ "arxiv_url": self._get_link(entry, "alternate", ns),
+ "pdf_url": self._get_link(entry, "related", ns),
+ "comment": self._get_text(entry, "arxiv:comment", ns),
+ "journal_ref": self._get_text(entry, "arxiv:journal_ref", ns),
+ "primary_category": self._get_category(entry, ns),
+ "categories": [cat.get("term") for cat in entry.findall("atom:category", ns)],
+ }
+ papers.append(paper)
+
+ return papers
+
+ def _get_text(self, element: Element, path: str, ns: dict) -> str | None:
+ """Safely extract text from an XML element."""
+ el = element.find(path, ns)
+ return el.text.strip() if el is not None and el.text else None
+
+ def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:
+ """Get link URL based on relation type."""
+ for link in element.findall("atom:link", ns):
+ if link.get("rel") == rel:
+ return link.get("href")
+ return None
+
+ def _get_category(self, element: Element, ns: dict) -> str | None:
+ """Get primary category."""
+ cat = element.find("arxiv:primary_category", ns)
+ return cat.get("term") if cat is not None else None
+
+ def search_papers(self) -> list[Data]:
+ """Search arXiv and return results."""
+ try:
+ # Build the query URL
+ url = self.build_query_url()
+
+ # Validate URL scheme and host
+ parsed_url = urlparse(url)
+ if parsed_url.scheme not in {"http", "https"}:
+ error_msg = f"Invalid URL scheme: {parsed_url.scheme}"
+ raise ValueError(error_msg)
+ if parsed_url.hostname != "export.arxiv.org":
+ error_msg = f"Invalid host: {parsed_url.hostname}"
+ raise ValueError(error_msg)
+
+ # Create a custom opener that only allows http/https schemes
+ class RestrictedHTTPHandler(urllib.request.HTTPHandler):
+ def http_open(self, req):
+ return super().http_open(req)
+
+ class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):
+ def https_open(self, req):
+ return super().https_open(req)
+
+ # Build opener with restricted handlers
+ opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)
+ urllib.request.install_opener(opener)
+
+ # Make the request with validated URL using restricted opener
+ response = opener.open(url)
+ response_text = response.read().decode("utf-8")
+
+ # Parse the response
+ papers = self.parse_atom_response(response_text)
+
+ # Convert to Data objects
+ results = [Data(data=paper) for paper in papers]
+ self.status = results
+ except (urllib.error.URLError, ValueError) as e:
+ error_data = Data(data={"error": f"Request error: {e!s}"})
+ self.status = error_data
+ return [error_data]
+ else:
+ return results
diff --git a/langflow/src/backend/base/langflow/components/tools/astradb.py b/langflow/src/backend/base/langflow/components/tools/astradb.py
new file mode 100644
index 0000000..0376f2d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/astradb.py
@@ -0,0 +1,155 @@
+import os
+from typing import Any
+
+from astrapy import Collection, DataAPIClient, Database
+from langchain.pydantic_v1 import BaseModel, Field, create_model
+from langchain_core.tools import StructuredTool, Tool
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.io import DictInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class AstraDBToolComponent(LCToolComponent):
+ display_name: str = "Astra DB Tool"
+ description: str = "Create a tool to get transactional data from DataStax Astra DB Collection"
+ documentation: str = "https://docs.langflow.org/Components/components-tools#astra-db-tool"
+ icon: str = "AstraDB"
+
+ inputs = [
+ StrInput(
+ name="tool_name",
+ display_name="Tool Name",
+ info="The name of the tool.",
+ required=True,
+ ),
+ StrInput(
+ name="tool_description",
+ display_name="Tool Description",
+ info="The description of the tool.",
+ required=True,
+ ),
+ StrInput(
+ name="namespace",
+ display_name="Namespace Name",
+ info="The name of the namespace within Astra where the collection is be stored.",
+ value="default_keyspace",
+ advanced=True,
+ ),
+ StrInput(
+ name="collection_name",
+ display_name="Collection Name",
+ info="The name of the collection within Astra DB where the vectors will be stored.",
+ required=True,
+ ),
+ SecretStrInput(
+ name="token",
+ display_name="Astra DB Application Token",
+ info="Authentication token for accessing Astra DB.",
+ value="ASTRA_DB_APPLICATION_TOKEN",
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_endpoint",
+ display_name="Database" if os.getenv("ASTRA_ENHANCED", "false").lower() == "true" else "API Endpoint",
+ info="API endpoint URL for the Astra DB service.",
+ value="ASTRA_DB_API_ENDPOINT",
+ required=True,
+ ),
+ StrInput(
+ name="projection_attributes",
+ display_name="Projection Attributes",
+ info="Attributes to return separated by comma.",
+ required=True,
+ value="*",
+ advanced=True,
+ ),
+ DictInput(
+ name="tool_params",
+ info="Attributes to filter and description to the model. Add ! for mandatory (e.g: !customerId)",
+ display_name="Tool params",
+ is_list=True,
+ ),
+ DictInput(
+ name="static_filters",
+ info="Attributes to filter and correspoding value",
+ display_name="Static filters",
+ advanced=True,
+ is_list=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=5,
+ ),
+ ]
+
+ _cached_client: DataAPIClient | None = None
+ _cached_db: Database | None = None
+ _cached_collection: Collection | None = None
+
+ def _build_collection(self):
+ if self._cached_collection:
+ return self._cached_collection
+
+ cached_client = DataAPIClient(self.token)
+ cached_db = cached_client.get_database(self.api_endpoint, namespace=self.namespace)
+ self._cached_collection = cached_db.get_collection(self.collection_name)
+ return self._cached_collection
+
+ def create_args_schema(self) -> dict[str, BaseModel]:
+ args: dict[str, tuple[Any, Field] | list[str]] = {}
+
+ for key in self.tool_params:
+ if key.startswith("!"): # Mandatory
+ args[key[1:]] = (str, Field(description=self.tool_params[key]))
+ else: # Optional
+ args[key] = (str | None, Field(description=self.tool_params[key], default=None))
+
+ model = create_model("ToolInput", **args, __base__=BaseModel)
+ return {"ToolInput": model}
+
+ def build_tool(self) -> Tool:
+ """Builds an Astra DB Collection tool.
+
+ Returns:
+ Tool: The built Astra DB tool.
+ """
+ schema_dict = self.create_args_schema()
+
+ tool = StructuredTool.from_function(
+ name=self.tool_name,
+ args_schema=schema_dict["ToolInput"],
+ description=self.tool_description,
+ func=self.run_model,
+ return_direct=False,
+ )
+ self.status = "Astra DB Tool created"
+
+ return tool
+
+ def projection_args(self, input_str: str) -> dict:
+ elements = input_str.split(",")
+ result = {}
+
+ for element in elements:
+ if element.startswith("!"):
+ result[element[1:]] = False
+ else:
+ result[element] = True
+
+ return result
+
+ def run_model(self, **args) -> Data | list[Data]:
+ collection = self._build_collection()
+ results = collection.find(
+ ({**args, **self.static_filters}),
+ projection=self.projection_args(self.projection_attributes),
+ limit=self.number_of_results,
+ )
+
+ data: list[Data] = [Data(data=doc) for doc in results]
+ self.status = data
+ return data
diff --git a/langflow/src/backend/base/langflow/components/tools/astradb_cql.py b/langflow/src/backend/base/langflow/components/tools/astradb_cql.py
new file mode 100644
index 0000000..652f4db
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/astradb_cql.py
@@ -0,0 +1,177 @@
+import urllib
+from http import HTTPStatus
+from typing import Any
+
+import requests
+from langchain.pydantic_v1 import BaseModel, Field, create_model
+from langchain_core.tools import StructuredTool, Tool
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.io import DictInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class AstraDBCQLToolComponent(LCToolComponent):
+ display_name: str = "Astra DB CQL"
+ description: str = "Create a tool to get transactional data from DataStax Astra DB CQL Table"
+ documentation: str = "https://docs.langflow.org/Components/components-tools#astra-db-cql-tool"
+ icon: str = "AstraDB"
+
+ inputs = [
+ StrInput(name="tool_name", display_name="Tool Name", info="The name of the tool.", required=True),
+ StrInput(
+ name="tool_description",
+ display_name="Tool Description",
+ info="The tool description to be passed to the model.",
+ required=True,
+ ),
+ StrInput(
+ name="keyspace",
+ display_name="Keyspace",
+ value="default_keyspace",
+ info="The keyspace name within Astra DB where the data is stored.",
+ required=True,
+ advanced=True,
+ ),
+ StrInput(
+ name="table_name",
+ display_name="Table Name",
+ info="The name of the table within Astra DB where the data is stored.",
+ required=True,
+ ),
+ SecretStrInput(
+ name="token",
+ display_name="Astra DB Application Token",
+ info="Authentication token for accessing Astra DB.",
+ value="ASTRA_DB_APPLICATION_TOKEN",
+ required=True,
+ ),
+ StrInput(
+ name="api_endpoint",
+ display_name="API Endpoint",
+ info="API endpoint URL for the Astra DB service.",
+ value="ASTRA_DB_API_ENDPOINT",
+ required=True,
+ ),
+ StrInput(
+ name="projection_fields",
+ display_name="Projection fields",
+ info="Attributes to return separated by comma.",
+ required=True,
+ value="*",
+ advanced=True,
+ ),
+ DictInput(
+ name="partition_keys",
+ display_name="Partition Keys",
+ is_list=True,
+ info="Field name and description to the model",
+ required=True,
+ ),
+ DictInput(
+ name="clustering_keys",
+ display_name="Clustering Keys",
+ is_list=True,
+ info="Field name and description to the model",
+ ),
+ DictInput(
+ name="static_filters",
+ display_name="Static Filters",
+ is_list=True,
+ advanced=True,
+ info="Field name and value. When filled, it will not be generated by the LLM.",
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=5,
+ ),
+ ]
+
+ def astra_rest(self, args):
+ headers = {"Accept": "application/json", "X-Cassandra-Token": f"{self.token}"}
+ astra_url = f"{self.api_endpoint}/api/rest/v2/keyspaces/{self.keyspace}/{self.table_name}/"
+ key = []
+
+ # Partition keys are mandatory
+ key = [self.partition_keys[k] for k in self.partition_keys]
+
+ # Clustering keys are optional
+ for k in self.clustering_keys:
+ if k in args:
+ key.append(args[k])
+ elif self.static_filters[k] is not None:
+ key.append(self.static_filters[k])
+
+ url = f"{astra_url}{'/'.join(key)}?page-size={self.number_of_results}"
+
+ if self.projection_fields != "*":
+ url += f"&fields={urllib.parse.quote(self.projection_fields.replace(' ', ''))}"
+
+ res = requests.request("GET", url=url, headers=headers, timeout=10)
+
+ if int(res.status_code) >= HTTPStatus.BAD_REQUEST:
+ return res.text
+
+ try:
+ res_data = res.json()
+ return res_data["data"]
+ except ValueError:
+ return res.status_code
+
+ def create_args_schema(self) -> dict[str, BaseModel]:
+ args: dict[str, tuple[Any, Field]] = {}
+
+ for key in self.partition_keys:
+ # Partition keys are mandatory is it doesn't have a static filter
+ if key not in self.static_filters:
+ args[key] = (str, Field(description=self.partition_keys[key]))
+
+ for key in self.clustering_keys:
+ # Partition keys are mandatory if has the exclamation mark and doesn't have a static filter
+ if key not in self.static_filters:
+ if key.startswith("!"): # Mandatory
+ args[key[1:]] = (str, Field(description=self.clustering_keys[key]))
+ else: # Optional
+ args[key] = (str | None, Field(description=self.clustering_keys[key], default=None))
+
+ model = create_model("ToolInput", **args, __base__=BaseModel)
+ return {"ToolInput": model}
+
+ def build_tool(self) -> Tool:
+ """Builds a Astra DB CQL Table tool.
+
+ Args:
+ name (str, optional): The name of the tool.
+
+ Returns:
+ Tool: The built AstraDB tool.
+ """
+ schema_dict = self.create_args_schema()
+ return StructuredTool.from_function(
+ name=self.tool_name,
+ args_schema=schema_dict["ToolInput"],
+ description=self.tool_description,
+ func=self.run_model,
+ return_direct=False,
+ )
+
+ def projection_args(self, input_str: str) -> dict:
+ elements = input_str.split(",")
+ result = {}
+
+ for element in elements:
+ if element.startswith("!"):
+ result[element[1:]] = False
+ else:
+ result[element] = True
+
+ return result
+
+ def run_model(self, **args) -> Data | list[Data]:
+ results = self.astra_rest(args)
+ data: list[Data] = [Data(data=doc) for doc in results]
+ self.status = data
+ return results
diff --git a/langflow/src/backend/base/langflow/components/tools/bing_search_api.py b/langflow/src/backend/base/langflow/components/tools/bing_search_api.py
new file mode 100644
index 0000000..c98f586
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/bing_search_api.py
@@ -0,0 +1,47 @@
+from typing import cast
+
+from langchain_community.tools.bing_search import BingSearchResults
+from langchain_community.utilities import BingSearchAPIWrapper
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import IntInput, MessageTextInput, MultilineInput, SecretStrInput
+from langflow.schema import Data
+
+
+class BingSearchAPIComponent(LCToolComponent):
+ display_name = "Bing Search API"
+ description = "Call the Bing Search API."
+ name = "BingSearchAPI"
+ icon = "Bing"
+
+ inputs = [
+ SecretStrInput(name="bing_subscription_key", display_name="Bing Subscription Key"),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ ),
+ MessageTextInput(name="bing_search_url", display_name="Bing Search URL", advanced=True),
+ IntInput(name="k", display_name="Number of results", value=4, required=True),
+ ]
+
+ def run_model(self) -> list[Data]:
+ if self.bing_search_url:
+ wrapper = BingSearchAPIWrapper(
+ bing_search_url=self.bing_search_url, bing_subscription_key=self.bing_subscription_key
+ )
+ else:
+ wrapper = BingSearchAPIWrapper(bing_subscription_key=self.bing_subscription_key)
+ results = wrapper.results(query=self.input_value, num_results=self.k)
+ data = [Data(data=result, text=result["snippet"]) for result in results]
+ self.status = data
+ return data
+
+ def build_tool(self) -> Tool:
+ if self.bing_search_url:
+ wrapper = BingSearchAPIWrapper(
+ bing_search_url=self.bing_search_url, bing_subscription_key=self.bing_subscription_key
+ )
+ else:
+ wrapper = BingSearchAPIWrapper(bing_subscription_key=self.bing_subscription_key)
+ return cast("Tool", BingSearchResults(api_wrapper=wrapper, num_results=self.k))
diff --git a/langflow/src/backend/base/langflow/components/tools/calculator.py b/langflow/src/backend/base/langflow/components/tools/calculator.py
new file mode 100644
index 0000000..1d13ed9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/calculator.py
@@ -0,0 +1,103 @@
+import ast
+import operator
+
+from langchain.tools import StructuredTool
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MessageTextInput
+from langflow.schema import Data
+
+
+class CalculatorToolComponent(LCToolComponent):
+ display_name = "Calculator [DEPRECATED]"
+ description = "Perform basic arithmetic operations on a given expression."
+ icon = "calculator"
+ name = "CalculatorTool"
+ legacy = True
+
+ inputs = [
+ MessageTextInput(
+ name="expression",
+ display_name="Expression",
+ info="The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').",
+ ),
+ ]
+
+ class CalculatorToolSchema(BaseModel):
+ expression: str = Field(..., description="The arithmetic expression to evaluate.")
+
+ def run_model(self) -> list[Data]:
+ return self._evaluate_expression(self.expression)
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="calculator",
+ description="Evaluate basic arithmetic expressions. Input should be a string containing the expression.",
+ func=self._eval_expr_with_error,
+ args_schema=self.CalculatorToolSchema,
+ )
+
+ def _eval_expr(self, node):
+ if isinstance(node, ast.Num):
+ return node.n
+ if isinstance(node, ast.BinOp):
+ left_val = self._eval_expr(node.left)
+ right_val = self._eval_expr(node.right)
+ return self.operators[type(node.op)](left_val, right_val)
+ if isinstance(node, ast.UnaryOp):
+ operand_val = self._eval_expr(node.operand)
+ return self.operators[type(node.op)](operand_val)
+ if isinstance(node, ast.Call):
+ msg = (
+ "Function calls like sqrt(), sin(), cos() etc. are not supported. "
+ "Only basic arithmetic operations (+, -, *, /, **) are allowed."
+ )
+ raise TypeError(msg)
+ msg = f"Unsupported operation or expression type: {type(node).__name__}"
+ raise TypeError(msg)
+
+ def _eval_expr_with_error(self, expression: str) -> list[Data]:
+ try:
+ return self._evaluate_expression(expression)
+ except Exception as e:
+ raise ToolException(str(e)) from e
+
+ def _evaluate_expression(self, expression: str) -> list[Data]:
+ try:
+ # Parse the expression and evaluate it
+ tree = ast.parse(expression, mode="eval")
+ result = self._eval_expr(tree.body)
+
+ # Format the result to a reasonable number of decimal places
+ formatted_result = f"{result:.6f}".rstrip("0").rstrip(".")
+
+ self.status = formatted_result
+ return [Data(data={"result": formatted_result})]
+
+ except (SyntaxError, TypeError, KeyError) as e:
+ error_message = f"Invalid expression: {e}"
+ self.status = error_message
+ return [Data(data={"error": error_message, "input": expression})]
+ except ZeroDivisionError:
+ error_message = "Error: Division by zero"
+ self.status = error_message
+ return [Data(data={"error": error_message, "input": expression})]
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error evaluating expression")
+ error_message = f"Error: {e}"
+ self.status = error_message
+ return [Data(data={"error": error_message, "input": expression})]
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.operators = {
+ ast.Add: operator.add,
+ ast.Sub: operator.sub,
+ ast.Mult: operator.mul,
+ ast.Div: operator.truediv,
+ ast.Pow: operator.pow,
+ }
diff --git a/langflow/src/backend/base/langflow/components/tools/calculator_core.py b/langflow/src/backend/base/langflow/components/tools/calculator_core.py
new file mode 100644
index 0000000..0eef657
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/calculator_core.py
@@ -0,0 +1,88 @@
+import ast
+import operator
+from collections.abc import Callable
+
+from langflow.custom import Component
+from langflow.inputs import MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+
+
+class CalculatorComponent(Component):
+ display_name = "Calculator"
+ description = "Perform basic arithmetic operations on a given expression."
+ icon = "calculator"
+
+ # Cache operators dictionary as a class variable
+ OPERATORS: dict[type[ast.operator], Callable] = {
+ ast.Add: operator.add,
+ ast.Sub: operator.sub,
+ ast.Mult: operator.mul,
+ ast.Div: operator.truediv,
+ ast.Pow: operator.pow,
+ }
+
+ inputs = [
+ MessageTextInput(
+ name="expression",
+ display_name="Expression",
+ info="The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').",
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="result", type_=Data, method="evaluate_expression"),
+ ]
+
+ def _eval_expr(self, node: ast.AST) -> float:
+ """Evaluate an AST node recursively."""
+ if isinstance(node, ast.Constant):
+ if isinstance(node.value, int | float):
+ return float(node.value)
+ error_msg = f"Unsupported constant type: {type(node.value).__name__}"
+ raise TypeError(error_msg)
+ if isinstance(node, ast.Num): # For backwards compatibility
+ if isinstance(node.n, int | float):
+ return float(node.n)
+ error_msg = f"Unsupported number type: {type(node.n).__name__}"
+ raise TypeError(error_msg)
+
+ if isinstance(node, ast.BinOp):
+ op_type = type(node.op)
+ if op_type not in self.OPERATORS:
+ error_msg = f"Unsupported binary operator: {op_type.__name__}"
+ raise TypeError(error_msg)
+
+ left = self._eval_expr(node.left)
+ right = self._eval_expr(node.right)
+ return self.OPERATORS[op_type](left, right)
+
+ error_msg = f"Unsupported operation or expression type: {type(node).__name__}"
+ raise TypeError(error_msg)
+
+ def evaluate_expression(self) -> Data:
+ """Evaluate the mathematical expression and return the result."""
+ try:
+ tree = ast.parse(self.expression, mode="eval")
+ result = self._eval_expr(tree.body)
+
+ formatted_result = f"{float(result):.6f}".rstrip("0").rstrip(".")
+ self.log(f"Calculation result: {formatted_result}")
+
+ self.status = formatted_result
+ return Data(data={"result": formatted_result})
+
+ except ZeroDivisionError:
+ error_message = "Error: Division by zero"
+ self.status = error_message
+ return Data(data={"error": error_message, "input": self.expression})
+
+ except (SyntaxError, TypeError, KeyError, ValueError, AttributeError, OverflowError) as e:
+ error_message = f"Invalid expression: {e!s}"
+ self.status = error_message
+ return Data(data={"error": error_message, "input": self.expression})
+
+ def build(self):
+ """Return the main evaluation function."""
+ return self.evaluate_expression
diff --git a/langflow/src/backend/base/langflow/components/tools/duck_duck_go_search_run.py b/langflow/src/backend/base/langflow/components/tools/duck_duck_go_search_run.py
new file mode 100644
index 0000000..2a9060c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/duck_duck_go_search_run.py
@@ -0,0 +1,91 @@
+from langchain_community.tools import DuckDuckGoSearchRun
+
+from langflow.custom import Component
+from langflow.inputs import IntInput, MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class DuckDuckGoSearchComponent(Component):
+ """Component for performing web searches using DuckDuckGo."""
+
+ display_name = "DuckDuckGo Search"
+ description = "Search the web using DuckDuckGo with customizable result limits"
+ documentation = "https://python.langchain.com/docs/integrations/tools/ddg"
+ icon = "DuckDuckGo"
+
+ inputs = [
+ MessageTextInput(
+ name="input_value",
+ display_name="Search Query",
+ required=True,
+ info="The search query to execute with DuckDuckGo",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ value=5,
+ required=False,
+ advanced=True,
+ info="Maximum number of search results to return",
+ ),
+ IntInput(
+ name="max_snippet_length",
+ display_name="Max Snippet Length",
+ value=100,
+ required=False,
+ advanced=True,
+ info="Maximum length of each result snippet",
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def _build_wrapper(self) -> DuckDuckGoSearchRun:
+ """Build the DuckDuckGo search wrapper."""
+ return DuckDuckGoSearchRun()
+
+ def run_model(self) -> list[Data]:
+ return self.fetch_content()
+
+ def fetch_content(self) -> list[Data]:
+ """Execute the search and return results as Data objects."""
+ try:
+ wrapper = self._build_wrapper()
+
+ full_results = wrapper.run(f"{self.input_value} (site:*)")
+
+ result_list = full_results.split("\n")[: self.max_results]
+
+ data_results = []
+ for result in result_list:
+ if result.strip():
+ snippet = result[: self.max_snippet_length]
+ data_results.append(
+ Data(
+ text=snippet,
+ data={
+ "content": result,
+ "snippet": snippet,
+ },
+ )
+ )
+ except (ValueError, AttributeError) as e:
+ error_data = [Data(text=str(e), data={"error": str(e)})]
+ self.status = error_data
+ return error_data
+ else:
+ self.status = data_results
+ return data_results
+
+ def fetch_content_text(self) -> Message:
+ """Return search results as a single text message."""
+ data = self.fetch_content()
+ result_string = "\n".join(item.text for item in data)
+ self.status = result_string
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/tools/exa_search.py b/langflow/src/backend/base/langflow/components/tools/exa_search.py
new file mode 100644
index 0000000..b0600fc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/exa_search.py
@@ -0,0 +1,68 @@
+from langchain_core.tools import tool
+from metaphor_python import Metaphor
+
+from langflow.custom import Component
+from langflow.field_typing import Tool
+from langflow.io import BoolInput, IntInput, Output, SecretStrInput
+
+
+class ExaSearchToolkit(Component):
+ display_name = "Exa Search"
+ description = "Exa Search toolkit for search and content retrieval"
+ documentation = "https://python.langchain.com/docs/integrations/tools/metaphor_search"
+ beta = True
+ name = "ExaSearch"
+ icon = "ExaSearch"
+
+ inputs = [
+ SecretStrInput(
+ name="metaphor_api_key",
+ display_name="Exa Search API Key",
+ password=True,
+ ),
+ BoolInput(
+ name="use_autoprompt",
+ display_name="Use Autoprompt",
+ value=True,
+ ),
+ IntInput(
+ name="search_num_results",
+ display_name="Search Number of Results",
+ value=5,
+ ),
+ IntInput(
+ name="similar_num_results",
+ display_name="Similar Number of Results",
+ value=5,
+ ),
+ ]
+
+ outputs = [
+ Output(name="tools", display_name="Tools", method="build_toolkit"),
+ ]
+
+ def build_toolkit(self) -> Tool:
+ client = Metaphor(api_key=self.metaphor_api_key)
+
+ @tool
+ def search(query: str):
+ """Call search engine with a query."""
+ return client.search(query, use_autoprompt=self.use_autoprompt, num_results=self.search_num_results)
+
+ @tool
+ def get_contents(ids: list[str]):
+ """Get contents of a webpage.
+
+ The ids passed in should be a list of ids as fetched from `search`.
+ """
+ return client.get_contents(ids)
+
+ @tool
+ def find_similar(url: str):
+ """Get search results similar to a given URL.
+
+ The url passed in should be a URL returned from `search`
+ """
+ return client.find_similar(url, num_results=self.similar_num_results)
+
+ return [search, get_contents, find_similar]
diff --git a/langflow/src/backend/base/langflow/components/tools/glean_search_api.py b/langflow/src/backend/base/langflow/components/tools/glean_search_api.py
new file mode 100644
index 0000000..b856526
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/glean_search_api.py
@@ -0,0 +1,159 @@
+import json
+from typing import Any
+from urllib.parse import urljoin
+
+import httpx
+from langchain_core.tools import StructuredTool, ToolException
+from pydantic import BaseModel
+from pydantic.v1 import Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import IntInput, MultilineInput, NestedDictInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class GleanSearchAPISchema(BaseModel):
+ query: str = Field(..., description="The search query")
+ page_size: int = Field(10, description="Maximum number of results to return")
+ request_options: dict[str, Any] | None = Field(default_factory=dict, description="Request Options")
+
+
+class GleanAPIWrapper(BaseModel):
+ """Wrapper around Glean API."""
+
+ glean_api_url: str
+ glean_access_token: str
+ act_as: str = "langflow-component@datastax.com" # TODO: Detect this
+
+ def _prepare_request(
+ self,
+ query: str,
+ page_size: int = 10,
+ request_options: dict[str, Any] | None = None,
+ ) -> dict:
+ # Ensure there's a trailing slash
+ url = self.glean_api_url
+ if not url.endswith("/"):
+ url += "/"
+
+ return {
+ "url": urljoin(url, "search"),
+ "headers": {
+ "Authorization": f"Bearer {self.glean_access_token}",
+ "X-Scio-ActAs": self.act_as,
+ },
+ "payload": {
+ "query": query,
+ "pageSize": page_size,
+ "requestOptions": request_options,
+ },
+ }
+
+ def results(self, query: str, **kwargs: Any) -> list[dict[str, Any]]:
+ results = self._search_api_results(query, **kwargs)
+
+ if len(results) == 0:
+ msg = "No good Glean Search Result was found"
+ raise AssertionError(msg)
+
+ return results
+
+ def run(self, query: str, **kwargs: Any) -> list[dict[str, Any]]:
+ try:
+ results = self.results(query, **kwargs)
+
+ processed_results = []
+ for result in results:
+ if "title" in result:
+ result["snippets"] = result.get("snippets", [{"snippet": {"text": result["title"]}}])
+ if "text" not in result["snippets"][0]:
+ result["snippets"][0]["text"] = result["title"]
+
+ processed_results.append(result)
+ except Exception as e:
+ error_message = f"Error in Glean Search API: {e!s}"
+ raise ToolException(error_message) from e
+
+ return processed_results
+
+ def _search_api_results(self, query: str, **kwargs: Any) -> list[dict[str, Any]]:
+ request_details = self._prepare_request(query, **kwargs)
+
+ response = httpx.post(
+ request_details["url"],
+ json=request_details["payload"],
+ headers=request_details["headers"],
+ )
+
+ response.raise_for_status()
+ response_json = response.json()
+
+ return response_json.get("results", [])
+
+ @staticmethod
+ def _result_as_string(result: dict) -> str:
+ return json.dumps(result, indent=4)
+
+
+class GleanSearchAPIComponent(LCToolComponent):
+ display_name = "Glean Search API"
+ description = "Call Glean Search API"
+ name = "GleanAPI"
+ icon = "Glean"
+
+ inputs = [
+ StrInput(
+ name="glean_api_url",
+ display_name="Glean API URL",
+ required=True,
+ ),
+ SecretStrInput(name="glean_access_token", display_name="Glean Access Token", required=True),
+ MultilineInput(name="query", display_name="Query", required=True),
+ IntInput(name="page_size", display_name="Page Size", value=10),
+ NestedDictInput(name="request_options", display_name="Request Options", required=False),
+ ]
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper(
+ glean_api_url=self.glean_api_url,
+ glean_access_token=self.glean_access_token,
+ )
+
+ tool = StructuredTool.from_function(
+ name="glean_search_api",
+ description="Search Glean for relevant results.",
+ func=wrapper.run,
+ args_schema=GleanSearchAPISchema,
+ )
+
+ self.status = "Glean Search API Tool for Langchain"
+
+ return tool
+
+ def run_model(self) -> list[Data]:
+ tool = self.build_tool()
+
+ results = tool.run(
+ {
+ "query": self.query,
+ "page_size": self.page_size,
+ "request_options": self.request_options,
+ }
+ )
+
+ # Build the data
+ data = [Data(data=result, text=result["snippets"][0]["text"]) for result in results]
+ self.status = data # type: ignore[assignment]
+
+ return data
+
+ def _build_wrapper(
+ self,
+ glean_api_url: str,
+ glean_access_token: str,
+ ):
+ return GleanAPIWrapper(
+ glean_api_url=glean_api_url,
+ glean_access_token=glean_access_token,
+ )
diff --git a/langflow/src/backend/base/langflow/components/tools/google_search_api.py b/langflow/src/backend/base/langflow/components/tools/google_search_api.py
new file mode 100644
index 0000000..2ede883
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/google_search_api.py
@@ -0,0 +1,45 @@
+from langchain_core.tools import Tool
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.inputs import IntInput, MultilineInput, SecretStrInput
+from langflow.schema import Data
+
+
+class GoogleSearchAPIComponent(LCToolComponent):
+ display_name = "Google Search API [DEPRECATED]"
+ description = "Call Google Search API."
+ name = "GoogleSearchAPI"
+ icon = "Google"
+ legacy = True
+ inputs = [
+ SecretStrInput(name="google_api_key", display_name="Google API Key", required=True),
+ SecretStrInput(name="google_cse_id", display_name="Google CSE ID", required=True),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ ),
+ IntInput(name="k", display_name="Number of results", value=4, required=True),
+ ]
+
+ def run_model(self) -> Data | list[Data]:
+ wrapper = self._build_wrapper()
+ results = wrapper.results(query=self.input_value, num_results=self.k)
+ data = [Data(data=result, text=result["snippet"]) for result in results]
+ self.status = data
+ return data
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper()
+ return Tool(
+ name="google_search",
+ description="Search Google for recent results.",
+ func=wrapper.run,
+ )
+
+ def _build_wrapper(self):
+ try:
+ from langchain_google_community import GoogleSearchAPIWrapper
+ except ImportError as e:
+ msg = "Please install langchain-google-community to use GoogleSearchAPIWrapper."
+ raise ImportError(msg) from e
+ return GoogleSearchAPIWrapper(google_api_key=self.google_api_key, google_cse_id=self.google_cse_id, k=self.k)
diff --git a/langflow/src/backend/base/langflow/components/tools/google_search_api_core.py b/langflow/src/backend/base/langflow/components/tools/google_search_api_core.py
new file mode 100644
index 0000000..fda4d6a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/google_search_api_core.py
@@ -0,0 +1,68 @@
+from langchain_google_community import GoogleSearchAPIWrapper
+
+from langflow.custom import Component
+from langflow.io import IntInput, MultilineInput, Output, SecretStrInput
+from langflow.schema import DataFrame
+
+
+class GoogleSearchAPICore(Component):
+ display_name = "Google Search API"
+ description = "Call Google Search API and return results as a DataFrame."
+ icon = "Google"
+
+ inputs = [
+ SecretStrInput(
+ name="google_api_key",
+ display_name="Google API Key",
+ required=True,
+ ),
+ SecretStrInput(
+ name="google_cse_id",
+ display_name="Google CSE ID",
+ required=True,
+ ),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="k",
+ display_name="Number of results",
+ value=4,
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Results",
+ name="results",
+ type_=DataFrame,
+ method="search_google",
+ ),
+ ]
+
+ def search_google(self) -> DataFrame:
+ """Search Google using the provided query."""
+ if not self.google_api_key:
+ return DataFrame([{"error": "Invalid Google API Key"}])
+
+ if not self.google_cse_id:
+ return DataFrame([{"error": "Invalid Google CSE ID"}])
+
+ try:
+ wrapper = GoogleSearchAPIWrapper(
+ google_api_key=self.google_api_key, google_cse_id=self.google_cse_id, k=self.k
+ )
+ results = wrapper.results(query=self.input_value, num_results=self.k)
+ return DataFrame(results)
+ except (ValueError, KeyError) as e:
+ return DataFrame([{"error": f"Invalid configuration: {e!s}"}])
+ except ConnectionError as e:
+ return DataFrame([{"error": f"Connection error: {e!s}"}])
+ except RuntimeError as e:
+ return DataFrame([{"error": f"Error occurred while searching: {e!s}"}])
+
+ def build(self):
+ return self.search_google
diff --git a/langflow/src/backend/base/langflow/components/tools/google_serper_api.py b/langflow/src/backend/base/langflow/components/tools/google_serper_api.py
new file mode 100644
index 0000000..0b44c4d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/google_serper_api.py
@@ -0,0 +1,115 @@
+from typing import Any
+
+from langchain.tools import StructuredTool
+from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import (
+ DictInput,
+ DropdownInput,
+ IntInput,
+ MultilineInput,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class QuerySchema(BaseModel):
+ query: str = Field(..., description="The query to search for.")
+ query_type: str = Field(
+ "search",
+ description="The type of search to perform (e.g., 'news' or 'search').",
+ )
+ k: int = Field(4, description="The number of results to return.")
+ query_params: dict[str, Any] = Field({}, description="Additional query parameters to pass to the API.")
+
+
+class GoogleSerperAPIComponent(LCToolComponent):
+ display_name = "Google Serper API [DEPRECATED]"
+ description = "Call the Serper.dev Google Search API."
+ name = "GoogleSerperAPI"
+ icon = "Google"
+ legacy = True
+ inputs = [
+ SecretStrInput(name="serper_api_key", display_name="Serper API Key", required=True),
+ MultilineInput(
+ name="query",
+ display_name="Query",
+ ),
+ IntInput(name="k", display_name="Number of results", value=4, required=True),
+ DropdownInput(
+ name="query_type",
+ display_name="Query Type",
+ required=False,
+ options=["news", "search"],
+ value="search",
+ ),
+ DictInput(
+ name="query_params",
+ display_name="Query Params",
+ required=False,
+ value={
+ "gl": "us",
+ "hl": "en",
+ },
+ list=True,
+ ),
+ ]
+
+ def run_model(self) -> Data | list[Data]:
+ wrapper = self._build_wrapper(self.k, self.query_type, self.query_params)
+ results = wrapper.results(query=self.query)
+
+ # Adjust the extraction based on the `type`
+ if self.query_type == "search":
+ list_results = results.get("organic", [])
+ elif self.query_type == "news":
+ list_results = results.get("news", [])
+ else:
+ list_results = []
+
+ data_list = []
+ for result in list_results:
+ result["text"] = result.pop("snippet", "")
+ data_list.append(Data(data=result))
+ self.status = data_list
+ return data_list
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="google_search",
+ description="Search Google for recent results.",
+ func=self._search,
+ args_schema=self.QuerySchema,
+ )
+
+ def _build_wrapper(
+ self,
+ k: int = 5,
+ query_type: str = "search",
+ query_params: dict | None = None,
+ ) -> GoogleSerperAPIWrapper:
+ wrapper_args = {
+ "serper_api_key": self.serper_api_key,
+ "k": k,
+ "type": query_type,
+ }
+
+ # Add query_params if provided
+ if query_params:
+ wrapper_args.update(query_params) # Merge with additional query params
+
+ # Dynamically pass parameters to the wrapper
+ return GoogleSerperAPIWrapper(**wrapper_args)
+
+ def _search(
+ self,
+ query: str,
+ k: int = 5,
+ query_type: str = "search",
+ query_params: dict | None = None,
+ ) -> dict:
+ wrapper = self._build_wrapper(k, query_type, query_params)
+ return wrapper.results(query=query)
diff --git a/langflow/src/backend/base/langflow/components/tools/google_serper_api_core.py b/langflow/src/backend/base/langflow/components/tools/google_serper_api_core.py
new file mode 100644
index 0000000..a46e190
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/google_serper_api_core.py
@@ -0,0 +1,74 @@
+from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
+
+from langflow.custom import Component
+from langflow.io import IntInput, MultilineInput, Output, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.schema.message import Message
+
+
+class GoogleSerperAPICore(Component):
+ display_name = "Google Serper API"
+ description = "Call the Serper.dev Google Search API."
+ icon = "Serper"
+
+ inputs = [
+ SecretStrInput(
+ name="serper_api_key",
+ display_name="Serper API Key",
+ required=True,
+ ),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ tool_mode=True,
+ ),
+ IntInput(
+ name="k",
+ display_name="Number of results",
+ value=4,
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Results",
+ name="results",
+ type_=DataFrame,
+ method="search_serper",
+ ),
+ ]
+
+ def search_serper(self) -> DataFrame:
+ try:
+ wrapper = self._build_wrapper()
+ results = wrapper.results(query=self.input_value)
+ list_results = results.get("organic", [])
+
+ # Convert results to DataFrame using list comprehension
+ df_data = [
+ {
+ "title": result.get("title", ""),
+ "link": result.get("link", ""),
+ "snippet": result.get("snippet", ""),
+ }
+ for result in list_results
+ ]
+
+ return DataFrame(df_data)
+ except (ValueError, KeyError, ConnectionError) as e:
+ error_message = f"Error occurred while searching: {e!s}"
+ self.status = error_message
+ # Return DataFrame with error as a list of dictionaries
+ return DataFrame([{"error": error_message}])
+
+ def text_search_serper(self) -> Message:
+ search_results = self.search_serper()
+ text_result = search_results.to_string(index=False) if not search_results.empty else "No results found."
+ return Message(text=text_result)
+
+ def _build_wrapper(self):
+ return GoogleSerperAPIWrapper(serper_api_key=self.serper_api_key, k=self.k)
+
+ def build(self):
+ return self.search_serper
diff --git a/langflow/src/backend/base/langflow/components/tools/mcp_sse.py b/langflow/src/backend/base/langflow/components/tools/mcp_sse.py
new file mode 100644
index 0000000..82e6c34
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/mcp_sse.py
@@ -0,0 +1,98 @@
+# from langflow.field_typing import Data
+from contextlib import AsyncExitStack
+
+import httpx
+from mcp import ClientSession, types
+from mcp.client.sse import sse_client
+
+from langflow.base.mcp.util import create_tool_coroutine, create_tool_func
+from langflow.components.tools.mcp_stdio import create_input_schema_from_json_schema
+from langflow.custom import Component
+from langflow.field_typing import Tool
+from langflow.io import MessageTextInput, Output
+from langflow.utils.async_helpers import timeout_context
+
+# Define constant for status code
+HTTP_TEMPORARY_REDIRECT = 307
+
+
+class MCPSseClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.write = None
+ self.sse = None
+ self.session: ClientSession | None = None
+ self.exit_stack = AsyncExitStack()
+
+ async def pre_check_redirect(self, url: str):
+ """Check if the URL responds with a 307 Redirect."""
+ async with httpx.AsyncClient(follow_redirects=False) as client:
+ response = await client.request("HEAD", url)
+ if response.status_code == HTTP_TEMPORARY_REDIRECT:
+ return response.headers.get("Location") # Return the redirect URL
+ return url # Return the original URL if no redirect
+
+ async def connect_to_server(
+ self, url: str, headers: dict[str, str] | None, timeout_seconds: int = 500, sse_read_timeout_seconds: int = 500
+ ):
+ if headers is None:
+ headers = {}
+ url = await self.pre_check_redirect(url)
+
+ async with timeout_context(timeout_seconds):
+ sse_transport = await self.exit_stack.enter_async_context(
+ sse_client(url, headers, timeout_seconds, sse_read_timeout_seconds)
+ )
+ self.sse, self.write = sse_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.sse, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ return response.tools
+
+
+class MCPSse(Component):
+ client = MCPSseClient()
+ tools = types.ListToolsResult
+ tool_names = [str]
+ display_name = "MCP Tools (SSE)"
+ description = "Connects to an MCP server over SSE and exposes it's tools as langflow tools to be used by an Agent."
+ documentation: str = "https://docs.langflow.org/components-custom-components"
+ icon = "code"
+ name = "MCPSse"
+
+ inputs = [
+ MessageTextInput(
+ name="url",
+ display_name="mcp sse url",
+ info="sse url",
+ value="http://localhost:7860/api/v1/mcp/sse",
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Tools", name="tools", method="build_output"),
+ ]
+
+ async def build_output(self) -> list[Tool]:
+ if self.client.session is None:
+ self.tools = await self.client.connect_to_server(self.url, {})
+
+ tool_list = []
+
+ for tool in self.tools:
+ args_schema = create_input_schema_from_json_schema(tool.inputSchema)
+ tool_list.append(
+ Tool(
+ name=tool.name, # maybe format this
+ description=tool.description,
+ coroutine=create_tool_coroutine(tool.name, args_schema, self.client.session),
+ func=create_tool_func(tool.name, self.client.session),
+ )
+ )
+
+ self.tool_names = [tool.name for tool in self.tools]
+ return tool_list
diff --git a/langflow/src/backend/base/langflow/components/tools/mcp_stdio.py b/langflow/src/backend/base/langflow/components/tools/mcp_stdio.py
new file mode 100644
index 0000000..f1bfe8b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/mcp_stdio.py
@@ -0,0 +1,122 @@
+# from langflow.field_typing import Data
+import os
+from contextlib import AsyncExitStack
+from typing import Any
+
+from mcp import ClientSession, StdioServerParameters, types
+from mcp.client.stdio import stdio_client
+from pydantic import BaseModel, Field, create_model
+
+from langflow.base.mcp.util import create_tool_coroutine, create_tool_func
+from langflow.custom import Component
+from langflow.field_typing import Tool
+from langflow.io import MessageTextInput, Output
+
+
+class MCPStdioClient:
+ def __init__(self):
+ # Initialize session and client objects
+ self.session: ClientSession | None = None
+ self.exit_stack = AsyncExitStack()
+
+ async def connect_to_server(self, command_str: str):
+ command = command_str.split(" ")
+ server_params = StdioServerParameters(
+ command=command[0], args=command[1:], env={"DEBUG": "true", "PATH": os.environ["PATH"]}
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+
+ await self.session.initialize()
+
+ # List available tools
+ response = await self.session.list_tools()
+ return response.tools
+
+
+def create_input_schema_from_json_schema(schema: dict[str, Any]) -> type[BaseModel]:
+ """Converts a JSON schema into a Pydantic model dynamically.
+
+ :param schema: The JSON schema as a dictionary.
+ :return: A Pydantic model class.
+ """
+ if schema.get("type") != "object":
+ msg = "JSON schema must be of type 'object' at the root level."
+ raise ValueError(msg)
+
+ fields = {}
+ properties = schema.get("properties", {})
+ required_fields = set(schema.get("required", []))
+
+ for field_name, field_def in properties.items():
+ # Extract type
+ field_type_str = field_def.get("type", "str") # Default to string type if not specified
+ field_type = {
+ "string": str,
+ "str": str,
+ "integer": int,
+ "int": int,
+ "number": float,
+ "boolean": bool,
+ "array": list,
+ "object": dict,
+ }.get(field_type_str, Any)
+
+ # Extract description and default if present
+ field_metadata = {"description": field_def.get("description", "")}
+ if field_name not in required_fields:
+ field_metadata["default"] = field_def.get("default", None)
+
+ # Create Pydantic field
+ fields[field_name] = (field_type, Field(**field_metadata))
+
+ # Dynamically create the model
+ return create_model("InputSchema", **fields)
+
+
+class MCPStdio(Component):
+ client = MCPStdioClient()
+ tools = types.ListToolsResult
+ tool_names = [str]
+ display_name = "MCP Tools (stdio)"
+ description = (
+ "Connects to an MCP server over stdio and exposes it's tools as langflow tools to be used by an Agent."
+ )
+ documentation: str = "https://docs.langflow.org/components-custom-components"
+ icon = "code"
+ name = "MCPStdio"
+
+ inputs = [
+ MessageTextInput(
+ name="command",
+ display_name="mcp command",
+ info="mcp command",
+ value="uvx mcp-sse-shim@latest",
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Tools", name="tools", method="build_output"),
+ ]
+
+ async def build_output(self) -> list[Tool]:
+ if self.client.session is None:
+ self.tools = await self.client.connect_to_server(self.command)
+
+ tool_list = []
+
+ for tool in self.tools:
+ args_schema = create_input_schema_from_json_schema(tool.inputSchema)
+ tool_list.append(
+ Tool(
+ name=tool.name,
+ description=tool.description,
+ coroutine=create_tool_coroutine(tool.name, args_schema, self.client.session),
+ func=create_tool_func(tool.name, args_schema),
+ )
+ )
+ self.tool_names = [tool.name for tool in self.tools]
+ return tool_list
diff --git a/langflow/src/backend/base/langflow/components/tools/python_code_structured_tool.py b/langflow/src/backend/base/langflow/components/tools/python_code_structured_tool.py
new file mode 100644
index 0000000..a37619a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/python_code_structured_tool.py
@@ -0,0 +1,334 @@
+import ast
+import json
+from typing import Any
+
+from langchain.agents import Tool
+from langchain_core.tools import StructuredTool
+from loguru import logger
+from pydantic.v1 import Field, create_model
+from pydantic.v1.fields import Undefined
+from typing_extensions import override
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.inputs.inputs import (
+ BoolInput,
+ DropdownInput,
+ FieldTypes,
+ HandleInput,
+ MessageTextInput,
+ MultilineInput,
+)
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.dotdict import dotdict
+
+
+class PythonCodeStructuredTool(LCToolComponent):
+ DEFAULT_KEYS = [
+ "code",
+ "_type",
+ "text_key",
+ "tool_code",
+ "tool_name",
+ "tool_description",
+ "return_direct",
+ "tool_function",
+ "global_variables",
+ "_classes",
+ "_functions",
+ ]
+ display_name = "Python Code Structured"
+ description = "structuredtool dataclass code to tool"
+ documentation = "https://python.langchain.com/docs/modules/tools/custom_tools/#structuredtool-dataclass"
+ name = "PythonCodeStructuredTool"
+ icon = "Python"
+ field_order = ["name", "description", "tool_code", "return_direct", "tool_function"]
+ legacy: bool = True
+
+ inputs = [
+ MultilineInput(
+ name="tool_code",
+ display_name="Tool Code",
+ info="Enter the dataclass code.",
+ placeholder="def my_function(args):\n pass",
+ required=True,
+ real_time_refresh=True,
+ refresh_button=True,
+ ),
+ MessageTextInput(
+ name="tool_name",
+ display_name="Tool Name",
+ info="Enter the name of the tool.",
+ required=True,
+ ),
+ MessageTextInput(
+ name="tool_description",
+ display_name="Description",
+ info="Enter the description of the tool.",
+ required=True,
+ ),
+ BoolInput(
+ name="return_direct",
+ display_name="Return Directly",
+ info="Should the tool return the function output directly?",
+ ),
+ DropdownInput(
+ name="tool_function",
+ display_name="Tool Function",
+ info="Select the function for additional expressions.",
+ options=[],
+ required=True,
+ real_time_refresh=True,
+ refresh_button=True,
+ ),
+ HandleInput(
+ name="global_variables",
+ display_name="Global Variables",
+ info="Enter the global variables or Create Data Component.",
+ input_types=["Data"],
+ field_type=FieldTypes.DICT,
+ is_list=True,
+ ),
+ MessageTextInput(name="_classes", display_name="Classes", advanced=True),
+ MessageTextInput(name="_functions", display_name="Functions", advanced=True),
+ ]
+
+ outputs = [
+ Output(display_name="Tool", name="result_tool", method="build_tool"),
+ ]
+
+ @override
+ async def update_build_config(
+ self, build_config: dotdict, field_value: Any, field_name: str | None = None
+ ) -> dotdict:
+ if field_name is None:
+ return build_config
+
+ if field_name not in {"tool_code", "tool_function"}:
+ return build_config
+
+ try:
+ named_functions = {}
+ [classes, functions] = self._parse_code(build_config["tool_code"]["value"])
+ existing_fields = {}
+ if len(build_config) > len(self.DEFAULT_KEYS):
+ for key in build_config.copy():
+ if key not in self.DEFAULT_KEYS:
+ existing_fields[key] = build_config.pop(key)
+
+ names = []
+ for func in functions:
+ named_functions[func["name"]] = func
+ names.append(func["name"])
+
+ for arg in func["args"]:
+ field_name = f"{func['name']}|{arg['name']}"
+ if field_name in existing_fields:
+ build_config[field_name] = existing_fields[field_name]
+ continue
+
+ field = MessageTextInput(
+ display_name=f"{arg['name']}: Description",
+ name=field_name,
+ info=f"Enter the description for {arg['name']}",
+ required=True,
+ )
+ build_config[field_name] = field.to_dict()
+ build_config["_functions"]["value"] = json.dumps(named_functions)
+ build_config["_classes"]["value"] = json.dumps(classes)
+ build_config["tool_function"]["options"] = names
+ except Exception as e: # noqa: BLE001
+ self.status = f"Failed to extract names: {e}"
+ logger.opt(exception=True).debug(self.status)
+ build_config["tool_function"]["options"] = ["Failed to parse", str(e)]
+ return build_config
+
+ async def build_tool(self) -> Tool:
+ local_namespace = {} # type: ignore[var-annotated]
+ modules = self._find_imports(self.tool_code)
+ import_code = ""
+ for module in modules["imports"]:
+ import_code += f"global {module}\nimport {module}\n"
+ for from_module in modules["from_imports"]:
+ for alias in from_module.names:
+ import_code += f"global {alias.name}\n"
+ import_code += (
+ f"from {from_module.module} import {', '.join([alias.name for alias in from_module.names])}\n"
+ )
+ exec(import_code, globals())
+ exec(self.tool_code, globals(), local_namespace)
+
+ class PythonCodeToolFunc:
+ params: dict = {}
+
+ def run(**kwargs):
+ for key, arg in kwargs.items():
+ if key not in PythonCodeToolFunc.params:
+ PythonCodeToolFunc.params[key] = arg
+ return local_namespace[self.tool_function](**PythonCodeToolFunc.params)
+
+ globals_ = globals()
+ local = {}
+ local[self.tool_function] = PythonCodeToolFunc
+ globals_.update(local)
+
+ if isinstance(self.global_variables, list):
+ for data in self.global_variables:
+ if isinstance(data, Data):
+ globals_.update(data.data)
+ elif isinstance(self.global_variables, dict):
+ globals_.update(self.global_variables)
+
+ classes = json.loads(self._attributes["_classes"])
+ for class_dict in classes:
+ exec("\n".join(class_dict["code"]), globals_)
+
+ named_functions = json.loads(self._attributes["_functions"])
+ schema_fields = {}
+
+ for attr in self._attributes:
+ if attr in self.DEFAULT_KEYS:
+ continue
+
+ func_name = attr.split("|")[0]
+ field_name = attr.split("|")[1]
+ func_arg = self._find_arg(named_functions, func_name, field_name)
+ if func_arg is None:
+ msg = f"Failed to find arg: {field_name}"
+ raise ValueError(msg)
+
+ field_annotation = func_arg["annotation"]
+ field_description = self._get_value(self._attributes[attr], str)
+
+ if field_annotation:
+ exec(f"temp_annotation_type = {field_annotation}", globals_)
+ schema_annotation = globals_["temp_annotation_type"]
+ else:
+ schema_annotation = Any
+ schema_fields[field_name] = (
+ schema_annotation,
+ Field(
+ default=func_arg.get("default", Undefined),
+ description=field_description,
+ ),
+ )
+
+ if "temp_annotation_type" in globals_:
+ globals_.pop("temp_annotation_type")
+
+ python_code_tool_schema = None
+ if schema_fields:
+ python_code_tool_schema = create_model("PythonCodeToolSchema", **schema_fields)
+
+ return StructuredTool.from_function(
+ func=local[self.tool_function].run,
+ args_schema=python_code_tool_schema,
+ name=self.tool_name,
+ description=self.tool_description,
+ return_direct=self.return_direct,
+ )
+
+ async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):
+ """This function is called after the code validation is done."""
+ frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)
+ frontend_node["template"] = await self.update_build_config(
+ frontend_node["template"],
+ frontend_node["template"]["tool_code"]["value"],
+ "tool_code",
+ )
+ frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)
+ for key in frontend_node["template"]:
+ if key in self.DEFAULT_KEYS:
+ continue
+ frontend_node["template"] = await self.update_build_config(
+ frontend_node["template"], frontend_node["template"][key]["value"], key
+ )
+ frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)
+ return frontend_node
+
+ def _parse_code(self, code: str) -> tuple[list[dict], list[dict]]:
+ parsed_code = ast.parse(code)
+ lines = code.split("\n")
+ classes = []
+ functions = []
+ for node in parsed_code.body:
+ if isinstance(node, ast.ClassDef):
+ class_lines = lines[node.lineno - 1 : node.end_lineno]
+ class_lines[-1] = class_lines[-1][: node.end_col_offset]
+ class_lines[0] = class_lines[0][node.col_offset :]
+ classes.append(
+ {
+ "name": node.name,
+ "code": class_lines,
+ }
+ )
+ continue
+
+ if not isinstance(node, ast.FunctionDef):
+ continue
+
+ func = {"name": node.name, "args": []}
+ for arg in node.args.args:
+ if arg.lineno != arg.end_lineno:
+ msg = "Multiline arguments are not supported"
+ raise ValueError(msg)
+
+ func_arg = {
+ "name": arg.arg,
+ "annotation": None,
+ }
+
+ for default in node.args.defaults:
+ if (
+ arg.lineno > default.lineno
+ or arg.col_offset > default.col_offset
+ or (
+ arg.end_lineno is not None
+ and default.end_lineno is not None
+ and arg.end_lineno < default.end_lineno
+ )
+ or (
+ arg.end_col_offset is not None
+ and default.end_col_offset is not None
+ and arg.end_col_offset < default.end_col_offset
+ )
+ ):
+ continue
+
+ if isinstance(default, ast.Name):
+ func_arg["default"] = default.id
+ elif isinstance(default, ast.Constant):
+ func_arg["default"] = default.value
+
+ if arg.annotation:
+ annotation_line = lines[arg.annotation.lineno - 1]
+ annotation_line = annotation_line[: arg.annotation.end_col_offset]
+ annotation_line = annotation_line[arg.annotation.col_offset :]
+ func_arg["annotation"] = annotation_line
+ if isinstance(func_arg["annotation"], str) and func_arg["annotation"].count("=") > 0:
+ func_arg["annotation"] = "=".join(func_arg["annotation"].split("=")[:-1]).strip()
+ if isinstance(func["args"], list):
+ func["args"].append(func_arg)
+ functions.append(func)
+
+ return classes, functions
+
+ def _find_imports(self, code: str) -> dotdict:
+ imports: list[str] = []
+ from_imports = []
+ parsed_code = ast.parse(code)
+ for node in parsed_code.body:
+ if isinstance(node, ast.Import):
+ imports.extend(alias.name for alias in node.names)
+ elif isinstance(node, ast.ImportFrom):
+ from_imports.append(node)
+ return dotdict({"imports": imports, "from_imports": from_imports})
+
+ def _get_value(self, value: Any, annotation: Any) -> Any:
+ return value if isinstance(value, annotation) else value["value"]
+
+ def _find_arg(self, named_functions: dict, func_name: str, arg_name: str) -> dict | None:
+ for arg in named_functions[func_name]["args"]:
+ if arg["name"] == arg_name:
+ return arg
+ return None
diff --git a/langflow/src/backend/base/langflow/components/tools/python_repl.py b/langflow/src/backend/base/langflow/components/tools/python_repl.py
new file mode 100644
index 0000000..0f453e4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/python_repl.py
@@ -0,0 +1,97 @@
+import importlib
+
+from langchain.tools import StructuredTool
+from langchain_core.tools import ToolException
+from langchain_experimental.utilities import PythonREPL
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import StrInput
+from langflow.schema import Data
+
+
+class PythonREPLToolComponent(LCToolComponent):
+ display_name = "Python REPL [DEPRECATED]"
+ description = "A tool for running Python code in a REPL environment."
+ name = "PythonREPLTool"
+ icon = "Python"
+ legacy = True
+
+ inputs = [
+ StrInput(
+ name="name",
+ display_name="Tool Name",
+ info="The name of the tool.",
+ value="python_repl",
+ ),
+ StrInput(
+ name="description",
+ display_name="Tool Description",
+ info="A description of the tool.",
+ value="A Python shell. Use this to execute python commands. "
+ "Input should be a valid python command. "
+ "If you want to see the output of a value, you should print it out with `print(...)`.",
+ ),
+ StrInput(
+ name="global_imports",
+ display_name="Global Imports",
+ info="A comma-separated list of modules to import globally, e.g. 'math,numpy'.",
+ value="math",
+ ),
+ StrInput(
+ name="code",
+ display_name="Python Code",
+ info="The Python code to execute.",
+ value="print('Hello, World!')",
+ ),
+ ]
+
+ class PythonREPLSchema(BaseModel):
+ code: str = Field(..., description="The Python code to execute.")
+
+ def get_globals(self, global_imports: str | list[str]) -> dict:
+ global_dict = {}
+ if isinstance(global_imports, str):
+ modules = [module.strip() for module in global_imports.split(",")]
+ elif isinstance(global_imports, list):
+ modules = global_imports
+ else:
+ msg = "global_imports must be either a string or a list"
+ raise TypeError(msg)
+
+ for module in modules:
+ try:
+ imported_module = importlib.import_module(module)
+ global_dict[imported_module.__name__] = imported_module
+ except ImportError as e:
+ msg = f"Could not import module {module}"
+ raise ImportError(msg) from e
+ return global_dict
+
+ def build_tool(self) -> Tool:
+ globals_ = self.get_globals(self.global_imports)
+ python_repl = PythonREPL(_globals=globals_)
+
+ def run_python_code(code: str) -> str:
+ try:
+ return python_repl.run(code)
+ except Exception as e:
+ logger.opt(exception=True).debug("Error running Python code")
+ raise ToolException(str(e)) from e
+
+ tool = StructuredTool.from_function(
+ name=self.name,
+ description=self.description,
+ func=run_python_code,
+ args_schema=self.PythonREPLSchema,
+ )
+
+ self.status = f"Python REPL Tool created with global imports: {self.global_imports}"
+ return tool
+
+ def run_model(self) -> list[Data]:
+ tool = self.build_tool()
+ result = tool.run(self.code)
+ return [Data(data={"result": result})]
diff --git a/langflow/src/backend/base/langflow/components/tools/python_repl_core.py b/langflow/src/backend/base/langflow/components/tools/python_repl_core.py
new file mode 100644
index 0000000..ea88f55
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/python_repl_core.py
@@ -0,0 +1,99 @@
+import importlib
+
+from langchain_experimental.utilities import PythonREPL
+
+from langflow.custom import Component
+from langflow.io import CodeInput, Output, StrInput
+from langflow.schema import Data
+
+
+class PythonREPLComponent(Component):
+ display_name = "Python REPL"
+ description = (
+ "A Python code executor that lets you run Python code with specific imported modules. "
+ "Remember to always use print() to see your results. Example: print(df.head())"
+ )
+ icon = "Python"
+
+ inputs = [
+ StrInput(
+ name="global_imports",
+ display_name="Global Imports",
+ info="A comma-separated list of modules to import globally, e.g. 'math,numpy,pandas'.",
+ value="math,pandas",
+ required=True,
+ ),
+ CodeInput(
+ name="python_code",
+ display_name="Python Code",
+ info="The Python code to execute. Only modules specified in Global Imports can be used.",
+ value="print('Hello, World!')",
+ tool_mode=True,
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(
+ display_name="Results",
+ name="results",
+ type_=Data,
+ method="run_python_repl",
+ ),
+ ]
+
+ def get_globals(self, global_imports: str | list[str]) -> dict:
+ """Create a globals dictionary with only the specified allowed imports."""
+ global_dict = {}
+
+ try:
+ if isinstance(global_imports, str):
+ modules = [module.strip() for module in global_imports.split(",")]
+ elif isinstance(global_imports, list):
+ modules = global_imports
+ else:
+ msg = "global_imports must be either a string or a list"
+ raise TypeError(msg)
+
+ for module in modules:
+ try:
+ imported_module = importlib.import_module(module)
+ global_dict[imported_module.__name__] = imported_module
+ except ImportError as e:
+ msg = f"Could not import module {module}: {e!s}"
+ raise ImportError(msg) from e
+
+ except Exception as e:
+ self.log(f"Error in global imports: {e!s}")
+ raise
+ else:
+ self.log(f"Successfully imported modules: {list(global_dict.keys())}")
+ return global_dict
+
+ def run_python_repl(self) -> Data:
+ try:
+ globals_ = self.get_globals(self.global_imports)
+ python_repl = PythonREPL(_globals=globals_)
+ result = python_repl.run(self.python_code)
+ result = result.strip() if result else ""
+
+ self.log("Code execution completed successfully")
+ return Data(data={"result": result})
+
+ except ImportError as e:
+ error_message = f"Import Error: {e!s}"
+ self.log(error_message)
+ return Data(data={"error": error_message})
+
+ except SyntaxError as e:
+ error_message = f"Syntax Error: {e!s}"
+ self.log(error_message)
+ return Data(data={"error": error_message})
+
+ except (NameError, TypeError, ValueError) as e:
+ error_message = f"Error during execution: {e!s}"
+ self.log(error_message)
+ return Data(data={"error": error_message})
+
+ def build(self):
+ return self.run_python_repl
diff --git a/langflow/src/backend/base/langflow/components/tools/search.py b/langflow/src/backend/base/langflow/components/tools/search.py
new file mode 100644
index 0000000..362cbdf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/search.py
@@ -0,0 +1,79 @@
+from typing import Any
+
+from langchain_community.utilities.searchapi import SearchApiAPIWrapper
+
+from langflow.custom import Component
+from langflow.inputs import DictInput, DropdownInput, IntInput, MultilineInput, SecretStrInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class SearchComponent(Component):
+ display_name: str = "Search API"
+ description: str = "Call the searchapi.io API with result limiting"
+ documentation: str = "https://www.searchapi.io/docs/google"
+ icon = "SearchAPI"
+
+ inputs = [
+ DropdownInput(name="engine", display_name="Engine", value="google", options=["google", "bing", "duckduckgo"]),
+ SecretStrInput(name="api_key", display_name="SearchAPI API Key", required=True),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ tool_mode=True,
+ ),
+ DictInput(name="search_params", display_name="Search parameters", advanced=True, is_list=True),
+ IntInput(name="max_results", display_name="Max Results", value=5, advanced=True),
+ IntInput(name="max_snippet_length", display_name="Max Snippet Length", value=100, advanced=True),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def _build_wrapper(self):
+ return SearchApiAPIWrapper(engine=self.engine, searchapi_api_key=self.api_key)
+
+ def run_model(self) -> list[Data]:
+ return self.fetch_content()
+
+ def fetch_content(self) -> list[Data]:
+ wrapper = self._build_wrapper()
+
+ def search_func(
+ query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100
+ ) -> list[Data]:
+ params = params or {}
+ full_results = wrapper.results(query=query, **params)
+ organic_results = full_results.get("organic_results", [])[:max_results]
+
+ return [
+ Data(
+ text=result.get("snippet", ""),
+ data={
+ "title": result.get("title", "")[:max_snippet_length],
+ "link": result.get("link", ""),
+ "snippet": result.get("snippet", "")[:max_snippet_length],
+ },
+ )
+ for result in organic_results
+ ]
+
+ results = search_func(
+ self.input_value,
+ self.search_params or {},
+ self.max_results,
+ self.max_snippet_length,
+ )
+ self.status = results
+ return results
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = ""
+ for item in data:
+ result_string += item.text + "\n"
+ self.status = result_string
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/tools/search_api.py b/langflow/src/backend/base/langflow/components/tools/search_api.py
new file mode 100644
index 0000000..80f8cc6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/search_api.py
@@ -0,0 +1,87 @@
+from typing import Any
+
+from langchain.tools import StructuredTool
+from langchain_community.utilities.searchapi import SearchApiAPIWrapper
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import DictInput, IntInput, MessageTextInput, MultilineInput, SecretStrInput
+from langflow.schema import Data
+
+
+class SearchAPIComponent(LCToolComponent):
+ display_name: str = "Search API [DEPRECATED]"
+ description: str = "Call the searchapi.io API with result limiting"
+ name = "SearchAPI"
+ documentation: str = "https://www.searchapi.io/docs/google"
+ icon = "SearchAPI"
+ legacy = True
+
+ inputs = [
+ MessageTextInput(name="engine", display_name="Engine", value="google"),
+ SecretStrInput(name="api_key", display_name="SearchAPI API Key", required=True),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ ),
+ DictInput(name="search_params", display_name="Search parameters", advanced=True, is_list=True),
+ IntInput(name="max_results", display_name="Max Results", value=5, advanced=True),
+ IntInput(name="max_snippet_length", display_name="Max Snippet Length", value=100, advanced=True),
+ ]
+
+ class SearchAPISchema(BaseModel):
+ query: str = Field(..., description="The search query")
+ params: dict[str, Any] = Field(default_factory=dict, description="Additional search parameters")
+ max_results: int = Field(5, description="Maximum number of results to return")
+ max_snippet_length: int = Field(100, description="Maximum length of each result snippet")
+
+ def _build_wrapper(self):
+ return SearchApiAPIWrapper(engine=self.engine, searchapi_api_key=self.api_key)
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper()
+
+ def search_func(
+ query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100
+ ) -> list[dict[str, Any]]:
+ params = params or {}
+ full_results = wrapper.results(query=query, **params)
+ organic_results = full_results.get("organic_results", [])[:max_results]
+
+ limited_results = []
+ for result in organic_results:
+ limited_result = {
+ "title": result.get("title", "")[:max_snippet_length],
+ "link": result.get("link", ""),
+ "snippet": result.get("snippet", "")[:max_snippet_length],
+ }
+ limited_results.append(limited_result)
+
+ return limited_results
+
+ tool = StructuredTool.from_function(
+ name="search_api",
+ description="Search for recent results using searchapi.io with result limiting",
+ func=search_func,
+ args_schema=self.SearchAPISchema,
+ )
+
+ self.status = f"Search API Tool created with engine: {self.engine}"
+ return tool
+
+ def run_model(self) -> list[Data]:
+ tool = self.build_tool()
+ results = tool.run(
+ {
+ "query": self.input_value,
+ "params": self.search_params or {},
+ "max_results": self.max_results,
+ "max_snippet_length": self.max_snippet_length,
+ }
+ )
+
+ data_list = [Data(data=result, text=result.get("snippet", "")) for result in results]
+
+ self.status = data_list
+ return data_list
diff --git a/langflow/src/backend/base/langflow/components/tools/searxng.py b/langflow/src/backend/base/langflow/components/tools/searxng.py
new file mode 100644
index 0000000..b87e762
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/searxng.py
@@ -0,0 +1,145 @@
+import json
+from collections.abc import Sequence
+from typing import Any
+
+import requests
+from langchain.agents import Tool
+from langchain_core.tools import StructuredTool
+from loguru import logger
+from pydantic.v1 import Field, create_model
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.inputs import DropdownInput, IntInput, MessageTextInput, MultiselectInput
+from langflow.io import Output
+from langflow.schema.dotdict import dotdict
+
+
+class SearXNGToolComponent(LCToolComponent):
+ search_headers: dict = {}
+ display_name = "SearXNG Search"
+ description = "A component that searches for tools using SearXNG."
+ name = "SearXNGTool"
+ legacy: bool = True
+
+ inputs = [
+ MessageTextInput(
+ name="url",
+ display_name="URL",
+ value="http://localhost",
+ required=True,
+ refresh_button=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ value=10,
+ required=True,
+ ),
+ MultiselectInput(
+ name="categories",
+ display_name="Categories",
+ options=[],
+ value=[],
+ ),
+ DropdownInput(
+ name="language",
+ display_name="Language",
+ options=[],
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Tool", name="result_tool", method="build_tool"),
+ ]
+
+ def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict:
+ if field_name is None:
+ return build_config
+
+ if field_name != "url":
+ return build_config
+
+ try:
+ url = f"{field_value}/config"
+
+ response = requests.get(url=url, headers=self.search_headers.copy(), timeout=10)
+ data = None
+ if response.headers.get("Content-Encoding") == "zstd":
+ data = json.loads(response.content)
+ else:
+ data = response.json()
+ build_config["categories"]["options"] = data["categories"].copy()
+ for selected_category in build_config["categories"]["value"]:
+ if selected_category not in build_config["categories"]["options"]:
+ build_config["categories"]["value"].remove(selected_category)
+ languages = list(data["locales"])
+ build_config["language"]["options"] = languages.copy()
+ except Exception as e: # noqa: BLE001
+ self.status = f"Failed to extract names: {e}"
+ logger.opt(exception=True).debug(self.status)
+ build_config["categories"]["options"] = ["Failed to parse", str(e)]
+ return build_config
+
+ def build_tool(self) -> Tool:
+ class SearxSearch:
+ _url: str = ""
+ _categories: list[str] = []
+ _language: str = ""
+ _headers: dict = {}
+ _max_results: int = 10
+
+ @staticmethod
+ def search(query: str, categories: Sequence[str] = ()) -> list:
+ if not SearxSearch._categories and not categories:
+ msg = "No categories provided."
+ raise ValueError(msg)
+ all_categories = SearxSearch._categories + list(set(categories) - set(SearxSearch._categories))
+ try:
+ url = f"{SearxSearch._url}/"
+ headers = SearxSearch._headers.copy()
+ response = requests.get(
+ url=url,
+ headers=headers,
+ params={
+ "q": query,
+ "categories": ",".join(all_categories),
+ "language": SearxSearch._language,
+ "format": "json",
+ },
+ timeout=10,
+ ).json()
+
+ num_results = min(SearxSearch._max_results, len(response["results"]))
+ return [response["results"][i] for i in range(num_results)]
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error running SearXNG Search")
+ return [f"Failed to search: {e}"]
+
+ SearxSearch._url = self.url
+ SearxSearch._categories = self.categories.copy()
+ SearxSearch._language = self.language
+ SearxSearch._headers = self.search_headers.copy()
+ SearxSearch._max_results = self.max_results
+
+ globals_ = globals()
+ local = {}
+ local["SearxSearch"] = SearxSearch
+ globals_.update(local)
+
+ schema_fields = {
+ "query": (str, Field(..., description="The query to search for.")),
+ "categories": (
+ list[str],
+ Field(default=[], description="The categories to search in."),
+ ),
+ }
+
+ searx_search_schema = create_model("SearxSearchSchema", **schema_fields)
+
+ return StructuredTool.from_function(
+ func=local["SearxSearch"].search,
+ args_schema=searx_search_schema,
+ name="searxng_search_tool",
+ description="A tool that searches for tools using SearXNG.\nThe available categories are: "
+ + ", ".join(self.categories),
+ )
diff --git a/langflow/src/backend/base/langflow/components/tools/serp.py b/langflow/src/backend/base/langflow/components/tools/serp.py
new file mode 100644
index 0000000..51e1f98
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/serp.py
@@ -0,0 +1,115 @@
+from typing import Any
+
+from langchain_community.utilities.serpapi import SerpAPIWrapper
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.custom import Component
+from langflow.inputs import DictInput, IntInput, MultilineInput, SecretStrInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class SerpAPISchema(BaseModel):
+ """Schema for SerpAPI search parameters."""
+
+ query: str = Field(..., description="The search query")
+ params: dict[str, Any] | None = Field(
+ default={
+ "engine": "google",
+ "google_domain": "google.com",
+ "gl": "us",
+ "hl": "en",
+ },
+ description="Additional search parameters",
+ )
+ max_results: int = Field(5, description="Maximum number of results to return")
+ max_snippet_length: int = Field(100, description="Maximum length of each result snippet")
+
+
+class SerpComponent(Component):
+ display_name = "Serp Search API"
+ description = "Call Serp Search API with result limiting"
+ name = "Serp"
+ icon = "SerpSearch"
+
+ inputs = [
+ SecretStrInput(name="serpapi_api_key", display_name="SerpAPI API Key", required=True),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ tool_mode=True,
+ ),
+ DictInput(name="search_params", display_name="Parameters", advanced=True, is_list=True),
+ IntInput(name="max_results", display_name="Max Results", value=5, advanced=True),
+ IntInput(name="max_snippet_length", display_name="Max Snippet Length", value=100, advanced=True),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def _build_wrapper(self, params: dict[str, Any] | None = None) -> SerpAPIWrapper:
+ """Build a SerpAPIWrapper with the provided parameters."""
+ params = params or {}
+ if params:
+ return SerpAPIWrapper(
+ serpapi_api_key=self.serpapi_api_key,
+ params=params,
+ )
+ return SerpAPIWrapper(serpapi_api_key=self.serpapi_api_key)
+
+ def run_model(self) -> list[Data]:
+ return self.fetch_content()
+
+ def fetch_content(self) -> list[Data]:
+ wrapper = self._build_wrapper(self.search_params)
+
+ def search_func(
+ query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100
+ ) -> list[Data]:
+ try:
+ local_wrapper = wrapper
+ if params:
+ local_wrapper = self._build_wrapper(params)
+
+ full_results = local_wrapper.results(query)
+ organic_results = full_results.get("organic_results", [])[:max_results]
+
+ limited_results = [
+ Data(
+ text=result.get("snippet", ""),
+ data={
+ "title": result.get("title", "")[:max_snippet_length],
+ "link": result.get("link", ""),
+ "snippet": result.get("snippet", "")[:max_snippet_length],
+ },
+ )
+ for result in organic_results
+ ]
+
+ except Exception as e:
+ error_message = f"Error in SerpAPI search: {e!s}"
+ logger.debug(error_message)
+ raise ToolException(error_message) from e
+ return limited_results
+
+ results = search_func(
+ self.input_value,
+ params=self.search_params,
+ max_results=self.max_results,
+ max_snippet_length=self.max_snippet_length,
+ )
+ self.status = results
+ return results
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = ""
+ for item in data:
+ result_string += item.text + "\n"
+ self.status = result_string
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/tools/serp_api.py b/langflow/src/backend/base/langflow/components/tools/serp_api.py
new file mode 100644
index 0000000..ba9913c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/serp_api.py
@@ -0,0 +1,119 @@
+from typing import Any
+
+from langchain.tools import StructuredTool
+from langchain_community.utilities.serpapi import SerpAPIWrapper
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import DictInput, IntInput, MultilineInput, SecretStrInput
+from langflow.schema import Data
+
+
+class SerpAPISchema(BaseModel):
+ """Schema for SerpAPI search parameters."""
+
+ query: str = Field(..., description="The search query")
+ params: dict[str, Any] | None = Field(
+ default={
+ "engine": "google",
+ "google_domain": "google.com",
+ "gl": "us",
+ "hl": "en",
+ },
+ description="Additional search parameters",
+ )
+ max_results: int = Field(5, description="Maximum number of results to return")
+ max_snippet_length: int = Field(100, description="Maximum length of each result snippet")
+
+
+class SerpAPIComponent(LCToolComponent):
+ display_name = "Serp Search API [DEPRECATED]"
+ description = "Call Serp Search API with result limiting"
+ name = "SerpAPI"
+ icon = "SerpSearch"
+ legacy = True
+
+ inputs = [
+ SecretStrInput(name="serpapi_api_key", display_name="SerpAPI API Key", required=True),
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ ),
+ DictInput(name="search_params", display_name="Parameters", advanced=True, is_list=True),
+ IntInput(name="max_results", display_name="Max Results", value=5, advanced=True),
+ IntInput(name="max_snippet_length", display_name="Max Snippet Length", value=100, advanced=True),
+ ]
+
+ def _build_wrapper(self, params: dict[str, Any] | None = None) -> SerpAPIWrapper:
+ """Build a SerpAPIWrapper with the provided parameters."""
+ params = params or {}
+ if params:
+ return SerpAPIWrapper(
+ serpapi_api_key=self.serpapi_api_key,
+ params=params,
+ )
+ return SerpAPIWrapper(serpapi_api_key=self.serpapi_api_key)
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper(self.search_params)
+
+ def search_func(
+ query: str, params: dict[str, Any] | None = None, max_results: int = 5, max_snippet_length: int = 100
+ ) -> list[dict[str, Any]]:
+ try:
+ local_wrapper = wrapper
+ if params:
+ local_wrapper = self._build_wrapper(params)
+
+ full_results = local_wrapper.results(query)
+ organic_results = full_results.get("organic_results", [])[:max_results]
+
+ limited_results = []
+ for result in organic_results:
+ limited_result = {
+ "title": result.get("title", "")[:max_snippet_length],
+ "link": result.get("link", ""),
+ "snippet": result.get("snippet", "")[:max_snippet_length],
+ }
+ limited_results.append(limited_result)
+
+ except Exception as e:
+ error_message = f"Error in SerpAPI search: {e!s}"
+ logger.debug(error_message)
+ raise ToolException(error_message) from e
+ return limited_results
+
+ tool = StructuredTool.from_function(
+ name="serp_search_api",
+ description="Search for recent results using SerpAPI with result limiting",
+ func=search_func,
+ args_schema=SerpAPISchema,
+ )
+
+ self.status = "SerpAPI Tool created"
+ return tool
+
+ def run_model(self) -> list[Data]:
+ tool = self.build_tool()
+ try:
+ results = tool.run(
+ {
+ "query": self.input_value,
+ "params": self.search_params or {},
+ "max_results": self.max_results,
+ "max_snippet_length": self.max_snippet_length,
+ }
+ )
+
+ data_list = [Data(data=result, text=result.get("snippet", "")) for result in results]
+
+ except Exception as e: # noqa: BLE001
+ logger.opt(exception=True).debug("Error running SerpAPI")
+ self.status = f"Error: {e}"
+ return [Data(data={"error": str(e)}, text=str(e))]
+
+ self.status = data_list # type: ignore[assignment]
+ return data_list
diff --git a/langflow/src/backend/base/langflow/components/tools/tavily.py b/langflow/src/backend/base/langflow/components/tools/tavily.py
new file mode 100644
index 0000000..0838f2f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/tavily.py
@@ -0,0 +1,148 @@
+import httpx
+from loguru import logger
+
+from langflow.custom import Component
+from langflow.helpers.data import data_to_text
+from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class TavilySearchComponent(Component):
+ display_name = "Tavily AI Search"
+ description = """**Tavily AI** is a search engine optimized for LLMs and RAG, \
+ aimed at efficient, quick, and persistent search results."""
+ icon = "TavilyIcon"
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Tavily API Key",
+ required=True,
+ info="Your Tavily API Key.",
+ ),
+ MessageTextInput(
+ name="query",
+ display_name="Search Query",
+ info="The search query you want to execute with Tavily.",
+ tool_mode=True,
+ ),
+ DropdownInput(
+ name="search_depth",
+ display_name="Search Depth",
+ info="The depth of the search.",
+ options=["basic", "advanced"],
+ value="advanced",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="topic",
+ display_name="Search Topic",
+ info="The category of the search.",
+ options=["general", "news"],
+ value="general",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="time_range",
+ display_name="Time Range",
+ info="The time range back from the current date to include in the search results.",
+ options=["day", "week", "month", "year"],
+ value=None,
+ advanced=True,
+ combobox=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ info="The maximum number of search results to return.",
+ value=5,
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_images",
+ display_name="Include Images",
+ info="Include a list of query-related images in the response.",
+ value=True,
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_answer",
+ display_name="Include Answer",
+ info="Include a short answer to original query.",
+ value=True,
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def fetch_content(self) -> list[Data]:
+ try:
+ url = "https://api.tavily.com/search"
+ headers = {
+ "content-type": "application/json",
+ "accept": "application/json",
+ }
+ payload = {
+ "api_key": self.api_key,
+ "query": self.query,
+ "search_depth": self.search_depth,
+ "topic": self.topic,
+ "max_results": self.max_results,
+ "include_images": self.include_images,
+ "include_answer": self.include_answer,
+ "time_range": self.time_range,
+ }
+
+ with httpx.Client() as client:
+ response = client.post(url, json=payload, headers=headers)
+
+ response.raise_for_status()
+ search_results = response.json()
+
+ data_results = []
+
+ if self.include_answer and search_results.get("answer"):
+ data_results.append(Data(text=search_results["answer"]))
+
+ for result in search_results.get("results", []):
+ content = result.get("content", "")
+ data_results.append(
+ Data(
+ text=content,
+ data={
+ "title": result.get("title"),
+ "url": result.get("url"),
+ "content": content,
+ "score": result.get("score"),
+ },
+ )
+ )
+
+ if self.include_images and search_results.get("images"):
+ data_results.append(Data(text="Images found", data={"images": search_results["images"]}))
+ except httpx.HTTPStatusError as exc:
+ error_message = f"HTTP error occurred: {exc.response.status_code} - {exc.response.text}"
+ logger.error(error_message)
+ return [Data(text=error_message, data={"error": error_message})]
+ except httpx.RequestError as exc:
+ error_message = f"Request error occurred: {exc}"
+ logger.error(error_message)
+ return [Data(text=error_message, data={"error": error_message})]
+ except ValueError as exc:
+ error_message = f"Invalid response format: {exc}"
+ logger.error(error_message)
+ return [Data(text=error_message, data={"error": error_message})]
+ else:
+ self.status = data_results
+ return data_results
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = data_to_text("{text}", data)
+ self.status = result_string
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/tools/tavily_search.py b/langflow/src/backend/base/langflow/components/tools/tavily_search.py
new file mode 100644
index 0000000..da755a0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/tavily_search.py
@@ -0,0 +1,206 @@
+from enum import Enum
+
+import httpx
+from langchain.tools import StructuredTool
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput
+from langflow.schema import Data
+
+
+class TavilySearchDepth(Enum):
+ BASIC = "basic"
+ ADVANCED = "advanced"
+
+
+class TavilySearchTopic(Enum):
+ GENERAL = "general"
+ NEWS = "news"
+
+
+class TavilySearchSchema(BaseModel):
+ query: str = Field(..., description="The search query you want to execute with Tavily.")
+ search_depth: TavilySearchDepth = Field(TavilySearchDepth.BASIC, description="The depth of the search.")
+ topic: TavilySearchTopic = Field(TavilySearchTopic.GENERAL, description="The category of the search.")
+ max_results: int = Field(5, description="The maximum number of search results to return.")
+ include_images: bool = Field(default=False, description="Include a list of query-related images in the response.")
+ include_answer: bool = Field(default=False, description="Include a short answer to original query.")
+
+
+class TavilySearchToolComponent(LCToolComponent):
+ display_name = "Tavily AI Search [DEPRECATED]"
+ description = """**Tavily AI** is a search engine optimized for LLMs and RAG, \
+ aimed at efficient, quick, and persistent search results. It can be used independently or as an agent tool.
+
+Note: Check 'Advanced' for all options.
+"""
+ icon = "TavilyIcon"
+ name = "TavilyAISearch"
+ documentation = "https://docs.tavily.com/"
+ legacy = True
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="Tavily API Key",
+ required=True,
+ info="Your Tavily API Key.",
+ ),
+ MessageTextInput(
+ name="query",
+ display_name="Search Query",
+ info="The search query you want to execute with Tavily.",
+ ),
+ DropdownInput(
+ name="search_depth",
+ display_name="Search Depth",
+ info="The depth of the search.",
+ options=list(TavilySearchDepth),
+ value=TavilySearchDepth.ADVANCED,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="topic",
+ display_name="Search Topic",
+ info="The category of the search.",
+ options=list(TavilySearchTopic),
+ value=TavilySearchTopic.GENERAL,
+ advanced=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ info="The maximum number of search results to return.",
+ value=5,
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_images",
+ display_name="Include Images",
+ info="Include a list of query-related images in the response.",
+ value=True,
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_answer",
+ display_name="Include Answer",
+ info="Include a short answer to original query.",
+ value=True,
+ advanced=True,
+ ),
+ ]
+
+ def run_model(self) -> list[Data]:
+ # Convert string values to enum instances with validation
+ try:
+ search_depth_enum = (
+ self.search_depth
+ if isinstance(self.search_depth, TavilySearchDepth)
+ else TavilySearchDepth(str(self.search_depth).lower())
+ )
+ except ValueError as e:
+ error_message = f"Invalid search depth value: {e!s}"
+ self.status = error_message
+ return [Data(data={"error": error_message})]
+
+ try:
+ topic_enum = (
+ self.topic if isinstance(self.topic, TavilySearchTopic) else TavilySearchTopic(str(self.topic).lower())
+ )
+ except ValueError as e:
+ error_message = f"Invalid topic value: {e!s}"
+ self.status = error_message
+ return [Data(data={"error": error_message})]
+
+ return self._tavily_search(
+ self.query,
+ search_depth=search_depth_enum,
+ topic=topic_enum,
+ max_results=self.max_results,
+ include_images=self.include_images,
+ include_answer=self.include_answer,
+ )
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="tavily_search",
+ description="Perform a web search using the Tavily API.",
+ func=self._tavily_search,
+ args_schema=TavilySearchSchema,
+ )
+
+ def _tavily_search(
+ self,
+ query: str,
+ *,
+ search_depth: TavilySearchDepth = TavilySearchDepth.BASIC,
+ topic: TavilySearchTopic = TavilySearchTopic.GENERAL,
+ max_results: int = 5,
+ include_images: bool = False,
+ include_answer: bool = False,
+ ) -> list[Data]:
+ # Validate enum values
+ if not isinstance(search_depth, TavilySearchDepth):
+ msg = f"Invalid search_depth value: {search_depth}"
+ raise TypeError(msg)
+ if not isinstance(topic, TavilySearchTopic):
+ msg = f"Invalid topic value: {topic}"
+ raise TypeError(msg)
+
+ try:
+ url = "https://api.tavily.com/search"
+ headers = {
+ "content-type": "application/json",
+ "accept": "application/json",
+ }
+ payload = {
+ "api_key": self.api_key,
+ "query": query,
+ "search_depth": search_depth.value,
+ "topic": topic.value,
+ "max_results": max_results,
+ "include_images": include_images,
+ "include_answer": include_answer,
+ }
+
+ with httpx.Client() as client:
+ response = client.post(url, json=payload, headers=headers)
+
+ response.raise_for_status()
+ search_results = response.json()
+
+ data_results = [
+ Data(
+ data={
+ "title": result.get("title"),
+ "url": result.get("url"),
+ "content": result.get("content"),
+ "score": result.get("score"),
+ }
+ )
+ for result in search_results.get("results", [])
+ ]
+
+ if include_answer and search_results.get("answer"):
+ data_results.insert(0, Data(data={"answer": search_results["answer"]}))
+
+ if include_images and search_results.get("images"):
+ data_results.append(Data(data={"images": search_results["images"]}))
+
+ self.status = data_results # type: ignore[assignment]
+
+ except httpx.HTTPStatusError as e:
+ error_message = f"HTTP error: {e.response.status_code} - {e.response.text}"
+ logger.debug(error_message)
+ self.status = error_message
+ raise ToolException(error_message) from e
+ except Exception as e:
+ error_message = f"Unexpected error: {e}"
+ logger.opt(exception=True).debug("Error running Tavily Search")
+ self.status = error_message
+ raise ToolException(error_message) from e
+ return data_results
diff --git a/langflow/src/backend/base/langflow/components/tools/wikidata.py b/langflow/src/backend/base/langflow/components/tools/wikidata.py
new file mode 100644
index 0000000..5fd0709
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/wikidata.py
@@ -0,0 +1,86 @@
+import httpx
+from httpx import HTTPError
+from langchain_core.tools import ToolException
+
+from langflow.custom import Component
+from langflow.helpers.data import data_to_text
+from langflow.io import MultilineInput, Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class WikidataComponent(Component):
+ display_name = "Wikidata"
+ description = "Performs a search using the Wikidata API."
+ icon = "Wikipedia"
+
+ inputs = [
+ MultilineInput(
+ name="query",
+ display_name="Query",
+ info="The text query for similarity search on Wikidata.",
+ required=True,
+ tool_mode=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Message", name="text", method="fetch_content_text"),
+ ]
+
+ def fetch_content(self) -> list[Data]:
+ try:
+ # Define request parameters for Wikidata API
+ params = {
+ "action": "wbsearchentities",
+ "format": "json",
+ "search": self.query,
+ "language": "en",
+ }
+
+ # Send request to Wikidata API
+ wikidata_api_url = "https://www.wikidata.org/w/api.php"
+ response = httpx.get(wikidata_api_url, params=params)
+ response.raise_for_status()
+ response_json = response.json()
+
+ # Extract search results
+ results = response_json.get("search", [])
+
+ if not results:
+ return [Data(data={"error": "No search results found for the given query."})]
+
+ # Transform the API response into Data objects
+ data = [
+ Data(
+ text=f"{result['label']}: {result.get('description', '')}",
+ data={
+ "label": result["label"],
+ "id": result.get("id"),
+ "url": result.get("url"),
+ "description": result.get("description", ""),
+ "concepturi": result.get("concepturi"),
+ },
+ )
+ for result in results
+ ]
+
+ self.status = data
+ except HTTPError as e:
+ error_message = f"HTTP Error in Wikidata Search API: {e!s}"
+ raise ToolException(error_message) from None
+ except KeyError as e:
+ error_message = f"Data parsing error in Wikidata API response: {e!s}"
+ raise ToolException(error_message) from None
+ except ValueError as e:
+ error_message = f"Value error in Wikidata API: {e!s}"
+ raise ToolException(error_message) from None
+ else:
+ return data
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = data_to_text("{text}", data)
+ self.status = result_string
+ return Message(text=result_string)
diff --git a/langflow/src/backend/base/langflow/components/tools/wikidata_api.py b/langflow/src/backend/base/langflow/components/tools/wikidata_api.py
new file mode 100644
index 0000000..eb9d0a2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/wikidata_api.py
@@ -0,0 +1,102 @@
+from typing import Any
+
+import httpx
+from langchain_core.tools import StructuredTool, ToolException
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput
+from langflow.schema import Data
+
+
+class WikidataSearchSchema(BaseModel):
+ query: str = Field(..., description="The search query for Wikidata")
+
+
+class WikidataAPIWrapper(BaseModel):
+ """Wrapper around Wikidata API."""
+
+ wikidata_api_url: str = "https://www.wikidata.org/w/api.php"
+
+ def results(self, query: str) -> list[dict[str, Any]]:
+ # Define request parameters for Wikidata API
+ params = {
+ "action": "wbsearchentities",
+ "format": "json",
+ "search": query,
+ "language": "en",
+ }
+
+ # Send request to Wikidata API
+ response = httpx.get(self.wikidata_api_url, params=params)
+ response.raise_for_status()
+ response_json = response.json()
+
+ # Extract and return search results
+ return response_json.get("search", [])
+
+ def run(self, query: str) -> list[dict[str, Any]]:
+ try:
+ results = self.results(query)
+ if results:
+ return results
+
+ error_message = "No search results found for the given query."
+
+ raise ToolException(error_message)
+
+ except Exception as e:
+ error_message = f"Error in Wikidata Search API: {e!s}"
+
+ raise ToolException(error_message) from e
+
+
+class WikidataAPIComponent(LCToolComponent):
+ display_name = "Wikidata API [Deprecated]"
+ description = "Performs a search using the Wikidata API."
+ name = "WikidataAPI"
+ icon = "Wikipedia"
+ legacy = True
+
+ inputs = [
+ MultilineInput(
+ name="query",
+ display_name="Query",
+ info="The text query for similarity search on Wikidata.",
+ required=True,
+ ),
+ ]
+
+ def build_tool(self) -> Tool:
+ wrapper = WikidataAPIWrapper()
+
+ # Define the tool using StructuredTool and wrapper's run method
+ tool = StructuredTool.from_function(
+ name="wikidata_search_api",
+ description="Perform similarity search on Wikidata API",
+ func=wrapper.run,
+ args_schema=WikidataSearchSchema,
+ )
+
+ self.status = "Wikidata Search API Tool for Langchain"
+
+ return tool
+
+ def run_model(self) -> list[Data]:
+ tool = self.build_tool()
+
+ results = tool.run({"query": self.query})
+
+ # Transform the API response into Data objects
+ data = [
+ Data(
+ text=result["label"],
+ metadata=result,
+ )
+ for result in results
+ ]
+
+ self.status = data # type: ignore[assignment]
+
+ return data
diff --git a/langflow/src/backend/base/langflow/components/tools/wikipedia.py b/langflow/src/backend/base/langflow/components/tools/wikipedia.py
new file mode 100644
index 0000000..a71bce5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/wikipedia.py
@@ -0,0 +1,55 @@
+from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, IntInput, MessageTextInput, MultilineInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class WikipediaComponent(Component):
+ display_name = "Wikipedia"
+ description = "Call Wikipedia API."
+ icon = "Wikipedia"
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ tool_mode=True,
+ ),
+ MessageTextInput(name="lang", display_name="Language", value="en"),
+ IntInput(name="k", display_name="Number of results", value=4, required=True),
+ BoolInput(name="load_all_available_meta", display_name="Load all available meta", value=False, advanced=True),
+ IntInput(
+ name="doc_content_chars_max", display_name="Document content characters max", value=4000, advanced=True
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def fetch_content(self) -> list[Data]:
+ wrapper = self._build_wrapper()
+ docs = wrapper.load(self.input_value)
+ data = [Data.from_document(doc) for doc in docs]
+ self.status = data
+ return data
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = ""
+ for item in data:
+ result_string += item.text + "\n"
+ self.status = result_string
+ return Message(text=result_string)
+
+ def _build_wrapper(self) -> WikipediaAPIWrapper:
+ return WikipediaAPIWrapper(
+ top_k_results=self.k,
+ lang=self.lang,
+ load_all_available_meta=self.load_all_available_meta,
+ doc_content_chars_max=self.doc_content_chars_max,
+ )
diff --git a/langflow/src/backend/base/langflow/components/tools/wikipedia_api.py b/langflow/src/backend/base/langflow/components/tools/wikipedia_api.py
new file mode 100644
index 0000000..06ab747
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/wikipedia_api.py
@@ -0,0 +1,49 @@
+from typing import cast
+
+from langchain_community.tools import WikipediaQueryRun
+from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import BoolInput, IntInput, MessageTextInput, MultilineInput
+from langflow.schema import Data
+
+
+class WikipediaAPIComponent(LCToolComponent):
+ display_name = "Wikipedia API [Deprecated]"
+ description = "Call Wikipedia API."
+ name = "WikipediaAPI"
+ icon = "Wikipedia"
+ legacy = True
+
+ inputs = [
+ MultilineInput(
+ name="input_value",
+ display_name="Input",
+ ),
+ MessageTextInput(name="lang", display_name="Language", value="en"),
+ IntInput(name="k", display_name="Number of results", value=4, required=True),
+ BoolInput(name="load_all_available_meta", display_name="Load all available meta", value=False, advanced=True),
+ IntInput(
+ name="doc_content_chars_max", display_name="Document content characters max", value=4000, advanced=True
+ ),
+ ]
+
+ def run_model(self) -> list[Data]:
+ wrapper = self._build_wrapper()
+ docs = wrapper.load(self.input_value)
+ data = [Data.from_document(doc) for doc in docs]
+ self.status = data
+ return data
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper()
+ return cast("Tool", WikipediaQueryRun(api_wrapper=wrapper))
+
+ def _build_wrapper(self) -> WikipediaAPIWrapper:
+ return WikipediaAPIWrapper(
+ top_k_results=self.k,
+ lang=self.lang,
+ load_all_available_meta=self.load_all_available_meta,
+ doc_content_chars_max=self.doc_content_chars_max,
+ )
diff --git a/langflow/src/backend/base/langflow/components/tools/wolfram_alpha_api.py b/langflow/src/backend/base/langflow/components/tools/wolfram_alpha_api.py
new file mode 100644
index 0000000..410677c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/wolfram_alpha_api.py
@@ -0,0 +1,36 @@
+from langchain_community.utilities.wolfram_alpha import WolframAlphaAPIWrapper
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import MultilineInput, SecretStrInput
+from langflow.schema import Data
+
+
+class WolframAlphaAPIComponent(LCToolComponent):
+ display_name = "WolframAlpha API"
+ description = """Enables queries to Wolfram Alpha for computational data, facts, and calculations across various \
+topics, delivering structured responses."""
+ name = "WolframAlphaAPI"
+
+ inputs = [
+ MultilineInput(
+ name="input_value", display_name="Input Query", info="Example query: 'What is the population of France?'"
+ ),
+ SecretStrInput(name="app_id", display_name="App ID", required=True),
+ ]
+
+ icon = "WolframAlphaAPI"
+
+ def run_model(self) -> list[Data]:
+ wrapper = self._build_wrapper()
+ result_str = wrapper.run(self.input_value)
+ data = [Data(text=result_str)]
+ self.status = data
+ return data
+
+ def build_tool(self) -> Tool:
+ wrapper = self._build_wrapper()
+ return Tool(name="wolfram_alpha_api", description="Answers mathematical questions.", func=wrapper.run)
+
+ def _build_wrapper(self) -> WolframAlphaAPIWrapper:
+ return WolframAlphaAPIWrapper(wolfram_alpha_appid=self.app_id)
diff --git a/langflow/src/backend/base/langflow/components/tools/yahoo.py b/langflow/src/backend/base/langflow/components/tools/yahoo.py
new file mode 100644
index 0000000..5874d51
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/yahoo.py
@@ -0,0 +1,142 @@
+import ast
+import pprint
+from enum import Enum
+
+import yfinance as yf
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.custom import Component
+from langflow.inputs import DropdownInput, IntInput, MessageTextInput
+from langflow.io import Output
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+class YahooFinanceMethod(Enum):
+ GET_INFO = "get_info"
+ GET_NEWS = "get_news"
+ GET_ACTIONS = "get_actions"
+ GET_ANALYSIS = "get_analysis"
+ GET_BALANCE_SHEET = "get_balance_sheet"
+ GET_CALENDAR = "get_calendar"
+ GET_CASHFLOW = "get_cashflow"
+ GET_INSTITUTIONAL_HOLDERS = "get_institutional_holders"
+ GET_RECOMMENDATIONS = "get_recommendations"
+ GET_SUSTAINABILITY = "get_sustainability"
+ GET_MAJOR_HOLDERS = "get_major_holders"
+ GET_MUTUALFUND_HOLDERS = "get_mutualfund_holders"
+ GET_INSIDER_PURCHASES = "get_insider_purchases"
+ GET_INSIDER_TRANSACTIONS = "get_insider_transactions"
+ GET_INSIDER_ROSTER_HOLDERS = "get_insider_roster_holders"
+ GET_DIVIDENDS = "get_dividends"
+ GET_CAPITAL_GAINS = "get_capital_gains"
+ GET_SPLITS = "get_splits"
+ GET_SHARES = "get_shares"
+ GET_FAST_INFO = "get_fast_info"
+ GET_SEC_FILINGS = "get_sec_filings"
+ GET_RECOMMENDATIONS_SUMMARY = "get_recommendations_summary"
+ GET_UPGRADES_DOWNGRADES = "get_upgrades_downgrades"
+ GET_EARNINGS = "get_earnings"
+ GET_INCOME_STMT = "get_income_stmt"
+
+
+class YahooFinanceSchema(BaseModel):
+ symbol: str = Field(..., description="The stock symbol to retrieve data for.")
+ method: YahooFinanceMethod = Field(YahooFinanceMethod.GET_INFO, description="The type of data to retrieve.")
+ num_news: int | None = Field(5, description="The number of news articles to retrieve.")
+
+
+class YfinanceComponent(Component):
+ display_name = "Yahoo Finance"
+ description = """Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) \
+to access financial data and market information from Yahoo Finance."""
+ icon = "trending-up"
+
+ inputs = [
+ MessageTextInput(
+ name="symbol",
+ display_name="Stock Symbol",
+ info="The stock symbol to retrieve data for (e.g., AAPL, GOOG).",
+ tool_mode=True,
+ ),
+ DropdownInput(
+ name="method",
+ display_name="Data Method",
+ info="The type of data to retrieve.",
+ options=list(YahooFinanceMethod),
+ value="get_news",
+ ),
+ IntInput(
+ name="num_news",
+ display_name="Number of News",
+ info="The number of news articles to retrieve (only applicable for get_news).",
+ value=5,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Data", name="data", method="fetch_content"),
+ Output(display_name="Text", name="text", method="fetch_content_text"),
+ ]
+
+ def run_model(self) -> list[Data]:
+ return self.fetch_content()
+
+ def fetch_content_text(self) -> Message:
+ data = self.fetch_content()
+ result_string = ""
+ for item in data:
+ result_string += item.text + "\n"
+ self.status = result_string
+ return Message(text=result_string)
+
+ def _fetch_yfinance_data(self, ticker: yf.Ticker, method: YahooFinanceMethod, num_news: int | None) -> str:
+ try:
+ if method == YahooFinanceMethod.GET_INFO:
+ result = ticker.info
+ elif method == YahooFinanceMethod.GET_NEWS:
+ result = ticker.news[:num_news]
+ else:
+ result = getattr(ticker, method.value)()
+ return pprint.pformat(result)
+ except Exception as e:
+ error_message = f"Error retrieving data: {e}"
+ logger.debug(error_message)
+ self.status = error_message
+ raise ToolException(error_message) from e
+
+ def fetch_content(self) -> list[Data]:
+ try:
+ return self._yahoo_finance_tool(
+ self.symbol,
+ YahooFinanceMethod(self.method),
+ self.num_news,
+ )
+ except ToolException:
+ raise
+ except Exception as e:
+ error_message = f"Unexpected error: {e}"
+ logger.debug(error_message)
+ self.status = error_message
+ raise ToolException(error_message) from e
+
+ def _yahoo_finance_tool(
+ self,
+ symbol: str,
+ method: YahooFinanceMethod,
+ num_news: int | None = 5,
+ ) -> list[Data]:
+ ticker = yf.Ticker(symbol)
+ result = self._fetch_yfinance_data(ticker, method, num_news)
+
+ if method == YahooFinanceMethod.GET_NEWS:
+ data_list = [
+ Data(text=f"{article['title']}: {article['link']}", data=article)
+ for article in ast.literal_eval(result)
+ ]
+ else:
+ data_list = [Data(text=result, data={"result": result})]
+
+ return data_list
diff --git a/langflow/src/backend/base/langflow/components/tools/yahoo_finance.py b/langflow/src/backend/base/langflow/components/tools/yahoo_finance.py
new file mode 100644
index 0000000..7f7e4a0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/tools/yahoo_finance.py
@@ -0,0 +1,124 @@
+import ast
+import pprint
+from enum import Enum
+
+import yfinance as yf
+from langchain.tools import StructuredTool
+from langchain_core.tools import ToolException
+from loguru import logger
+from pydantic import BaseModel, Field
+
+from langflow.base.langchain_utilities.model import LCToolComponent
+from langflow.field_typing import Tool
+from langflow.inputs import DropdownInput, IntInput, MessageTextInput
+from langflow.schema import Data
+
+
+class YahooFinanceMethod(Enum):
+ GET_INFO = "get_info"
+ GET_NEWS = "get_news"
+ GET_ACTIONS = "get_actions"
+ GET_ANALYSIS = "get_analysis"
+ GET_BALANCE_SHEET = "get_balance_sheet"
+ GET_CALENDAR = "get_calendar"
+ GET_CASHFLOW = "get_cashflow"
+ GET_INSTITUTIONAL_HOLDERS = "get_institutional_holders"
+ GET_RECOMMENDATIONS = "get_recommendations"
+ GET_SUSTAINABILITY = "get_sustainability"
+ GET_MAJOR_HOLDERS = "get_major_holders"
+ GET_MUTUALFUND_HOLDERS = "get_mutualfund_holders"
+ GET_INSIDER_PURCHASES = "get_insider_purchases"
+ GET_INSIDER_TRANSACTIONS = "get_insider_transactions"
+ GET_INSIDER_ROSTER_HOLDERS = "get_insider_roster_holders"
+ GET_DIVIDENDS = "get_dividends"
+ GET_CAPITAL_GAINS = "get_capital_gains"
+ GET_SPLITS = "get_splits"
+ GET_SHARES = "get_shares"
+ GET_FAST_INFO = "get_fast_info"
+ GET_SEC_FILINGS = "get_sec_filings"
+ GET_RECOMMENDATIONS_SUMMARY = "get_recommendations_summary"
+ GET_UPGRADES_DOWNGRADES = "get_upgrades_downgrades"
+ GET_EARNINGS = "get_earnings"
+ GET_INCOME_STMT = "get_income_stmt"
+
+
+class YahooFinanceSchema(BaseModel):
+ symbol: str = Field(..., description="The stock symbol to retrieve data for.")
+ method: YahooFinanceMethod = Field(YahooFinanceMethod.GET_INFO, description="The type of data to retrieve.")
+ num_news: int | None = Field(5, description="The number of news articles to retrieve.")
+
+
+class YfinanceToolComponent(LCToolComponent):
+ display_name = "Yahoo Finance [DEPRECATED]"
+ description = """Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) \
+to access financial data and market information from Yahoo Finance."""
+ icon = "trending-up"
+ name = "YahooFinanceTool"
+ legacy = True
+
+ inputs = [
+ MessageTextInput(
+ name="symbol",
+ display_name="Stock Symbol",
+ info="The stock symbol to retrieve data for (e.g., AAPL, GOOG).",
+ ),
+ DropdownInput(
+ name="method",
+ display_name="Data Method",
+ info="The type of data to retrieve.",
+ options=list(YahooFinanceMethod),
+ value="get_news",
+ ),
+ IntInput(
+ name="num_news",
+ display_name="Number of News",
+ info="The number of news articles to retrieve (only applicable for get_news).",
+ value=5,
+ ),
+ ]
+
+ def run_model(self) -> list[Data]:
+ return self._yahoo_finance_tool(
+ self.symbol,
+ self.method,
+ self.num_news,
+ )
+
+ def build_tool(self) -> Tool:
+ return StructuredTool.from_function(
+ name="yahoo_finance",
+ description="Access financial data and market information from Yahoo Finance.",
+ func=self._yahoo_finance_tool,
+ args_schema=YahooFinanceSchema,
+ )
+
+ def _yahoo_finance_tool(
+ self,
+ symbol: str,
+ method: YahooFinanceMethod,
+ num_news: int | None = 5,
+ ) -> list[Data]:
+ ticker = yf.Ticker(symbol)
+
+ try:
+ if method == YahooFinanceMethod.GET_INFO:
+ result = ticker.info
+ elif method == YahooFinanceMethod.GET_NEWS:
+ result = ticker.news[:num_news]
+ else:
+ result = getattr(ticker, method.value)()
+
+ result = pprint.pformat(result)
+
+ if method == YahooFinanceMethod.GET_NEWS:
+ data_list = [Data(data=article) for article in ast.literal_eval(result)]
+ else:
+ data_list = [Data(data={"result": result})]
+
+ except Exception as e:
+ error_message = f"Error retrieving data: {e}"
+ logger.debug(error_message)
+ self.status = error_message
+ raise ToolException(error_message) from e
+
+ return data_list
diff --git a/langflow/src/backend/base/langflow/components/unstructured/__init__.py b/langflow/src/backend/base/langflow/components/unstructured/__init__.py
new file mode 100644
index 0000000..a9bceaa
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/unstructured/__init__.py
@@ -0,0 +1,3 @@
+from .unstructured import UnstructuredComponent
+
+__all__ = ["UnstructuredComponent"]
diff --git a/langflow/src/backend/base/langflow/components/unstructured/unstructured.py b/langflow/src/backend/base/langflow/components/unstructured/unstructured.py
new file mode 100644
index 0000000..f4946fc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/unstructured/unstructured.py
@@ -0,0 +1,121 @@
+from langchain_unstructured import UnstructuredLoader
+
+from langflow.base.data import BaseFileComponent
+from langflow.inputs import DropdownInput, MessageTextInput, NestedDictInput, SecretStrInput
+from langflow.schema import Data
+
+
+class UnstructuredComponent(BaseFileComponent):
+ display_name = "Unstructured API"
+ description = (
+ "Uses Unstructured.io API to extract clean text from raw source documents. Supports a wide range of file types."
+ )
+ documentation = (
+ "https://python.langchain.com/api_reference/unstructured/document_loaders/"
+ "langchain_unstructured.document_loaders.UnstructuredLoader.html"
+ )
+ trace_type = "tool"
+ icon = "Unstructured"
+ name = "Unstructured"
+
+ # https://docs.unstructured.io/api-reference/api-services/overview#supported-file-types
+ VALID_EXTENSIONS = [
+ "bmp",
+ "csv",
+ "doc",
+ "docx",
+ "eml",
+ "epub",
+ "heic",
+ "html",
+ "jpeg",
+ "png",
+ "md",
+ "msg",
+ "odt",
+ "org",
+ "p7s",
+ "pdf",
+ "png",
+ "ppt",
+ "pptx",
+ "rst",
+ "rtf",
+ "tiff",
+ "txt",
+ "tsv",
+ "xls",
+ "xlsx",
+ "xml",
+ ]
+
+ inputs = [
+ *BaseFileComponent._base_inputs,
+ SecretStrInput(
+ name="api_key",
+ display_name="Unstructured.io Serverless API Key",
+ required=True,
+ info="Unstructured API Key. Create at: https://app.unstructured.io/",
+ ),
+ MessageTextInput(
+ name="api_url",
+ display_name="Unstructured.io API URL",
+ required=False,
+ info="Unstructured API URL.",
+ ),
+ DropdownInput(
+ name="chunking_strategy",
+ display_name="Chunking Strategy",
+ info="Chunking strategy to use, see https://docs.unstructured.io/api-reference/api-services/chunking",
+ options=["", "basic", "by_title", "by_page", "by_similarity"],
+ real_time_refresh=False,
+ value="",
+ ),
+ NestedDictInput(
+ name="unstructured_args",
+ display_name="Additional Arguments",
+ required=False,
+ info=(
+ "Optional dictionary of additional arguments to the Loader. "
+ "See https://docs.unstructured.io/api-reference/api-services/api-parameters for more information."
+ ),
+ ),
+ ]
+
+ outputs = [
+ *BaseFileComponent._base_outputs,
+ ]
+
+ def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:
+ file_paths = [str(file.path) for file in file_list if file.path]
+
+ if not file_paths:
+ self.log("No files to process.")
+ return file_list
+
+ # https://docs.unstructured.io/api-reference/api-services/api-parameters
+ args = self.unstructured_args or {}
+
+ if self.chunking_strategy:
+ args["chunking_strategy"] = self.chunking_strategy
+
+ args["api_key"] = self.api_key
+ args["partition_via_api"] = True
+ if self.api_url:
+ args["url"] = self.api_url
+
+ loader = UnstructuredLoader(
+ file_paths,
+ **args,
+ )
+
+ documents = loader.load()
+
+ processed_data: list[Data | None] = [Data.from_document(doc) if doc else None for doc in documents]
+
+ # Rename the `source` field to `self.SERVER_FILE_PATH_FIELDNAME`, to avoid conflicts with the `source` field
+ for data in processed_data:
+ if data and "source" in data.data:
+ data.data[self.SERVER_FILE_PATH_FIELDNAME] = data.data.pop("source")
+
+ return self.rollup_data(file_list, processed_data)
diff --git a/langflow/src/backend/base/langflow/components/vectara/__init__.py b/langflow/src/backend/base/langflow/components/vectara/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/__init__.py b/langflow/src/backend/base/langflow/components/vectorstores/__init__.py
new file mode 100644
index 0000000..6fccd25
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/__init__.py
@@ -0,0 +1,49 @@
+from .astradb import AstraDBVectorStoreComponent
+from .astradb_graph import AstraDBGraphVectorStoreComponent
+from .cassandra import CassandraVectorStoreComponent
+from .cassandra_graph import CassandraGraphVectorStoreComponent
+from .chroma import ChromaVectorStoreComponent
+from .clickhouse import ClickhouseVectorStoreComponent
+from .couchbase import CouchbaseVectorStoreComponent
+from .elasticsearch import ElasticsearchVectorStoreComponent
+from .faiss import FaissVectorStoreComponent
+from .hcd import HCDVectorStoreComponent
+from .milvus import MilvusVectorStoreComponent
+from .mongodb_atlas import MongoVectorStoreComponent
+from .opensearch import OpenSearchVectorStoreComponent
+from .pgvector import PGVectorStoreComponent
+from .pinecone import PineconeVectorStoreComponent
+from .qdrant import QdrantVectorStoreComponent
+from .redis import RedisVectorStoreComponent
+from .supabase import SupabaseVectorStoreComponent
+from .upstash import UpstashVectorStoreComponent
+from .vectara import VectaraVectorStoreComponent
+from .vectara_rag import VectaraRagComponent
+from .vectara_self_query import VectaraSelfQueryRetriverComponent
+from .weaviate import WeaviateVectorStoreComponent
+
+__all__ = [
+ "AstraDBGraphVectorStoreComponent",
+ "AstraDBVectorStoreComponent",
+ "CassandraGraphVectorStoreComponent",
+ "CassandraVectorStoreComponent",
+ "ChromaVectorStoreComponent",
+ "ClickhouseVectorStoreComponent",
+ "CouchbaseVectorStoreComponent",
+ "ElasticsearchVectorStoreComponent",
+ "FaissVectorStoreComponent",
+ "HCDVectorStoreComponent",
+ "MilvusVectorStoreComponent",
+ "MongoVectorStoreComponent",
+ "OpenSearchVectorStoreComponent",
+ "PGVectorStoreComponent",
+ "PineconeVectorStoreComponent",
+ "QdrantVectorStoreComponent",
+ "RedisVectorStoreComponent",
+ "SupabaseVectorStoreComponent",
+ "UpstashVectorStoreComponent",
+ "VectaraRagComponent",
+ "VectaraSelfQueryRetriverComponent",
+ "VectaraVectorStoreComponent",
+ "WeaviateVectorStoreComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/astradb.py b/langflow/src/backend/base/langflow/components/vectorstores/astradb.py
new file mode 100644
index 0000000..7f47b8a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/astradb.py
@@ -0,0 +1,1040 @@
+from collections import defaultdict
+from dataclasses import asdict, dataclass, field
+
+from astrapy import AstraDBAdmin, DataAPIClient, Database
+from astrapy.info import CollectionDescriptor
+from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers import docs_to_data
+from langflow.inputs import FloatInput, NestedDictInput
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+from langflow.utils.version import get_version_info
+
+
+class AstraDBVectorStoreComponent(LCVectorStoreComponent):
+ display_name: str = "Astra DB"
+ description: str = "Ingest and search documents in Astra DB"
+ documentation: str = "https://docs.datastax.com/en/langflow/astra-components.html"
+ name = "AstraDB"
+ icon: str = "AstraDB"
+
+ _cached_vector_store: AstraDBVectorStore | None = None
+
+ @dataclass
+ class NewDatabaseInput:
+ functionality: str = "create"
+ fields: dict[str, dict] = field(
+ default_factory=lambda: {
+ "data": {
+ "node": {
+ "name": "create_database",
+ "description": "Please allow several minutes for creation to complete.",
+ "display_name": "Create new database",
+ "field_order": ["01_new_database_name", "02_cloud_provider", "03_region"],
+ "template": {
+ "01_new_database_name": StrInput(
+ name="new_database_name",
+ display_name="Name",
+ info="Name of the new database to create in Astra DB.",
+ required=True,
+ ),
+ "02_cloud_provider": DropdownInput(
+ name="cloud_provider",
+ display_name="Cloud provider",
+ info="Cloud provider for the new database.",
+ options=[],
+ required=True,
+ real_time_refresh=True,
+ ),
+ "03_region": DropdownInput(
+ name="region",
+ display_name="Region",
+ info="Region for the new database.",
+ options=[],
+ required=True,
+ ),
+ },
+ },
+ }
+ }
+ )
+
+ @dataclass
+ class NewCollectionInput:
+ functionality: str = "create"
+ fields: dict[str, dict] = field(
+ default_factory=lambda: {
+ "data": {
+ "node": {
+ "name": "create_collection",
+ "description": "Please allow several seconds for creation to complete.",
+ "display_name": "Create new collection",
+ "field_order": [
+ "01_new_collection_name",
+ "02_embedding_generation_provider",
+ "03_embedding_generation_model",
+ "04_dimension",
+ ],
+ "template": {
+ "01_new_collection_name": StrInput(
+ name="new_collection_name",
+ display_name="Name",
+ info="Name of the new collection to create in Astra DB.",
+ required=True,
+ ),
+ "02_embedding_generation_provider": DropdownInput(
+ name="embedding_generation_provider",
+ display_name="Embedding generation method",
+ info="Provider to use for generating embeddings.",
+ real_time_refresh=True,
+ required=True,
+ options=[],
+ ),
+ "03_embedding_generation_model": DropdownInput(
+ name="embedding_generation_model",
+ display_name="Embedding model",
+ info="Model to use for generating embeddings.",
+ required=True,
+ options=[],
+ ),
+ "04_dimension": IntInput(
+ name="dimension",
+ display_name="Dimensions (Required only for `Bring your own`)",
+ info="Dimensions of the embeddings to generate.",
+ required=False,
+ value=1024,
+ ),
+ },
+ },
+ }
+ }
+ )
+
+ inputs = [
+ SecretStrInput(
+ name="token",
+ display_name="Astra DB Application Token",
+ info="Authentication token for accessing Astra DB.",
+ value="ASTRA_DB_APPLICATION_TOKEN",
+ required=True,
+ real_time_refresh=True,
+ input_types=[],
+ ),
+ StrInput(
+ name="environment",
+ display_name="Environment",
+ info="The environment for the Astra DB API Endpoint.",
+ advanced=True,
+ real_time_refresh=True,
+ ),
+ DropdownInput(
+ name="database_name",
+ display_name="Database",
+ info="The Database name for the Astra DB instance.",
+ required=True,
+ refresh_button=True,
+ real_time_refresh=True,
+ dialog_inputs=asdict(NewDatabaseInput()),
+ combobox=True,
+ ),
+ StrInput(
+ name="api_endpoint",
+ display_name="Astra DB API Endpoint",
+ info="The API Endpoint for the Astra DB instance. Supercedes database selection.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="collection_name",
+ display_name="Collection",
+ info="The name of the collection within Astra DB where the vectors will be stored.",
+ required=True,
+ refresh_button=True,
+ real_time_refresh=True,
+ dialog_inputs=asdict(NewCollectionInput()),
+ combobox=True,
+ advanced=True,
+ ),
+ StrInput(
+ name="keyspace",
+ display_name="Keyspace",
+ info="Optional keyspace within Astra DB to use for the collection.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="embedding_choice",
+ display_name="Embedding Model or Astra Vectorize",
+ info="Choose an embedding model or use Astra Vectorize.",
+ options=["Embedding Model", "Astra Vectorize"],
+ value="Embedding Model",
+ advanced=True,
+ real_time_refresh=True,
+ ),
+ HandleInput(
+ name="embedding_model",
+ display_name="Embedding Model",
+ input_types=["Embeddings"],
+ info="Specify the Embedding Model. Not required for Astra Vectorize collections.",
+ required=False,
+ ),
+ *LCVectorStoreComponent.inputs,
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Search Results",
+ info="Number of search results to return.",
+ advanced=True,
+ value=4,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ info="Search type to use",
+ options=["Similarity", "Similarity with score threshold", "MMR (Max Marginal Relevance)"],
+ value="Similarity",
+ advanced=True,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results. "
+ "(when using 'Similarity with score threshold')",
+ value=0,
+ advanced=True,
+ ),
+ NestedDictInput(
+ name="advanced_search_filter",
+ display_name="Search Metadata Filter",
+ info="Optional dictionary of filters to apply to the search query.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="autodetect_collection",
+ display_name="Autodetect Collection",
+ info="Boolean flag to determine whether to autodetect the collection.",
+ advanced=True,
+ value=True,
+ ),
+ StrInput(
+ name="content_field",
+ display_name="Content Field",
+ info="Field to use as the text content field for the vector store.",
+ advanced=True,
+ ),
+ StrInput(
+ name="deletion_field",
+ display_name="Deletion Based On Field",
+ info="When this parameter is provided, documents in the target collection with "
+ "metadata field values matching the input metadata field value will be deleted "
+ "before new data is loaded.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="ignore_invalid_documents",
+ display_name="Ignore Invalid Documents",
+ info="Boolean flag to determine whether to ignore invalid documents at runtime.",
+ advanced=True,
+ ),
+ NestedDictInput(
+ name="astradb_vectorstore_kwargs",
+ display_name="AstraDBVectorStore Parameters",
+ info="Optional dictionary of additional parameters for the AstraDBVectorStore.",
+ advanced=True,
+ ),
+ ]
+
+ @classmethod
+ def map_cloud_providers(cls):
+ # TODO: Programmatically fetch the regions for each cloud provider
+ return {
+ "dev": {
+ "Google Cloud Platform": {
+ "id": "gcp",
+ "regions": ["us-central1"],
+ },
+ },
+ # TODO: Check test regions
+ "test": {
+ "Google Cloud Platform": {
+ "id": "gcp",
+ "regions": ["us-central1"],
+ },
+ },
+ "prod": {
+ "Amazon Web Services": {
+ "id": "aws",
+ "regions": ["us-east-2", "ap-south-1", "eu-west-1"],
+ },
+ "Google Cloud Platform": {
+ "id": "gcp",
+ "regions": ["us-east1"],
+ },
+ "Microsoft Azure": {
+ "id": "azure",
+ "regions": ["westus3"],
+ },
+ },
+ }
+
+ @classmethod
+ def get_vectorize_providers(cls, token: str, environment: str | None = None, api_endpoint: str | None = None):
+ try:
+ # Get the admin object
+ admin = AstraDBAdmin(token=token, environment=environment)
+ db_admin = admin.get_database_admin(api_endpoint=api_endpoint)
+
+ # Get the list of embedding providers
+ embedding_providers = db_admin.find_embedding_providers().as_dict()
+
+ vectorize_providers_mapping = {}
+ # Map the provider display name to the provider key and models
+ for provider_key, provider_data in embedding_providers["embeddingProviders"].items():
+ # Get the provider display name and models
+ display_name = provider_data["displayName"]
+ models = [model["name"] for model in provider_data["models"]]
+
+ # Build our mapping
+ vectorize_providers_mapping[display_name] = [provider_key, models]
+
+ # Sort the resulting dictionary
+ return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))
+ except Exception as e:
+ msg = f"Error fetching vectorize providers: {e}"
+ raise ValueError(msg) from e
+
+ @classmethod
+ async def create_database_api(
+ cls,
+ new_database_name: str,
+ cloud_provider: str,
+ region: str,
+ token: str,
+ environment: str | None = None,
+ keyspace: str | None = None,
+ ):
+ client = DataAPIClient(token=token, environment=environment)
+
+ # Get the admin object
+ admin_client = client.get_admin(token=token)
+
+ # Get the environment, set to prod if null like
+ my_env = environment or "prod"
+
+ # Call the create database function
+ return await admin_client.async_create_database(
+ name=new_database_name,
+ cloud_provider=cls.map_cloud_providers()[my_env][cloud_provider]["id"],
+ region=region,
+ keyspace=keyspace,
+ wait_until_active=False,
+ )
+
+ @classmethod
+ async def create_collection_api(
+ cls,
+ new_collection_name: str,
+ token: str,
+ api_endpoint: str,
+ environment: str | None = None,
+ keyspace: str | None = None,
+ dimension: int | None = None,
+ embedding_generation_provider: str | None = None,
+ embedding_generation_model: str | None = None,
+ ):
+ # Create the data API client
+ client = DataAPIClient(token=token, environment=environment)
+
+ # Get the database object
+ database = client.get_async_database(api_endpoint=api_endpoint, token=token)
+
+ # Build vectorize options, if needed
+ vectorize_options = None
+ if not dimension:
+ vectorize_options = CollectionVectorServiceOptions(
+ provider=cls.get_vectorize_providers(
+ token=token, environment=environment, api_endpoint=api_endpoint
+ ).get(embedding_generation_provider, [None, []])[0],
+ model_name=embedding_generation_model,
+ )
+
+ # Create the collection
+ return await database.create_collection(
+ name=new_collection_name,
+ keyspace=keyspace,
+ dimension=dimension,
+ service=vectorize_options,
+ )
+
+ @classmethod
+ def get_database_list_static(cls, token: str, environment: str | None = None):
+ client = DataAPIClient(token=token, environment=environment)
+
+ # Get the admin object
+ admin_client = client.get_admin(token=token)
+
+ # Get the list of databases
+ db_list = list(admin_client.list_databases())
+
+ # Set the environment properly
+ env_string = ""
+ if environment and environment != "prod":
+ env_string = f"-{environment}"
+
+ # Generate the api endpoint for each database
+ db_info_dict = {}
+ for db in db_list:
+ try:
+ # Get the API endpoint for the database
+ api_endpoint = f"https://{db.info.id}-{db.info.region}.apps.astra{env_string}.datastax.com"
+
+ # Get the number of collections
+ try:
+ num_collections = len(
+ list(
+ client.get_database(
+ api_endpoint=api_endpoint, token=token, keyspace=db.info.keyspace
+ ).list_collection_names(keyspace=db.info.keyspace)
+ )
+ )
+ except Exception: # noqa: BLE001
+ num_collections = 0
+ if db.status != "PENDING":
+ continue
+
+ # Add the database to the dictionary
+ db_info_dict[db.info.name] = {
+ "api_endpoint": api_endpoint,
+ "collections": num_collections,
+ "status": db.status if db.status != "ACTIVE" else None,
+ }
+ except Exception: # noqa: BLE001, S110
+ pass
+
+ return db_info_dict
+
+ def get_database_list(self):
+ return self.get_database_list_static(token=self.token, environment=self.environment)
+
+ @classmethod
+ def get_api_endpoint_static(
+ cls,
+ token: str,
+ environment: str | None = None,
+ api_endpoint: str | None = None,
+ database_name: str | None = None,
+ ):
+ # If the api_endpoint is set, return it
+ if api_endpoint:
+ return api_endpoint
+
+ # Check if the database_name is like a url
+ if database_name and database_name.startswith("https://"):
+ return database_name
+
+ # If the database is not set, nothing we can do.
+ if not database_name:
+ return None
+
+ # Grab the database object
+ db = cls.get_database_list_static(token=token, environment=environment).get(database_name)
+ if not db:
+ return None
+
+ # Otherwise, get the URL from the database list
+ return db.get("api_endpoint")
+
+ def get_api_endpoint(self):
+ return self.get_api_endpoint_static(
+ token=self.token,
+ environment=self.environment,
+ api_endpoint=self.api_endpoint,
+ database_name=self.database_name,
+ )
+
+ def get_keyspace(self):
+ keyspace = self.keyspace
+
+ if keyspace:
+ return keyspace.strip()
+
+ return None
+
+ def get_database_object(self, api_endpoint: str | None = None):
+ try:
+ client = DataAPIClient(token=self.token, environment=self.environment)
+
+ return client.get_database(
+ api_endpoint=api_endpoint or self.get_api_endpoint(),
+ token=self.token,
+ keyspace=self.get_keyspace(),
+ )
+ except Exception as e:
+ msg = f"Error fetching database object: {e}"
+ raise ValueError(msg) from e
+
+ def collection_data(self, collection_name: str, database: Database | None = None):
+ try:
+ if not database:
+ client = DataAPIClient(token=self.token, environment=self.environment)
+
+ database = client.get_database(
+ api_endpoint=self.get_api_endpoint(),
+ token=self.token,
+ keyspace=self.get_keyspace(),
+ )
+
+ collection = database.get_collection(collection_name, keyspace=self.get_keyspace())
+
+ return collection.estimated_document_count()
+ except Exception as e: # noqa: BLE001
+ self.log(f"Error checking collection data: {e}")
+
+ return None
+
+ def _initialize_database_options(self):
+ try:
+ return [
+ {
+ "name": name,
+ "status": info["status"],
+ "collections": info["collections"],
+ "api_endpoint": info["api_endpoint"],
+ "icon": "data",
+ }
+ for name, info in self.get_database_list().items()
+ ]
+ except Exception as e:
+ msg = f"Error fetching database options: {e}"
+ raise ValueError(msg) from e
+
+ @classmethod
+ def get_provider_icon(cls, collection: CollectionDescriptor | None = None, provider_name: str | None = None) -> str:
+ # Get the provider name from the collection
+ provider_name = provider_name or (
+ collection.options.vector.service.provider
+ if collection and collection.options and collection.options.vector and collection.options.vector.service
+ else None
+ )
+
+ # If there is no provider, use the vector store icon
+ if not provider_name or provider_name == "Bring your own":
+ return "vectorstores"
+
+ # Map provider casings
+ case_map = {
+ "nvidia": "NVIDIA",
+ "openai": "OpenAI",
+ "amazon bedrock": "AmazonBedrockEmbeddings",
+ "azure openai": "AzureOpenAiEmbeddings",
+ "cohere": "Cohere",
+ "jina ai": "JinaAI",
+ "mistral ai": "MistralAI",
+ "upstage": "Upstage",
+ "voyage ai": "VoyageAI",
+ }
+
+ # Adjust the casing on some like nvidia
+ return case_map[provider_name.lower()] if provider_name.lower() in case_map else provider_name.title()
+
+ def _initialize_collection_options(self, api_endpoint: str | None = None):
+ # Nothing to generate if we don't have an API endpoint yet
+ api_endpoint = api_endpoint or self.get_api_endpoint()
+ if not api_endpoint:
+ return []
+
+ # Retrieve the database object
+ database = self.get_database_object(api_endpoint=api_endpoint)
+
+ # Get the list of collections
+ collection_list = list(database.list_collections(keyspace=self.get_keyspace()))
+
+ # Return the list of collections and metadata associated
+ return [
+ {
+ "name": col.name,
+ "records": self.collection_data(collection_name=col.name, database=database),
+ "provider": (
+ col.options.vector.service.provider if col.options.vector and col.options.vector.service else None
+ ),
+ "icon": self.get_provider_icon(collection=col),
+ "model": (
+ col.options.vector.service.model_name if col.options.vector and col.options.vector.service else None
+ ),
+ }
+ for col in collection_list
+ ]
+
+ def reset_provider_options(self, build_config: dict):
+ # Get the list of vectorize providers
+ vectorize_providers = self.get_vectorize_providers(
+ token=self.token,
+ environment=self.environment,
+ api_endpoint=build_config["api_endpoint"]["value"],
+ )
+
+ # Append a special case for Bring your own
+ vectorize_providers["Bring your own"] = [None, ["Bring your own"]]
+
+ # If the collection is set, allow user to see embedding options
+ build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
+ "02_embedding_generation_provider"
+ ]["options"] = [
+ "Bring your own",
+ "Nvidia",
+ *[key for key in vectorize_providers if key not in ["Bring your own", "Nvidia"]],
+ ]
+
+ # For all not Bring your own or Nvidia providers, add metadata saying configure in Astra DB Portal
+ provider_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
+ "02_embedding_generation_provider"
+ ]["options"]
+
+ # Go over each possible provider and add metadata to configure in Astra DB Portal
+ for provider in provider_options:
+ # Add the icon for the provider
+ my_metadata = {"icon": self.get_provider_icon(provider_name=provider)}
+
+ # Skip Bring your own and Nvidia, automatically configured
+ if provider not in {"Bring your own", "Nvidia"}:
+ # Add metadata to configure in Astra DB Portal
+ my_metadata[" "] = "Configure in Astra DB Portal"
+
+ # Add the metadata to the options metadata
+ build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
+ "02_embedding_generation_provider"
+ ]["options_metadata"].append(my_metadata)
+
+ # And allow the user to see the models based on a selected provider
+ embedding_provider = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
+ "02_embedding_generation_provider"
+ ]["value"]
+
+ # Set the options for the embedding model based on the provider
+ build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
+ "03_embedding_generation_model"
+ ]["options"] = vectorize_providers.get(embedding_provider, [[], []])[1]
+
+ return build_config
+
+ def reset_collection_list(self, build_config: dict):
+ # Get the list of options we have based on the token provided
+ collection_options = self._initialize_collection_options(api_endpoint=build_config["api_endpoint"]["value"])
+
+ # If we retrieved options based on the token, show the dropdown
+ build_config["collection_name"]["options"] = [col["name"] for col in collection_options]
+ build_config["collection_name"]["options_metadata"] = [
+ {k: v for k, v in col.items() if k != "name"} for col in collection_options
+ ]
+
+ # Reset the selected collection
+ if build_config["collection_name"]["value"] not in build_config["collection_name"]["options"]:
+ build_config["collection_name"]["value"] = ""
+
+ # If we have a database, collection name should not be advanced
+ build_config["collection_name"]["advanced"] = not build_config["database_name"]["value"]
+
+ return build_config
+
+ def reset_database_list(self, build_config: dict):
+ # Get the list of options we have based on the token provided
+ database_options = self._initialize_database_options()
+
+ # Update the list of cloud providers
+ my_env = self.environment or "prod"
+ build_config["database_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["02_cloud_provider"][
+ "options"
+ ] = list(self.map_cloud_providers()[my_env].keys())
+
+ # If we retrieved options based on the token, show the dropdown
+ build_config["database_name"]["options"] = [db["name"] for db in database_options]
+ build_config["database_name"]["options_metadata"] = [
+ {k: v for k, v in db.items() if k != "name"} for db in database_options
+ ]
+
+ # Reset the selected database
+ if build_config["database_name"]["value"] not in build_config["database_name"]["options"]:
+ build_config["database_name"]["value"] = ""
+ build_config["api_endpoint"]["value"] = ""
+ build_config["collection_name"]["advanced"] = True
+
+ # If we have a token, database name should not be advanced
+ build_config["database_name"]["advanced"] = not build_config["token"]["value"]
+
+ return build_config
+
+ def reset_build_config(self, build_config: dict):
+ # Reset the list of databases we have based on the token provided
+ build_config["database_name"]["options"] = []
+ build_config["database_name"]["options_metadata"] = []
+ build_config["database_name"]["value"] = ""
+ build_config["database_name"]["advanced"] = True
+ build_config["api_endpoint"]["value"] = ""
+
+ # Reset the list of collections and metadata associated
+ build_config["collection_name"]["options"] = []
+ build_config["collection_name"]["options_metadata"] = []
+ build_config["collection_name"]["value"] = ""
+ build_config["collection_name"]["advanced"] = True
+
+ return build_config
+
+ async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
+ # Callback for database creation
+ if field_name == "database_name" and isinstance(field_value, dict) and "01_new_database_name" in field_value:
+ try:
+ await self.create_database_api(
+ new_database_name=field_value["01_new_database_name"],
+ token=self.token,
+ keyspace=self.get_keyspace(),
+ environment=self.environment,
+ cloud_provider=field_value["02_cloud_provider"],
+ region=field_value["03_region"],
+ )
+ except Exception as e:
+ msg = f"Error creating database: {e}"
+ raise ValueError(msg) from e
+
+ # Add the new database to the list of options
+ build_config["database_name"]["options"] += [field_value["01_new_database_name"]]
+ build_config["database_name"]["options_metadata"] += [{"status": "PENDING"}]
+
+ return self.reset_collection_list(build_config)
+
+ # This is the callback required to update the list of regions for a cloud provider
+ if (
+ field_name == "database_name"
+ and isinstance(field_value, dict)
+ and "01_new_database_name" not in field_value
+ ):
+ # Get the cloud provider and environment
+ cloud_provider = field_value["02_cloud_provider"]
+ my_env = self.environment or "prod"
+
+ # Update the list of regions for the cloud provider
+ build_config["database_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["03_region"][
+ "options"
+ ] = self.map_cloud_providers()[my_env][cloud_provider]["regions"]
+
+ return build_config
+
+ # Callback for the creation of collections
+ if (
+ field_name == "collection_name"
+ and isinstance(field_value, dict)
+ and "01_new_collection_name" in field_value
+ ):
+ try:
+ # Get the dimension if its a BYO provider
+ dimension = (
+ field_value["04_dimension"]
+ if field_value["02_embedding_generation_provider"] == "Bring your own"
+ else None
+ )
+
+ # Create the collection
+ await self.create_collection_api(
+ new_collection_name=field_value["01_new_collection_name"],
+ token=self.token,
+ api_endpoint=build_config["api_endpoint"]["value"],
+ environment=self.environment,
+ keyspace=self.get_keyspace(),
+ dimension=dimension,
+ embedding_generation_provider=field_value["02_embedding_generation_provider"],
+ embedding_generation_model=field_value["03_embedding_generation_model"],
+ )
+ except Exception as e:
+ msg = f"Error creating collection: {e}"
+ raise ValueError(msg) from e
+
+ # Add the new collection to the list of options
+ build_config["collection_name"]["value"] = field_value["01_new_collection_name"]
+ build_config["collection_name"]["options"].append(field_value["01_new_collection_name"])
+
+ # Get the provider and model for the new collection
+ generation_provider = field_value["02_embedding_generation_provider"]
+ provider = (
+ generation_provider.lower() if generation_provider and generation_provider != "Bring your own" else None
+ )
+ generation_model = field_value["03_embedding_generation_model"]
+ model = generation_model if generation_model and generation_model != "Bring your own" else None
+
+ # Set the embedding choice
+ build_config["embedding_choice"]["value"] = "Astra Vectorize" if provider else "Embedding Model"
+ build_config["embedding_model"]["advanced"] = bool(provider)
+
+ # Add the new collection to the list of options
+ icon = self.get_provider_icon(provider_name=generation_provider)
+ build_config["collection_name"]["options_metadata"] += [
+ {"records": 0, "provider": provider, "icon": icon, "model": model}
+ ]
+
+ return build_config
+
+ # Callback to update the model list based on the embedding provider
+ if (
+ field_name == "collection_name"
+ and isinstance(field_value, dict)
+ and "01_new_collection_name" not in field_value
+ ):
+ return self.reset_provider_options(build_config)
+
+ # When the component first executes, this is the update refresh call
+ first_run = field_name == "collection_name" and not field_value and not build_config["database_name"]["options"]
+
+ # If the token has not been provided, simply return the empty build config
+ if not self.token:
+ return self.reset_build_config(build_config)
+
+ # If this is the first execution of the component, reset and build database list
+ if first_run or field_name in {"token", "environment"}:
+ return self.reset_database_list(build_config)
+
+ # Refresh the collection name options
+ if field_name == "database_name" and not isinstance(field_value, dict):
+ # If missing, refresh the database options
+ if field_value not in build_config["database_name"]["options"]:
+ build_config = await self.update_build_config(build_config, field_value=self.token, field_name="token")
+ build_config["database_name"]["value"] = ""
+ else:
+ # Find the position of the selected database to align with metadata
+ index_of_name = build_config["database_name"]["options"].index(field_value)
+
+ # Initializing database condition
+ pending = build_config["database_name"]["options_metadata"][index_of_name]["status"] == "PENDING"
+ if pending:
+ return self.update_build_config(build_config, field_value=self.token, field_name="token")
+
+ # Set the API endpoint based on the selected database
+ build_config["api_endpoint"]["value"] = build_config["database_name"]["options_metadata"][
+ index_of_name
+ ]["api_endpoint"]
+
+ # Reset the provider options
+ build_config = self.reset_provider_options(build_config)
+
+ # Reset the list of collections we have based on the token provided
+ return self.reset_collection_list(build_config)
+
+ # Hide embedding model option if opriona_metadata provider is not null
+ if field_name == "collection_name" and not isinstance(field_value, dict):
+ # Assume we will be autodetecting the collection:
+ build_config["autodetect_collection"]["value"] = True
+
+ # Reload the collection list
+ build_config = self.reset_collection_list(build_config)
+
+ # Set the options for collection name to be the field value if its a new collection
+ if field_value and field_value not in build_config["collection_name"]["options"]:
+ # Add the new collection to the list of options
+ build_config["collection_name"]["options"].append(field_value)
+ build_config["collection_name"]["options_metadata"].append(
+ {
+ "records": 0,
+ "provider": None,
+ "icon": "vectorstores",
+ "model": None,
+ }
+ )
+
+ # Ensure that autodetect collection is set to False, since its a new collection
+ build_config["autodetect_collection"]["value"] = False
+
+ # If nothing is selected, can't detect provider - return
+ if not field_value:
+ return build_config
+
+ # Find the position of the selected collection to align with metadata
+ index_of_name = build_config["collection_name"]["options"].index(field_value)
+ value_of_provider = build_config["collection_name"]["options_metadata"][index_of_name]["provider"]
+
+ # If we were able to determine the Vectorize provider, set it accordingly
+ build_config["embedding_model"]["advanced"] = bool(value_of_provider)
+ build_config["embedding_choice"]["value"] = "Astra Vectorize" if value_of_provider else "Embedding Model"
+
+ return build_config
+
+ return build_config
+
+ @check_cached_vector_store
+ def build_vector_store(self):
+ try:
+ from langchain_astradb import AstraDBVectorStore
+ except ImportError as e:
+ msg = (
+ "Could not import langchain Astra DB integration package. "
+ "Please install it with `pip install langchain-astradb`."
+ )
+ raise ImportError(msg) from e
+
+ # Get the embedding model and additional params
+ embedding_params = (
+ {"embedding": self.embedding_model}
+ if self.embedding_model and self.embedding_choice == "Embedding Model"
+ else {}
+ )
+
+ # Get the additional parameters
+ additional_params = self.astradb_vectorstore_kwargs or {}
+
+ # Get Langflow version and platform information
+ __version__ = get_version_info()["version"]
+ langflow_prefix = ""
+ # if os.getenv("AWS_EXECUTION_ENV") == "AWS_ECS_FARGATE": # TODO: More precise way of detecting
+ # langflow_prefix = "ds-"
+
+ # Get the database object
+ database = self.get_database_object()
+ autodetect = self.collection_name in database.list_collection_names() and self.autodetect_collection
+
+ # Bundle up the auto-detect parameters
+ autodetect_params = {
+ "autodetect_collection": autodetect,
+ "content_field": (
+ self.content_field
+ if self.content_field and embedding_params
+ else (
+ "page_content"
+ if embedding_params
+ and self.collection_data(collection_name=self.collection_name, database=database) == 0
+ else None
+ )
+ ),
+ "ignore_invalid_documents": self.ignore_invalid_documents,
+ }
+
+ # Attempt to build the Vector Store object
+ try:
+ vector_store = AstraDBVectorStore(
+ # Astra DB Authentication Parameters
+ token=self.token,
+ api_endpoint=database.api_endpoint,
+ namespace=database.keyspace,
+ collection_name=self.collection_name,
+ environment=self.environment,
+ # Astra DB Usage Tracking Parameters
+ ext_callers=[(f"{langflow_prefix}langflow", __version__)],
+ # Astra DB Vector Store Parameters
+ **autodetect_params,
+ **embedding_params,
+ **additional_params,
+ )
+ except Exception as e:
+ msg = f"Error initializing AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ # Add documents to the vector store
+ self._add_documents_to_vector_store(vector_store)
+
+ return vector_store
+
+ def _add_documents_to_vector_store(self, vector_store) -> None:
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ msg = "Vector Store Inputs must be Data objects."
+ raise TypeError(msg)
+
+ if documents and self.deletion_field:
+ self.log(f"Deleting documents where {self.deletion_field}")
+ try:
+ database = self.get_database_object()
+ collection = database.get_collection(self.collection_name, keyspace=database.keyspace)
+ delete_values = list({doc.metadata[self.deletion_field] for doc in documents})
+ self.log(f"Deleting documents where {self.deletion_field} matches {delete_values}.")
+ collection.delete_many({f"metadata.{self.deletion_field}": {"$in": delete_values}})
+ except Exception as e:
+ msg = f"Error deleting documents from AstraDBVectorStore based on '{self.deletion_field}': {e}"
+ raise ValueError(msg) from e
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ try:
+ vector_store.add_documents(documents)
+ except Exception as e:
+ msg = f"Error adding documents to AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+ else:
+ self.log("No documents to add to the Vector Store.")
+
+ def _map_search_type(self) -> str:
+ search_type_mapping = {
+ "Similarity with score threshold": "similarity_score_threshold",
+ "MMR (Max Marginal Relevance)": "mmr",
+ }
+
+ return search_type_mapping.get(self.search_type, "similarity")
+
+ def _build_search_args(self):
+ query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None
+
+ if query:
+ args = {
+ "query": query,
+ "search_type": self._map_search_type(),
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ }
+ elif self.advanced_search_filter:
+ args = {
+ "n": self.number_of_results,
+ }
+ else:
+ return {}
+
+ filter_arg = self.advanced_search_filter or {}
+ if filter_arg:
+ args["filter"] = filter_arg
+
+ return args
+
+ def search_documents(self, vector_store=None) -> list[Data]:
+ vector_store = vector_store or self.build_vector_store()
+
+ self.log(f"Search input: {self.search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ try:
+ search_args = self._build_search_args()
+ except Exception as e:
+ msg = f"Error in AstraDBVectorStore._build_search_args: {e}"
+ raise ValueError(msg) from e
+
+ if not search_args:
+ self.log("No search input or filters provided. Skipping search.")
+ return []
+
+ docs = []
+ search_method = "search" if "query" in search_args else "metadata_search"
+
+ try:
+ self.log(f"Calling vector_store.{search_method} with args: {search_args}")
+ docs = getattr(vector_store, search_method)(**search_args)
+ except Exception as e:
+ msg = f"Error performing {search_method} in AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ self.log(f"Retrieved documents: {len(docs)}")
+
+ data = docs_to_data(docs)
+ self.log(f"Converted documents to data: {len(data)}")
+ self.status = data
+
+ return data
+
+ def get_retriever_kwargs(self):
+ search_args = self._build_search_args()
+
+ return {
+ "search_type": self._map_search_type(),
+ "search_kwargs": search_args,
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/astradb_graph.py b/langflow/src/backend/base/langflow/components/vectorstores/astradb_graph.py
new file mode 100644
index 0000000..7f279a8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/astradb_graph.py
@@ -0,0 +1,317 @@
+import os
+
+import orjson
+from astrapy.admin import parse_api_endpoint
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers import docs_to_data
+from langflow.inputs import (
+ BoolInput,
+ DictInput,
+ DropdownInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class AstraDBGraphVectorStoreComponent(LCVectorStoreComponent):
+ display_name: str = "Astra DB Graph"
+ description: str = "Implementation of Graph Vector Store using Astra DB"
+ name = "AstraDBGraph"
+ icon: str = "AstraDB"
+
+ inputs = [
+ SecretStrInput(
+ name="token",
+ display_name="Astra DB Application Token",
+ info="Authentication token for accessing Astra DB.",
+ value="ASTRA_DB_APPLICATION_TOKEN",
+ required=True,
+ advanced=os.getenv("ASTRA_ENHANCED", "false").lower() == "true",
+ ),
+ SecretStrInput(
+ name="api_endpoint",
+ display_name="Database" if os.getenv("ASTRA_ENHANCED", "false").lower() == "true" else "API Endpoint",
+ info="API endpoint URL for the Astra DB service.",
+ value="ASTRA_DB_API_ENDPOINT",
+ required=True,
+ ),
+ StrInput(
+ name="collection_name",
+ display_name="Collection Name",
+ info="The name of the collection within Astra DB where the vectors will be stored.",
+ required=True,
+ ),
+ StrInput(
+ name="metadata_incoming_links_key",
+ display_name="Metadata incoming links key",
+ info="Metadata key used for incoming links.",
+ advanced=True,
+ ),
+ *LCVectorStoreComponent.inputs,
+ StrInput(
+ name="keyspace",
+ display_name="Keyspace",
+ info="Optional keyspace within Astra DB to use for the collection.",
+ advanced=True,
+ ),
+ HandleInput(
+ name="embedding_model",
+ display_name="Embedding Model",
+ input_types=["Embeddings"],
+ info="Allows an embedding model configuration.",
+ ),
+ DropdownInput(
+ name="metric",
+ display_name="Metric",
+ info="Optional distance metric for vector comparisons in the vector store.",
+ options=["cosine", "dot_product", "euclidean"],
+ value="cosine",
+ advanced=True,
+ ),
+ IntInput(
+ name="batch_size",
+ display_name="Batch Size",
+ info="Optional number of data to process in a single batch.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_insert_batch_concurrency",
+ display_name="Bulk Insert Batch Concurrency",
+ info="Optional concurrency level for bulk insert operations.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_insert_overwrite_concurrency",
+ display_name="Bulk Insert Overwrite Concurrency",
+ info="Optional concurrency level for bulk insert operations that overwrite existing data.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_delete_concurrency",
+ display_name="Bulk Delete Concurrency",
+ info="Optional concurrency level for bulk delete operations.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="setup_mode",
+ display_name="Setup Mode",
+ info="Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.",
+ options=["Sync", "Off"],
+ advanced=True,
+ value="Sync",
+ ),
+ BoolInput(
+ name="pre_delete_collection",
+ display_name="Pre Delete Collection",
+ info="Boolean flag to determine whether to delete the collection before creating a new one.",
+ advanced=True,
+ value=False,
+ ),
+ StrInput(
+ name="metadata_indexing_include",
+ display_name="Metadata Indexing Include",
+ info="Optional list of metadata fields to include in the indexing.",
+ advanced=True,
+ list=True,
+ ),
+ StrInput(
+ name="metadata_indexing_exclude",
+ display_name="Metadata Indexing Exclude",
+ info="Optional list of metadata fields to exclude from the indexing.",
+ advanced=True,
+ list=True,
+ ),
+ StrInput(
+ name="collection_indexing_policy",
+ display_name="Collection Indexing Policy",
+ info='Optional JSON string for the "indexing" field of the collection. '
+ "See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option",
+ advanced=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=4,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ info="Search type to use",
+ options=[
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ "Graph Traversal",
+ "MMR (Max Marginal Relevance) Graph Traversal",
+ ],
+ value="MMR (Max Marginal Relevance) Graph Traversal",
+ advanced=True,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results. "
+ "(when using 'Similarity with score threshold')",
+ value=0,
+ advanced=True,
+ ),
+ DictInput(
+ name="search_filter",
+ display_name="Search Metadata Filter",
+ info="Optional dictionary of filters to apply to the search query.",
+ advanced=True,
+ is_list=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self):
+ try:
+ from langchain_astradb import AstraDBGraphVectorStore
+ from langchain_astradb.utils.astradb import SetupMode
+ except ImportError as e:
+ msg = (
+ "Could not import langchain Astra DB integration package. "
+ "Please install it with `pip install langchain-astradb`."
+ )
+ raise ImportError(msg) from e
+
+ try:
+ if not self.setup_mode:
+ self.setup_mode = self._inputs["setup_mode"].options[0]
+
+ setup_mode_value = SetupMode[self.setup_mode.upper()]
+ except KeyError as e:
+ msg = f"Invalid setup mode: {self.setup_mode}"
+ raise ValueError(msg) from e
+
+ try:
+ self.log(f"Initializing Graph Vector Store {self.collection_name}")
+
+ vector_store = AstraDBGraphVectorStore(
+ embedding=self.embedding_model,
+ collection_name=self.collection_name,
+ metadata_incoming_links_key=self.metadata_incoming_links_key or "incoming_links",
+ token=self.token,
+ api_endpoint=self.api_endpoint,
+ namespace=self.keyspace or None,
+ environment=parse_api_endpoint(self.api_endpoint).environment if self.api_endpoint else None,
+ metric=self.metric or None,
+ batch_size=self.batch_size or None,
+ bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,
+ bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,
+ bulk_delete_concurrency=self.bulk_delete_concurrency or None,
+ setup_mode=setup_mode_value,
+ pre_delete_collection=self.pre_delete_collection,
+ metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,
+ metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,
+ collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode("utf-8"))
+ if self.collection_indexing_policy
+ else None,
+ )
+ except Exception as e:
+ msg = f"Error initializing AstraDBGraphVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ self.log(f"Vector Store initialized: {vector_store.astra_env.collection_name}")
+ self._add_documents_to_vector_store(vector_store)
+
+ return vector_store
+
+ def _add_documents_to_vector_store(self, vector_store) -> None:
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ msg = "Vector Store Inputs must be Data objects."
+ raise TypeError(msg)
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ try:
+ vector_store.add_documents(documents)
+ except Exception as e:
+ msg = f"Error adding documents to AstraDBGraphVectorStore: {e}"
+ raise ValueError(msg) from e
+ else:
+ self.log("No documents to add to the Vector Store.")
+
+ def _map_search_type(self) -> str:
+ match self.search_type:
+ case "Similarity":
+ return "similarity"
+ case "Similarity with score threshold":
+ return "similarity_score_threshold"
+ case "MMR (Max Marginal Relevance)":
+ return "mmr"
+ case "Graph Traversal":
+ return "traversal"
+ case "MMR (Max Marginal Relevance) Graph Traversal":
+ return "mmr_traversal"
+ case _:
+ return "similarity"
+
+ def _build_search_args(self):
+ args = {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ }
+
+ if self.search_filter:
+ clean_filter = {k: v for k, v in self.search_filter.items() if k and v}
+ if len(clean_filter) > 0:
+ args["filter"] = clean_filter
+ return args
+
+ def search_documents(self, vector_store=None) -> list[Data]:
+ if not vector_store:
+ vector_store = self.build_vector_store()
+
+ self.log("Searching for documents in AstraDBGraphVectorStore.")
+ self.log(f"Search query: {self.search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ try:
+ search_type = self._map_search_type()
+ search_args = self._build_search_args()
+
+ docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)
+
+ # Drop links from the metadata. At this point the links don't add any value for building the
+ # context and haven't been restored to json which causes the conversion to fail.
+ self.log("Removing links from metadata.")
+ for doc in docs:
+ if "links" in doc.metadata:
+ doc.metadata.pop("links")
+
+ except Exception as e:
+ msg = f"Error performing search in AstraDBGraphVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ self.log(f"Retrieved documents: {len(docs)}")
+
+ data = docs_to_data(docs)
+
+ self.log(f"Converted documents to data: {len(data)}")
+
+ self.status = data
+ return data
+ self.log("No search input provided. Skipping search.")
+ return []
+
+ def get_retriever_kwargs(self):
+ search_args = self._build_search_args()
+ return {
+ "search_type": self._map_search_type(),
+ "search_kwargs": search_args,
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/cassandra.py b/langflow/src/backend/base/langflow/components/vectorstores/cassandra.py
new file mode 100644
index 0000000..1f13abd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/cassandra.py
@@ -0,0 +1,261 @@
+from langchain_community.vectorstores import Cassandra
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.inputs import BoolInput, DictInput, FloatInput
+from langflow.io import (
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ MessageTextInput,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class CassandraVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Cassandra"
+ description = "Cassandra Vector Store with search capabilities"
+ documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/cassandra"
+ name = "Cassandra"
+ icon = "Cassandra"
+
+ inputs = [
+ MessageTextInput(
+ name="database_ref",
+ display_name="Contact Points / Astra Database ID",
+ info="Contact points for the database (or AstraDB database ID)",
+ required=True,
+ ),
+ MessageTextInput(
+ name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
+ ),
+ SecretStrInput(
+ name="token",
+ display_name="Password / AstraDB Token",
+ info="User password for the database (or AstraDB token).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="keyspace",
+ display_name="Keyspace",
+ info="Table Keyspace (or AstraDB namespace).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="table_name",
+ display_name="Table Name",
+ info="The name of the table (or AstraDB collection) where vectors will be stored.",
+ required=True,
+ ),
+ IntInput(
+ name="ttl_seconds",
+ display_name="TTL Seconds",
+ info="Optional time-to-live for the added texts.",
+ advanced=True,
+ ),
+ IntInput(
+ name="batch_size",
+ display_name="Batch Size",
+ info="Optional number of data to process in a single batch.",
+ value=16,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="setup_mode",
+ display_name="Setup Mode",
+ info="Configuration mode for setting up the Cassandra table, with options like 'Sync', 'Async', or 'Off'.",
+ options=["Sync", "Async", "Off"],
+ value="Sync",
+ advanced=True,
+ ),
+ DictInput(
+ name="cluster_kwargs",
+ display_name="Cluster arguments",
+ info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
+ advanced=True,
+ list=True,
+ ),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ info="Search type to use",
+ options=["Similarity", "Similarity with score threshold", "MMR (Max Marginal Relevance)"],
+ value="Similarity",
+ advanced=True,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results. "
+ "(when using 'Similarity with score threshold')",
+ value=0,
+ advanced=True,
+ ),
+ DictInput(
+ name="search_filter",
+ display_name="Search Metadata Filter",
+ info="Optional dictionary of filters to apply to the search query.",
+ advanced=True,
+ list=True,
+ ),
+ MessageTextInput(
+ name="body_search",
+ display_name="Search Body",
+ info="Document textual search terms to apply to the search query.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="enable_body_search",
+ display_name="Enable Body Search",
+ info="Flag to enable body search. This must be enabled BEFORE the table is created.",
+ value=False,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> Cassandra:
+ try:
+ import cassio
+ from langchain_community.utilities.cassandra import SetupMode
+ except ImportError as e:
+ msg = "Could not import cassio integration package. Please install it with `pip install cassio`."
+ raise ImportError(msg) from e
+
+ from uuid import UUID
+
+ database_ref = self.database_ref
+
+ try:
+ UUID(self.database_ref)
+ is_astra = True
+ except ValueError:
+ is_astra = False
+ if "," in self.database_ref:
+ # use a copy because we can't change the type of the parameter
+ database_ref = self.database_ref.split(",")
+
+ if is_astra:
+ cassio.init(
+ database_id=database_ref,
+ token=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+ else:
+ cassio.init(
+ contact_points=database_ref,
+ username=self.username,
+ password=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+ documents = []
+
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ body_index_options = [("index_analyzer", "STANDARD")] if self.enable_body_search else None
+
+ if self.setup_mode == "Off":
+ setup_mode = SetupMode.OFF
+ elif self.setup_mode == "Sync":
+ setup_mode = SetupMode.SYNC
+ else:
+ setup_mode = SetupMode.ASYNC
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ table = Cassandra.from_documents(
+ documents=documents,
+ embedding=self.embedding,
+ table_name=self.table_name,
+ keyspace=self.keyspace,
+ ttl_seconds=self.ttl_seconds or None,
+ batch_size=self.batch_size,
+ body_index_options=body_index_options,
+ )
+ else:
+ self.log("No documents to add to the Vector Store.")
+ table = Cassandra(
+ embedding=self.embedding,
+ table_name=self.table_name,
+ keyspace=self.keyspace,
+ ttl_seconds=self.ttl_seconds or None,
+ body_index_options=body_index_options,
+ setup_mode=setup_mode,
+ )
+ return table
+
+ def _map_search_type(self) -> str:
+ if self.search_type == "Similarity with score threshold":
+ return "similarity_score_threshold"
+ if self.search_type == "MMR (Max Marginal Relevance)":
+ return "mmr"
+ return "similarity"
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ self.log(f"Search input: {self.search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ try:
+ search_type = self._map_search_type()
+ search_args = self._build_search_args()
+
+ self.log(f"Search args: {search_args}")
+
+ docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)
+ except KeyError as e:
+ if "content" in str(e):
+ msg = (
+ "You should ingest data through Langflow (or LangChain) to query it in Langflow. "
+ "Your collection does not contain a field name 'content'."
+ )
+ raise ValueError(msg) from e
+ raise
+
+ self.log(f"Retrieved documents: {len(docs)}")
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
+
+ def _build_search_args(self):
+ args = {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ }
+
+ if self.search_filter:
+ clean_filter = {k: v for k, v in self.search_filter.items() if k and v}
+ if len(clean_filter) > 0:
+ args["filter"] = clean_filter
+ if self.body_search:
+ if not self.enable_body_search:
+ msg = "You should enable body search when creating the table to search the body field."
+ raise ValueError(msg)
+ args["body_search"] = self.body_search
+ return args
+
+ def get_retriever_kwargs(self):
+ search_args = self._build_search_args()
+ return {
+ "search_type": self._map_search_type(),
+ "search_kwargs": search_args,
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/cassandra_graph.py b/langflow/src/backend/base/langflow/components/vectorstores/cassandra_graph.py
new file mode 100644
index 0000000..1216a90
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/cassandra_graph.py
@@ -0,0 +1,235 @@
+from uuid import UUID
+
+from langchain_community.graph_vectorstores import CassandraGraphVectorStore
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.inputs import DictInput, FloatInput
+from langflow.io import (
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ MessageTextInput,
+ SecretStrInput,
+)
+from langflow.schema import Data
+
+
+class CassandraGraphVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Cassandra Graph"
+ description = "Cassandra Graph Vector Store"
+ name = "CassandraGraph"
+ icon = "Cassandra"
+
+ inputs = [
+ MessageTextInput(
+ name="database_ref",
+ display_name="Contact Points / Astra Database ID",
+ info="Contact points for the database (or AstraDB database ID)",
+ required=True,
+ ),
+ MessageTextInput(
+ name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
+ ),
+ SecretStrInput(
+ name="token",
+ display_name="Password / AstraDB Token",
+ info="User password for the database (or AstraDB token).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="keyspace",
+ display_name="Keyspace",
+ info="Table Keyspace (or AstraDB namespace).",
+ required=True,
+ ),
+ MessageTextInput(
+ name="table_name",
+ display_name="Table Name",
+ info="The name of the table (or AstraDB collection) where vectors will be stored.",
+ required=True,
+ ),
+ DropdownInput(
+ name="setup_mode",
+ display_name="Setup Mode",
+ info="Configuration mode for setting up the Cassandra table, with options like 'Sync' or 'Off'.",
+ options=["Sync", "Off"],
+ value="Sync",
+ advanced=True,
+ ),
+ DictInput(
+ name="cluster_kwargs",
+ display_name="Cluster arguments",
+ info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
+ advanced=True,
+ list=True,
+ ),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ info="Search type to use",
+ options=[
+ "Traversal",
+ "MMR traversal",
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ ],
+ value="Traversal",
+ advanced=True,
+ ),
+ IntInput(
+ name="depth",
+ display_name="Depth of traversal",
+ info="The maximum depth of edges to traverse. (when using 'Traversal' or 'MMR traversal')",
+ value=1,
+ advanced=True,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results. "
+ "(when using 'Similarity with score threshold')",
+ value=0,
+ advanced=True,
+ ),
+ DictInput(
+ name="search_filter",
+ display_name="Search Metadata Filter",
+ info="Optional dictionary of filters to apply to the search query.",
+ advanced=True,
+ list=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> CassandraGraphVectorStore:
+ try:
+ import cassio
+ from langchain_community.utilities.cassandra import SetupMode
+ except ImportError as e:
+ msg = "Could not import cassio integration package. Please install it with `pip install cassio`."
+ raise ImportError(msg) from e
+
+ database_ref = self.database_ref
+
+ try:
+ UUID(self.database_ref)
+ is_astra = True
+ except ValueError:
+ is_astra = False
+ if "," in self.database_ref:
+ # use a copy because we can't change the type of the parameter
+ database_ref = self.database_ref.split(",")
+
+ if is_astra:
+ cassio.init(
+ database_id=database_ref,
+ token=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+ else:
+ cassio.init(
+ contact_points=database_ref,
+ username=self.username,
+ password=self.token,
+ cluster_kwargs=self.cluster_kwargs,
+ )
+ documents = []
+
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ setup_mode = SetupMode.OFF if self.setup_mode == "Off" else SetupMode.SYNC
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ store = CassandraGraphVectorStore.from_documents(
+ documents=documents,
+ embedding=self.embedding,
+ node_table=self.table_name,
+ keyspace=self.keyspace,
+ )
+ else:
+ self.log("No documents to add to the Vector Store.")
+ store = CassandraGraphVectorStore(
+ embedding=self.embedding,
+ node_table=self.table_name,
+ keyspace=self.keyspace,
+ setup_mode=setup_mode,
+ )
+ return store
+
+ def _map_search_type(self) -> str:
+ if self.search_type == "Similarity":
+ return "similarity"
+ if self.search_type == "Similarity with score threshold":
+ return "similarity_score_threshold"
+ if self.search_type == "MMR (Max Marginal Relevance)":
+ return "mmr"
+ if self.search_type == "MMR Traversal":
+ return "mmr_traversal"
+ return "traversal"
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ self.log(f"Search input: {self.search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ try:
+ search_type = self._map_search_type()
+ search_args = self._build_search_args()
+
+ self.log(f"Search args: {search_args}")
+
+ docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)
+ except KeyError as e:
+ if "content" in str(e):
+ msg = (
+ "You should ingest data through Langflow (or LangChain) to query it in Langflow. "
+ "Your collection does not contain a field name 'content'."
+ )
+ raise ValueError(msg) from e
+ raise
+
+ self.log(f"Retrieved documents: {len(docs)}")
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
+
+ def _build_search_args(self):
+ args = {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ "depth": self.depth,
+ }
+
+ if self.search_filter:
+ clean_filter = {k: v for k, v in self.search_filter.items() if k and v}
+ if len(clean_filter) > 0:
+ args["filter"] = clean_filter
+ return args
+
+ def get_retriever_kwargs(self):
+ search_args = self._build_search_args()
+ return {
+ "search_type": self._map_search_type(),
+ "search_kwargs": search_args,
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/chroma.py b/langflow/src/backend/base/langflow/components/vectorstores/chroma.py
new file mode 100644
index 0000000..970b535
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/chroma.py
@@ -0,0 +1,151 @@
+from copy import deepcopy
+
+from chromadb.config import Settings
+from langchain_chroma import Chroma
+from typing_extensions import override
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.base.vectorstores.utils import chroma_collection_to_data
+from langflow.io import BoolInput, DropdownInput, HandleInput, IntInput, StrInput
+from langflow.schema import Data
+
+
+class ChromaVectorStoreComponent(LCVectorStoreComponent):
+ """Chroma Vector Store with search capabilities."""
+
+ display_name: str = "Chroma DB"
+ description: str = "Chroma Vector Store with search capabilities"
+ name = "Chroma"
+ icon = "Chroma"
+
+ inputs = [
+ StrInput(
+ name="collection_name",
+ display_name="Collection Name",
+ value="langflow",
+ ),
+ StrInput(
+ name="persist_directory",
+ display_name="Persist Directory",
+ ),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ StrInput(
+ name="chroma_server_cors_allow_origins",
+ display_name="Server CORS Allow Origins",
+ advanced=True,
+ ),
+ StrInput(
+ name="chroma_server_host",
+ display_name="Server Host",
+ advanced=True,
+ ),
+ IntInput(
+ name="chroma_server_http_port",
+ display_name="Server HTTP Port",
+ advanced=True,
+ ),
+ IntInput(
+ name="chroma_server_grpc_port",
+ display_name="Server gRPC Port",
+ advanced=True,
+ ),
+ BoolInput(
+ name="chroma_server_ssl_enabled",
+ display_name="Server SSL Enabled",
+ advanced=True,
+ ),
+ BoolInput(
+ name="allow_duplicates",
+ display_name="Allow Duplicates",
+ advanced=True,
+ info="If false, will not add documents that are already in the Vector Store.",
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ options=["Similarity", "MMR"],
+ value="Similarity",
+ advanced=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=10,
+ ),
+ IntInput(
+ name="limit",
+ display_name="Limit",
+ advanced=True,
+ info="Limit the number of records to compare when Allow Duplicates is False.",
+ ),
+ ]
+
+ @override
+ @check_cached_vector_store
+ def build_vector_store(self) -> Chroma:
+ """Builds the Chroma object."""
+ try:
+ from chromadb import Client
+ from langchain_chroma import Chroma
+ except ImportError as e:
+ msg = "Could not import Chroma integration package. Please install it with `pip install langchain-chroma`."
+ raise ImportError(msg) from e
+ # Chroma settings
+ chroma_settings = None
+ client = None
+ if self.chroma_server_host:
+ chroma_settings = Settings(
+ chroma_server_cors_allow_origins=self.chroma_server_cors_allow_origins or [],
+ chroma_server_host=self.chroma_server_host,
+ chroma_server_http_port=self.chroma_server_http_port or None,
+ chroma_server_grpc_port=self.chroma_server_grpc_port or None,
+ chroma_server_ssl_enabled=self.chroma_server_ssl_enabled,
+ )
+ client = Client(settings=chroma_settings)
+
+ # Check persist_directory and expand it if it is a relative path
+ persist_directory = self.resolve_path(self.persist_directory) if self.persist_directory is not None else None
+
+ chroma = Chroma(
+ persist_directory=persist_directory,
+ client=client,
+ embedding_function=self.embedding,
+ collection_name=self.collection_name,
+ )
+
+ self._add_documents_to_vector_store(chroma)
+ self.status = chroma_collection_to_data(chroma.get(limit=self.limit))
+ return chroma
+
+ def _add_documents_to_vector_store(self, vector_store: "Chroma") -> None:
+ """Adds documents to the Vector Store."""
+ if not self.ingest_data:
+ self.status = ""
+ return
+
+ stored_documents_without_id = []
+ if self.allow_duplicates:
+ stored_data = []
+ else:
+ stored_data = chroma_collection_to_data(vector_store.get(limit=self.limit))
+ for value in deepcopy(stored_data):
+ del value.id
+ stored_documents_without_id.append(value)
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ if _input not in stored_documents_without_id:
+ documents.append(_input.to_lc_document())
+ else:
+ msg = "Vector Store Inputs must be Data objects."
+ raise TypeError(msg)
+
+ if documents and self.embedding is not None:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ vector_store.add_documents(documents)
+ else:
+ self.log("No documents to add to the Vector Store.")
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/clickhouse.py b/langflow/src/backend/base/langflow/components/vectorstores/clickhouse.py
new file mode 100644
index 0000000..f528d19
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/clickhouse.py
@@ -0,0 +1,132 @@
+from langchain_community.vectorstores import Clickhouse, ClickhouseSettings
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.inputs import BoolInput, FloatInput
+from langflow.io import (
+ DictInput,
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class ClickhouseVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Clickhouse"
+ description = "Clickhouse Vector Store with search capabilities"
+ name = "Clickhouse"
+ icon = "Clickhouse"
+
+ inputs = [
+ StrInput(name="host", display_name="hostname", required=True, value="localhost"),
+ IntInput(name="port", display_name="port", required=True, value=8123),
+ StrInput(name="database", display_name="database", required=True),
+ StrInput(name="table", display_name="Table name", required=True),
+ StrInput(name="username", display_name="The ClickHouse user name.", required=True),
+ SecretStrInput(name="password", display_name="The password for username.", required=True),
+ DropdownInput(
+ name="index_type",
+ display_name="index_type",
+ options=["annoy", "vector_similarity"],
+ info="Type of the index.",
+ value="annoy",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="metric",
+ display_name="metric",
+ options=["angular", "euclidean", "manhattan", "hamming", "dot"],
+ info="Metric to compute distance.",
+ value="angular",
+ advanced=True,
+ ),
+ BoolInput(
+ name="secure",
+ display_name="Use https/TLS. This overrides inferred values from the interface or port arguments.",
+ value=False,
+ advanced=True,
+ ),
+ StrInput(name="index_param", display_name="Param of the index", value="100,'L2Distance'", advanced=True),
+ DictInput(name="index_query_params", display_name="index query params", advanced=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ FloatInput(name="score_threshold", display_name="Score threshold", advanced=True),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> Clickhouse:
+ try:
+ import clickhouse_connect
+ except ImportError as e:
+ msg = (
+ "Failed to import Clickhouse dependencies. "
+ "Install it using `pip install langflow[clickhouse-connect] --pre`"
+ )
+ raise ImportError(msg) from e
+
+ try:
+ client = clickhouse_connect.get_client(
+ host=self.host, port=self.port, username=self.username, password=self.password
+ )
+ client.command("SELECT 1")
+ except Exception as e:
+ msg = f"Failed to connect to Clickhouse: {e}"
+ raise ValueError(msg) from e
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ kwargs = {}
+ if self.index_param:
+ kwargs["index_param"] = self.index_param.split(",")
+ if self.index_query_params:
+ kwargs["index_query_params"] = self.index_query_params
+
+ settings = ClickhouseSettings(
+ table=self.table,
+ database=self.database,
+ host=self.host,
+ index_type=self.index_type,
+ metric=self.metric,
+ password=self.password,
+ port=self.port,
+ secure=self.secure,
+ username=self.username,
+ **kwargs,
+ )
+ if documents:
+ clickhouse_vs = Clickhouse.from_documents(documents=documents, embedding=self.embedding, config=settings)
+
+ else:
+ clickhouse_vs = Clickhouse(embedding=self.embedding, config=settings)
+
+ return clickhouse_vs
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ kwargs = {}
+ if self.score_threshold:
+ kwargs["score_threshold"] = self.score_threshold
+
+ docs = vector_store.similarity_search(query=self.search_query, k=self.number_of_results, **kwargs)
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/couchbase.py b/langflow/src/backend/base/langflow/components/vectorstores/couchbase.py
new file mode 100644
index 0000000..fce3474
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/couchbase.py
@@ -0,0 +1,100 @@
+from datetime import timedelta
+
+from langchain_community.vectorstores import CouchbaseVectorStore
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class CouchbaseVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Couchbase"
+ description = "Couchbase Vector Store with search capabilities"
+ name = "Couchbase"
+ icon = "Couchbase"
+
+ inputs = [
+ SecretStrInput(
+ name="couchbase_connection_string", display_name="Couchbase Cluster connection string", required=True
+ ),
+ StrInput(name="couchbase_username", display_name="Couchbase username", required=True),
+ SecretStrInput(name="couchbase_password", display_name="Couchbase password", required=True),
+ StrInput(name="bucket_name", display_name="Bucket Name", required=True),
+ StrInput(name="scope_name", display_name="Scope Name", required=True),
+ StrInput(name="collection_name", display_name="Collection Name", required=True),
+ StrInput(name="index_name", display_name="Index Name", required=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> CouchbaseVectorStore:
+ try:
+ from couchbase.auth import PasswordAuthenticator
+ from couchbase.cluster import Cluster
+ from couchbase.options import ClusterOptions
+ except ImportError as e:
+ msg = "Failed to import Couchbase dependencies. Install it using `pip install langflow[couchbase] --pre`"
+ raise ImportError(msg) from e
+
+ try:
+ auth = PasswordAuthenticator(self.couchbase_username, self.couchbase_password)
+ options = ClusterOptions(auth)
+ cluster = Cluster(self.couchbase_connection_string, options)
+
+ cluster.wait_until_ready(timedelta(seconds=5))
+ except Exception as e:
+ msg = f"Failed to connect to Couchbase: {e}"
+ raise ValueError(msg) from e
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ couchbase_vs = CouchbaseVectorStore.from_documents(
+ documents=documents,
+ cluster=cluster,
+ bucket_name=self.bucket_name,
+ scope_name=self.scope_name,
+ collection_name=self.collection_name,
+ embedding=self.embedding,
+ index_name=self.index_name,
+ )
+
+ else:
+ couchbase_vs = CouchbaseVectorStore(
+ cluster=cluster,
+ bucket_name=self.bucket_name,
+ scope_name=self.scope_name,
+ collection_name=self.collection_name,
+ embedding=self.embedding,
+ index_name=self.index_name,
+ )
+
+ return couchbase_vs
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/elasticsearch.py b/langflow/src/backend/base/langflow/components/vectorstores/elasticsearch.py
new file mode 100644
index 0000000..b4627c1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/elasticsearch.py
@@ -0,0 +1,237 @@
+from typing import Any
+
+from langchain.schema import Document
+from langchain_elasticsearch import ElasticsearchStore
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.io import (
+ DropdownInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class ElasticsearchVectorStoreComponent(LCVectorStoreComponent):
+ """Elasticsearch Vector Store with with advanced, customizable search capabilities."""
+
+ display_name: str = "Elasticsearch"
+ description: str = "Elasticsearch Vector Store with with advanced, customizable search capabilities."
+ name = "Elasticsearch"
+ icon = "ElasticsearchStore"
+
+ inputs = [
+ StrInput(
+ name="elasticsearch_url",
+ display_name="Elasticsearch URL",
+ value="http://localhost:9200",
+ info="URL for self-managed Elasticsearch deployments (e.g., http://localhost:9200). "
+ "Do not use with Elastic Cloud deployments, use Elastic Cloud ID instead.",
+ ),
+ SecretStrInput(
+ name="cloud_id",
+ display_name="Elastic Cloud ID",
+ value="",
+ info="Use this for Elastic Cloud deployments. Do not use together with 'Elasticsearch URL'.",
+ ),
+ StrInput(
+ name="index_name",
+ display_name="Index Name",
+ value="langflow",
+ info="The index name where the vectors will be stored in Elasticsearch cluster.",
+ ),
+ *LCVectorStoreComponent.inputs,
+ StrInput(
+ name="username",
+ display_name="Username",
+ value="",
+ advanced=False,
+ info=(
+ "Elasticsearch username (e.g., 'elastic'). "
+ "Required for both local and Elastic Cloud setups unless API keys are used."
+ ),
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="Password",
+ value="",
+ advanced=False,
+ info=(
+ "Elasticsearch password for the specified user. "
+ "Required for both local and Elastic Cloud setups unless API keys are used."
+ ),
+ ),
+ HandleInput(
+ name="embedding",
+ display_name="Embedding",
+ input_types=["Embeddings"],
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ options=["similarity", "mmr"],
+ value="similarity",
+ advanced=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=4,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results.",
+ value=0.0,
+ advanced=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="Elastic API Key",
+ value="",
+ advanced=True,
+ info="API Key for Elastic Cloud authentication. If used, 'username' and 'password' are not required.",
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> ElasticsearchStore:
+ """Builds the Elasticsearch Vector Store object."""
+ if self.cloud_id and self.elasticsearch_url:
+ msg = (
+ "Both 'cloud_id' and 'elasticsearch_url' provided. "
+ "Please use only one based on your deployment (Cloud or Local)."
+ )
+ raise ValueError(msg)
+
+ es_params = {
+ "index_name": self.index_name,
+ "embedding": self.embedding,
+ "es_user": self.username or None,
+ "es_password": self.password or None,
+ }
+
+ if self.cloud_id:
+ es_params["es_cloud_id"] = self.cloud_id
+ else:
+ es_params["es_url"] = self.elasticsearch_url
+
+ if self.api_key:
+ es_params["api_key"] = self.api_key
+
+ elasticsearch = ElasticsearchStore(**es_params)
+
+ # If documents are provided, add them to the store
+ if self.ingest_data:
+ documents = self._prepare_documents()
+ if documents:
+ elasticsearch.add_documents(documents)
+
+ return elasticsearch
+
+ def _prepare_documents(self) -> list[Document]:
+ """Prepares documents from the input data to add to the vector store."""
+ documents = []
+ for data in self.ingest_data:
+ if isinstance(data, Data):
+ documents.append(data.to_lc_document())
+ else:
+ error_message = "Vector Store Inputs must be Data objects."
+ self.log(error_message)
+ raise TypeError(error_message)
+ return documents
+
+ def _add_documents_to_vector_store(self, vector_store: "ElasticsearchStore") -> None:
+ """Adds documents to the Vector Store."""
+ documents = self._prepare_documents()
+ if documents and self.embedding:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ vector_store.add_documents(documents)
+ else:
+ self.log("No documents to add to the Vector Store.")
+
+ def search(self, query: str | None = None) -> list[dict[str, Any]]:
+ """Search for similar documents in the vector store or retrieve all documents if no query is provided."""
+ vector_store = self.build_vector_store()
+ search_kwargs = {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ }
+
+ if query:
+ search_type = self.search_type.lower()
+ if search_type not in {"similarity", "mmr"}:
+ msg = f"Invalid search type: {self.search_type}"
+ self.log(msg)
+ raise ValueError(msg)
+ try:
+ if search_type == "similarity":
+ results = vector_store.similarity_search_with_score(query, **search_kwargs)
+ elif search_type == "mmr":
+ results = vector_store.max_marginal_relevance_search(query, **search_kwargs)
+ except Exception as e:
+ msg = (
+ "Error occurred while querying the Elasticsearch VectorStore,"
+ " there is no Data into the VectorStore."
+ )
+ self.log(msg)
+ raise ValueError(msg) from e
+ return [
+ {"page_content": doc.page_content, "metadata": doc.metadata, "score": score} for doc, score in results
+ ]
+ results = self.get_all_documents(vector_store, **search_kwargs)
+ return [{"page_content": doc.page_content, "metadata": doc.metadata, "score": score} for doc, score in results]
+
+ def get_all_documents(self, vector_store: ElasticsearchStore, **kwargs) -> list[tuple[Document, float]]:
+ """Retrieve all documents from the vector store."""
+ client = vector_store.client
+ index_name = self.index_name
+
+ query = {
+ "query": {"match_all": {}},
+ "size": kwargs.get("k", self.number_of_results),
+ }
+
+ response = client.search(index=index_name, body=query)
+
+ results = []
+ for hit in response["hits"]["hits"]:
+ doc = Document(
+ page_content=hit["_source"].get("text", ""),
+ metadata=hit["_source"].get("metadata", {}),
+ )
+ score = hit["_score"]
+ results.append((doc, score))
+
+ return results
+
+ def search_documents(self) -> list[Data]:
+ """Search for documents in the vector store based on the search input.
+
+ If no search input is provided, retrieve all documents.
+ """
+ results = self.search(self.search_query)
+ retrieved_data = [
+ Data(
+ text=result["page_content"],
+ file_path=result["metadata"].get("file_path", ""),
+ )
+ for result in results
+ ]
+ self.status = retrieved_data
+ return retrieved_data
+
+ def get_retriever_kwargs(self):
+ """Get the keyword arguments for the retriever."""
+ return {
+ "search_type": self.search_type.lower(),
+ "search_kwargs": {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ },
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/faiss.py b/langflow/src/backend/base/langflow/components/vectorstores/faiss.py
new file mode 100644
index 0000000..fd1b9c3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/faiss.py
@@ -0,0 +1,108 @@
+from pathlib import Path
+
+from langchain_community.vectorstores import FAISS
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import BoolInput, HandleInput, IntInput, StrInput
+from langflow.schema import Data
+
+
+class FaissVectorStoreComponent(LCVectorStoreComponent):
+ """FAISS Vector Store with search capabilities."""
+
+ display_name: str = "FAISS"
+ description: str = "FAISS Vector Store with search capabilities"
+ name = "FAISS"
+ icon = "FAISS"
+
+ inputs = [
+ StrInput(
+ name="index_name",
+ display_name="Index Name",
+ value="langflow_index",
+ ),
+ StrInput(
+ name="persist_directory",
+ display_name="Persist Directory",
+ info="Path to save the FAISS index. It will be relative to where Langflow is running.",
+ ),
+ *LCVectorStoreComponent.inputs,
+ BoolInput(
+ name="allow_dangerous_deserialization",
+ display_name="Allow Dangerous Deserialization",
+ info="Set to True to allow loading pickle files from untrusted sources. "
+ "Only enable this if you trust the source of the data.",
+ advanced=True,
+ value=True,
+ ),
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=4,
+ ),
+ ]
+
+ @staticmethod
+ def resolve_path(path: str) -> str:
+ """Resolve the path relative to the Langflow root.
+
+ Args:
+ path: The path to resolve
+ Returns:
+ str: The resolved path as a string
+ """
+ return str(Path(path).resolve())
+
+ def get_persist_directory(self) -> Path:
+ """Returns the resolved persist directory path or the current directory if not set."""
+ if self.persist_directory:
+ return Path(self.resolve_path(self.persist_directory))
+ return Path()
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> FAISS:
+ """Builds the FAISS object."""
+ path = self.get_persist_directory()
+ path.mkdir(parents=True, exist_ok=True)
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)
+ faiss.save_local(str(path), self.index_name)
+ return faiss
+
+ def search_documents(self) -> list[Data]:
+ """Search for documents in the FAISS vector store."""
+ path = self.get_persist_directory()
+ index_path = path / f"{self.index_name}.faiss"
+
+ if not index_path.exists():
+ vector_store = self.build_vector_store()
+ else:
+ vector_store = FAISS.load_local(
+ folder_path=str(path),
+ embeddings=self.embedding,
+ index_name=self.index_name,
+ allow_dangerous_deserialization=self.allow_dangerous_deserialization,
+ )
+
+ if not vector_store:
+ msg = "Failed to load the FAISS index."
+ raise ValueError(msg)
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+ return docs_to_data(docs)
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/hcd.py b/langflow/src/backend/base/langflow/components/vectorstores/hcd.py
new file mode 100644
index 0000000..8b12b94
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/hcd.py
@@ -0,0 +1,313 @@
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers import docs_to_data
+from langflow.inputs import DictInput, FloatInput
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ MultilineInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class HCDVectorStoreComponent(LCVectorStoreComponent):
+ display_name: str = "Hyper-Converged Database"
+ description: str = "Implementation of Vector Store using Hyper-Converged Database (HCD) with search capabilities"
+ name = "HCD"
+ icon: str = "HCD"
+
+ inputs = [
+ StrInput(
+ name="collection_name",
+ display_name="Collection Name",
+ info="The name of the collection within HCD where the vectors will be stored.",
+ required=True,
+ ),
+ StrInput(
+ name="username",
+ display_name="HCD Username",
+ info="Authentication username for accessing HCD.",
+ value="hcd-superuser",
+ required=True,
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="HCD Password",
+ info="Authentication password for accessing HCD.",
+ value="HCD_PASSWORD",
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_endpoint",
+ display_name="HCD API Endpoint",
+ info="API endpoint URL for the HCD service.",
+ value="HCD_API_ENDPOINT",
+ required=True,
+ ),
+ *LCVectorStoreComponent.inputs,
+ StrInput(
+ name="namespace",
+ display_name="Namespace",
+ info="Optional namespace within HCD to use for the collection.",
+ value="default_namespace",
+ advanced=True,
+ ),
+ MultilineInput(
+ name="ca_certificate",
+ display_name="CA Certificate",
+ info="Optional CA certificate for TLS connections to HCD.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="metric",
+ display_name="Metric",
+ info="Optional distance metric for vector comparisons in the vector store.",
+ options=["cosine", "dot_product", "euclidean"],
+ advanced=True,
+ ),
+ IntInput(
+ name="batch_size",
+ display_name="Batch Size",
+ info="Optional number of data to process in a single batch.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_insert_batch_concurrency",
+ display_name="Bulk Insert Batch Concurrency",
+ info="Optional concurrency level for bulk insert operations.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_insert_overwrite_concurrency",
+ display_name="Bulk Insert Overwrite Concurrency",
+ info="Optional concurrency level for bulk insert operations that overwrite existing data.",
+ advanced=True,
+ ),
+ IntInput(
+ name="bulk_delete_concurrency",
+ display_name="Bulk Delete Concurrency",
+ info="Optional concurrency level for bulk delete operations.",
+ advanced=True,
+ ),
+ DropdownInput(
+ name="setup_mode",
+ display_name="Setup Mode",
+ info="Configuration mode for setting up the vector store, with options like 'Sync', 'Async', or 'Off'.",
+ options=["Sync", "Async", "Off"],
+ advanced=True,
+ value="Sync",
+ ),
+ BoolInput(
+ name="pre_delete_collection",
+ display_name="Pre Delete Collection",
+ info="Boolean flag to determine whether to delete the collection before creating a new one.",
+ advanced=True,
+ ),
+ StrInput(
+ name="metadata_indexing_include",
+ display_name="Metadata Indexing Include",
+ info="Optional list of metadata fields to include in the indexing.",
+ advanced=True,
+ ),
+ HandleInput(
+ name="embedding",
+ display_name="Embedding or Astra Vectorize",
+ input_types=["Embeddings", "dict"],
+ # TODO: This should be optional, but need to refactor langchain-astradb first.
+ info="Allows either an embedding model or an Astra Vectorize configuration.",
+ ),
+ StrInput(
+ name="metadata_indexing_exclude",
+ display_name="Metadata Indexing Exclude",
+ info="Optional list of metadata fields to exclude from the indexing.",
+ advanced=True,
+ ),
+ StrInput(
+ name="collection_indexing_policy",
+ display_name="Collection Indexing Policy",
+ info="Optional dictionary defining the indexing policy for the collection.",
+ advanced=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=4,
+ ),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ info="Search type to use",
+ options=["Similarity", "Similarity with score threshold", "MMR (Max Marginal Relevance)"],
+ value="Similarity",
+ advanced=True,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results. "
+ "(when using 'Similarity with score threshold')",
+ value=0,
+ advanced=True,
+ ),
+ DictInput(
+ name="search_filter",
+ display_name="Search Metadata Filter",
+ info="Optional dictionary of filters to apply to the search query.",
+ advanced=True,
+ is_list=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self):
+ try:
+ from langchain_astradb import AstraDBVectorStore
+ from langchain_astradb.utils.astradb import SetupMode
+ except ImportError as e:
+ msg = (
+ "Could not import langchain Astra DB integration package. "
+ "Please install it with `pip install langchain-astradb`."
+ )
+ raise ImportError(msg) from e
+
+ try:
+ from astrapy.authentication import UsernamePasswordTokenProvider
+ from astrapy.constants import Environment
+ except ImportError as e:
+ msg = "Could not import astrapy integration package. Please install it with `pip install astrapy`."
+ raise ImportError(msg) from e
+
+ try:
+ if not self.setup_mode:
+ self.setup_mode = self._inputs["setup_mode"].options[0]
+
+ setup_mode_value = SetupMode[self.setup_mode.upper()]
+ except KeyError as e:
+ msg = f"Invalid setup mode: {self.setup_mode}"
+ raise ValueError(msg) from e
+
+ if not isinstance(self.embedding, dict):
+ embedding_dict = {"embedding": self.embedding}
+ else:
+ from astrapy.info import CollectionVectorServiceOptions
+
+ dict_options = self.embedding.get("collection_vector_service_options", {})
+ dict_options["authentication"] = {
+ k: v for k, v in dict_options.get("authentication", {}).items() if k and v
+ }
+ dict_options["parameters"] = {k: v for k, v in dict_options.get("parameters", {}).items() if k and v}
+ embedding_dict = {
+ "collection_vector_service_options": CollectionVectorServiceOptions.from_dict(dict_options)
+ }
+ collection_embedding_api_key = self.embedding.get("collection_embedding_api_key")
+ if collection_embedding_api_key:
+ embedding_dict["collection_embedding_api_key"] = collection_embedding_api_key
+
+ token_provider = UsernamePasswordTokenProvider(self.username, self.password)
+ vector_store_kwargs = {
+ **embedding_dict,
+ "collection_name": self.collection_name,
+ "token": token_provider,
+ "api_endpoint": self.api_endpoint,
+ "namespace": self.namespace,
+ "metric": self.metric or None,
+ "batch_size": self.batch_size or None,
+ "bulk_insert_batch_concurrency": self.bulk_insert_batch_concurrency or None,
+ "bulk_insert_overwrite_concurrency": self.bulk_insert_overwrite_concurrency or None,
+ "bulk_delete_concurrency": self.bulk_delete_concurrency or None,
+ "setup_mode": setup_mode_value,
+ "pre_delete_collection": self.pre_delete_collection or False,
+ "environment": Environment.HCD,
+ }
+
+ if self.metadata_indexing_include:
+ vector_store_kwargs["metadata_indexing_include"] = self.metadata_indexing_include
+ elif self.metadata_indexing_exclude:
+ vector_store_kwargs["metadata_indexing_exclude"] = self.metadata_indexing_exclude
+ elif self.collection_indexing_policy:
+ vector_store_kwargs["collection_indexing_policy"] = self.collection_indexing_policy
+
+ try:
+ vector_store = AstraDBVectorStore(**vector_store_kwargs)
+ except Exception as e:
+ msg = f"Error initializing AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ self._add_documents_to_vector_store(vector_store)
+ return vector_store
+
+ def _add_documents_to_vector_store(self, vector_store) -> None:
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ msg = "Vector Store Inputs must be Data objects."
+ raise TypeError(msg)
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ try:
+ vector_store.add_documents(documents)
+ except Exception as e:
+ msg = f"Error adding documents to AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+ else:
+ self.log("No documents to add to the Vector Store.")
+
+ def _map_search_type(self) -> str:
+ if self.search_type == "Similarity with score threshold":
+ return "similarity_score_threshold"
+ if self.search_type == "MMR (Max Marginal Relevance)":
+ return "mmr"
+ return "similarity"
+
+ def _build_search_args(self):
+ args = {
+ "k": self.number_of_results,
+ "score_threshold": self.search_score_threshold,
+ }
+
+ if self.search_filter:
+ clean_filter = {k: v for k, v in self.search_filter.items() if k and v}
+ if len(clean_filter) > 0:
+ args["filter"] = clean_filter
+ return args
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ self.log(f"Search query: {self.search_query}")
+ self.log(f"Search type: {self.search_type}")
+ self.log(f"Number of results: {self.number_of_results}")
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ try:
+ search_type = self._map_search_type()
+ search_args = self._build_search_args()
+
+ docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)
+ except Exception as e:
+ msg = f"Error performing search in AstraDBVectorStore: {e}"
+ raise ValueError(msg) from e
+
+ self.log(f"Retrieved documents: {len(docs)}")
+
+ data = docs_to_data(docs)
+ self.log(f"Converted documents to data: {len(data)}")
+ self.status = data
+ return data
+ self.log("No search input provided. Skipping search.")
+ return []
+
+ def get_retriever_kwargs(self):
+ search_args = self._build_search_args()
+ return {
+ "search_type": self._map_search_type(),
+ "search_kwargs": search_args,
+ }
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/milvus.py b/langflow/src/backend/base/langflow/components/vectorstores/milvus.py
new file mode 100644
index 0000000..4300ddf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/milvus.py
@@ -0,0 +1,112 @@
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import (
+ BoolInput,
+ DictInput,
+ DropdownInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class MilvusVectorStoreComponent(LCVectorStoreComponent):
+ """Milvus vector store with search capabilities."""
+
+ display_name: str = "Milvus"
+ description: str = "Milvus vector store with search capabilities"
+ name = "Milvus"
+ icon = "Milvus"
+
+ inputs = [
+ StrInput(name="collection_name", display_name="Collection Name", value="langflow"),
+ StrInput(name="collection_description", display_name="Collection Description", value=""),
+ StrInput(
+ name="uri",
+ display_name="Connection URI",
+ value="http://localhost:19530",
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="Token",
+ value="",
+ info="Ignore this field if no token is required to make connection.",
+ ),
+ DictInput(name="connection_args", display_name="Other Connection Arguments", advanced=True),
+ StrInput(name="primary_field", display_name="Primary Field Name", value="pk"),
+ StrInput(name="text_field", display_name="Text Field Name", value="text"),
+ StrInput(name="vector_field", display_name="Vector Field Name", value="vector"),
+ DropdownInput(
+ name="consistency_level",
+ display_name="Consistencey Level",
+ options=["Bounded", "Session", "Strong", "Eventual"],
+ value="Session",
+ advanced=True,
+ ),
+ DictInput(name="index_params", display_name="Index Parameters", advanced=True),
+ DictInput(name="search_params", display_name="Search Parameters", advanced=True),
+ BoolInput(name="drop_old", display_name="Drop Old Collection", value=False, advanced=True),
+ FloatInput(name="timeout", display_name="Timeout", advanced=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self):
+ try:
+ from langchain_milvus.vectorstores import Milvus as LangchainMilvus
+ except ImportError as e:
+ msg = "Could not import Milvus integration package. Please install it with `pip install langchain-milvus`."
+ raise ImportError(msg) from e
+ self.connection_args.update(uri=self.uri, token=self.password)
+ milvus_store = LangchainMilvus(
+ embedding_function=self.embedding,
+ collection_name=self.collection_name,
+ collection_description=self.collection_description,
+ connection_args=self.connection_args,
+ consistency_level=self.consistency_level,
+ index_params=self.index_params,
+ search_params=self.search_params,
+ drop_old=self.drop_old,
+ auto_id=True,
+ primary_field=self.primary_field,
+ text_field=self.text_field,
+ vector_field=self.vector_field,
+ timeout=self.timeout,
+ )
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ milvus_store.add_documents(documents)
+
+ return milvus_store
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/mongodb_atlas.py b/langflow/src/backend/base/langflow/components/vectorstores/mongodb_atlas.py
new file mode 100644
index 0000000..f0a9d23
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/mongodb_atlas.py
@@ -0,0 +1,123 @@
+import tempfile
+
+import certifi
+from langchain_community.vectorstores import MongoDBAtlasVectorSearch
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import BoolInput, HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class MongoVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "MongoDB Atlas"
+ description = "MongoDB Atlas Vector Store with search capabilities"
+ name = "MongoDBAtlasVector"
+ icon = "MongoDB"
+
+ inputs = [
+ SecretStrInput(name="mongodb_atlas_cluster_uri", display_name="MongoDB Atlas Cluster URI", required=True),
+ BoolInput(name="enable_mtls", display_name="Enable mTLS", value=False, advanced=True, required=True),
+ SecretStrInput(
+ name="mongodb_atlas_client_cert",
+ display_name="MongoDB Atlas Combined Client Certificate",
+ required=False,
+ info="Client Certificate combined with the private key in the following format:\n "
+ "-----BEGIN PRIVATE KEY-----\n...\n -----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n"
+ "...\n-----END CERTIFICATE-----\n",
+ ),
+ StrInput(name="db_name", display_name="Database Name", required=True),
+ StrInput(name="collection_name", display_name="Collection Name", required=True),
+ StrInput(name="index_name", display_name="Index Name", required=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> MongoDBAtlasVectorSearch:
+ try:
+ from pymongo import MongoClient
+ except ImportError as e:
+ msg = "Please install pymongo to use MongoDB Atlas Vector Store"
+ raise ImportError(msg) from e
+
+ # Create temporary files for the client certificate
+ if self.enable_mtls:
+ client_cert_path = None
+ try:
+ client_cert = self.mongodb_atlas_client_cert.replace(" ", "\n")
+ client_cert = client_cert.replace("-----BEGIN\nPRIVATE\nKEY-----", "-----BEGIN PRIVATE KEY-----")
+ client_cert = client_cert.replace(
+ "-----END\nPRIVATE\nKEY-----\n-----BEGIN\nCERTIFICATE-----",
+ "-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----",
+ )
+ client_cert = client_cert.replace("-----END\nCERTIFICATE-----", "-----END CERTIFICATE-----")
+ with tempfile.NamedTemporaryFile(delete=False) as client_cert_file:
+ client_cert_file.write(client_cert.encode("utf-8"))
+ client_cert_path = client_cert_file.name
+
+ except Exception as e:
+ msg = f"Failed to write certificate to temporary file: {e}"
+ raise ValueError(msg) from e
+
+ try:
+ mongo_client: MongoClient = (
+ MongoClient(
+ self.mongodb_atlas_cluster_uri,
+ tls=True,
+ tlsCertificateKeyFile=client_cert_path,
+ tlsCAFile=certifi.where(),
+ )
+ if self.enable_mtls
+ else MongoClient(self.mongodb_atlas_cluster_uri)
+ )
+
+ collection = mongo_client[self.db_name][self.collection_name]
+ collection.drop() # Drop collection to override the vector store
+ except Exception as e:
+ msg = f"Failed to connect to MongoDB Atlas: {e}"
+ raise ValueError(msg) from e
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ return MongoDBAtlasVectorSearch.from_documents(
+ documents=documents, embedding=self.embedding, collection=collection, index_name=self.index_name
+ )
+ return MongoDBAtlasVectorSearch(
+ embedding=self.embedding,
+ collection=collection,
+ index_name=self.index_name,
+ )
+
+ def search_documents(self) -> list[Data]:
+ from bson.objectid import ObjectId
+
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str):
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+ for doc in docs:
+ doc.metadata = {
+ key: str(value) if isinstance(value, ObjectId) else value for key, value in doc.metadata.items()
+ }
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/opensearch.py b/langflow/src/backend/base/langflow/components/vectorstores/opensearch.py
new file mode 100644
index 0000000..1de82bf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/opensearch.py
@@ -0,0 +1,238 @@
+import json
+from typing import Any
+
+from langchain_community.vectorstores import OpenSearchVectorSearch
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.io import (
+ BoolInput,
+ DropdownInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ MultilineInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class OpenSearchVectorStoreComponent(LCVectorStoreComponent):
+ """OpenSearch Vector Store with advanced, customizable search capabilities."""
+
+ display_name: str = "OpenSearch"
+ description: str = "OpenSearch Vector Store with advanced, customizable search capabilities."
+ name = "OpenSearch"
+ icon = "OpenSearch"
+
+ inputs = [
+ StrInput(
+ name="opensearch_url",
+ display_name="OpenSearch URL",
+ value="http://localhost:9200",
+ info="URL for OpenSearch cluster (e.g. https://192.168.1.1:9200).",
+ ),
+ StrInput(
+ name="index_name",
+ display_name="Index Name",
+ value="langflow",
+ info="The index name where the vectors will be stored in OpenSearch cluster.",
+ ),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ DropdownInput(
+ name="search_type",
+ display_name="Search Type",
+ options=["similarity", "similarity_score_threshold", "mmr"],
+ value="similarity",
+ advanced=True,
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ advanced=True,
+ value=4,
+ ),
+ FloatInput(
+ name="search_score_threshold",
+ display_name="Search Score Threshold",
+ info="Minimum similarity score threshold for search results.",
+ value=0.0,
+ advanced=True,
+ ),
+ StrInput(
+ name="username",
+ display_name="Username",
+ value="admin",
+ advanced=True,
+ ),
+ SecretStrInput(
+ name="password",
+ display_name="Password",
+ value="admin",
+ advanced=True,
+ ),
+ BoolInput(
+ name="use_ssl",
+ display_name="Use SSL",
+ value=True,
+ advanced=True,
+ ),
+ BoolInput(
+ name="verify_certs",
+ display_name="Verify Certificates",
+ value=False,
+ advanced=True,
+ ),
+ MultilineInput(
+ name="hybrid_search_query",
+ display_name="Hybrid Search Query",
+ value="",
+ advanced=True,
+ info=(
+ "Provide a custom hybrid search query in JSON format. This allows you to combine "
+ "vector similarity and keyword matching."
+ ),
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> OpenSearchVectorSearch:
+ """Builds the OpenSearch Vector Store object."""
+ try:
+ from langchain_community.vectorstores import OpenSearchVectorSearch
+ except ImportError as e:
+ error_message = f"Failed to import required modules: {e}"
+ self.log(error_message)
+ raise ImportError(error_message) from e
+
+ try:
+ opensearch = OpenSearchVectorSearch(
+ index_name=self.index_name,
+ embedding_function=self.embedding,
+ opensearch_url=self.opensearch_url,
+ http_auth=(self.username, self.password),
+ use_ssl=self.use_ssl,
+ verify_certs=self.verify_certs,
+ ssl_assert_hostname=False,
+ ssl_show_warn=False,
+ )
+ except Exception as e:
+ error_message = f"Failed to create OpenSearchVectorSearch instance: {e}"
+ self.log(error_message)
+ raise RuntimeError(error_message) from e
+
+ if self.ingest_data:
+ self._add_documents_to_vector_store(opensearch)
+
+ return opensearch
+
+ def _add_documents_to_vector_store(self, vector_store: "OpenSearchVectorSearch") -> None:
+ """Adds documents to the Vector Store."""
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ error_message = f"Expected Data object, got {type(_input)}"
+ self.log(error_message)
+ raise TypeError(error_message)
+
+ if documents and self.embedding is not None:
+ self.log(f"Adding {len(documents)} documents to the Vector Store.")
+ try:
+ vector_store.add_documents(documents)
+ except Exception as e:
+ error_message = f"Error adding documents to Vector Store: {e}"
+ self.log(error_message)
+ raise RuntimeError(error_message) from e
+ else:
+ self.log("No documents to add to the Vector Store.")
+
+ def search(self, query: str | None = None) -> list[dict[str, Any]]:
+ """Search for similar documents in the vector store or retrieve all documents if no query is provided."""
+ try:
+ vector_store = self.build_vector_store()
+
+ query = query or ""
+
+ if self.hybrid_search_query.strip():
+ try:
+ hybrid_query = json.loads(self.hybrid_search_query)
+ except json.JSONDecodeError as e:
+ error_message = f"Invalid hybrid search query JSON: {e}"
+ self.log(error_message)
+ raise ValueError(error_message) from e
+
+ results = vector_store.client.search(index=self.index_name, body=hybrid_query)
+
+ processed_results = []
+ for hit in results.get("hits", {}).get("hits", []):
+ source = hit.get("_source", {})
+ text = source.get("text", "")
+ metadata = source.get("metadata", {})
+
+ if isinstance(text, dict):
+ text = text.get("text", "")
+
+ processed_results.append(
+ {
+ "page_content": text,
+ "metadata": metadata,
+ }
+ )
+ return processed_results
+
+ search_kwargs = {"k": self.number_of_results}
+ search_type = self.search_type.lower()
+
+ if search_type == "similarity":
+ results = vector_store.similarity_search(query, **search_kwargs)
+ return [{"page_content": doc.page_content, "metadata": doc.metadata} for doc in results]
+ if search_type == "similarity_score_threshold":
+ search_kwargs["score_threshold"] = self.search_score_threshold
+ results = vector_store.similarity_search_with_relevance_scores(query, **search_kwargs)
+ return [
+ {
+ "page_content": doc.page_content,
+ "metadata": doc.metadata,
+ "score": score,
+ }
+ for doc, score in results
+ ]
+ if search_type == "mmr":
+ results = vector_store.max_marginal_relevance_search(query, **search_kwargs)
+ return [{"page_content": doc.page_content, "metadata": doc.metadata} for doc in results]
+
+ except Exception as e:
+ error_message = f"Error during search: {e}"
+ self.log(error_message)
+ raise RuntimeError(error_message) from e
+
+ error_message = f"Error during search. Invalid search type: {self.search_type}"
+ self.log(error_message)
+ raise ValueError(error_message)
+
+ def search_documents(self) -> list[Data]:
+ """Search for documents in the vector store based on the search input.
+
+ If no search input is provided, retrieve all documents.
+ """
+ try:
+ query = self.search_query.strip() if self.search_query else None
+ results = self.search(query)
+ retrieved_data = [
+ Data(
+ file_path=result["metadata"].get("file_path", ""),
+ text=result["page_content"],
+ )
+ for result in results
+ ]
+ except Exception as e:
+ error_message = f"Error during document search: {e}"
+ self.log(error_message)
+ raise RuntimeError(error_message) from e
+
+ self.status = retrieved_data
+ return retrieved_data
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/pgvector.py b/langflow/src/backend/base/langflow/components/vectorstores/pgvector.py
new file mode 100644
index 0000000..7ae0847
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/pgvector.py
@@ -0,0 +1,69 @@
+from langchain_community.vectorstores import PGVector
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+from langflow.utils.connection_string_parser import transform_connection_string
+
+
+class PGVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "PGVector"
+ description = "PGVector Vector Store with search capabilities"
+ name = "pgvector"
+ icon = "cpu"
+
+ inputs = [
+ SecretStrInput(name="pg_server_url", display_name="PostgreSQL Server Connection String", required=True),
+ StrInput(name="collection_name", display_name="Table", required=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"], required=True),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> PGVector:
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ connection_string_parsed = transform_connection_string(self.pg_server_url)
+
+ if documents:
+ pgvector = PGVector.from_documents(
+ embedding=self.embedding,
+ documents=documents,
+ collection_name=self.collection_name,
+ connection_string=connection_string_parsed,
+ )
+ else:
+ pgvector = PGVector.from_existing_index(
+ embedding=self.embedding,
+ collection_name=self.collection_name,
+ connection_string=connection_string_parsed,
+ )
+
+ return pgvector
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/pinecone.py b/langflow/src/backend/base/langflow/components/vectorstores/pinecone.py
new file mode 100644
index 0000000..f71a2ae
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/pinecone.py
@@ -0,0 +1,130 @@
+import numpy as np
+from langchain_core.vectorstores import VectorStore
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import DropdownInput, HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class PineconeVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Pinecone"
+ description = "Pinecone Vector Store with search capabilities"
+ name = "Pinecone"
+ icon = "Pinecone"
+ inputs = [
+ StrInput(name="index_name", display_name="Index Name", required=True),
+ StrInput(name="namespace", display_name="Namespace", info="Namespace for the index."),
+ DropdownInput(
+ name="distance_strategy",
+ display_name="Distance Strategy",
+ options=["Cosine", "Euclidean", "Dot Product"],
+ value="Cosine",
+ advanced=True,
+ ),
+ SecretStrInput(name="pinecone_api_key", display_name="Pinecone API Key", required=True),
+ StrInput(
+ name="text_key",
+ display_name="Text Key",
+ info="Key in the record to use as text.",
+ value="text",
+ advanced=True,
+ ),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> VectorStore:
+ """Build and return a Pinecone vector store instance."""
+ try:
+ from langchain_pinecone import PineconeVectorStore
+ except ImportError as e:
+ msg = "langchain-pinecone is not installed. Please install it with `pip install langchain-pinecone`."
+ raise ValueError(msg) from e
+
+ try:
+ from langchain_pinecone._utilities import DistanceStrategy
+
+ # Wrap the embedding model to ensure float32 output
+ wrapped_embeddings = Float32Embeddings(self.embedding)
+
+ # Convert distance strategy
+ distance_strategy = self.distance_strategy.replace(" ", "_").upper()
+ distance_strategy = DistanceStrategy[distance_strategy]
+
+ # Initialize Pinecone instance with wrapped embeddings
+ pinecone = PineconeVectorStore(
+ index_name=self.index_name,
+ embedding=wrapped_embeddings, # Use wrapped embeddings
+ text_key=self.text_key,
+ namespace=self.namespace,
+ distance_strategy=distance_strategy,
+ pinecone_api_key=self.pinecone_api_key,
+ )
+ except Exception as e:
+ error_msg = "Error building Pinecone vector store"
+ raise ValueError(error_msg) from e
+ else:
+ # Process documents if any
+ documents = []
+ if self.ingest_data:
+ for doc in self.ingest_data:
+ if isinstance(doc, Data):
+ documents.append(doc.to_lc_document())
+ else:
+ documents.append(doc)
+
+ if documents:
+ pinecone.add_documents(documents)
+
+ return pinecone
+
+ def search_documents(self) -> list[Data]:
+ """Search documents in the vector store."""
+ try:
+ if not self.search_query or not isinstance(self.search_query, str) or not self.search_query.strip():
+ return []
+
+ vector_store = self.build_vector_store()
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+ except Exception as e:
+ error_msg = "Error searching documents"
+ raise ValueError(error_msg) from e
+ else:
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+
+
+class Float32Embeddings:
+ """Wrapper class to ensure float32 embeddings."""
+
+ def __init__(self, base_embeddings):
+ self.base_embeddings = base_embeddings
+
+ def embed_documents(self, texts):
+ embeddings = self.base_embeddings.embed_documents(texts)
+ if isinstance(embeddings, np.ndarray):
+ return [[self._force_float32(x) for x in vec] for vec in embeddings]
+ return [[self._force_float32(x) for x in vec] for vec in embeddings]
+
+ def embed_query(self, text):
+ embedding = self.base_embeddings.embed_query(text)
+ if isinstance(embedding, np.ndarray):
+ return [self._force_float32(x) for x in embedding]
+ return [self._force_float32(x) for x in embedding]
+
+ def _force_float32(self, value):
+ """Convert any numeric type to Python float."""
+ return float(np.float32(value))
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/qdrant.py b/langflow/src/backend/base/langflow/components/vectorstores/qdrant.py
new file mode 100644
index 0000000..7a37b24
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/qdrant.py
@@ -0,0 +1,106 @@
+from langchain.embeddings.base import Embeddings
+from langchain_community.vectorstores import Qdrant
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import (
+ DropdownInput,
+ HandleInput,
+ IntInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class QdrantVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Qdrant"
+ description = "Qdrant Vector Store with search capabilities"
+ icon = "Qdrant"
+
+ inputs = [
+ StrInput(name="collection_name", display_name="Collection Name", required=True),
+ StrInput(name="host", display_name="Host", value="localhost", advanced=True),
+ IntInput(name="port", display_name="Port", value=6333, advanced=True),
+ IntInput(name="grpc_port", display_name="gRPC Port", value=6334, advanced=True),
+ SecretStrInput(name="api_key", display_name="API Key", advanced=True),
+ StrInput(name="prefix", display_name="Prefix", advanced=True),
+ IntInput(name="timeout", display_name="Timeout", advanced=True),
+ StrInput(name="path", display_name="Path", advanced=True),
+ StrInput(name="url", display_name="URL", advanced=True),
+ DropdownInput(
+ name="distance_func",
+ display_name="Distance Function",
+ options=["Cosine", "Euclidean", "Dot Product"],
+ value="Cosine",
+ advanced=True,
+ ),
+ StrInput(name="content_payload_key", display_name="Content Payload Key", value="page_content", advanced=True),
+ StrInput(name="metadata_payload_key", display_name="Metadata Payload Key", value="metadata", advanced=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> Qdrant:
+ qdrant_kwargs = {
+ "collection_name": self.collection_name,
+ "content_payload_key": self.content_payload_key,
+ "metadata_payload_key": self.metadata_payload_key,
+ }
+
+ server_kwargs = {
+ "host": self.host or None,
+ "port": int(self.port), # Ensure port is an integer
+ "grpc_port": int(self.grpc_port), # Ensure grpc_port is an integer
+ "api_key": self.api_key,
+ "prefix": self.prefix,
+ # Ensure timeout is an integer
+ "timeout": int(self.timeout) if self.timeout else None,
+ "path": self.path or None,
+ "url": self.url or None,
+ }
+
+ server_kwargs = {k: v for k, v in server_kwargs.items() if v is not None}
+ documents = []
+
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if not isinstance(self.embedding, Embeddings):
+ msg = "Invalid embedding object"
+ raise TypeError(msg)
+
+ if documents:
+ qdrant = Qdrant.from_documents(documents, embedding=self.embedding, **qdrant_kwargs, **server_kwargs)
+ else:
+ from qdrant_client import QdrantClient
+
+ client = QdrantClient(**server_kwargs)
+ qdrant = Qdrant(embeddings=self.embedding, client=client, **qdrant_kwargs)
+
+ return qdrant
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/redis.py b/langflow/src/backend/base/langflow/components/vectorstores/redis.py
new file mode 100644
index 0000000..f628e75
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/redis.py
@@ -0,0 +1,87 @@
+from pathlib import Path
+
+from langchain.text_splitter import CharacterTextSplitter
+from langchain_community.vectorstores.redis import Redis
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class RedisVectorStoreComponent(LCVectorStoreComponent):
+ """A custom component for implementing a Vector Store using Redis."""
+
+ display_name: str = "Redis"
+ description: str = "Implementation of Vector Store using Redis"
+ name = "Redis"
+ icon = "Redis"
+
+ inputs = [
+ SecretStrInput(name="redis_server_url", display_name="Redis Server Connection String", required=True),
+ StrInput(
+ name="redis_index_name",
+ display_name="Redis Index",
+ ),
+ StrInput(name="code", display_name="Code", advanced=True),
+ StrInput(
+ name="schema",
+ display_name="Schema",
+ ),
+ *LCVectorStoreComponent.inputs,
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> Redis:
+ documents = []
+
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+ Path("docuemnts.txt").write_text(str(documents), encoding="utf-8")
+
+ if not documents:
+ if self.schema is None:
+ msg = "If no documents are provided, a schema must be provided."
+ raise ValueError(msg)
+ redis_vs = Redis.from_existing_index(
+ embedding=self.embedding,
+ index_name=self.redis_index_name,
+ schema=self.schema,
+ key_prefix=None,
+ redis_url=self.redis_server_url,
+ )
+ else:
+ text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
+ docs = text_splitter.split_documents(documents)
+ redis_vs = Redis.from_documents(
+ documents=docs,
+ embedding=self.embedding,
+ redis_url=self.redis_server_url,
+ index_name=self.redis_index_name,
+ )
+ return redis_vs
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/supabase.py b/langflow/src/backend/base/langflow/components/vectorstores/supabase.py
new file mode 100644
index 0000000..2dc8d8c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/supabase.py
@@ -0,0 +1,73 @@
+from langchain_community.vectorstores import SupabaseVectorStore
+from supabase.client import Client, create_client
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class SupabaseVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Supabase"
+ description = "Supabase Vector Store with search capabilities"
+ name = "SupabaseVectorStore"
+ icon = "Supabase"
+
+ inputs = [
+ StrInput(name="supabase_url", display_name="Supabase URL", required=True),
+ SecretStrInput(name="supabase_service_key", display_name="Supabase Service Key", required=True),
+ StrInput(name="table_name", display_name="Table Name", advanced=True),
+ StrInput(name="query_name", display_name="Query Name"),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> SupabaseVectorStore:
+ supabase: Client = create_client(self.supabase_url, supabase_key=self.supabase_service_key)
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ supabase_vs = SupabaseVectorStore.from_documents(
+ documents=documents,
+ embedding=self.embedding,
+ query_name=self.query_name,
+ client=supabase,
+ table_name=self.table_name,
+ )
+ else:
+ supabase_vs = SupabaseVectorStore(
+ client=supabase,
+ embedding=self.embedding,
+ table_name=self.table_name,
+ query_name=self.query_name,
+ )
+
+ return supabase_vs
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/upstash.py b/langflow/src/backend/base/langflow/components/vectorstores/upstash.py
new file mode 100644
index 0000000..b1f94f1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/upstash.py
@@ -0,0 +1,121 @@
+from langchain_community.vectorstores import UpstashVectorStore
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import (
+ HandleInput,
+ IntInput,
+ MultilineInput,
+ SecretStrInput,
+ StrInput,
+)
+from langflow.schema import Data
+
+
+class UpstashVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Upstash"
+ description = "Upstash Vector Store with search capabilities"
+ name = "Upstash"
+ icon = "Upstash"
+
+ inputs = [
+ StrInput(
+ name="index_url",
+ display_name="Index URL",
+ info="The URL of the Upstash index.",
+ required=True,
+ ),
+ SecretStrInput(
+ name="index_token",
+ display_name="Index Token",
+ info="The token for the Upstash index.",
+ required=True,
+ ),
+ StrInput(
+ name="text_key",
+ display_name="Text Key",
+ info="The key in the record to use as text.",
+ value="text",
+ advanced=True,
+ ),
+ StrInput(
+ name="namespace",
+ display_name="Namespace",
+ info="Leave empty for default namespace.",
+ ),
+ *LCVectorStoreComponent.inputs,
+ MultilineInput(
+ name="metadata_filter",
+ display_name="Metadata Filter",
+ info="Filters documents by metadata. Look at the documentation for more information.",
+ ),
+ HandleInput(
+ name="embedding",
+ display_name="Embedding",
+ input_types=["Embeddings"],
+ info="To use Upstash's embeddings, don't provide an embedding.",
+ ),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> UpstashVectorStore:
+ use_upstash_embedding = self.embedding is None
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ if use_upstash_embedding:
+ upstash_vs = UpstashVectorStore(
+ embedding=use_upstash_embedding,
+ text_key=self.text_key,
+ index_url=self.index_url,
+ index_token=self.index_token,
+ namespace=self.namespace,
+ )
+ upstash_vs.add_documents(documents)
+ else:
+ upstash_vs = UpstashVectorStore.from_documents(
+ documents=documents,
+ embedding=self.embedding,
+ text_key=self.text_key,
+ index_url=self.index_url,
+ index_token=self.index_token,
+ namespace=self.namespace,
+ )
+ else:
+ upstash_vs = UpstashVectorStore(
+ embedding=self.embedding or use_upstash_embedding,
+ text_key=self.text_key,
+ index_url=self.index_url,
+ index_token=self.index_token,
+ namespace=self.namespace,
+ )
+
+ return upstash_vs
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ filter=self.metadata_filter,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/vectara.py b/langflow/src/backend/base/langflow/components/vectorstores/vectara.py
new file mode 100644
index 0000000..82f146a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/vectara.py
@@ -0,0 +1,93 @@
+from typing import TYPE_CHECKING
+
+from langchain_community.vectorstores import Vectara
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+if TYPE_CHECKING:
+ from langchain_community.vectorstores import Vectara
+
+
+class VectaraVectorStoreComponent(LCVectorStoreComponent):
+ """Vectara Vector Store with search capabilities."""
+
+ display_name: str = "Vectara"
+ description: str = "Vectara Vector Store with search capabilities"
+ name = "Vectara"
+ icon = "Vectara"
+
+ inputs = [
+ StrInput(name="vectara_customer_id", display_name="Vectara Customer ID", required=True),
+ StrInput(name="vectara_corpus_id", display_name="Vectara Corpus ID", required=True),
+ SecretStrInput(name="vectara_api_key", display_name="Vectara API Key", required=True),
+ HandleInput(
+ name="embedding",
+ display_name="Embedding",
+ input_types=["Embeddings"],
+ ),
+ *LCVectorStoreComponent.inputs,
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> "Vectara":
+ """Builds the Vectara object."""
+ try:
+ from langchain_community.vectorstores import Vectara
+ except ImportError as e:
+ msg = "Could not import Vectara. Please install it with `pip install langchain-community`."
+ raise ImportError(msg) from e
+
+ vectara = Vectara(
+ vectara_customer_id=self.vectara_customer_id,
+ vectara_corpus_id=self.vectara_corpus_id,
+ vectara_api_key=self.vectara_api_key,
+ )
+
+ self._add_documents_to_vector_store(vectara)
+ return vectara
+
+ def _add_documents_to_vector_store(self, vector_store: "Vectara") -> None:
+ """Adds documents to the Vector Store."""
+ if not self.ingest_data:
+ self.status = "No documents to add to Vectara"
+ return
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents:
+ self.log(f"Adding {len(documents)} documents to Vectara.")
+ vector_store.add_documents(documents)
+ self.status = f"Added {len(documents)} documents to Vectara"
+ else:
+ self.log("No documents to add to Vectara.")
+ self.status = "No valid documents to add to Vectara"
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = f"Found {len(data)} results for the query: {self.search_query}"
+ return data
+ self.status = "No search query provided"
+ return []
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/vectara_rag.py b/langflow/src/backend/base/langflow/components/vectorstores/vectara_rag.py
new file mode 100644
index 0000000..fda11a2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/vectara_rag.py
@@ -0,0 +1,164 @@
+from langflow.custom import Component
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.io import DropdownInput, FloatInput, IntInput, MessageTextInput, Output, SecretStrInput, StrInput
+from langflow.schema.message import Message
+
+
+class VectaraRagComponent(Component):
+ display_name = "Vectara RAG"
+ description = "Vectara's full end to end RAG"
+ documentation = "https://docs.vectara.com/docs"
+ icon = "Vectara"
+ name = "VectaraRAG"
+ SUMMARIZER_PROMPTS = [
+ "vectara-summary-ext-24-05-sml",
+ "vectara-summary-ext-24-05-med-omni",
+ "vectara-summary-ext-24-05-large",
+ "vectara-summary-ext-24-05-med",
+ "vectara-summary-ext-v1.3.0",
+ ]
+
+ RERANKER_TYPES = ["mmr", "rerank_multilingual_v1", "none"]
+
+ RESPONSE_LANGUAGES = [
+ "auto",
+ "eng",
+ "spa",
+ "fra",
+ "zho",
+ "deu",
+ "hin",
+ "ara",
+ "por",
+ "ita",
+ "jpn",
+ "kor",
+ "rus",
+ "tur",
+ "fas",
+ "vie",
+ "tha",
+ "heb",
+ "nld",
+ "ind",
+ "pol",
+ "ukr",
+ "ron",
+ "swe",
+ "ces",
+ "ell",
+ "ben",
+ "msa",
+ "urd",
+ ]
+
+ field_order = ["vectara_customer_id", "vectara_corpus_id", "vectara_api_key", "search_query", "reranker"]
+
+ inputs = [
+ StrInput(name="vectara_customer_id", display_name="Vectara Customer ID", required=True),
+ StrInput(name="vectara_corpus_id", display_name="Vectara Corpus ID", required=True),
+ SecretStrInput(name="vectara_api_key", display_name="Vectara API Key", required=True),
+ MessageTextInput(
+ name="search_query",
+ display_name="Search Query",
+ info="The query to receive an answer on.",
+ tool_mode=True,
+ ),
+ FloatInput(
+ name="lexical_interpolation",
+ display_name="Hybrid Search Factor",
+ range_spec=RangeSpec(min=0.005, max=0.1, step=0.005),
+ value=0.005,
+ advanced=True,
+ info="How much to weigh lexical scores compared to the embedding score. "
+ "0 means lexical search is not used at all, and 1 means only lexical search is used.",
+ ),
+ MessageTextInput(
+ name="filter",
+ display_name="Metadata Filters",
+ value="",
+ advanced=True,
+ info="The filter string to narrow the search to according to metadata attributes.",
+ ),
+ DropdownInput(
+ name="reranker",
+ display_name="Reranker Type",
+ options=RERANKER_TYPES,
+ value=RERANKER_TYPES[0],
+ info="How to rerank the retrieved search results.",
+ ),
+ IntInput(
+ name="reranker_k",
+ display_name="Number of Results to Rerank",
+ value=50,
+ range_spec=RangeSpec(min=1, max=100, step=1),
+ advanced=True,
+ ),
+ FloatInput(
+ name="diversity_bias",
+ display_name="Diversity Bias",
+ value=0.2,
+ range_spec=RangeSpec(min=0, max=1, step=0.01),
+ advanced=True,
+ info="Ranges from 0 to 1, with higher values indicating greater diversity (only applies to MMR reranker).",
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results to Summarize",
+ value=7,
+ range_spec=RangeSpec(min=1, max=100, step=1),
+ advanced=True,
+ info="The maximum number of search results to be available to the prompt.",
+ ),
+ DropdownInput(
+ name="response_lang",
+ display_name="Response Language",
+ options=RESPONSE_LANGUAGES,
+ value="eng",
+ advanced=True,
+ info="Use the ISO 639-1 or 639-3 language code or auto to automatically detect the language.",
+ ),
+ DropdownInput(
+ name="prompt",
+ display_name="Prompt Name",
+ options=SUMMARIZER_PROMPTS,
+ value=SUMMARIZER_PROMPTS[0],
+ advanced=True,
+ info="Only vectara-summary-ext-24-05-sml is for Growth customers; "
+ "all other prompts are for Scale customers only.",
+ ),
+ ]
+
+ outputs = [
+ Output(name="answer", display_name="Answer", method="generate_response"),
+ ]
+
+ def generate_response(
+ self,
+ ) -> Message:
+ text_output = ""
+
+ try:
+ from langchain_community.vectorstores import Vectara
+ from langchain_community.vectorstores.vectara import RerankConfig, SummaryConfig, VectaraQueryConfig
+ except ImportError as e:
+ msg = "Could not import Vectara. Please install it with `pip install langchain-community`."
+ raise ImportError(msg) from e
+
+ vectara = Vectara(self.vectara_customer_id, self.vectara_corpus_id, self.vectara_api_key)
+ rerank_config = RerankConfig(self.reranker, self.reranker_k, self.diversity_bias)
+ summary_config = SummaryConfig(
+ is_enabled=True, max_results=self.max_results, response_lang=self.response_lang, prompt_name=self.prompt
+ )
+ config = VectaraQueryConfig(
+ lambda_val=self.lexical_interpolation,
+ filter=self.filter,
+ summary_config=summary_config,
+ rerank_config=rerank_config,
+ )
+ rag = vectara.as_rag(config)
+ response = rag.invoke(self.search_query, config={"callbacks": self.get_langchain_callbacks()})
+
+ text_output = response["answer"]
+
+ return Message(text=text_output)
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/vectara_self_query.py b/langflow/src/backend/base/langflow/components/vectorstores/vectara_self_query.py
new file mode 100644
index 0000000..62d2cfc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/vectara_self_query.py
@@ -0,0 +1,66 @@
+import json
+from typing import cast
+
+from langchain.chains.query_constructor.base import AttributeInfo
+from langchain.retrievers.self_query.base import SelfQueryRetriever
+from langchain_core.vectorstores import VectorStore
+
+from langflow.custom import CustomComponent
+from langflow.field_typing import Retriever
+from langflow.field_typing.constants import LanguageModel
+
+
+class VectaraSelfQueryRetriverComponent(CustomComponent):
+ """A custom component for implementing Vectara Self Query Retriever using a vector store."""
+
+ display_name: str = "Vectara Self Query Retriever for Vectara Vector Store"
+ description: str = "Implementation of Vectara Self Query Retriever"
+ name = "VectaraSelfQueryRetriver"
+ icon = "Vectara"
+ legacy = True
+
+ field_config = {
+ "code": {"show": True},
+ "vectorstore": {"display_name": "Vector Store", "info": "Input Vectara Vectore Store"},
+ "llm": {"display_name": "LLM", "info": "For self query retriever"},
+ "document_content_description": {
+ "display_name": "Document Content Description",
+ "info": "For self query retriever",
+ },
+ "metadata_field_info": {
+ "display_name": "Metadata Field Info",
+ "info": "Each metadata field info is a string in the form of key value pair dictionary containing "
+ "additional search metadata.\n"
+ 'Example input: {"name":"speech","description":"what name of the speech","type":'
+ '"string or list[string]"}.\n'
+ "The keys should remain constant(name, description, type)",
+ },
+ }
+
+ def build(
+ self,
+ vectorstore: VectorStore,
+ document_content_description: str,
+ llm: LanguageModel,
+ metadata_field_info: list[str],
+ ) -> Retriever:
+ metadata_field_obj = []
+
+ for meta in metadata_field_info:
+ meta_obj = json.loads(meta)
+ if "name" not in meta_obj or "description" not in meta_obj or "type" not in meta_obj:
+ msg = "Incorrect metadata field info format."
+ raise ValueError(msg)
+ attribute_info = AttributeInfo(
+ name=meta_obj["name"],
+ description=meta_obj["description"],
+ type=meta_obj["type"],
+ )
+ metadata_field_obj.append(attribute_info)
+
+ return cast(
+ "Retriever",
+ SelfQueryRetriever.from_llm(
+ llm, vectorstore, document_content_description, metadata_field_obj, verbose=True
+ ),
+ )
diff --git a/langflow/src/backend/base/langflow/components/vectorstores/weaviate.py b/langflow/src/backend/base/langflow/components/vectorstores/weaviate.py
new file mode 100644
index 0000000..feaabdb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/vectorstores/weaviate.py
@@ -0,0 +1,86 @@
+import weaviate
+from langchain_community.vectorstores import Weaviate
+
+from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
+from langflow.helpers.data import docs_to_data
+from langflow.io import BoolInput, HandleInput, IntInput, SecretStrInput, StrInput
+from langflow.schema import Data
+
+
+class WeaviateVectorStoreComponent(LCVectorStoreComponent):
+ display_name = "Weaviate"
+ description = "Weaviate Vector Store with search capabilities"
+ name = "Weaviate"
+ icon = "Weaviate"
+
+ inputs = [
+ StrInput(name="url", display_name="Weaviate URL", value="http://localhost:8080", required=True),
+ SecretStrInput(name="api_key", display_name="API Key", required=False),
+ StrInput(
+ name="index_name",
+ display_name="Index Name",
+ required=True,
+ info="Requires capitalized index name.",
+ ),
+ StrInput(name="text_key", display_name="Text Key", value="text", advanced=True),
+ *LCVectorStoreComponent.inputs,
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
+ IntInput(
+ name="number_of_results",
+ display_name="Number of Results",
+ info="Number of results to return.",
+ value=4,
+ advanced=True,
+ ),
+ BoolInput(name="search_by_text", display_name="Search By Text", advanced=True),
+ ]
+
+ @check_cached_vector_store
+ def build_vector_store(self) -> Weaviate:
+ if self.api_key:
+ auth_config = weaviate.AuthApiKey(api_key=self.api_key)
+ client = weaviate.Client(url=self.url, auth_client_secret=auth_config)
+ else:
+ client = weaviate.Client(url=self.url)
+
+ if self.index_name != self.index_name.capitalize():
+ msg = f"Weaviate requires the index name to be capitalized. Use: {self.index_name.capitalize()}"
+ raise ValueError(msg)
+
+ documents = []
+ for _input in self.ingest_data or []:
+ if isinstance(_input, Data):
+ documents.append(_input.to_lc_document())
+ else:
+ documents.append(_input)
+
+ if documents and self.embedding:
+ return Weaviate.from_documents(
+ client=client,
+ index_name=self.index_name,
+ documents=documents,
+ embedding=self.embedding,
+ by_text=self.search_by_text,
+ )
+
+ return Weaviate(
+ client=client,
+ index_name=self.index_name,
+ text_key=self.text_key,
+ embedding=self.embedding,
+ by_text=self.search_by_text,
+ )
+
+ def search_documents(self) -> list[Data]:
+ vector_store = self.build_vector_store()
+
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
+ docs = vector_store.similarity_search(
+ query=self.search_query,
+ k=self.number_of_results,
+ )
+
+ data = docs_to_data(docs)
+ self.status = data
+ return data
+ return []
diff --git a/langflow/src/backend/base/langflow/components/youtube/__init__.py b/langflow/src/backend/base/langflow/components/youtube/__init__.py
new file mode 100644
index 0000000..4c4ab6f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/__init__.py
@@ -0,0 +1,17 @@
+from .channel import YouTubeChannelComponent
+from .comments import YouTubeCommentsComponent
+from .playlist import YouTubePlaylistComponent
+from .search import YouTubeSearchComponent
+from .trending import YouTubeTrendingComponent
+from .video_details import YouTubeVideoDetailsComponent
+from .youtube_transcripts import YouTubeTranscriptsComponent
+
+__all__ = [
+ "YouTubeChannelComponent",
+ "YouTubeCommentsComponent",
+ "YouTubePlaylistComponent",
+ "YouTubeSearchComponent",
+ "YouTubeTranscriptsComponent",
+ "YouTubeTrendingComponent",
+ "YouTubeVideoDetailsComponent",
+]
diff --git a/langflow/src/backend/base/langflow/components/youtube/channel.py b/langflow/src/backend/base/langflow/components/youtube/channel.py
new file mode 100644
index 0000000..62a6e5b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/channel.py
@@ -0,0 +1,227 @@
+from typing import Any
+from urllib.error import HTTPError
+
+import pandas as pd
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, MessageTextInput, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.template import Output
+
+
+class YouTubeChannelComponent(Component):
+ """A component that retrieves detailed information about YouTube channels."""
+
+ display_name: str = "YouTube Channel"
+ description: str = "Retrieves detailed information and statistics about YouTube channels as a DataFrame."
+ icon: str = "YouTube"
+
+ # Constants
+ CHANNEL_ID_LENGTH = 24
+ QUOTA_EXCEEDED_STATUS = 403
+ NOT_FOUND_STATUS = 404
+ MAX_PLAYLIST_RESULTS = 10
+
+ inputs = [
+ MessageTextInput(
+ name="channel_url",
+ display_name="Channel URL or ID",
+ info="The URL or ID of the YouTube channel.",
+ tool_mode=True,
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="YouTube API Key",
+ info="Your YouTube Data API key.",
+ required=True,
+ ),
+ BoolInput(
+ name="include_statistics",
+ display_name="Include Statistics",
+ value=True,
+ info="Include channel statistics (views, subscribers, videos).",
+ ),
+ BoolInput(
+ name="include_branding",
+ display_name="Include Branding",
+ value=True,
+ info="Include channel branding settings (banner, thumbnails).",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_playlists",
+ display_name="Include Playlists",
+ value=False,
+ info="Include channel's public playlists.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="channel_df", display_name="Channel Info", method="get_channel_info"),
+ ]
+
+ def _extract_channel_id(self, channel_url: str) -> str:
+ """Extracts the channel ID from various YouTube channel URL formats."""
+ import re
+
+ if channel_url.startswith("UC") and len(channel_url) == self.CHANNEL_ID_LENGTH:
+ return channel_url
+
+ patterns = {
+ "custom_url": r"youtube\.com\/c\/([^\/\n?]+)",
+ "channel_id": r"youtube\.com\/channel\/([^\/\n?]+)",
+ "user": r"youtube\.com\/user\/([^\/\n?]+)",
+ "handle": r"youtube\.com\/@([^\/\n?]+)",
+ }
+
+ for pattern_type, pattern in patterns.items():
+ match = re.search(pattern, channel_url)
+ if match:
+ if pattern_type == "channel_id":
+ return match.group(1)
+ return self._get_channel_id_by_name(match.group(1), pattern_type)
+
+ return channel_url
+
+ def _get_channel_id_by_name(self, channel_name: str, identifier_type: str) -> str:
+ """Gets the channel ID using the channel name or custom URL."""
+ youtube = None
+ try:
+ youtube = build("youtube", "v3", developerKey=self.api_key)
+
+ if identifier_type == "handle":
+ channel_name = channel_name.lstrip("@")
+
+ request = youtube.search().list(part="id", q=channel_name, type="channel", maxResults=1)
+ response = request.execute()
+
+ if response["items"]:
+ return response["items"][0]["id"]["channelId"]
+
+ error_msg = f"Could not find channel ID for: {channel_name}"
+ raise ValueError(error_msg)
+
+ except (HttpError, HTTPError) as e:
+ error_msg = f"YouTube API error while getting channel ID: {e!s}"
+ raise RuntimeError(error_msg) from e
+ except Exception as e:
+ error_msg = f"Unexpected error while getting channel ID: {e!s}"
+ raise ValueError(error_msg) from e
+ finally:
+ if youtube:
+ youtube.close()
+
+ def _get_channel_playlists(self, youtube: Any, channel_id: str) -> list[dict[str, Any]]:
+ """Gets the public playlists for a channel."""
+ try:
+ playlists_request = youtube.playlists().list(
+ part="snippet,contentDetails",
+ channelId=channel_id,
+ maxResults=self.MAX_PLAYLIST_RESULTS,
+ )
+ playlists_response = playlists_request.execute()
+ playlists = []
+
+ for item in playlists_response.get("items", []):
+ playlist_data = {
+ "playlist_title": item["snippet"]["title"],
+ "playlist_description": item["snippet"]["description"],
+ "playlist_id": item["id"],
+ "playlist_video_count": item["contentDetails"]["itemCount"],
+ "playlist_published_at": item["snippet"]["publishedAt"],
+ "playlist_thumbnail_url": item["snippet"]["thumbnails"]["default"]["url"],
+ }
+ playlists.append(playlist_data)
+
+ return playlists
+ except (HttpError, HTTPError) as e:
+ return [{"error": str(e)}]
+ else:
+ return playlists
+
+ def get_channel_info(self) -> DataFrame:
+ """Retrieves channel information and returns it as a DataFrame."""
+ youtube = None
+ try:
+ # Get channel ID and initialize YouTube API client
+ channel_id = self._extract_channel_id(self.channel_url)
+ youtube = build("youtube", "v3", developerKey=self.api_key)
+
+ # Prepare parts for the API request
+ parts = ["snippet", "contentDetails"]
+ if self.include_statistics:
+ parts.append("statistics")
+ if self.include_branding:
+ parts.append("brandingSettings")
+
+ # Get channel information
+ channel_response = youtube.channels().list(part=",".join(parts), id=channel_id).execute()
+
+ if not channel_response["items"]:
+ return DataFrame(pd.DataFrame({"error": ["Channel not found"]}))
+
+ channel_info = channel_response["items"][0]
+
+ # Build basic channel data
+ channel_data = {
+ "title": [channel_info["snippet"]["title"]],
+ "description": [channel_info["snippet"]["description"]],
+ "custom_url": [channel_info["snippet"].get("customUrl", "")],
+ "published_at": [channel_info["snippet"]["publishedAt"]],
+ "country": [channel_info["snippet"].get("country", "Not specified")],
+ "channel_id": [channel_id],
+ }
+
+ # Add thumbnails
+ for size, thumb in channel_info["snippet"]["thumbnails"].items():
+ channel_data[f"thumbnail_{size}"] = [thumb["url"]]
+
+ # Add statistics if requested
+ if self.include_statistics:
+ stats = channel_info["statistics"]
+ channel_data.update(
+ {
+ "view_count": [int(stats.get("viewCount", 0))],
+ "subscriber_count": [int(stats.get("subscriberCount", 0))],
+ "hidden_subscriber_count": [stats.get("hiddenSubscriberCount", False)],
+ "video_count": [int(stats.get("videoCount", 0))],
+ }
+ )
+
+ # Add branding if requested
+ if self.include_branding:
+ branding = channel_info.get("brandingSettings", {})
+ channel_data.update(
+ {
+ "brand_title": [branding.get("channel", {}).get("title", "")],
+ "brand_description": [branding.get("channel", {}).get("description", "")],
+ "brand_keywords": [branding.get("channel", {}).get("keywords", "")],
+ "brand_banner_url": [branding.get("image", {}).get("bannerExternalUrl", "")],
+ }
+ )
+
+ # Create the initial DataFrame
+ channel_df = pd.DataFrame(channel_data)
+
+ # Add playlists if requested
+ if self.include_playlists:
+ playlists = self._get_channel_playlists(youtube, channel_id)
+ if playlists and "error" not in playlists[0]:
+ # Create a DataFrame for playlists
+ playlists_df = pd.DataFrame(playlists)
+ # Join with main DataFrame
+ channel_df = pd.concat([channel_df] * len(playlists_df), ignore_index=True)
+ for column in playlists_df.columns:
+ channel_df[column] = playlists_df[column].to_numpy()
+
+ return DataFrame(channel_df)
+
+ except (HttpError, HTTPError, Exception) as e:
+ return DataFrame(pd.DataFrame({"error": [str(e)]}))
+ finally:
+ if youtube:
+ youtube.close()
diff --git a/langflow/src/backend/base/langflow/components/youtube/comments.py b/langflow/src/backend/base/langflow/components/youtube/comments.py
new file mode 100644
index 0000000..05fccce
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/comments.py
@@ -0,0 +1,231 @@
+from contextlib import contextmanager
+
+import pandas as pd
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.template import Output
+
+
+class YouTubeCommentsComponent(Component):
+ """A component that retrieves comments from YouTube videos."""
+
+ display_name: str = "YouTube Comments"
+ description: str = "Retrieves and analyzes comments from YouTube videos."
+ icon: str = "YouTube"
+
+ # Constants
+ COMMENTS_DISABLED_STATUS = 403
+ NOT_FOUND_STATUS = 404
+ API_MAX_RESULTS = 100
+
+ inputs = [
+ MessageTextInput(
+ name="video_url",
+ display_name="Video URL",
+ info="The URL of the YouTube video to get comments from.",
+ tool_mode=True,
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="YouTube API Key",
+ info="Your YouTube Data API key.",
+ required=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ value=20,
+ info="The maximum number of comments to return.",
+ ),
+ DropdownInput(
+ name="sort_by",
+ display_name="Sort By",
+ options=["time", "relevance"],
+ value="relevance",
+ info="Sort comments by time or relevance.",
+ ),
+ BoolInput(
+ name="include_replies",
+ display_name="Include Replies",
+ value=False,
+ info="Whether to include replies to comments.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_metrics",
+ display_name="Include Metrics",
+ value=True,
+ info="Include metrics like like count and reply count.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="comments", display_name="Comments", method="get_video_comments"),
+ ]
+
+ def _extract_video_id(self, video_url: str) -> str:
+ """Extracts the video ID from a YouTube URL."""
+ import re
+
+ patterns = [
+ r"(?:youtube\.com\/watch\?v=|youtu.be\/|youtube.com\/embed\/)([^&\n?#]+)",
+ r"youtube.com\/shorts\/([^&\n?#]+)",
+ ]
+
+ for pattern in patterns:
+ match = re.search(pattern, video_url)
+ if match:
+ return match.group(1)
+
+ return video_url.strip()
+
+ def _process_reply(self, reply: dict, parent_id: str, *, include_metrics: bool = True) -> dict:
+ """Process a single reply comment."""
+ reply_snippet = reply["snippet"]
+ reply_data = {
+ "comment_id": reply["id"],
+ "parent_comment_id": parent_id,
+ "author": reply_snippet["authorDisplayName"],
+ "text": reply_snippet["textDisplay"],
+ "published_at": reply_snippet["publishedAt"],
+ "is_reply": True,
+ }
+ if include_metrics:
+ reply_data["like_count"] = reply_snippet["likeCount"]
+ reply_data["reply_count"] = 0 # Replies can't have replies
+
+ return reply_data
+
+ def _process_comment(
+ self, item: dict, *, include_metrics: bool = True, include_replies: bool = False
+ ) -> list[dict]:
+ """Process a single comment thread."""
+ comment = item["snippet"]["topLevelComment"]["snippet"]
+ comment_id = item["snippet"]["topLevelComment"]["id"]
+
+ # Basic comment data
+ processed_comments = [
+ {
+ "comment_id": comment_id,
+ "parent_comment_id": "", # Empty for top-level comments
+ "author": comment["authorDisplayName"],
+ "author_channel_url": comment.get("authorChannelUrl", ""),
+ "text": comment["textDisplay"],
+ "published_at": comment["publishedAt"],
+ "updated_at": comment["updatedAt"],
+ "is_reply": False,
+ }
+ ]
+
+ # Add metrics if requested
+ if include_metrics:
+ processed_comments[0].update(
+ {
+ "like_count": comment["likeCount"],
+ "reply_count": item["snippet"]["totalReplyCount"],
+ }
+ )
+
+ # Add replies if requested
+ if include_replies and item["snippet"]["totalReplyCount"] > 0 and "replies" in item:
+ for reply in item["replies"]["comments"]:
+ reply_data = self._process_reply(reply, parent_id=comment_id, include_metrics=include_metrics)
+ processed_comments.append(reply_data)
+
+ return processed_comments
+
+ @contextmanager
+ def youtube_client(self):
+ """Context manager for YouTube API client."""
+ client = build("youtube", "v3", developerKey=self.api_key)
+ try:
+ yield client
+ finally:
+ client.close()
+
+ def get_video_comments(self) -> DataFrame:
+ """Retrieves comments from a YouTube video and returns as DataFrame."""
+ try:
+ # Extract video ID from URL
+ video_id = self._extract_video_id(self.video_url)
+
+ # Use context manager for YouTube API client
+ with self.youtube_client() as youtube:
+ comments_data = []
+ results_count = 0
+ request = youtube.commentThreads().list(
+ part="snippet,replies",
+ videoId=video_id,
+ maxResults=min(self.API_MAX_RESULTS, self.max_results),
+ order=self.sort_by,
+ textFormat="plainText",
+ )
+
+ while request and results_count < self.max_results:
+ response = request.execute()
+
+ for item in response.get("items", []):
+ if results_count >= self.max_results:
+ break
+
+ comments = self._process_comment(
+ item, include_metrics=self.include_metrics, include_replies=self.include_replies
+ )
+ comments_data.extend(comments)
+ results_count += 1
+
+ # Get the next page if available and needed
+ if "nextPageToken" in response and results_count < self.max_results:
+ request = youtube.commentThreads().list(
+ part="snippet,replies",
+ videoId=video_id,
+ maxResults=min(self.API_MAX_RESULTS, self.max_results - results_count),
+ order=self.sort_by,
+ textFormat="plainText",
+ pageToken=response["nextPageToken"],
+ )
+ else:
+ request = None
+
+ # Convert to DataFrame
+ comments_df = pd.DataFrame(comments_data)
+
+ # Add video metadata
+ comments_df["video_id"] = video_id
+ comments_df["video_url"] = self.video_url
+
+ # Sort columns for better organization
+ column_order = [
+ "video_id",
+ "video_url",
+ "comment_id",
+ "parent_comment_id",
+ "is_reply",
+ "author",
+ "author_channel_url",
+ "text",
+ "published_at",
+ "updated_at",
+ ]
+
+ if self.include_metrics:
+ column_order.extend(["like_count", "reply_count"])
+
+ comments_df = comments_df[column_order]
+
+ return DataFrame(comments_df)
+
+ except HttpError as e:
+ error_message = f"YouTube API error: {e!s}"
+ if e.resp.status == self.COMMENTS_DISABLED_STATUS:
+ error_message = "Comments are disabled for this video or API quota exceeded."
+ elif e.resp.status == self.NOT_FOUND_STATUS:
+ error_message = "Video not found."
+
+ return DataFrame(pd.DataFrame({"error": [error_message]}))
diff --git a/langflow/src/backend/base/langflow/components/youtube/playlist.py b/langflow/src/backend/base/langflow/components/youtube/playlist.py
new file mode 100644
index 0000000..d81d657
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/playlist.py
@@ -0,0 +1,32 @@
+from pytube import Playlist # Ensure you have pytube installed
+
+from langflow.custom import Component
+from langflow.inputs import MessageTextInput
+from langflow.schema import Data, DataFrame
+from langflow.template import Output
+
+
+class YouTubePlaylistComponent(Component):
+ display_name = "Youtube Playlist"
+ description = "Extracts all video URLs from a YouTube playlist."
+ icon = "YouTube" # Replace with a suitable icon
+
+ inputs = [
+ MessageTextInput(
+ name="playlist_url",
+ display_name="Playlist URL",
+ info="URL of the YouTube playlist.",
+ required=True,
+ ),
+ ]
+
+ outputs = [
+ Output(display_name="Video URLs", name="video_urls", method="extract_video_urls"),
+ ]
+
+ def extract_video_urls(self) -> DataFrame:
+ playlist_url = self.playlist_url
+ playlist = Playlist(playlist_url)
+ video_urls = [video.watch_url for video in playlist.videos]
+
+ return DataFrame([Data(data={"video_url": url}) for url in video_urls])
diff --git a/langflow/src/backend/base/langflow/components/youtube/search.py b/langflow/src/backend/base/langflow/components/youtube/search.py
new file mode 100644
index 0000000..1efdee7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/search.py
@@ -0,0 +1,120 @@
+from contextlib import contextmanager
+
+import pandas as pd
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.template import Output
+
+
+class YouTubeSearchComponent(Component):
+ """A component that searches YouTube videos."""
+
+ display_name: str = "YouTube Search"
+ description: str = "Searches YouTube videos based on query."
+ icon: str = "YouTube"
+
+ inputs = [
+ MessageTextInput(
+ name="query",
+ display_name="Search Query",
+ info="The search query to look for on YouTube.",
+ tool_mode=True,
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="YouTube API Key",
+ info="Your YouTube Data API key.",
+ required=True,
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ value=10,
+ info="The maximum number of results to return.",
+ ),
+ DropdownInput(
+ name="order",
+ display_name="Sort Order",
+ options=["relevance", "date", "rating", "title", "viewCount"],
+ value="relevance",
+ info="Sort order for the search results.",
+ ),
+ BoolInput(
+ name="include_metadata",
+ display_name="Include Metadata",
+ value=True,
+ info="Include video metadata like description and statistics.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="results", display_name="Search Results", method="search_videos"),
+ ]
+
+ @contextmanager
+ def youtube_client(self):
+ """Context manager for YouTube API client."""
+ client = build("youtube", "v3", developerKey=self.api_key)
+ try:
+ yield client
+ finally:
+ client.close()
+
+ def search_videos(self) -> DataFrame:
+ """Searches YouTube videos and returns results as DataFrame."""
+ try:
+ with self.youtube_client() as youtube:
+ search_response = (
+ youtube.search()
+ .list(
+ q=self.query,
+ part="id,snippet",
+ maxResults=self.max_results,
+ order=self.order,
+ type="video",
+ )
+ .execute()
+ )
+
+ results = []
+ for search_result in search_response.get("items", []):
+ video_id = search_result["id"]["videoId"]
+ snippet = search_result["snippet"]
+
+ result = {
+ "video_id": video_id,
+ "title": snippet["title"],
+ "description": snippet["description"],
+ "published_at": snippet["publishedAt"],
+ "channel_title": snippet["channelTitle"],
+ "thumbnail_url": snippet["thumbnails"]["default"]["url"],
+ }
+
+ if self.include_metadata:
+ # Get video details for additional metadata
+ video_response = youtube.videos().list(part="statistics,contentDetails", id=video_id).execute()
+
+ if video_response.get("items"):
+ video_details = video_response["items"][0]
+ result.update(
+ {
+ "view_count": int(video_details["statistics"]["viewCount"]),
+ "like_count": int(video_details["statistics"].get("likeCount", 0)),
+ "comment_count": int(video_details["statistics"].get("commentCount", 0)),
+ "duration": video_details["contentDetails"]["duration"],
+ }
+ )
+
+ results.append(result)
+
+ return DataFrame(pd.DataFrame(results))
+
+ except HttpError as e:
+ error_message = f"YouTube API error: {e!s}"
+ return DataFrame(pd.DataFrame({"error": [error_message]}))
diff --git a/langflow/src/backend/base/langflow/components/youtube/trending.py b/langflow/src/backend/base/langflow/components/youtube/trending.py
new file mode 100644
index 0000000..85ad669
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/trending.py
@@ -0,0 +1,286 @@
+from contextlib import contextmanager
+
+import pandas as pd
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, DropdownInput, IntInput, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.template import Output
+
+HTTP_FORBIDDEN = 403
+HTTP_NOT_FOUND = 404
+MAX_API_RESULTS = 50
+
+
+class YouTubeTrendingComponent(Component):
+ """A component that retrieves trending videos from YouTube."""
+
+ display_name: str = "YouTube Trending"
+ description: str = "Retrieves trending videos from YouTube with filtering options."
+ icon: str = "YouTube"
+
+ # Dictionary of country codes and names
+ COUNTRY_CODES = {
+ "Global": "US", # Default to US for global
+ "United States": "US",
+ "Brazil": "BR",
+ "United Kingdom": "GB",
+ "India": "IN",
+ "Japan": "JP",
+ "South Korea": "KR",
+ "Germany": "DE",
+ "France": "FR",
+ "Canada": "CA",
+ "Australia": "AU",
+ "Spain": "ES",
+ "Italy": "IT",
+ "Mexico": "MX",
+ "Russia": "RU",
+ "Netherlands": "NL",
+ "Poland": "PL",
+ "Argentina": "AR",
+ }
+
+ # Dictionary of video categories
+ VIDEO_CATEGORIES = {
+ "All": "0",
+ "Film & Animation": "1",
+ "Autos & Vehicles": "2",
+ "Music": "10",
+ "Pets & Animals": "15",
+ "Sports": "17",
+ "Travel & Events": "19",
+ "Gaming": "20",
+ "People & Blogs": "22",
+ "Comedy": "23",
+ "Entertainment": "24",
+ "News & Politics": "25",
+ "Education": "27",
+ "Science & Technology": "28",
+ "Nonprofits & Activism": "29",
+ }
+
+ inputs = [
+ SecretStrInput(
+ name="api_key",
+ display_name="YouTube API Key",
+ info="Your YouTube Data API key.",
+ required=True,
+ ),
+ DropdownInput(
+ name="region",
+ display_name="Region",
+ options=list(COUNTRY_CODES.keys()),
+ value="Global",
+ info="The region to get trending videos from.",
+ ),
+ DropdownInput(
+ name="category",
+ display_name="Category",
+ options=list(VIDEO_CATEGORIES.keys()),
+ value="All",
+ info="The category of videos to retrieve.",
+ ),
+ IntInput(
+ name="max_results",
+ display_name="Max Results",
+ value=10,
+ info="Maximum number of trending videos to return (1-50).",
+ ),
+ BoolInput(
+ name="include_statistics",
+ display_name="Include Statistics",
+ value=True,
+ info="Include video statistics (views, likes, comments).",
+ ),
+ BoolInput(
+ name="include_content_details",
+ display_name="Include Content Details",
+ value=True,
+ info="Include video duration and quality info.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_thumbnails",
+ display_name="Include Thumbnails",
+ value=True,
+ info="Include video thumbnail URLs.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="trending_videos", display_name="Trending Videos", method="get_trending_videos"),
+ ]
+
+ max_results: int
+
+ def _format_duration(self, duration: str) -> str:
+ """Formats ISO 8601 duration to readable format."""
+ import re
+
+ # Remove 'PT' from the start of duration
+ duration = duration[2:]
+
+ hours = 0
+ minutes = 0
+ seconds = 0
+
+ # Extract hours, minutes and seconds
+ time_dict = {}
+ for time_unit in ["H", "M", "S"]:
+ match = re.search(r"(\d+)" + time_unit, duration)
+ if match:
+ time_dict[time_unit] = int(match.group(1))
+
+ if "H" in time_dict:
+ hours = time_dict["H"]
+ if "M" in time_dict:
+ minutes = time_dict["M"]
+ if "S" in time_dict:
+ seconds = time_dict["S"]
+
+ # Format the time string
+ if hours > 0:
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
+ return f"{minutes:02d}:{seconds:02d}"
+
+ @contextmanager
+ def youtube_client(self):
+ """Context manager for YouTube API client."""
+ client = build("youtube", "v3", developerKey=self.api_key)
+ try:
+ yield client
+ finally:
+ client.close()
+
+ def get_trending_videos(self) -> DataFrame:
+ """Retrieves trending videos from YouTube and returns as DataFrame."""
+ try:
+ # Validate max_results
+ if not 1 <= self.max_results <= MAX_API_RESULTS:
+ self.max_results = min(max(1, self.max_results), MAX_API_RESULTS)
+
+ # Use context manager for YouTube API client
+ with self.youtube_client() as youtube:
+ # Get country code
+ region_code = self.COUNTRY_CODES[self.region]
+
+ # Prepare API request parts
+ parts = ["snippet"]
+ if self.include_statistics:
+ parts.append("statistics")
+ if self.include_content_details:
+ parts.append("contentDetails")
+
+ # Prepare API request parameters
+ request_params = {
+ "part": ",".join(parts),
+ "chart": "mostPopular",
+ "regionCode": region_code,
+ "maxResults": self.max_results,
+ }
+
+ # Add category filter if not "All"
+ if self.category != "All":
+ request_params["videoCategoryId"] = self.VIDEO_CATEGORIES[self.category]
+
+ # Get trending videos
+ request = youtube.videos().list(**request_params)
+ response = request.execute()
+
+ videos_data = []
+ for item in response.get("items", []):
+ video_data = {
+ "video_id": item["id"],
+ "title": item["snippet"]["title"],
+ "description": item["snippet"]["description"],
+ "channel_id": item["snippet"]["channelId"],
+ "channel_title": item["snippet"]["channelTitle"],
+ "published_at": item["snippet"]["publishedAt"],
+ "url": f"https://www.youtube.com/watch?v={item['id']}",
+ "region": self.region,
+ "category": self.category,
+ }
+
+ # Add thumbnails if requested
+ if self.include_thumbnails:
+ for size, thumb in item["snippet"]["thumbnails"].items():
+ video_data[f"thumbnail_{size}_url"] = thumb["url"]
+ video_data[f"thumbnail_{size}_width"] = thumb.get("width", 0)
+ video_data[f"thumbnail_{size}_height"] = thumb.get("height", 0)
+
+ # Add statistics if requested
+ if self.include_statistics and "statistics" in item:
+ video_data.update(
+ {
+ "view_count": int(item["statistics"].get("viewCount", 0)),
+ "like_count": int(item["statistics"].get("likeCount", 0)),
+ "comment_count": int(item["statistics"].get("commentCount", 0)),
+ }
+ )
+
+ # Add content details if requested
+ if self.include_content_details and "contentDetails" in item:
+ content_details = item["contentDetails"]
+ video_data.update(
+ {
+ "duration": self._format_duration(content_details["duration"]),
+ "definition": content_details.get("definition", "hd").upper(),
+ "has_captions": content_details.get("caption", "false") == "true",
+ "licensed_content": content_details.get("licensedContent", False),
+ "projection": content_details.get("projection", "rectangular"),
+ }
+ )
+
+ videos_data.append(video_data)
+
+ # Convert to DataFrame
+ videos_df = pd.DataFrame(videos_data)
+
+ # Organize columns
+ column_order = [
+ "video_id",
+ "title",
+ "channel_id",
+ "channel_title",
+ "category",
+ "region",
+ "published_at",
+ "url",
+ "description",
+ ]
+
+ if self.include_statistics:
+ column_order.extend(["view_count", "like_count", "comment_count"])
+
+ if self.include_content_details:
+ column_order.extend(["duration", "definition", "has_captions", "licensed_content", "projection"])
+
+ # Add thumbnail columns at the end if included
+ if self.include_thumbnails:
+ thumbnail_cols = [col for col in videos_df.columns if col.startswith("thumbnail_")]
+ column_order.extend(sorted(thumbnail_cols))
+
+ # Reorder columns, including any that might not be in column_order
+ remaining_cols = [col for col in videos_df.columns if col not in column_order]
+ videos_df = videos_df[column_order + remaining_cols]
+
+ return DataFrame(videos_df)
+
+ except HttpError as e:
+ error_message = f"YouTube API error: {e}"
+ if e.resp.status == HTTP_FORBIDDEN:
+ error_message = "API quota exceeded or access forbidden."
+ elif e.resp.status == HTTP_NOT_FOUND:
+ error_message = "Resource not found."
+
+ return DataFrame(pd.DataFrame({"error": [error_message]}))
+
+ except Exception as e:
+ import logging
+
+ logging.exception("An unexpected error occurred:")
+ return DataFrame(pd.DataFrame({"error": [str(e)]}))
diff --git a/langflow/src/backend/base/langflow/components/youtube/video_details.py b/langflow/src/backend/base/langflow/components/youtube/video_details.py
new file mode 100644
index 0000000..0e8332e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/video_details.py
@@ -0,0 +1,263 @@
+from contextlib import contextmanager
+
+import googleapiclient
+import pandas as pd
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+from langflow.custom import Component
+from langflow.inputs import BoolInput, MessageTextInput, SecretStrInput
+from langflow.schema import DataFrame
+from langflow.template import Output
+
+
+class YouTubeVideoDetailsComponent(Component):
+ """A component that retrieves detailed information about YouTube videos."""
+
+ display_name: str = "YouTube Video Details"
+ description: str = "Retrieves detailed information and statistics about YouTube videos."
+ icon: str = "YouTube"
+
+ inputs = [
+ MessageTextInput(
+ name="video_url",
+ display_name="Video URL",
+ info="The URL of the YouTube video.",
+ tool_mode=True,
+ required=True,
+ ),
+ SecretStrInput(
+ name="api_key",
+ display_name="YouTube API Key",
+ info="Your YouTube Data API key.",
+ required=True,
+ ),
+ BoolInput(
+ name="include_statistics",
+ display_name="Include Statistics",
+ value=True,
+ info="Include video statistics (views, likes, comments).",
+ ),
+ BoolInput(
+ name="include_content_details",
+ display_name="Include Content Details",
+ value=True,
+ info="Include video duration, quality, and age restriction info.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_tags",
+ display_name="Include Tags",
+ value=True,
+ info="Include video tags and keywords.",
+ advanced=True,
+ ),
+ BoolInput(
+ name="include_thumbnails",
+ display_name="Include Thumbnails",
+ value=True,
+ info="Include video thumbnail URLs in different resolutions.",
+ advanced=True,
+ ),
+ ]
+
+ outputs = [
+ Output(name="video_data", display_name="Video Data", method="get_video_details"),
+ ]
+
+ API_FORBIDDEN = 403
+ VIDEO_NOT_FOUND = 404
+
+ @contextmanager
+ def youtube_client(self):
+ """Context manager for YouTube API client."""
+ client = build("youtube", "v3", developerKey=self.api_key)
+ try:
+ yield client
+ finally:
+ client.close()
+
+ def _extract_video_id(self, video_url: str) -> str:
+ """Extracts the video ID from a YouTube URL."""
+ import re
+
+ patterns = [
+ r"(?:youtube\.com\/watch\?v=|youtu.be\/|youtube.com\/embed\/)([^&\n?#]+)",
+ r"youtube.com\/shorts\/([^&\n?#]+)",
+ ]
+
+ for pattern in patterns:
+ match = re.search(pattern, video_url)
+ if match:
+ return match.group(1)
+
+ return video_url.strip()
+
+ def _format_duration(self, duration: str) -> str:
+ """Formats the ISO 8601 duration to a readable format."""
+ import re
+
+ hours = 0
+ minutes = 0
+ seconds = 0
+
+ hours_match = re.search(r"(\d+)H", duration)
+ minutes_match = re.search(r"(\d+)M", duration)
+ seconds_match = re.search(r"(\d+)S", duration)
+
+ if hours_match:
+ hours = int(hours_match.group(1))
+ if minutes_match:
+ minutes = int(minutes_match.group(1))
+ if seconds_match:
+ seconds = int(seconds_match.group(1))
+
+ if hours > 0:
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
+ return f"{minutes:02d}:{seconds:02d}"
+
+ def get_video_details(self) -> DataFrame:
+ """Retrieves detailed information about a YouTube video and returns as DataFrame."""
+ try:
+ with self.youtube_client() as youtube:
+ # Extract video ID
+ video_id = self._extract_video_id(self.video_url)
+
+ # Prepare parts for the API request
+ parts = ["snippet"]
+ if self.include_statistics:
+ parts.append("statistics")
+ if self.include_content_details:
+ parts.append("contentDetails")
+
+ # Get video information
+ video_response = youtube.videos().list(part=",".join(parts), id=video_id).execute()
+
+ if not video_response["items"]:
+ return DataFrame(pd.DataFrame({"error": ["Video not found"]}))
+
+ video_info = video_response["items"][0]
+ snippet = video_info["snippet"]
+
+ # Build video data dictionary
+ video_data = {
+ "video_id": [video_id],
+ "url": [f"https://www.youtube.com/watch?v={video_id}"],
+ "title": [snippet["title"]],
+ "description": [snippet["description"]],
+ "published_at": [snippet["publishedAt"]],
+ "channel_id": [snippet["channelId"]],
+ "channel_title": [snippet["channelTitle"]],
+ "category_id": [snippet.get("categoryId", "Unknown")],
+ "live_broadcast_content": [snippet.get("liveBroadcastContent", "none")],
+ }
+
+ # Add thumbnails if requested
+ if self.include_thumbnails:
+ for size, thumb in snippet["thumbnails"].items():
+ video_data[f"thumbnail_{size}_url"] = [thumb["url"]]
+ video_data[f"thumbnail_{size}_width"] = [thumb.get("width", 0)]
+ video_data[f"thumbnail_{size}_height"] = [thumb.get("height", 0)]
+
+ # Add tags if requested
+ if self.include_tags and "tags" in snippet:
+ video_data["tags"] = [", ".join(snippet["tags"])]
+ video_data["tags_count"] = [len(snippet["tags"])]
+
+ # Add statistics if requested
+ if self.include_statistics and "statistics" in video_info:
+ stats = video_info["statistics"]
+ video_data.update(
+ {
+ "view_count": [int(stats.get("viewCount", 0))],
+ "like_count": [int(stats.get("likeCount", 0))],
+ "favorite_count": [int(stats.get("favoriteCount", 0))],
+ "comment_count": [int(stats.get("commentCount", 0))],
+ }
+ )
+
+ # Add content details if requested
+ if self.include_content_details and "contentDetails" in video_info:
+ content_details = video_info["contentDetails"]
+ video_data.update(
+ {
+ "duration": [self._format_duration(content_details["duration"])],
+ "dimension": [content_details.get("dimension", "2d")],
+ "definition": [content_details.get("definition", "hd").upper()],
+ "has_captions": [content_details.get("caption", "false") == "true"],
+ "licensed_content": [content_details.get("licensedContent", False)],
+ "projection": [content_details.get("projection", "rectangular")],
+ "has_custom_thumbnails": [content_details.get("hasCustomThumbnail", False)],
+ }
+ )
+
+ # Add content rating if available
+ if "contentRating" in content_details:
+ rating_info = content_details["contentRating"]
+ video_data["content_rating"] = [str(rating_info)]
+
+ # Create DataFrame with organized columns
+ video_df = pd.DataFrame(video_data)
+
+ # Organize columns in logical groups
+ basic_cols = [
+ "video_id",
+ "title",
+ "url",
+ "channel_id",
+ "channel_title",
+ "published_at",
+ "category_id",
+ "live_broadcast_content",
+ "description",
+ ]
+
+ stat_cols = ["view_count", "like_count", "favorite_count", "comment_count"]
+
+ content_cols = [
+ "duration",
+ "dimension",
+ "definition",
+ "has_captions",
+ "licensed_content",
+ "projection",
+ "has_custom_thumbnails",
+ "content_rating",
+ ]
+
+ tag_cols = ["tags", "tags_count"]
+
+ thumb_cols = [col for col in video_df.columns if col.startswith("thumbnail_")]
+
+ # Reorder columns based on what's included
+ ordered_cols = basic_cols.copy()
+
+ if self.include_statistics:
+ ordered_cols.extend([col for col in stat_cols if col in video_df.columns])
+
+ if self.include_content_details:
+ ordered_cols.extend([col for col in content_cols if col in video_df.columns])
+
+ if self.include_tags:
+ ordered_cols.extend([col for col in tag_cols if col in video_df.columns])
+
+ if self.include_thumbnails:
+ ordered_cols.extend(sorted(thumb_cols))
+
+ # Add any remaining columns
+ remaining_cols = [col for col in video_df.columns if col not in ordered_cols]
+ ordered_cols.extend(remaining_cols)
+
+ return DataFrame(video_df[ordered_cols])
+
+ except (HttpError, googleapiclient.errors.HttpError) as e:
+ error_message = f"YouTube API error: {e!s}"
+ if e.resp.status == self.API_FORBIDDEN:
+ error_message = "API quota exceeded or access forbidden."
+ elif e.resp.status == self.VIDEO_NOT_FOUND:
+ error_message = "Video not found."
+
+ return DataFrame(pd.DataFrame({"error": [error_message]}))
+
+ except KeyError as e:
+ return DataFrame(pd.DataFrame({"error": [str(e)]}))
diff --git a/langflow/src/backend/base/langflow/components/youtube/youtube_transcripts.py b/langflow/src/backend/base/langflow/components/youtube/youtube_transcripts.py
new file mode 100644
index 0000000..28ed2ed
--- /dev/null
+++ b/langflow/src/backend/base/langflow/components/youtube/youtube_transcripts.py
@@ -0,0 +1,116 @@
+import pandas as pd
+import youtube_transcript_api
+from langchain_community.document_loaders import YoutubeLoader
+from langchain_community.document_loaders.youtube import TranscriptFormat
+
+from langflow.custom import Component
+from langflow.inputs import DropdownInput, IntInput, MultilineInput
+from langflow.schema import Data, DataFrame, Message
+from langflow.template import Output
+
+
+class YouTubeTranscriptsComponent(Component):
+ """A component that extracts spoken content from YouTube videos as transcripts."""
+
+ display_name: str = "YouTube Transcripts"
+ description: str = "Extracts spoken content from YouTube videos with multiple output options."
+ icon: str = "YouTube"
+ name = "YouTubeTranscripts"
+
+ inputs = [
+ MultilineInput(
+ name="url",
+ display_name="Video URL",
+ info="Enter the YouTube video URL to get transcripts from.",
+ tool_mode=True,
+ required=True,
+ ),
+ IntInput(
+ name="chunk_size_seconds",
+ display_name="Chunk Size (seconds)",
+ value=60,
+ info="The size of each transcript chunk in seconds.",
+ ),
+ DropdownInput(
+ name="translation",
+ display_name="Translation Language",
+ advanced=True,
+ options=["", "en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "hi", "ar", "id"],
+ info="Translate the transcripts to the specified language. Leave empty for no translation.",
+ ),
+ ]
+
+ outputs = [
+ Output(name="dataframe", display_name="Chunks", method="get_dataframe_output"),
+ Output(name="message", display_name="Transcript", method="get_message_output"),
+ Output(name="data_output", display_name="Transcript + Source", method="get_data_output"),
+ ]
+
+ def _load_transcripts(self, *, as_chunks: bool = True):
+ """Internal method to load transcripts from YouTube."""
+ loader = YoutubeLoader.from_youtube_url(
+ self.url,
+ transcript_format=TranscriptFormat.CHUNKS if as_chunks else TranscriptFormat.TEXT,
+ chunk_size_seconds=self.chunk_size_seconds,
+ translation=self.translation or None,
+ )
+ return loader.load()
+
+ def get_dataframe_output(self) -> DataFrame:
+ """Provides transcript output as a DataFrame with timestamp and text columns."""
+ try:
+ transcripts = self._load_transcripts(as_chunks=True)
+
+ # Create DataFrame with timestamp and text columns
+ data = []
+ for doc in transcripts:
+ start_seconds = int(doc.metadata["start_seconds"])
+ start_minutes = start_seconds // 60
+ start_seconds %= 60
+ timestamp = f"{start_minutes:02d}:{start_seconds:02d}"
+ data.append({"timestamp": timestamp, "text": doc.page_content})
+
+ return DataFrame(pd.DataFrame(data))
+
+ except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:
+ return DataFrame(pd.DataFrame({"error": [f"Failed to get YouTube transcripts: {exc!s}"]}))
+
+ def get_message_output(self) -> Message:
+ """Provides transcript output as continuous text."""
+ try:
+ transcripts = self._load_transcripts(as_chunks=False)
+ result = transcripts[0].page_content
+ return Message(text=result)
+
+ except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:
+ error_msg = f"Failed to get YouTube transcripts: {exc!s}"
+ return Message(text=error_msg)
+
+ def get_data_output(self) -> Data:
+ """Creates a structured data object with transcript and metadata.
+
+ Returns a Data object containing transcript text, video URL, and any error
+ messages that occurred during processing. The object includes:
+ - 'transcript': continuous text from the entire video (concatenated if multiple parts)
+ - 'video_url': the input YouTube URL
+ - 'error': error message if an exception occurs
+ """
+ default_data = {"transcript": "", "video_url": self.url, "error": None}
+
+ try:
+ transcripts = self._load_transcripts(as_chunks=False)
+ if not transcripts:
+ default_data["error"] = "No transcripts found."
+ return Data(data=default_data)
+
+ # Combine all transcript parts
+ full_transcript = " ".join(doc.page_content for doc in transcripts)
+ return Data(data={"transcript": full_transcript, "video_url": self.url})
+
+ except (
+ youtube_transcript_api.TranscriptsDisabled,
+ youtube_transcript_api.NoTranscriptFound,
+ youtube_transcript_api.CouldNotRetrieveTranscript,
+ ) as exc:
+ default_data["error"] = str(exc)
+ return Data(data=default_data)
diff --git a/langflow/src/backend/base/langflow/core/__init__.py b/langflow/src/backend/base/langflow/core/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/core/celery_app.py b/langflow/src/backend/base/langflow/core/celery_app.py
new file mode 100644
index 0000000..74e29c5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/core/celery_app.py
@@ -0,0 +1,11 @@
+from celery import Celery
+
+
+def make_celery(app_name: str, config: str) -> Celery:
+ celery_app = Celery(app_name)
+ celery_app.config_from_object(config)
+ celery_app.conf.task_routes = {"langflow.worker.tasks.*": {"queue": "langflow"}}
+ return celery_app
+
+
+celery_app = make_celery("langflow", "langflow.core.celeryconfig")
diff --git a/langflow/src/backend/base/langflow/core/celeryconfig.py b/langflow/src/backend/base/langflow/core/celeryconfig.py
new file mode 100644
index 0000000..1281390
--- /dev/null
+++ b/langflow/src/backend/base/langflow/core/celeryconfig.py
@@ -0,0 +1,18 @@
+# celeryconfig.py
+import os
+
+langflow_redis_host = os.environ.get("LANGFLOW_REDIS_HOST")
+langflow_redis_port = os.environ.get("LANGFLOW_REDIS_PORT")
+# broker default user
+
+if langflow_redis_host and langflow_redis_port:
+ broker_url = f"redis://{langflow_redis_host}:{langflow_redis_port}/0"
+ result_backend = f"redis://{langflow_redis_host}:{langflow_redis_port}/0"
+else:
+ # RabbitMQ
+ mq_user = os.environ.get("RABBITMQ_DEFAULT_USER", "langflow")
+ mq_password = os.environ.get("RABBITMQ_DEFAULT_PASS", "langflow")
+ broker_url = os.environ.get("BROKER_URL", f"amqp://{mq_user}:{mq_password}@localhost:5672//")
+ result_backend = os.environ.get("RESULT_BACKEND", "redis://localhost:6379/0")
+# tasks should be json or pickle
+accept_content = ["json", "pickle"]
diff --git a/langflow/src/backend/base/langflow/custom/__init__.py b/langflow/src/backend/base/langflow/custom/__init__.py
new file mode 100644
index 0000000..55a2b19
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/__init__.py
@@ -0,0 +1,4 @@
+from langflow.custom.custom_component.component import Component
+from langflow.custom.custom_component.custom_component import CustomComponent
+
+__all__ = ["Component", "CustomComponent"]
diff --git a/langflow/src/backend/base/langflow/custom/attributes.py b/langflow/src/backend/base/langflow/custom/attributes.py
new file mode 100644
index 0000000..a9c1ebc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/attributes.py
@@ -0,0 +1,78 @@
+from collections.abc import Callable
+
+import emoji
+from loguru import logger
+
+
+def validate_icon(value: str):
+ # we are going to use the emoji library to validate the emoji
+ # emojis can be defined using the :emoji_name: syntax
+
+ if not value.startswith(":") and not value.endswith(":"):
+ return value
+ if not value.startswith(":") or not value.endswith(":"):
+ # emoji should have both starting and ending colons
+ # so if one of them is missing, we will raise
+ msg = f"Invalid emoji. {value} is not a valid emoji."
+ raise ValueError(msg)
+
+ emoji_value = emoji.emojize(value, variant="emoji_type")
+ if value == emoji_value:
+ logger.warning(f"Invalid emoji. {value} is not a valid emoji.")
+ return value
+ return emoji_value
+
+
+def getattr_return_str(value):
+ return str(value) if value else ""
+
+
+def getattr_return_bool(value):
+ if isinstance(value, bool):
+ return value
+ return None
+
+
+def getattr_return_list_of_str(value):
+ if isinstance(value, list):
+ return [str(val) for val in value]
+ return []
+
+
+def getattr_return_list_of_object(value):
+ if isinstance(value, list):
+ return value
+ return []
+
+
+def getattr_return_list_of_values_from_dict(value):
+ if isinstance(value, dict):
+ return list(value.values())
+ return []
+
+
+def getattr_return_dict(value):
+ if isinstance(value, dict):
+ return value
+ return {}
+
+
+ATTR_FUNC_MAPPING: dict[str, Callable] = {
+ "display_name": getattr_return_str,
+ "description": getattr_return_str,
+ "beta": getattr_return_bool,
+ "legacy": getattr_return_bool,
+ "documentation": getattr_return_str,
+ "icon": validate_icon,
+ "minimized": getattr_return_bool,
+ "frozen": getattr_return_bool,
+ "is_input": getattr_return_bool,
+ "is_output": getattr_return_bool,
+ "conditional_paths": getattr_return_list_of_str,
+ "_outputs_map": getattr_return_list_of_values_from_dict,
+ "_inputs": getattr_return_list_of_values_from_dict,
+ "outputs": getattr_return_list_of_object,
+ "inputs": getattr_return_list_of_object,
+ "metadata": getattr_return_dict,
+ "tool_mode": getattr_return_bool,
+}
diff --git a/langflow/src/backend/base/langflow/custom/code_parser/__init__.py b/langflow/src/backend/base/langflow/custom/code_parser/__init__.py
new file mode 100644
index 0000000..328fa4d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/code_parser/__init__.py
@@ -0,0 +1,3 @@
+from .code_parser import CodeParser
+
+__all__ = ["CodeParser"]
diff --git a/langflow/src/backend/base/langflow/custom/code_parser/code_parser.py b/langflow/src/backend/base/langflow/custom/code_parser/code_parser.py
new file mode 100644
index 0000000..72522d2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/code_parser/code_parser.py
@@ -0,0 +1,361 @@
+import ast
+import contextlib
+import inspect
+import traceback
+from itertools import starmap
+from pathlib import Path
+from typing import Any
+
+from cachetools import TTLCache, keys
+from fastapi import HTTPException
+from loguru import logger
+
+from langflow.custom.eval import eval_custom_component_code
+from langflow.custom.schema import CallableCodeDetails, ClassCodeDetails, MissingDefault
+
+
+class CodeSyntaxError(HTTPException):
+ pass
+
+
+def get_data_type():
+ from langflow.field_typing import Data
+
+ return Data
+
+
+def find_class_ast_node(class_obj):
+ """Finds the AST node corresponding to the given class object."""
+ # Get the source file where the class is defined
+ source_file = inspect.getsourcefile(class_obj)
+ if not source_file:
+ return None, []
+
+ # Read the source code from the file
+ source_code = Path(source_file).read_text(encoding="utf-8")
+
+ # Parse the source code into an AST
+ tree = ast.parse(source_code)
+
+ # Search for the class definition node in the AST
+ class_node = None
+ import_nodes = []
+ for node in ast.walk(tree):
+ if isinstance(node, ast.ClassDef) and node.name == class_obj.__name__:
+ class_node = node
+ elif isinstance(node, ast.Import | ast.ImportFrom):
+ import_nodes.append(node)
+
+ return class_node, import_nodes
+
+
+def imports_key(*args, **kwargs):
+ imports = kwargs.pop("imports")
+ key = keys.methodkey(*args, **kwargs)
+ key += tuple(imports)
+ return key
+
+
+class CodeParser:
+ """A parser for Python source code, extracting code details."""
+
+ def __init__(self, code: str | type) -> None:
+ """Initializes the parser with the provided code."""
+ self.cache: TTLCache = TTLCache(maxsize=1024, ttl=60)
+ if isinstance(code, type):
+ if not inspect.isclass(code):
+ msg = "The provided code must be a class."
+ raise ValueError(msg)
+ # If the code is a class, get its source code
+ code = inspect.getsource(code)
+ self.code = code
+ self.data: dict[str, Any] = {
+ "imports": [],
+ "functions": [],
+ "classes": [],
+ "global_vars": [],
+ }
+ self.handlers = {
+ ast.Import: self.parse_imports,
+ ast.ImportFrom: self.parse_imports,
+ ast.FunctionDef: self.parse_functions,
+ ast.ClassDef: self.parse_classes,
+ ast.Assign: self.parse_global_vars,
+ }
+
+ def get_tree(self):
+ """Parses the provided code to validate its syntax.
+
+ It tries to parse the code into an abstract syntax tree (AST).
+ """
+ try:
+ tree = ast.parse(self.code)
+ except SyntaxError as err:
+ raise CodeSyntaxError(
+ status_code=400,
+ detail={"error": err.msg, "traceback": traceback.format_exc()},
+ ) from err
+
+ return tree
+
+ def parse_node(self, node: ast.stmt | ast.AST) -> None:
+ """Parses an AST node and updates the data dictionary with the relevant information."""
+ if handler := self.handlers.get(type(node)):
+ handler(node) # type: ignore[operator]
+
+ def parse_imports(self, node: ast.Import | ast.ImportFrom) -> None:
+ """Extracts "imports" from the code, including aliases."""
+ if isinstance(node, ast.Import):
+ for alias in node.names:
+ if alias.asname:
+ self.data["imports"].append(f"{alias.name} as {alias.asname}")
+ else:
+ self.data["imports"].append(alias.name)
+ elif isinstance(node, ast.ImportFrom):
+ for alias in node.names:
+ if alias.asname:
+ self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
+ else:
+ self.data["imports"].append((node.module, alias.name))
+
+ def parse_functions(self, node: ast.FunctionDef) -> None:
+ """Extracts "functions" from the code."""
+ self.data["functions"].append(self.parse_callable_details(node))
+
+ def parse_arg(self, arg, default):
+ """Parses an argument and its default value."""
+ arg_dict = {"name": arg.arg, "default": default}
+ if arg.annotation:
+ arg_dict["type"] = ast.unparse(arg.annotation)
+ return arg_dict
+
+ # @cachedmethod(operator.attrgetter("cache"))
+ def construct_eval_env(self, return_type_str: str, imports) -> dict:
+ """Constructs an evaluation environment.
+
+ Constructs an evaluation environment with the necessary imports for the return type,
+ taking into account module aliases.
+ """
+ eval_env: dict = {}
+ for import_entry in imports:
+ if isinstance(import_entry, tuple): # from module import name
+ module, name = import_entry
+ if name in return_type_str:
+ exec(f"import {module}", eval_env)
+ exec(f"from {module} import {name}", eval_env)
+ else: # import module
+ module = import_entry
+ alias = None
+ if " as " in module:
+ module, alias = module.split(" as ")
+ if module in return_type_str or (alias and alias in return_type_str):
+ exec(f"import {module} as {alias or module}", eval_env)
+ return eval_env
+
+ def parse_callable_details(self, node: ast.FunctionDef) -> dict[str, Any]:
+ """Extracts details from a single function or method node."""
+ return_type = None
+ if node.returns:
+ return_type_str = ast.unparse(node.returns)
+ eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
+
+ # Handle cases where the type is not found in the constructed environment
+ with contextlib.suppress(NameError):
+ return_type = eval(return_type_str, eval_env) # noqa: S307
+
+ func = CallableCodeDetails(
+ name=node.name,
+ doc=ast.get_docstring(node),
+ args=self.parse_function_args(node),
+ body=self.parse_function_body(node),
+ return_type=return_type,
+ has_return=self.parse_return_statement(node),
+ )
+
+ return func.model_dump()
+
+ def parse_function_args(self, node: ast.FunctionDef) -> list[dict[str, Any]]:
+ """Parses the arguments of a function or method node."""
+ args = []
+
+ args += self.parse_positional_args(node)
+ args += self.parse_varargs(node)
+ args += self.parse_keyword_args(node)
+ # Commented out because we don't want kwargs
+ # showing up as fields in the frontend
+ args += self.parse_kwargs(node)
+
+ return args
+
+ def parse_positional_args(self, node: ast.FunctionDef) -> list[dict[str, Any]]:
+ """Parses the positional arguments of a function or method node."""
+ num_args = len(node.args.args)
+ num_defaults = len(node.args.defaults)
+ num_missing_defaults = num_args - num_defaults
+ missing_defaults = [MissingDefault()] * num_missing_defaults
+ default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
+ # Now check all default values to see if there
+ # are any "None" values in the middle
+ default_values = [None if value == "None" else value for value in default_values]
+
+ defaults = missing_defaults + default_values
+
+ return list(starmap(self.parse_arg, zip(node.args.args, defaults, strict=True)))
+
+ def parse_varargs(self, node: ast.FunctionDef) -> list[dict[str, Any]]:
+ """Parses the *args argument of a function or method node."""
+ args = []
+
+ if node.args.vararg:
+ args.append(self.parse_arg(node.args.vararg, None))
+
+ return args
+
+ def parse_keyword_args(self, node: ast.FunctionDef) -> list[dict[str, Any]]:
+ """Parses the keyword-only arguments of a function or method node."""
+ kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
+ ast.unparse(default) if default else None for default in node.args.kw_defaults
+ ]
+
+ return list(starmap(self.parse_arg, zip(node.args.kwonlyargs, kw_defaults, strict=True)))
+
+ def parse_kwargs(self, node: ast.FunctionDef) -> list[dict[str, Any]]:
+ """Parses the **kwargs argument of a function or method node."""
+ args = []
+
+ if node.args.kwarg:
+ args.append(self.parse_arg(node.args.kwarg, None))
+
+ return args
+
+ def parse_function_body(self, node: ast.FunctionDef) -> list[str]:
+ """Parses the body of a function or method node."""
+ return [ast.unparse(line) for line in node.body]
+
+ def parse_return_statement(self, node: ast.FunctionDef) -> bool:
+ """Parses the return statement of a function or method node, including nested returns."""
+
+ def has_return(node):
+ if isinstance(node, ast.Return):
+ return True
+ if isinstance(node, ast.If):
+ return any(has_return(child) for child in node.body) or any(has_return(child) for child in node.orelse)
+ if isinstance(node, ast.Try):
+ return (
+ any(has_return(child) for child in node.body)
+ or any(has_return(child) for child in node.handlers)
+ or any(has_return(child) for child in node.finalbody)
+ )
+ if isinstance(node, ast.For | ast.While):
+ return any(has_return(child) for child in node.body) or any(has_return(child) for child in node.orelse)
+ if isinstance(node, ast.With):
+ return any(has_return(child) for child in node.body)
+ return False
+
+ return any(has_return(child) for child in node.body)
+
+ def parse_assign(self, stmt):
+ """Parses an Assign statement and returns a dictionary with the target's name and value."""
+ for target in stmt.targets:
+ if isinstance(target, ast.Name):
+ return {"name": target.id, "value": ast.unparse(stmt.value)}
+ return None
+
+ def parse_ann_assign(self, stmt):
+ """Parses an AnnAssign statement and returns a dictionary with the target's name, value, and annotation."""
+ if isinstance(stmt.target, ast.Name):
+ return {
+ "name": stmt.target.id,
+ "value": ast.unparse(stmt.value) if stmt.value else None,
+ "annotation": ast.unparse(stmt.annotation),
+ }
+ return None
+
+ def parse_function_def(self, stmt):
+ """Parse a FunctionDef statement.
+
+ Parse a FunctionDef statement and return the parsed method and a boolean indicating if it's an __init__ method.
+ """
+ method = self.parse_callable_details(stmt)
+ return (method, True) if stmt.name == "__init__" else (method, False)
+
+ def get_base_classes(self):
+ """Returns the base classes of the custom component class."""
+ try:
+ bases = self.execute_and_inspect_classes(self.code)
+ except Exception:
+ # If the code cannot be executed, return an empty list
+ bases = []
+ raise
+ return bases
+
+ def parse_classes(self, node: ast.ClassDef) -> None:
+ """Extracts "classes" from the code, including inheritance and init methods."""
+ bases = self.get_base_classes()
+ nodes = []
+ for base in bases:
+ if base.__name__ == node.name or base.__name__ in {"CustomComponent", "Component", "BaseComponent"}:
+ continue
+ try:
+ class_node, import_nodes = find_class_ast_node(base)
+ if class_node is None:
+ continue
+ for import_node in import_nodes:
+ self.parse_imports(import_node)
+ nodes.append(class_node)
+ except Exception: # noqa: BLE001
+ logger.exception("Error finding base class node")
+ nodes.insert(0, node)
+ class_details = ClassCodeDetails(
+ name=node.name,
+ doc=ast.get_docstring(node),
+ bases=[b.__name__ for b in bases],
+ attributes=[],
+ methods=[],
+ init=None,
+ )
+ for _node in nodes:
+ self.process_class_node(_node, class_details)
+ self.data["classes"].append(class_details.model_dump())
+
+ def process_class_node(self, node, class_details) -> None:
+ for stmt in node.body:
+ if isinstance(stmt, ast.Assign):
+ if attr := self.parse_assign(stmt):
+ class_details.attributes.append(attr)
+ elif isinstance(stmt, ast.AnnAssign):
+ if attr := self.parse_ann_assign(stmt):
+ class_details.attributes.append(attr)
+ elif isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef):
+ method, is_init = self.parse_function_def(stmt)
+ if is_init:
+ class_details.init = method
+ else:
+ class_details.methods.append(method)
+
+ def parse_global_vars(self, node: ast.Assign) -> None:
+ """Extracts global variables from the code."""
+ global_var = {
+ "targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
+ "value": ast.unparse(node.value),
+ }
+ self.data["global_vars"].append(global_var)
+
+ def execute_and_inspect_classes(self, code: str):
+ custom_component_class = eval_custom_component_code(code)
+ custom_component = custom_component_class(_code=code)
+ dunder_class = custom_component.__class__
+ # Get the base classes at two levels of inheritance
+ bases = []
+ for base in dunder_class.__bases__:
+ bases.append(base)
+ bases.extend(base.__bases__)
+ return bases
+
+ def parse_code(self) -> dict[str, Any]:
+ """Runs all parsing operations and returns the resulting data."""
+ tree = self.get_tree()
+
+ for node in ast.walk(tree):
+ self.parse_node(node)
+ return self.data
diff --git a/langflow/src/backend/base/langflow/custom/custom_component/__init__.py b/langflow/src/backend/base/langflow/custom/custom_component/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/custom/custom_component/base_component.py b/langflow/src/backend/base/langflow/custom/custom_component/base_component.py
new file mode 100644
index 0000000..37035f2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/custom_component/base_component.py
@@ -0,0 +1,118 @@
+import copy
+import operator
+import re
+from typing import TYPE_CHECKING, Any, ClassVar
+
+from cachetools import TTLCache, cachedmethod
+from fastapi import HTTPException
+from loguru import logger
+
+from langflow.custom.attributes import ATTR_FUNC_MAPPING
+from langflow.custom.code_parser import CodeParser
+from langflow.custom.eval import eval_custom_component_code
+from langflow.utils import validate
+
+if TYPE_CHECKING:
+ from uuid import UUID
+
+
+class ComponentCodeNullError(HTTPException):
+ pass
+
+
+class ComponentFunctionEntrypointNameNullError(HTTPException):
+ pass
+
+
+class BaseComponent:
+ ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
+ ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
+
+ def __init__(self, **data) -> None:
+ self._code: str | None = None
+ self._function_entrypoint_name: str = "build"
+ self.field_config: dict = {}
+ self._user_id: str | UUID | None = None
+ self._template_config: dict = {}
+
+ self.cache: TTLCache = TTLCache(maxsize=1024, ttl=60)
+
+ for key, value in data.items():
+ if key == "user_id":
+ self._user_id = value
+ else:
+ setattr(self, key, value)
+
+ def __setattr__(self, key, value) -> None:
+ if key == "_user_id":
+ try:
+ if self._user_id is not None:
+ logger.warning("user_id is immutable and cannot be changed.")
+ except (KeyError, AttributeError):
+ pass
+ super().__setattr__(key, value)
+
+ @cachedmethod(cache=operator.attrgetter("cache"))
+ def get_code_tree(self, code: str):
+ parser = CodeParser(code)
+ return parser.parse_code()
+
+ def get_function(self):
+ if not self._code:
+ raise ComponentCodeNullError(
+ status_code=400,
+ detail={"error": self.ERROR_CODE_NULL, "traceback": ""},
+ )
+
+ if not self._function_entrypoint_name:
+ raise ComponentFunctionEntrypointNameNullError(
+ status_code=400,
+ detail={
+ "error": self.ERROR_FUNCTION_ENTRYPOINT_NAME_NULL,
+ "traceback": "",
+ },
+ )
+
+ return validate.create_function(self._code, self._function_entrypoint_name)
+
+ @staticmethod
+ def get_template_config(component):
+ """Gets the template configuration for the custom component itself."""
+ template_config = {}
+
+ for attribute, func in ATTR_FUNC_MAPPING.items():
+ if hasattr(component, attribute):
+ value = getattr(component, attribute)
+ if value is not None:
+ value_copy = copy.deepcopy(value)
+ template_config[attribute] = func(value=value_copy)
+
+ for key in template_config.copy():
+ if key not in ATTR_FUNC_MAPPING:
+ template_config.pop(key, None)
+
+ return template_config
+
+ def build_template_config(self) -> dict:
+ """Builds the template configuration for the custom component.
+
+ Returns:
+ A dictionary representing the template configuration.
+ """
+ if not self._code:
+ return {}
+
+ try:
+ cc_class = eval_custom_component_code(self._code)
+
+ except AttributeError as e:
+ pattern = r"module '.*?' has no attribute '.*?'"
+ if re.search(pattern, str(e)):
+ raise ImportError(e) from e
+ raise
+
+ component_instance = cc_class(_code=self._code)
+ return self.get_template_config(component_instance)
+
+ def build(self, *args: Any, **kwargs: Any) -> Any:
+ raise NotImplementedError
diff --git a/langflow/src/backend/base/langflow/custom/custom_component/component.py b/langflow/src/backend/base/langflow/custom/custom_component/component.py
new file mode 100644
index 0000000..44e7a3e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/custom_component/component.py
@@ -0,0 +1,1391 @@
+from __future__ import annotations
+
+import ast
+import asyncio
+import inspect
+from collections.abc import AsyncIterator, Iterator
+from copy import deepcopy
+from textwrap import dedent
+from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, get_type_hints
+from uuid import UUID
+
+import nanoid
+import yaml
+from langchain_core.tools import StructuredTool
+from pydantic import BaseModel, ValidationError
+
+from langflow.base.tools.constants import (
+ TOOL_OUTPUT_DISPLAY_NAME,
+ TOOL_OUTPUT_NAME,
+ TOOL_TABLE_SCHEMA,
+ TOOLS_METADATA_INFO,
+ TOOLS_METADATA_INPUT_NAME,
+)
+from langflow.custom.tree_visitor import RequiredInputsVisitor
+from langflow.exceptions.component import StreamingError
+from langflow.field_typing import Tool # noqa: TC001 Needed by _add_toolkit_output
+from langflow.graph.state.model import create_state_model
+from langflow.graph.utils import has_chat_output
+from langflow.helpers.custom import format_type
+from langflow.memory import astore_message, aupdate_messages, delete_message
+from langflow.schema.artifact import get_artifact_type, post_process_raw
+from langflow.schema.data import Data
+from langflow.schema.message import ErrorMessage, Message
+from langflow.schema.properties import Source
+from langflow.schema.table import FieldParserType, TableOptions
+from langflow.services.tracing.schema import Log
+from langflow.template.field.base import UNDEFINED, Input, Output
+from langflow.template.frontend_node.custom_components import ComponentFrontendNode
+from langflow.utils.async_helpers import run_until_complete
+from langflow.utils.util import find_closest_match
+
+from .custom_component import CustomComponent
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from langflow.base.tools.component_tool import ComponentToolkit
+ from langflow.events.event_manager import EventManager
+ from langflow.graph.edge.schema import EdgeData
+ from langflow.graph.vertex.base import Vertex
+ from langflow.inputs.inputs import InputTypes
+ from langflow.schema.log import LoggableType
+
+
+_ComponentToolkit = None
+
+
+def _get_component_toolkit():
+ global _ComponentToolkit # noqa: PLW0603
+ if _ComponentToolkit is None:
+ from langflow.base.tools.component_tool import ComponentToolkit
+
+ _ComponentToolkit = ComponentToolkit
+ return _ComponentToolkit
+
+
+BACKWARDS_COMPATIBLE_ATTRIBUTES = ["user_id", "vertex", "tracing_service"]
+CONFIG_ATTRIBUTES = ["_display_name", "_description", "_icon", "_name", "_metadata"]
+
+
+class PlaceholderGraph(NamedTuple):
+ """A placeholder graph structure for components, providing backwards compatibility.
+
+ and enabling component execution without a full graph object.
+
+ This lightweight structure contains essential information typically found in a complete graph,
+ allowing components to function in isolation or in simplified contexts.
+
+ Attributes:
+ flow_id (str | None): Unique identifier for the flow, if applicable.
+ user_id (str | None): Identifier of the user associated with the flow, if any.
+ session_id (str | None): Identifier for the current session, if applicable.
+ context (dict): Additional contextual information for the component's execution.
+ flow_name (str | None): Name of the flow, if available.
+ """
+
+ flow_id: str | None
+ user_id: str | None
+ session_id: str | None
+ context: dict
+ flow_name: str | None
+
+
+class Component(CustomComponent):
+ inputs: list[InputTypes] = []
+ outputs: list[Output] = []
+ code_class_base_inheritance: ClassVar[str] = "Component"
+
+ def __init__(self, **kwargs) -> None:
+ # Initialize instance-specific attributes first
+ if overlap := self._there_is_overlap_in_inputs_and_outputs():
+ msg = f"Inputs and outputs have overlapping names: {overlap}"
+ raise ValueError(msg)
+ self._output_logs: dict[str, list[Log]] = {}
+ self._current_output: str = ""
+ self._metadata: dict = {}
+ self._ctx: dict = {}
+ self._code: str | None = None
+ self._logs: list[Log] = []
+
+ # Initialize component-specific collections
+ self._inputs: dict[str, InputTypes] = {}
+ self._outputs_map: dict[str, Output] = {}
+ self._results: dict[str, Any] = {}
+ self._attributes: dict[str, Any] = {}
+ self._edges: list[EdgeData] = []
+ self._components: list[Component] = []
+ self._event_manager: EventManager | None = None
+ self._state_model = None
+
+ # Process input kwargs
+ inputs = {}
+ config = {}
+ for key, value in kwargs.items():
+ if key.startswith("_"):
+ config[key] = value
+ elif key in CONFIG_ATTRIBUTES:
+ config[key[1:]] = value
+ else:
+ inputs[key] = value
+
+ self._parameters = inputs or {}
+ self.set_attributes(self._parameters)
+
+ # Store original inputs and config for reference
+ self.__inputs = inputs
+ self.__config = config or {}
+
+ # Add unique ID if not provided
+ if "_id" not in self.__config:
+ self.__config |= {"_id": f"{self.__class__.__name__}-{nanoid.generate(size=5)}"}
+
+ # Initialize base class
+ super().__init__(**self.__config)
+
+ # Post-initialization setup
+ if hasattr(self, "_trace_type"):
+ self.trace_type = self._trace_type
+ if not hasattr(self, "trace_type"):
+ self.trace_type = "chain"
+
+ # Setup inputs and outputs
+ self._reset_all_output_values()
+ if self.inputs is not None:
+ self.map_inputs(self.inputs)
+ if self.outputs is not None:
+ self.map_outputs(self.outputs)
+
+ # Final setup
+ self._set_output_types(list(self._outputs_map.values()))
+ self.set_class_code()
+ self._set_output_required_inputs()
+
+ def _there_is_overlap_in_inputs_and_outputs(self) -> set[str]:
+ """Check the `.name` of inputs and outputs to see if there is overlap.
+
+ Returns:
+ set[str]: Set of names that overlap between inputs and outputs.
+ """
+ # Create sets of input and output names for O(1) lookup
+ input_names = {input_.name for input_ in self.inputs if input_.name is not None}
+ output_names = {output.name for output in self.outputs}
+
+ # Return the intersection of the sets
+ return input_names & output_names
+
+ def get_base_args(self):
+ """Get the base arguments required for component initialization.
+
+ Returns:
+ dict: A dictionary containing the base arguments:
+ - _user_id: The ID of the current user
+ - _session_id: The ID of the current session
+ - _tracing_service: The tracing service instance for logging/monitoring
+ """
+ return {
+ "_user_id": self.user_id,
+ "_session_id": self.session_id,
+ "_tracing_service": self._tracing_service,
+ }
+
+ @property
+ def ctx(self):
+ if not hasattr(self, "graph") or self.graph is None:
+ msg = "Graph not found. Please build the graph first."
+ raise ValueError(msg)
+ return self.graph.context
+
+ def add_to_ctx(self, key: str, value: Any, *, overwrite: bool = False) -> None:
+ """Add a key-value pair to the context.
+
+ Args:
+ key (str): The key to add.
+ value (Any): The value to associate with the key.
+ overwrite (bool, optional): Whether to overwrite the existing value. Defaults to False.
+
+ Raises:
+ ValueError: If the graph is not built.
+ """
+ if not hasattr(self, "graph") or self.graph is None:
+ msg = "Graph not found. Please build the graph first."
+ raise ValueError(msg)
+ if key in self.graph.context and not overwrite:
+ msg = f"Key {key} already exists in context. Set overwrite=True to overwrite."
+ raise ValueError(msg)
+ self.graph.context.update({key: value})
+
+ def update_ctx(self, value_dict: dict[str, Any]) -> None:
+ """Update the context with a dictionary of values.
+
+ Args:
+ value_dict (dict[str, Any]): The dictionary of values to update.
+
+ Raises:
+ ValueError: If the graph is not built.
+ """
+ if not hasattr(self, "graph") or self.graph is None:
+ msg = "Graph not found. Please build the graph first."
+ raise ValueError(msg)
+ if not isinstance(value_dict, dict):
+ msg = "Value dict must be a dictionary"
+ raise TypeError(msg)
+
+ self.graph.context.update(value_dict)
+
+ def _pre_run_setup(self):
+ pass
+
+ def set_event_manager(self, event_manager: EventManager | None = None) -> None:
+ self._event_manager = event_manager
+
+ def _reset_all_output_values(self) -> None:
+ if isinstance(self._outputs_map, dict):
+ for output in self._outputs_map.values():
+ output.value = UNDEFINED
+
+ def _build_state_model(self):
+ if self._state_model:
+ return self._state_model
+ name = self.name or self.__class__.__name__
+ model_name = f"{name}StateModel"
+ fields = {}
+ for output in self._outputs_map.values():
+ fields[output.name] = getattr(self, output.method)
+ self._state_model = create_state_model(model_name=model_name, **fields)
+ return self._state_model
+
+ def get_state_model_instance_getter(self):
+ state_model = self._build_state_model()
+
+ def _instance_getter(_):
+ return state_model()
+
+ _instance_getter.__annotations__["return"] = state_model
+ return _instance_getter
+
+ def __deepcopy__(self, memo: dict) -> Component:
+ if id(self) in memo:
+ return memo[id(self)]
+ kwargs = deepcopy(self.__config, memo)
+ kwargs["inputs"] = deepcopy(self.__inputs, memo)
+ new_component = type(self)(**kwargs)
+ new_component._code = self._code
+ new_component._outputs_map = self._outputs_map
+ new_component._inputs = self._inputs
+ new_component._edges = self._edges
+ new_component._components = self._components
+ new_component._parameters = self._parameters
+ new_component._attributes = self._attributes
+ new_component._output_logs = self._output_logs
+ new_component._logs = self._logs # type: ignore[attr-defined]
+ memo[id(self)] = new_component
+ return new_component
+
+ def set_class_code(self) -> None:
+ # Get the source code of the calling class
+ if self._code:
+ return
+ try:
+ module = inspect.getmodule(self.__class__)
+ if module is None:
+ msg = "Could not find module for class"
+ raise ValueError(msg)
+ class_code = inspect.getsource(module)
+ self._code = class_code
+ except OSError as e:
+ msg = f"Could not find source code for {self.__class__.__name__}"
+ raise ValueError(msg) from e
+
+ def set(self, **kwargs):
+ """Connects the component to other components or sets parameters and attributes.
+
+ Args:
+ **kwargs: Keyword arguments representing the connections, parameters, and attributes.
+
+ Returns:
+ None
+
+ Raises:
+ KeyError: If the specified input name does not exist.
+ """
+ for key, value in kwargs.items():
+ self._process_connection_or_parameters(key, value)
+ return self
+
+ def list_inputs(self):
+ """Returns a list of input names."""
+ return [_input.name for _input in self.inputs]
+
+ def list_outputs(self):
+ """Returns a list of output names."""
+ return [_output.name for _output in self._outputs_map.values()]
+
+ async def run(self):
+ """Executes the component's logic and returns the result.
+
+ Returns:
+ The result of executing the component's logic.
+ """
+ return await self._run()
+
+ def set_vertex(self, vertex: Vertex) -> None:
+ """Sets the vertex for the component.
+
+ Args:
+ vertex (Vertex): The vertex to set.
+
+ Returns:
+ None
+ """
+ self._vertex = vertex
+
+ def get_input(self, name: str) -> Any:
+ """Retrieves the value of the input with the specified name.
+
+ Args:
+ name (str): The name of the input.
+
+ Returns:
+ Any: The value of the input.
+
+ Raises:
+ ValueError: If the input with the specified name is not found.
+ """
+ if name in self._inputs:
+ return self._inputs[name]
+ msg = f"Input {name} not found in {self.__class__.__name__}"
+ raise ValueError(msg)
+
+ def get_output(self, name: str) -> Any:
+ """Retrieves the output with the specified name.
+
+ Args:
+ name (str): The name of the output to retrieve.
+
+ Returns:
+ Any: The output value.
+
+ Raises:
+ ValueError: If the output with the specified name is not found.
+ """
+ if name in self._outputs_map:
+ return self._outputs_map[name]
+ msg = f"Output {name} not found in {self.__class__.__name__}"
+ raise ValueError(msg)
+
+ def set_on_output(self, name: str, **kwargs) -> None:
+ output = self.get_output(name)
+ for key, value in kwargs.items():
+ if not hasattr(output, key):
+ msg = f"Output {name} does not have a method {key}"
+ raise ValueError(msg)
+ setattr(output, key, value)
+
+ def set_output_value(self, name: str, value: Any) -> None:
+ if name in self._outputs_map:
+ self._outputs_map[name].value = value
+ else:
+ msg = f"Output {name} not found in {self.__class__.__name__}"
+ raise ValueError(msg)
+
+ def map_outputs(self, outputs: list[Output]) -> None:
+ """Maps the given list of outputs to the component.
+
+ Args:
+ outputs (List[Output]): The list of outputs to be mapped.
+
+ Raises:
+ ValueError: If the output name is None.
+
+ Returns:
+ None
+ """
+ for output in outputs:
+ if output.name is None:
+ msg = "Output name cannot be None."
+ raise ValueError(msg)
+ # Deepcopy is required to avoid modifying the original component;
+ # allows each instance of each component to modify its own output
+ self._outputs_map[output.name] = deepcopy(output)
+
+ def map_inputs(self, inputs: list[InputTypes]) -> None:
+ """Maps the given inputs to the component.
+
+ Args:
+ inputs (List[InputTypes]): A list of InputTypes objects representing the inputs.
+
+ Raises:
+ ValueError: If the input name is None.
+
+ """
+ for input_ in inputs:
+ if input_.name is None:
+ msg = self.build_component_error_message("Input name cannot be None")
+ raise ValueError(msg)
+ self._inputs[input_.name] = deepcopy(input_)
+
+ def validate(self, params: dict) -> None:
+ """Validates the component parameters.
+
+ Args:
+ params (dict): A dictionary containing the component parameters.
+
+ Raises:
+ ValueError: If the inputs are not valid.
+ ValueError: If the outputs are not valid.
+ """
+ self._validate_inputs(params)
+ self._validate_outputs()
+
+ async def run_and_validate_update_outputs(self, frontend_node: dict, field_name: str, field_value: Any):
+ frontend_node = self.update_outputs(frontend_node, field_name, field_value)
+ if field_name == "tool_mode" or frontend_node.get("tool_mode"):
+ is_tool_mode = field_value or frontend_node.get("tool_mode")
+ frontend_node["outputs"] = [self._build_tool_output()] if is_tool_mode else frontend_node["outputs"]
+ if is_tool_mode:
+ frontend_node.setdefault("template", {})
+ frontend_node["tool_mode"] = True
+ tools_metadata_input = await self._build_tools_metadata_input()
+ frontend_node["template"][TOOLS_METADATA_INPUT_NAME] = tools_metadata_input.to_dict()
+ self._append_tool_to_outputs_map()
+ elif "template" in frontend_node:
+ frontend_node["template"].pop(TOOLS_METADATA_INPUT_NAME, None)
+ self.tools_metadata = frontend_node.get("template", {}).get(TOOLS_METADATA_INPUT_NAME, {}).get("value")
+ return self._validate_frontend_node(frontend_node)
+
+ def _validate_frontend_node(self, frontend_node: dict):
+ # Check if all outputs are either Output or a valid Output model
+ for index, output in enumerate(frontend_node["outputs"]):
+ if isinstance(output, dict):
+ try:
+ output_ = Output(**output)
+ self._set_output_return_type(output_)
+ output_dict = output_.model_dump()
+ except ValidationError as e:
+ msg = f"Invalid output: {e}"
+ raise ValueError(msg) from e
+ elif isinstance(output, Output):
+ # we need to serialize it
+ self._set_output_return_type(output)
+ output_dict = output.model_dump()
+ else:
+ msg = f"Invalid output type: {type(output)}"
+ raise TypeError(msg)
+ frontend_node["outputs"][index] = output_dict
+ return frontend_node
+
+ def update_outputs(self, frontend_node: dict, field_name: str, field_value: Any) -> dict: # noqa: ARG002
+ """Default implementation for updating outputs based on field changes.
+
+ Subclasses can override this to modify outputs based on field_name and field_value.
+ """
+ return frontend_node
+
+ def _set_output_types(self, outputs: list[Output]) -> None:
+ for output in outputs:
+ self._set_output_return_type(output)
+
+ def _set_output_return_type(self, output: Output) -> None:
+ if output.method is None:
+ msg = f"Output {output.name} does not have a method"
+ raise ValueError(msg)
+ return_types = self._get_method_return_type(output.method)
+ output.add_types(return_types)
+ output.set_selected()
+
+ def _set_output_required_inputs(self) -> None:
+ for output in self.outputs:
+ if not output.method:
+ continue
+ method = getattr(self, output.method, None)
+ if not method or not callable(method):
+ continue
+ try:
+ source_code = inspect.getsource(method)
+ ast_tree = ast.parse(dedent(source_code))
+ except Exception: # noqa: BLE001
+ ast_tree = ast.parse(dedent(self._code or ""))
+
+ visitor = RequiredInputsVisitor(self._inputs)
+ visitor.visit(ast_tree)
+ output.required_inputs = sorted(visitor.required_inputs)
+
+ def get_output_by_method(self, method: Callable):
+ # method is a callable and output.method is a string
+ # we need to find the output that has the same method
+ output = next((output for output in self._outputs_map.values() if output.method == method.__name__), None)
+ if output is None:
+ method_name = method.__name__ if hasattr(method, "__name__") else str(method)
+ msg = f"Output with method {method_name} not found"
+ raise ValueError(msg)
+ return output
+
+ def _inherits_from_component(self, method: Callable):
+ # check if the method is a method from a class that inherits from Component
+ # and that it is an output of that class
+ return hasattr(method, "__self__") and isinstance(method.__self__, Component)
+
+ def _method_is_valid_output(self, method: Callable):
+ # check if the method is a method from a class that inherits from Component
+ # and that it is an output of that class
+ return (
+ hasattr(method, "__self__")
+ and isinstance(method.__self__, Component)
+ and method.__self__.get_output_by_method(method)
+ )
+
+ def _build_error_string_from_matching_pairs(self, matching_pairs: list[tuple[Output, Input]]):
+ text = ""
+ for output, input_ in matching_pairs:
+ text += f"{output.name}[{','.join(output.types)}]->{input_.name}[{','.join(input_.input_types or [])}]\n"
+ return text
+
+ def _find_matching_output_method(self, input_name: str, value: Component):
+ """Find the output method from the given component and input name.
+
+ Find the output method from the given component (`value`) that matches the specified input (`input_name`)
+ in the current component.
+ This method searches through all outputs of the provided component to find outputs whose types match
+ the input types of the specified input in the current component. If exactly one matching output is found,
+ it returns the corresponding method. If multiple matching outputs are found, it raises an error indicating
+ ambiguity. If no matching outputs are found, it raises an error indicating that no suitable output was found.
+
+ Args:
+ input_name (str): The name of the input in the current component to match.
+ value (Component): The component whose outputs are to be considered.
+
+ Returns:
+ Callable: The method corresponding to the matching output.
+
+ Raises:
+ ValueError: If multiple matching outputs are found, if no matching outputs are found,
+ or if the output method is invalid.
+ """
+ # Retrieve all outputs from the given component
+ outputs = value._outputs_map.values()
+ # Prepare to collect matching output-input pairs
+ matching_pairs = []
+ # Get the input object from the current component
+ input_ = self._inputs[input_name]
+ # Iterate over outputs to find matches based on types
+ matching_pairs = [
+ (output, input_)
+ for output in outputs
+ for output_type in output.types
+ # Check if the output type matches the input's accepted types
+ if input_.input_types and output_type in input_.input_types
+ ]
+ # If multiple matches are found, raise an error indicating ambiguity
+ if len(matching_pairs) > 1:
+ matching_pairs_str = self._build_error_string_from_matching_pairs(matching_pairs)
+ msg = self.build_component_error_message(
+ f"There are multiple outputs from {value.display_name} that can connect to inputs: {matching_pairs_str}"
+ )
+ raise ValueError(msg)
+ # If no matches are found, raise an error indicating no suitable output
+ if not matching_pairs:
+ msg = self.build_input_error_message(input_name, f"No matching output from {value.display_name} found")
+ raise ValueError(msg)
+ # Get the matching output and input pair
+ output, input_ = matching_pairs[0]
+ # Ensure that the output method is a valid method name (string)
+ if not isinstance(output.method, str):
+ msg = self.build_component_error_message(
+ f"Method {output.method} is not a valid output of {value.display_name}"
+ )
+ raise TypeError(msg)
+ return getattr(value, output.method)
+
+ def _process_connection_or_parameter(self, key, value) -> None:
+ input_ = self._get_or_create_input(key)
+ # We need to check if callable AND if it is a method from a class that inherits from Component
+ if isinstance(value, Component):
+ # We need to find the Output that can connect to an input of the current component
+ # if there's more than one output that matches, we need to raise an error
+ # because we don't know which one to connect to
+ value = self._find_matching_output_method(key, value)
+ if callable(value) and self._inherits_from_component(value):
+ try:
+ self._method_is_valid_output(value)
+ except ValueError as e:
+ msg = f"Method {value.__name__} is not a valid output of {value.__self__.__class__.__name__}"
+ raise ValueError(msg) from e
+ self._connect_to_component(key, value, input_)
+ else:
+ self._set_parameter_or_attribute(key, value)
+
+ def _process_connection_or_parameters(self, key, value) -> None:
+ # if value is a list of components, we need to process each component
+ # Note this update make sure it is not a list str | int | float | bool | type(None)
+ if isinstance(value, list) and not any(
+ isinstance(val, str | int | float | bool | type(None) | Message | Data | StructuredTool) for val in value
+ ):
+ for val in value:
+ self._process_connection_or_parameter(key, val)
+ else:
+ self._process_connection_or_parameter(key, value)
+
+ def _get_or_create_input(self, key):
+ try:
+ return self._inputs[key]
+ except KeyError:
+ input_ = self._get_fallback_input(name=key, display_name=key)
+ self._inputs[key] = input_
+ self.inputs.append(input_)
+ return input_
+
+ def _connect_to_component(self, key, value, input_) -> None:
+ component = value.__self__
+ self._components.append(component)
+ output = component.get_output_by_method(value)
+ self._add_edge(component, key, output, input_)
+
+ def _add_edge(self, component, key, output, input_) -> None:
+ self._edges.append(
+ {
+ "source": component._id,
+ "target": self._id,
+ "data": {
+ "sourceHandle": {
+ "dataType": component.name or component.__class__.__name__,
+ "id": component._id,
+ "name": output.name,
+ "output_types": output.types,
+ },
+ "targetHandle": {
+ "fieldName": key,
+ "id": self._id,
+ "inputTypes": input_.input_types,
+ "type": input_.field_type,
+ },
+ },
+ }
+ )
+
+ def _set_parameter_or_attribute(self, key, value) -> None:
+ if isinstance(value, Component):
+ methods = ", ".join([f"'{output.method}'" for output in value.outputs])
+ msg = f"You set {value.display_name} as value for `{key}`. You should pass one of the following: {methods}"
+ raise TypeError(msg)
+ self._set_input_value(key, value)
+ self._parameters[key] = value
+ self._attributes[key] = value
+
+ def __call__(self, **kwargs):
+ self.set(**kwargs)
+
+ return run_until_complete(self.run())
+
+ async def _run(self):
+ # Resolve callable inputs
+ for key, _input in self._inputs.items():
+ if asyncio.iscoroutinefunction(_input.value):
+ self._inputs[key].value = await _input.value()
+ elif callable(_input.value):
+ self._inputs[key].value = await asyncio.to_thread(_input.value)
+
+ self.set_attributes({})
+
+ return await self.build_results()
+
+ def __getattr__(self, name: str) -> Any:
+ if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]:
+ return self.__dict__["_attributes"][name]
+ if "_inputs" in self.__dict__ and name in self.__dict__["_inputs"]:
+ return self.__dict__["_inputs"][name].value
+ if "_outputs_map" in self.__dict__ and name in self.__dict__["_outputs_map"]:
+ return self.__dict__["_outputs_map"][name]
+ if name in BACKWARDS_COMPATIBLE_ATTRIBUTES:
+ return self.__dict__[f"_{name}"]
+ if name.startswith("_") and name[1:] in BACKWARDS_COMPATIBLE_ATTRIBUTES:
+ return self.__dict__[name]
+ if name == "graph":
+ # If it got up to here it means it was going to raise
+ session_id = self._session_id if hasattr(self, "_session_id") else None
+ user_id = self._user_id if hasattr(self, "_user_id") else None
+ flow_name = self._flow_name if hasattr(self, "_flow_name") else None
+ flow_id = self._flow_id if hasattr(self, "_flow_id") else None
+ return PlaceholderGraph(
+ flow_id=flow_id, user_id=str(user_id), session_id=session_id, context={}, flow_name=flow_name
+ )
+ msg = f"Attribute {name} not found in {self.__class__.__name__}"
+ raise AttributeError(msg)
+
+ def _set_input_value(self, name: str, value: Any) -> None:
+ if name in self._inputs:
+ input_value = self._inputs[name].value
+ if isinstance(input_value, Component):
+ methods = ", ".join([f"'{output.method}'" for output in input_value.outputs])
+ msg = self.build_input_error_message(
+ name,
+ f"You set {input_value.display_name} as value. You should pass one of the following: {methods}",
+ )
+ raise ValueError(msg)
+ if callable(input_value) and hasattr(input_value, "__self__"):
+ msg = self.build_input_error_message(
+ name, f"Input is connected to {input_value.__self__.display_name}.{input_value.__name__}"
+ )
+ raise ValueError(msg)
+ try:
+ self._inputs[name].value = value
+ except Exception as e:
+ msg = f"Error setting input value for {name}: {e}"
+ raise ValueError(msg) from e
+ if hasattr(self._inputs[name], "load_from_db"):
+ self._inputs[name].load_from_db = False
+ else:
+ msg = self.build_component_error_message(f"Input {name} not found")
+ raise ValueError(msg)
+
+ def _validate_outputs(self) -> None:
+ # Raise Error if some rule isn't met
+ pass
+
+ def _map_parameters_on_frontend_node(self, frontend_node: ComponentFrontendNode) -> None:
+ for name, value in self._parameters.items():
+ frontend_node.set_field_value_in_template(name, value)
+
+ def _map_parameters_on_template(self, template: dict) -> None:
+ for name, value in self._parameters.items():
+ try:
+ template[name]["value"] = value
+ except KeyError as e:
+ close_match = find_closest_match(name, list(template.keys()))
+ if close_match:
+ msg = f"Parameter '{name}' not found in {self.__class__.__name__}. Did you mean '{close_match}'?"
+ raise ValueError(msg) from e
+ msg = f"Parameter {name} not found in {self.__class__.__name__}. "
+ raise ValueError(msg) from e
+
+ def _get_method_return_type(self, method_name: str) -> list[str]:
+ method = getattr(self, method_name)
+ return_type = get_type_hints(method)["return"]
+ extracted_return_types = self._extract_return_type(return_type)
+ return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types]
+
+ def _update_template(self, frontend_node: dict):
+ return frontend_node
+
+ def to_frontend_node(self):
+ # ! This part here is clunky but we need it like this for
+ # ! backwards compatibility. We can change how prompt component
+ # ! works and then update this later
+ field_config = self.get_template_config(self)
+ frontend_node = ComponentFrontendNode.from_inputs(**field_config)
+ for key in self._inputs:
+ frontend_node.set_field_load_from_db_in_template(key, value=False)
+ self._map_parameters_on_frontend_node(frontend_node)
+
+ frontend_node_dict = frontend_node.to_dict(keep_name=False)
+ frontend_node_dict = self._update_template(frontend_node_dict)
+ self._map_parameters_on_template(frontend_node_dict["template"])
+
+ frontend_node = ComponentFrontendNode.from_dict(frontend_node_dict)
+ if not self._code:
+ self.set_class_code()
+ code_field = Input(
+ dynamic=True,
+ required=True,
+ placeholder="",
+ multiline=True,
+ value=self._code,
+ password=False,
+ name="code",
+ advanced=True,
+ field_type="code",
+ is_list=False,
+ )
+ frontend_node.template.add_field(code_field)
+
+ for output in frontend_node.outputs:
+ if output.types:
+ continue
+ return_types = self._get_method_return_type(output.method)
+ output.add_types(return_types)
+ output.set_selected()
+
+ frontend_node.validate_component()
+ frontend_node.set_base_classes_from_outputs()
+ return {
+ "data": {
+ "node": frontend_node.to_dict(keep_name=False),
+ "type": self.name or self.__class__.__name__,
+ "id": self._id,
+ },
+ "id": self._id,
+ }
+
+ def _validate_inputs(self, params: dict) -> None:
+ # Params keys are the `name` attribute of the Input objects
+ for key, value in params.copy().items():
+ if key not in self._inputs:
+ continue
+ input_ = self._inputs[key]
+ # BaseInputMixin has a `validate_assignment=True`
+
+ input_.value = value
+ params[input_.name] = input_.value
+
+ def set_attributes(self, params: dict) -> None:
+ self._validate_inputs(params)
+ attributes = {}
+ for key, value in params.items():
+ if key in self.__dict__ and value != getattr(self, key):
+ msg = (
+ f"{self.__class__.__name__} defines an input parameter named '{key}' "
+ f"that is a reserved word and cannot be used."
+ )
+ raise ValueError(msg)
+ attributes[key] = value
+ for key, input_obj in self._inputs.items():
+ if key not in attributes and key not in self._attributes:
+ attributes[key] = input_obj.value or None
+
+ self._attributes.update(attributes)
+
+ def _set_outputs(self, outputs: list[dict]) -> None:
+ self.outputs = [Output(**output) for output in outputs]
+ for output in self.outputs:
+ setattr(self, output.name, output)
+ self._outputs_map[output.name] = output
+
+ def get_trace_as_inputs(self):
+ predefined_inputs = {
+ input_.name: input_.value
+ for input_ in self.inputs
+ if hasattr(input_, "trace_as_input") and input_.trace_as_input
+ }
+ # Runtime inputs
+ runtime_inputs = {name: input_.value for name, input_ in self._inputs.items() if hasattr(input_, "value")}
+ return {**predefined_inputs, **runtime_inputs}
+
+ def get_trace_as_metadata(self):
+ return {
+ input_.name: input_.value
+ for input_ in self.inputs
+ if hasattr(input_, "trace_as_metadata") and input_.trace_as_metadata
+ }
+
+ async def _build_with_tracing(self):
+ inputs = self.get_trace_as_inputs()
+ metadata = self.get_trace_as_metadata()
+ async with self._tracing_service.trace_context(self, self.trace_name, inputs, metadata):
+ results, artifacts = await self._build_results()
+ self._tracing_service.set_outputs(self.trace_name, results)
+
+ return results, artifacts
+
+ async def _build_without_tracing(self):
+ return await self._build_results()
+
+ async def build_results(self):
+ """Build the results of the component."""
+ if hasattr(self, "graph"):
+ session_id = self.graph.session_id
+ elif hasattr(self, "_session_id"):
+ session_id = self._session_id
+ else:
+ session_id = None
+ try:
+ if self._tracing_service:
+ return await self._build_with_tracing()
+ return await self._build_without_tracing()
+ except StreamingError as e:
+ await self.send_error(
+ exception=e.cause,
+ session_id=session_id,
+ trace_name=getattr(self, "trace_name", None),
+ source=e.source,
+ )
+ raise e.cause # noqa: B904
+ except Exception as e:
+ await self.send_error(
+ exception=e,
+ session_id=session_id,
+ source=Source(id=self._id, display_name=self.display_name, source=self.display_name),
+ trace_name=getattr(self, "trace_name", None),
+ )
+ raise
+
+ async def _build_results(self) -> tuple[dict, dict]:
+ results, artifacts = {}, {}
+
+ self._pre_run_setup_if_needed()
+ self._handle_tool_mode()
+
+ for output in self._get_outputs_to_process():
+ self._current_output = output.name
+ result = await self._get_output_result(output)
+ results[output.name] = result
+ artifacts[output.name] = self._build_artifact(result)
+ self._log_output(output)
+
+ self._finalize_results(results, artifacts)
+ return results, artifacts
+
+ def _pre_run_setup_if_needed(self):
+ if hasattr(self, "_pre_run_setup"):
+ self._pre_run_setup()
+
+ def _handle_tool_mode(self):
+ if (
+ hasattr(self, "outputs") and any(getattr(_input, "tool_mode", False) for _input in self.inputs)
+ ) or self.add_tool_output:
+ self._append_tool_to_outputs_map()
+
+ def _should_process_output(self, output):
+ if not self._vertex or not self._vertex.outgoing_edges:
+ return True
+ return output.name in self._vertex.edges_source_names
+
+ def _get_outputs_to_process(self):
+ return (output for output in self._outputs_map.values() if self._should_process_output(output))
+
+ async def _get_output_result(self, output):
+ if output.cache and output.value != UNDEFINED:
+ return output.value
+
+ if output.method is None:
+ msg = f'Output "{output.name}" does not have a method defined.'
+ raise ValueError(msg)
+
+ method = getattr(self, output.method)
+ try:
+ result = await method() if inspect.iscoroutinefunction(method) else await asyncio.to_thread(method)
+ except TypeError as e:
+ msg = f'Error running method "{output.method}": {e}'
+ raise TypeError(msg) from e
+
+ if (
+ self._vertex is not None
+ and isinstance(result, Message)
+ and result.flow_id is None
+ and self._vertex.graph.flow_id is not None
+ ):
+ result.set_flow_id(self._vertex.graph.flow_id)
+
+ output.value = result
+ return result
+
+ def _build_artifact(self, result):
+ custom_repr = self.custom_repr()
+ if custom_repr is None and isinstance(result, dict | Data | str):
+ custom_repr = result
+ if not isinstance(custom_repr, str):
+ custom_repr = str(custom_repr)
+
+ raw = self._process_raw_result(result)
+ artifact_type = get_artifact_type(self.status or raw, result)
+ raw, artifact_type = post_process_raw(raw, artifact_type)
+ return {"repr": custom_repr, "raw": raw, "type": artifact_type}
+
+ def _process_raw_result(self, result):
+ return self.extract_data(result)
+
+ def extract_data(self, result):
+ """Extract the data from the result. this is where the self.status is set."""
+ if isinstance(result, Message):
+ self.status = result.get_text()
+ return (
+ self.status if self.status is not None else "No text available"
+ ) # Provide a default message if .text_key is missing
+ if hasattr(result, "data"):
+ return result.data
+ if hasattr(result, "model_dump"):
+ return result.model_dump()
+ if isinstance(result, Data | dict | str):
+ return result.data if isinstance(result, Data) else result
+
+ if self.status:
+ return self.status
+ return result
+
+ def _log_output(self, output):
+ self._output_logs[output.name] = self._logs
+ self._logs = []
+ self._current_output = ""
+
+ def _finalize_results(self, results, artifacts):
+ self._artifacts = artifacts
+ self._results = results
+ if self._tracing_service:
+ self._tracing_service.set_outputs(self.trace_name, results)
+
+ def custom_repr(self):
+ if self.repr_value == "":
+ self.repr_value = self.status
+ if isinstance(self.repr_value, dict):
+ return yaml.dump(self.repr_value)
+ if isinstance(self.repr_value, str):
+ return self.repr_value
+ if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data):
+ return str(self.repr_value)
+ return self.repr_value
+
+ def build_inputs(self):
+ """Builds the inputs for the custom component.
+
+ Returns:
+ List[Input]: The list of inputs.
+ """
+ # This function is similar to build_config, but it will process the inputs
+ # and return them as a dict with keys being the Input.name and values being the Input.model_dump()
+ self.inputs = self.template_config.get("inputs", [])
+ if not self.inputs:
+ return {}
+ return {_input.name: _input.model_dump(by_alias=True, exclude_none=True) for _input in self.inputs}
+
+ def _get_field_order(self):
+ try:
+ inputs = self.template_config["inputs"]
+ return [field.name for field in inputs]
+ except KeyError:
+ return []
+
+ def build(self, **kwargs) -> None:
+ self.set_attributes(kwargs)
+
+ def _get_fallback_input(self, **kwargs):
+ return Input(**kwargs)
+
+ async def to_toolkit(self) -> list[Tool]:
+ component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
+ tools = component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks())
+ if hasattr(self, TOOLS_METADATA_INPUT_NAME):
+ tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
+ return tools
+
+ def get_project_name(self):
+ if hasattr(self, "_tracing_service") and self._tracing_service:
+ return self._tracing_service.project_name
+ return "Langflow"
+
+ def log(self, message: LoggableType | list[LoggableType], name: str | None = None) -> None:
+ """Logs a message.
+
+ Args:
+ message (LoggableType | list[LoggableType]): The message to log.
+ name (str, optional): The name of the log. Defaults to None.
+ """
+ if name is None:
+ name = f"Log {len(self._logs) + 1}"
+ log = Log(message=message, type=get_artifact_type(message), name=name)
+ self._logs.append(log)
+ if self._tracing_service and self._vertex:
+ self._tracing_service.add_log(trace_name=self.trace_name, log=log)
+ if self._event_manager is not None and self._current_output:
+ data = log.model_dump()
+ data["output"] = self._current_output
+ data["component_id"] = self._id
+ self._event_manager.on_log(data=data)
+
+ def _append_tool_output(self) -> None:
+ if next((output for output in self.outputs if output.name == TOOL_OUTPUT_NAME), None) is None:
+ self.outputs.append(
+ Output(
+ name=TOOL_OUTPUT_NAME,
+ display_name=TOOL_OUTPUT_DISPLAY_NAME,
+ method="to_toolkit",
+ types=["Tool"],
+ )
+ )
+
+ def _should_skip_message(self, message: Message) -> bool:
+ """Check if the message should be skipped based on vertex configuration and message type."""
+ return (
+ self._vertex is not None
+ and not (self._vertex.is_output or self._vertex.is_input)
+ and not has_chat_output(self.graph.get_vertex_neighbors(self._vertex))
+ and not isinstance(message, ErrorMessage)
+ )
+
+ async def send_message(self, message: Message, id_: str | None = None):
+ if self._should_skip_message(message):
+ return message
+ if (hasattr(self, "graph") and self.graph.session_id) and (message is not None and not message.session_id):
+ session_id = (
+ UUID(self.graph.session_id) if isinstance(self.graph.session_id, str) else self.graph.session_id
+ )
+ message.session_id = session_id
+ if hasattr(message, "flow_id") and isinstance(message.flow_id, str):
+ message.flow_id = UUID(message.flow_id)
+ stored_message = await self._store_message(message)
+
+ self._stored_message_id = stored_message.id
+ try:
+ complete_message = ""
+ if (
+ self._should_stream_message(stored_message, message)
+ and message is not None
+ and isinstance(message.text, AsyncIterator | Iterator)
+ ):
+ complete_message = await self._stream_message(message.text, stored_message)
+ stored_message.text = complete_message
+ stored_message = await self._update_stored_message(stored_message)
+ else:
+ # Only send message event for non-streaming messages
+ await self._send_message_event(stored_message, id_=id_)
+ except Exception:
+ # remove the message from the database
+ await delete_message(stored_message.id)
+ raise
+ self.status = stored_message
+ return stored_message
+
+ async def _store_message(self, message: Message) -> Message:
+ flow_id: str | None = None
+ if hasattr(self, "graph"):
+ # Convert UUID to str if needed
+ flow_id = str(self.graph.flow_id) if self.graph.flow_id else None
+ stored_messages = await astore_message(message, flow_id=flow_id)
+ if len(stored_messages) != 1:
+ msg = "Only one message can be stored at a time."
+ raise ValueError(msg)
+ stored_message = stored_messages[0]
+ return await Message.create(**stored_message.model_dump())
+
+ async def _send_message_event(self, message: Message, id_: str | None = None, category: str | None = None) -> None:
+ if hasattr(self, "_event_manager") and self._event_manager:
+ data_dict = message.data.copy() if hasattr(message, "data") else message.model_dump()
+ if id_ and not data_dict.get("id"):
+ data_dict["id"] = id_
+ category = category or data_dict.get("category", None)
+
+ def _send_event():
+ match category:
+ case "error":
+ self._event_manager.on_error(data=data_dict)
+ case "remove_message":
+ self._event_manager.on_remove_message(data={"id": data_dict["id"]})
+ case _:
+ self._event_manager.on_message(data=data_dict)
+
+ await asyncio.to_thread(_send_event)
+
+ def _should_stream_message(self, stored_message: Message, original_message: Message) -> bool:
+ return bool(
+ hasattr(self, "_event_manager")
+ and self._event_manager
+ and stored_message.id
+ and not isinstance(original_message.text, str)
+ )
+
+ async def _update_stored_message(self, message: Message) -> Message:
+ """Update the stored message."""
+ if hasattr(self, "_vertex") and self._vertex is not None and hasattr(self._vertex, "graph"):
+ flow_id = (
+ UUID(self._vertex.graph.flow_id)
+ if isinstance(self._vertex.graph.flow_id, str)
+ else self._vertex.graph.flow_id
+ )
+
+ message.flow_id = flow_id
+
+ message_tables = await aupdate_messages(message)
+ if not message_tables:
+ msg = "Failed to update message"
+ raise ValueError(msg)
+ message_table = message_tables[0]
+ return await Message.create(**message_table.model_dump())
+
+ async def _stream_message(self, iterator: AsyncIterator | Iterator, message: Message) -> str:
+ if not isinstance(iterator, AsyncIterator | Iterator):
+ msg = "The message must be an iterator or an async iterator."
+ raise TypeError(msg)
+
+ if isinstance(iterator, AsyncIterator):
+ return await self._handle_async_iterator(iterator, message.id, message)
+ try:
+ complete_message = ""
+ first_chunk = True
+ for chunk in iterator:
+ complete_message = await self._process_chunk(
+ chunk.content, complete_message, message.id, message, first_chunk=first_chunk
+ )
+ first_chunk = False
+ except Exception as e:
+ raise StreamingError(cause=e, source=message.properties.source) from e
+ else:
+ return complete_message
+
+ async def _handle_async_iterator(self, iterator: AsyncIterator, message_id: str, message: Message) -> str:
+ complete_message = ""
+ first_chunk = True
+ async for chunk in iterator:
+ complete_message = await self._process_chunk(
+ chunk.content, complete_message, message_id, message, first_chunk=first_chunk
+ )
+ first_chunk = False
+ return complete_message
+
+ async def _process_chunk(
+ self, chunk: str, complete_message: str, message_id: str, message: Message, *, first_chunk: bool = False
+ ) -> str:
+ complete_message += chunk
+ if self._event_manager:
+ if first_chunk:
+ # Send the initial message only on the first chunk
+ msg_copy = message.model_copy()
+ msg_copy.text = complete_message
+ await self._send_message_event(msg_copy, id_=message_id)
+ await asyncio.to_thread(
+ self._event_manager.on_token,
+ data={
+ "chunk": chunk,
+ "id": str(message_id),
+ },
+ )
+ return complete_message
+
+ async def send_error(
+ self,
+ exception: Exception,
+ session_id: str,
+ trace_name: str,
+ source: Source,
+ ) -> Message | None:
+ """Send an error message to the frontend."""
+ flow_id = self.graph.flow_id if hasattr(self, "graph") else None
+ if not session_id:
+ return None
+ error_message = ErrorMessage(
+ flow_id=flow_id,
+ exception=exception,
+ session_id=session_id,
+ trace_name=trace_name,
+ source=source,
+ )
+ await self.send_message(error_message)
+ return error_message
+
+ def _append_tool_to_outputs_map(self):
+ self._outputs_map[TOOL_OUTPUT_NAME] = self._build_tool_output()
+ # add a new input for the tool schema
+ # self.inputs.append(self._build_tool_schema())
+
+ def _build_tool_output(self) -> Output:
+ return Output(name=TOOL_OUTPUT_NAME, display_name=TOOL_OUTPUT_DISPLAY_NAME, method="to_toolkit", types=["Tool"])
+
+ async def _build_tools_metadata_input(self):
+ tools = await self.to_toolkit()
+ tool_data = (
+ self.tools_metadata
+ if hasattr(self, TOOLS_METADATA_INPUT_NAME)
+ else [{"name": tool.name, "description": tool.description, "tags": tool.tags} for tool in tools]
+ )
+ try:
+ from langflow.io import TableInput
+ except ImportError as e:
+ msg = "Failed to import TableInput from langflow.io"
+ raise ImportError(msg) from e
+
+ return TableInput(
+ name=TOOLS_METADATA_INPUT_NAME,
+ display_name="Edit tools",
+ real_time_refresh=True,
+ table_schema=TOOL_TABLE_SCHEMA,
+ value=tool_data,
+ table_icon="Hammer",
+ trigger_icon="Hammer",
+ trigger_text="",
+ table_options=TableOptions(
+ block_add=True,
+ block_delete=True,
+ block_edit=True,
+ block_sort=True,
+ block_filter=True,
+ block_hide=True,
+ block_select=True,
+ hide_options=True,
+ field_parsers={
+ "name": [FieldParserType.SNAKE_CASE, FieldParserType.NO_BLANK],
+ "commands": FieldParserType.COMMANDS,
+ },
+ description=TOOLS_METADATA_INFO,
+ ),
+ )
+
+ def get_input_display_name(self, input_name: str) -> str:
+ """Get the display name of an input.
+
+ This is a public utility method that subclasses can use to get user-friendly
+ display names for inputs when building error messages or UI elements.
+
+ Usage:
+ msg = f"Input {self.get_input_display_name(input_name)} not found"
+
+ Args:
+ input_name (str): The name of the input.
+
+ Returns:
+ str: The display name of the input, or the input name if not found.
+ """
+ if input_name in self._inputs:
+ return getattr(self._inputs[input_name], "display_name", input_name)
+ return input_name
+
+ def get_output_display_name(self, output_name: str) -> str:
+ """Get the display name of an output.
+
+ This is a public utility method that subclasses can use to get user-friendly
+ display names for outputs when building error messages or UI elements.
+
+ Args:
+ output_name (str): The name of the output.
+
+ Returns:
+ str: The display name of the output, or the output name if not found.
+ """
+ if output_name in self._outputs_map:
+ return getattr(self._outputs_map[output_name], "display_name", output_name)
+ return output_name
+
+ def build_input_error_message(self, input_name: str, message: str) -> str:
+ """Build an error message for an input.
+
+ This is a public utility method that subclasses can use to create consistent,
+ user-friendly error messages that reference inputs by their display names.
+ The input name is placed at the beginning to ensure it's visible even if the message is truncated.
+
+ Args:
+ input_name (str): The name of the input.
+ message (str): The error message.
+
+ Returns:
+ str: The formatted error message with display name.
+ """
+ display_name = self.get_input_display_name(input_name)
+ return f"[Input: {display_name}] {message}"
+
+ def build_output_error_message(self, output_name: str, message: str) -> str:
+ """Build an error message for an output.
+
+ This is a public utility method that subclasses can use to create consistent,
+ user-friendly error messages that reference outputs by their display names.
+ The output name is placed at the beginning to ensure it's visible even if the message is truncated.
+
+ Args:
+ output_name (str): The name of the output.
+ message (str): The error message.
+
+ Returns:
+ str: The formatted error message with display name.
+ """
+ display_name = self.get_output_display_name(output_name)
+ return f"[Output: {display_name}] {message}"
+
+ def build_component_error_message(self, message: str) -> str:
+ """Build an error message for the component.
+
+ This is a public utility method that subclasses can use to create consistent,
+ user-friendly error messages that reference the component by its display name.
+ The component name is placed at the beginning to ensure it's visible even if the message is truncated.
+
+ Args:
+ message (str): The error message.
+
+ Returns:
+ str: The formatted error message with component display name.
+ """
+ return f"[Component: {self.display_name or self.__class__.__name__}] {message}"
diff --git a/langflow/src/backend/base/langflow/custom/custom_component/component_with_cache.py b/langflow/src/backend/base/langflow/custom/custom_component/component_with_cache.py
new file mode 100644
index 0000000..e8a6888
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/custom_component/component_with_cache.py
@@ -0,0 +1,8 @@
+from langflow.custom import Component
+from langflow.services.deps import get_shared_component_cache_service
+
+
+class ComponentWithCache(Component):
+ def __init__(self, **data) -> None:
+ super().__init__(**data)
+ self._shared_component_cache = get_shared_component_cache_service()
diff --git a/langflow/src/backend/base/langflow/custom/custom_component/custom_component.py b/langflow/src/backend/base/langflow/custom/custom_component/custom_component.py
new file mode 100644
index 0000000..9107dcc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/custom_component/custom_component.py
@@ -0,0 +1,564 @@
+from __future__ import annotations
+
+import uuid
+from collections.abc import Callable, Sequence
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, ClassVar
+
+import yaml
+from cachetools import TTLCache
+from langchain_core.documents import Document
+from pydantic import BaseModel
+
+from langflow.custom.custom_component.base_component import BaseComponent
+from langflow.helpers.flow import list_flows, load_flow, run_flow
+from langflow.schema import Data
+from langflow.services.deps import get_storage_service, get_variable_service, session_scope
+from langflow.services.storage.service import StorageService
+from langflow.template.utils import update_frontend_node_with_template_values
+from langflow.type_extraction.type_extraction import post_process_type
+from langflow.utils import validate
+from langflow.utils.async_helpers import run_until_complete
+
+if TYPE_CHECKING:
+ from langchain.callbacks.base import BaseCallbackHandler
+
+ from langflow.graph.graph.base import Graph
+ from langflow.graph.vertex.base import Vertex
+ from langflow.schema.dotdict import dotdict
+ from langflow.schema.schema import OutputValue
+ from langflow.services.storage.service import StorageService
+ from langflow.services.tracing.schema import Log
+ from langflow.services.tracing.service import TracingService
+
+
+class CustomComponent(BaseComponent):
+ """Represents a custom component in Langflow.
+
+ Attributes:
+ name (Optional[str]): This attribute helps the frontend apply styles to known components.
+ display_name (Optional[str]): The display name of the custom component.
+ description (Optional[str]): The description of the custom component.
+ code (Optional[str]): The code of the custom component.
+ field_config (dict): The field configuration of the custom component.
+ code_class_base_inheritance (ClassVar[str]): The base class name for the custom component.
+ function_entrypoint_name (ClassVar[str]): The name of the function entrypoint for the custom component.
+ function (Optional[Callable]): The function associated with the custom component.
+ repr_value (Optional[Any]): The representation value of the custom component.
+ user_id (Optional[Union[UUID, str]]): The user ID associated with the custom component.
+ status (Optional[Any]): The status of the custom component.
+ _tree (Optional[dict]): The code tree of the custom component.
+ """
+
+ # True constants that should be shared (using ClassVar)
+ _code_class_base_inheritance: ClassVar[str] = "CustomComponent"
+ function_entrypoint_name: ClassVar[str] = "build"
+ name: str | None = None
+ """The name of the component used to styles. Defaults to None."""
+ display_name: str | None = None
+ """The display name of the component. Defaults to None."""
+ description: str | None = None
+ """The description of the component. Defaults to None."""
+ icon: str | None = None
+ """The icon of the component. It should be an emoji. Defaults to None."""
+
+ def __init__(self, **data) -> None:
+ """Initializes a new instance of the CustomComponent class.
+
+ Args:
+ **data: Additional keyword arguments to initialize the custom component.
+ """
+ # Initialize instance-specific attributes first
+ self.is_input: bool | None = None
+ self.is_output: bool | None = None
+ self.add_tool_output: bool = False
+ self.field_config: dict = {}
+ self.field_order: list[str] | None = None
+ self.frozen: bool = False
+ self.build_parameters: dict | None = None
+ self._vertex: Vertex | None = None
+ self.function: Callable | None = None
+ self.repr_value: Any = ""
+ self.status: Any | None = None
+
+ # Initialize collections with empty defaults
+ self._flows_data: list[Data] | None = None
+ self._outputs: list[OutputValue] = []
+ self._logs: list[Log] = []
+ self._output_logs: dict[str, list[Log] | Log] = {}
+ self._tracing_service: TracingService | None = None
+ self._tree: dict | None = None
+
+ # Initialize additional instance state
+ self.cache: TTLCache = TTLCache(maxsize=1024, ttl=60)
+ self._results: dict = {}
+ self._artifacts: dict = {}
+
+ # Call parent's init after setting up our attributes
+ super().__init__(**data)
+
+ def set_attributes(self, parameters: dict) -> None:
+ pass
+
+ def set_parameters(self, parameters: dict) -> None:
+ self._parameters = parameters
+ self.set_attributes(self._parameters)
+
+ @property
+ def trace_name(self) -> str:
+ if hasattr(self, "_id") and self._id is None:
+ msg = "Component id is not set"
+ raise ValueError(msg)
+ if hasattr(self, "_id"):
+ return f"{self.display_name} ({self._id})"
+ return f"{self.display_name}"
+
+ def update_state(self, name: str, value: Any) -> None:
+ if not self._vertex:
+ msg = "Vertex is not set"
+ raise ValueError(msg)
+ try:
+ self._vertex.graph.update_state(name=name, record=value, caller=self._vertex.id)
+ except Exception as e:
+ msg = f"Error updating state: {e}"
+ raise ValueError(msg) from e
+
+ def stop(self, output_name: str | None = None) -> None:
+ if not output_name and self._vertex and len(self._vertex.outputs) == 1:
+ output_name = self._vertex.outputs[0]["name"]
+ elif not output_name:
+ msg = "You must specify an output name to call stop"
+ raise ValueError(msg)
+ if not self._vertex:
+ msg = "Vertex is not set"
+ raise ValueError(msg)
+ try:
+ self.graph.mark_branch(vertex_id=self._vertex.id, output_name=output_name, state="INACTIVE")
+ except Exception as e:
+ msg = f"Error stopping {self.display_name}: {e}"
+ raise ValueError(msg) from e
+
+ def start(self, output_name: str | None = None) -> None:
+ if not output_name and self._vertex and len(self._vertex.outputs) == 1:
+ output_name = self._vertex.outputs[0]["name"]
+ elif not output_name:
+ msg = "You must specify an output name to call start"
+ raise ValueError(msg)
+ if not self._vertex:
+ msg = "Vertex is not set"
+ raise ValueError(msg)
+ try:
+ self.graph.mark_branch(vertex_id=self._vertex.id, output_name=output_name, state="ACTIVE")
+ except Exception as e:
+ msg = f"Error starting {self.display_name}: {e}"
+ raise ValueError(msg) from e
+
+ def append_state(self, name: str, value: Any) -> None:
+ if not self._vertex:
+ msg = "Vertex is not set"
+ raise ValueError(msg)
+ try:
+ self._vertex.graph.append_state(name=name, record=value, caller=self._vertex.id)
+ except Exception as e:
+ msg = f"Error appending state: {e}"
+ raise ValueError(msg) from e
+
+ def get_state(self, name: str):
+ if not self._vertex:
+ msg = "Vertex is not set"
+ raise ValueError(msg)
+ try:
+ return self._vertex.graph.get_state(name=name)
+ except Exception as e:
+ msg = f"Error getting state: {e}"
+ raise ValueError(msg) from e
+
+ @staticmethod
+ def resolve_path(path: str) -> str:
+ """Resolves the path to an absolute path."""
+ if not path:
+ return path
+ path_object = Path(path)
+
+ if path_object.parts and path_object.parts[0] == "~":
+ path_object = path_object.expanduser()
+ elif path_object.is_relative_to("."):
+ path_object = path_object.resolve()
+ return str(path_object)
+
+ def get_full_path(self, path: str) -> str:
+ storage_svc: StorageService = get_storage_service()
+
+ flow_id, file_name = path.split("/", 1)
+ return storage_svc.build_full_path(flow_id, file_name)
+
+ @property
+ def graph(self):
+ return self._vertex.graph
+
+ @property
+ def user_id(self):
+ if hasattr(self, "_user_id") and self._user_id:
+ return self._user_id
+ return self.graph.user_id
+
+ @property
+ def flow_id(self):
+ return self.graph.flow_id
+
+ @property
+ def flow_name(self):
+ return self.graph.flow_name
+
+ def _get_field_order(self):
+ return self.field_order or list(self.field_config.keys())
+
+ def custom_repr(self):
+ """Returns the custom representation of the custom component.
+
+ Returns:
+ str: The custom representation of the custom component.
+ """
+ if self.repr_value == "":
+ self.repr_value = self.status
+ if isinstance(self.repr_value, dict):
+ return yaml.dump(self.repr_value)
+ if isinstance(self.repr_value, str):
+ return self.repr_value
+ if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data):
+ return str(self.repr_value)
+ return self.repr_value
+
+ def build_config(self):
+ """Builds the configuration for the custom component.
+
+ Returns:
+ dict: The configuration for the custom component.
+ """
+ return self.field_config
+
+ def update_build_config(
+ self,
+ build_config: dotdict,
+ field_value: Any,
+ field_name: str | None = None,
+ ):
+ """Updates the build configuration for the custom component.
+
+ Do not call directly as implementation can be a coroutine.
+ """
+ build_config[field_name]["value"] = field_value
+ return build_config
+
+ @property
+ def tree(self):
+ """Gets the code tree of the custom component.
+
+ Returns:
+ dict: The code tree of the custom component.
+ """
+ return self.get_code_tree(self._code or "")
+
+ def to_data(self, data: Any, *, keys: list[str] | None = None, silent_errors: bool = False) -> list[Data]:
+ """Converts input data into a list of Data objects.
+
+ Args:
+ data (Any): The input data to be converted. It can be a single item or a sequence of items.
+ If the input data is a Langchain Document, text_key and data_key are ignored.
+
+ keys (List[str], optional): The keys to access the text and data values in each item.
+ It should be a list of strings where the first element is the text key and the second element
+ is the data key.
+ Defaults to None, in which case the default keys "text" and "data" are used.
+ silent_errors (bool, optional): Whether to suppress errors when the specified keys are not found
+ in the data.
+
+ Returns:
+ List[Data]: A list of Data objects.
+
+ Raises:
+ ValueError: If the input data is not of a valid type or if the specified keys are not found in the data.
+
+ """
+ if not keys:
+ keys = []
+ data_objects = []
+ if not isinstance(data, Sequence):
+ data = [data]
+ for item in data:
+ data_dict = {}
+ if isinstance(item, Document):
+ data_dict = item.metadata
+ data_dict["text"] = item.page_content
+ elif isinstance(item, BaseModel):
+ model_dump = item.model_dump()
+ for key in keys:
+ if silent_errors:
+ data_dict[key] = model_dump.get(key, "")
+ else:
+ try:
+ data_dict[key] = model_dump[key]
+ except KeyError as e:
+ msg = f"Key {key} not found in {item}"
+ raise ValueError(msg) from e
+
+ elif isinstance(item, str):
+ data_dict = {"text": item}
+ elif isinstance(item, dict):
+ data_dict = item.copy()
+ else:
+ msg = f"Invalid data type: {type(item)}"
+ raise TypeError(msg)
+
+ data_objects.append(Data(data=data_dict))
+
+ return data_objects
+
+ def get_method_return_type(self, method_name: str):
+ build_method = self.get_method(method_name)
+ if not build_method or not build_method.get("has_return"):
+ return []
+ return_type = build_method["return_type"]
+
+ return self._extract_return_type(return_type)
+
+ def create_references_from_data(self, data: list[Data], *, include_data: bool = False) -> str:
+ """Create references from a list of data.
+
+ Args:
+ data (List[dict]): A list of data, where each record is a dictionary.
+ include_data (bool, optional): Whether to include data in the references. Defaults to False.
+
+ Returns:
+ str: A string containing the references in markdown format.
+ """
+ if not data:
+ return ""
+ markdown_string = "---\n"
+ for value in data:
+ markdown_string += f"- Text: {value.get_text()}"
+ if include_data:
+ markdown_string += f" Data: {value.data}"
+ markdown_string += "\n"
+ return markdown_string
+
+ @property
+ def get_function_entrypoint_args(self) -> list:
+ """Gets the arguments of the function entrypoint for the custom component.
+
+ Returns:
+ list: The arguments of the function entrypoint.
+ """
+ build_method = self.get_method(self._function_entrypoint_name)
+ if not build_method:
+ return []
+
+ args = build_method["args"]
+ for arg in args:
+ if not arg.get("type") and arg.get("name") != "self":
+ # Set the type to Data
+ arg["type"] = "Data"
+ return args
+
+ def get_method(self, method_name: str):
+ """Gets the build method for the custom component.
+
+ Returns:
+ dict: The build method for the custom component.
+ """
+ if not self._code:
+ return {}
+
+ component_classes = [
+ cls for cls in self.tree["classes"] if "Component" in cls["bases"] or "CustomComponent" in cls["bases"]
+ ]
+ if not component_classes:
+ return {}
+
+ # Assume the first Component class is the one we're interested in
+ component_class = component_classes[0]
+ build_methods = [method for method in component_class["methods"] if method["name"] == (method_name)]
+
+ return build_methods[0] if build_methods else {}
+
+ @property
+ def _get_function_entrypoint_return_type(self) -> list[Any]:
+ """Gets the return type of the function entrypoint for the custom component.
+
+ Returns:
+ List[Any]: The return type of the function entrypoint.
+ """
+ return self.get_method_return_type(self._function_entrypoint_name)
+
+ def _extract_return_type(self, return_type: Any) -> list[Any]:
+ return post_process_type(return_type)
+
+ @property
+ def get_main_class_name(self):
+ """Gets the main class name of the custom component.
+
+ Returns:
+ str: The main class name of the custom component.
+ """
+ if not self._code:
+ return ""
+
+ base_name = self._code_class_base_inheritance
+ method_name = self._function_entrypoint_name
+
+ classes = []
+ for item in self.tree.get("classes", []):
+ if base_name in item["bases"]:
+ method_names = [method["name"] for method in item["methods"]]
+ if method_name in method_names:
+ classes.append(item["name"])
+
+ # Get just the first item
+ return next(iter(classes), "")
+
+ @property
+ def template_config(self):
+ """Gets the template configuration for the custom component.
+
+ Returns:
+ dict: The template configuration for the custom component.
+ """
+ if not self._template_config:
+ self._template_config = self.build_template_config()
+ return self._template_config
+
+ def variables(self, name: str, field: str):
+ """DEPRECATED - This is kept for backward compatibility. Use get_variables instead."""
+ return run_until_complete(self.get_variables(name, field))
+
+ async def get_variables(self, name: str, field: str):
+ """Returns the variable for the current user with the specified name.
+
+ Raises:
+ ValueError: If the user id is not set.
+
+ Returns:
+ The variable for the current user with the specified name.
+ """
+ if hasattr(self, "_user_id") and not self.user_id:
+ msg = f"User id is not set for {self.__class__.__name__}"
+ raise ValueError(msg)
+ variable_service = get_variable_service() # Get service instance
+ # Retrieve and decrypt the variable by name for the current user
+ if isinstance(self.user_id, str):
+ user_id = uuid.UUID(self.user_id)
+ elif isinstance(self.user_id, uuid.UUID):
+ user_id = self.user_id
+ else:
+ msg = f"Invalid user id: {self.user_id}"
+ raise TypeError(msg)
+ async with session_scope() as session:
+ return await variable_service.get_variable(user_id=user_id, name=name, field=field, session=session)
+
+ async def list_key_names(self):
+ """Lists the names of the variables for the current user.
+
+ Raises:
+ ValueError: If the user id is not set.
+
+ Returns:
+ List[str]: The names of the variables for the current user.
+ """
+ if hasattr(self, "_user_id") and not self.user_id:
+ msg = f"User id is not set for {self.__class__.__name__}"
+ raise ValueError(msg)
+ variable_service = get_variable_service()
+
+ async with session_scope() as session:
+ return await variable_service.list_variables(user_id=self.user_id, session=session)
+
+ def index(self, value: int = 0):
+ """Returns a function that returns the value at the given index in the iterable.
+
+ Args:
+ value (int): The index value.
+
+ Returns:
+ Callable: A function that returns the value at the given index.
+ """
+
+ def get_index(iterable: list[Any]):
+ return iterable[value] if iterable else iterable
+
+ return get_index
+
+ def get_function(self):
+ """Gets the function associated with the custom component.
+
+ Returns:
+ Callable: The function associated with the custom component.
+ """
+ return validate.create_function(self._code, self._function_entrypoint_name)
+
+ async def load_flow(self, flow_id: str, tweaks: dict | None = None) -> Graph:
+ if not self.user_id:
+ msg = "Session is invalid"
+ raise ValueError(msg)
+ return await load_flow(user_id=str(self.user_id), flow_id=flow_id, tweaks=tweaks)
+
+ async def run_flow(
+ self,
+ inputs: dict | list[dict] | None = None,
+ flow_id: str | None = None,
+ flow_name: str | None = None,
+ output_type: str | None = "chat",
+ tweaks: dict | None = None,
+ ) -> Any:
+ return await run_flow(
+ inputs=inputs,
+ output_type=output_type,
+ flow_id=flow_id,
+ flow_name=flow_name,
+ tweaks=tweaks,
+ user_id=str(self.user_id),
+ run_id=self.graph.run_id,
+ )
+
+ def list_flows(self) -> list[Data]:
+ """DEPRECATED - This is kept for backward compatibility. Using alist_flows instead is recommended."""
+ return run_until_complete(self.alist_flows())
+
+ async def alist_flows(self) -> list[Data]:
+ if not self.user_id:
+ msg = "Session is invalid"
+ raise ValueError(msg)
+ try:
+ return await list_flows(user_id=str(self.user_id))
+ except Exception as e:
+ msg = f"Error listing flows: {e}"
+ raise ValueError(msg) from e
+
+ def build(self, *args: Any, **kwargs: Any) -> Any:
+ """Builds the custom component.
+
+ Args:
+ *args: The positional arguments.
+ **kwargs: The keyword arguments.
+
+ Returns:
+ Any: The result of the build process.
+ """
+ raise NotImplementedError
+
+ def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):
+ """DEPRECATED - Kept for backward compatibility. Use update_frontend_node instead."""
+ run_until_complete(self.update_frontend_node(new_frontend_node, current_frontend_node))
+
+ async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):
+ """Updates the given new frontend node with values from the current frontend node.
+
+ This function is called after the code validation is done.
+ """
+ return update_frontend_node_with_template_values(
+ frontend_node=new_frontend_node, raw_frontend_node=current_frontend_node
+ )
+
+ def get_langchain_callbacks(self) -> list[BaseCallbackHandler]:
+ if self._tracing_service:
+ return self._tracing_service.get_langchain_callbacks()
+ return []
diff --git a/langflow/src/backend/base/langflow/custom/directory_reader/__init__.py b/langflow/src/backend/base/langflow/custom/directory_reader/__init__.py
new file mode 100644
index 0000000..2e8e0b9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/directory_reader/__init__.py
@@ -0,0 +1,3 @@
+from .directory_reader import DirectoryReader
+
+__all__ = ["DirectoryReader"]
diff --git a/langflow/src/backend/base/langflow/custom/directory_reader/directory_reader.py b/langflow/src/backend/base/langflow/custom/directory_reader/directory_reader.py
new file mode 100644
index 0000000..fb03880
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/directory_reader/directory_reader.py
@@ -0,0 +1,359 @@
+import ast
+import asyncio
+import zlib
+from pathlib import Path
+
+import anyio
+from aiofile import async_open
+from loguru import logger
+
+from langflow.custom import Component
+
+MAX_DEPTH = 2
+
+
+class CustomComponentPathValueError(ValueError):
+ pass
+
+
+class StringCompressor:
+ def __init__(self, input_string) -> None:
+ """Initialize StringCompressor with a string to compress."""
+ self.input_string = input_string
+
+ def compress_string(self):
+ """Compress the initial string and return the compressed data."""
+ # Convert string to bytes
+ byte_data = self.input_string.encode("utf-8")
+ # Compress the bytes
+ self.compressed_data = zlib.compress(byte_data)
+
+ return self.compressed_data
+
+ def decompress_string(self):
+ """Decompress the compressed data and return the original string."""
+ # Decompress the bytes
+ decompressed_data = zlib.decompress(self.compressed_data)
+ # Convert bytes back to string
+ return decompressed_data.decode("utf-8")
+
+
+class DirectoryReader:
+ # Ensure the base path to read the files that contain
+ # the custom components from this directory.
+ base_path = ""
+
+ def __init__(self, directory_path, *, compress_code_field=False) -> None:
+ """Initialize DirectoryReader with a directory path and a flag indicating whether to compress the code."""
+ self.directory_path = directory_path
+ self.compress_code_field = compress_code_field
+
+ def get_safe_path(self):
+ """Check if the path is valid and return it, or None if it's not."""
+ return self.directory_path if self.is_valid_path() else None
+
+ def is_valid_path(self) -> bool:
+ """Check if the directory path is valid by comparing it to the base path."""
+ fullpath = Path(self.directory_path).resolve()
+ return not self.base_path or fullpath.is_relative_to(self.base_path)
+
+ def is_empty_file(self, file_content):
+ """Check if the file content is empty."""
+ return len(file_content.strip()) == 0
+
+ def filter_loaded_components(self, data: dict, *, with_errors: bool) -> dict:
+ from langflow.custom.utils import build_component
+
+ items = []
+ for menu in data["menu"]:
+ components = []
+ for component in menu["components"]:
+ try:
+ if component["error"] if with_errors else not component["error"]:
+ component_tuple = (*build_component(component), component)
+ components.append(component_tuple)
+ except Exception: # noqa: BLE001
+ logger.debug(f"Error while loading component {component['name']} from {component['file']}")
+ continue
+ items.append({"name": menu["name"], "path": menu["path"], "components": components})
+ filtered = [menu for menu in items if menu["components"]]
+ logger.debug(f"Filtered components {'with errors' if with_errors else ''}: {len(filtered)}")
+ return {"menu": filtered}
+
+ def validate_code(self, file_content) -> bool:
+ """Validate the Python code by trying to parse it with ast.parse."""
+ try:
+ ast.parse(file_content)
+ except SyntaxError:
+ return False
+ return True
+
+ def validate_build(self, file_content):
+ """Check if the file content contains a function named 'build'."""
+ return "def build" in file_content
+
+ def read_file_content(self, file_path):
+ """Read and return the content of a file."""
+ file_path_ = Path(file_path)
+ if not file_path_.is_file():
+ return None
+ try:
+ with file_path_.open(encoding="utf-8") as file:
+ # UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 3069:
+ # character maps to
+ return file.read()
+ except UnicodeDecodeError:
+ # This is happening in Windows, so we need to open the file in binary mode
+ # The file is always just a python file, so we can safely read it as utf-8
+ with file_path_.open("rb") as f:
+ return f.read().decode("utf-8")
+
+ async def aread_file_content(self, file_path):
+ """Read and return the content of a file."""
+ file_path_ = anyio.Path(file_path)
+ if not await file_path_.is_file():
+ return None
+ try:
+ async with async_open(str(file_path_), encoding="utf-8") as file:
+ # UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 3069:
+ # character maps to
+ return await file.read()
+ except UnicodeDecodeError:
+ # This is happening in Windows, so we need to open the file in binary mode
+ # The file is always just a python file, so we can safely read it as utf-8
+ async with async_open(str(file_path_), "rb") as f:
+ return (await f.read()).decode("utf-8")
+
+ def get_files(self):
+ """Walk through the directory path and return a list of all .py files."""
+ if not (safe_path := self.get_safe_path()):
+ msg = f"The path needs to start with '{self.base_path}'."
+ raise CustomComponentPathValueError(msg)
+
+ file_list = []
+ safe_path_obj = Path(safe_path)
+ for file_path in safe_path_obj.rglob("*.py"):
+ # Check if the file is in the folder `deactivated` and if so, skip it
+ if "deactivated" in file_path.parent.name:
+ continue
+
+ # Calculate the depth of the file relative to the safe path
+ relative_depth = len(file_path.relative_to(safe_path_obj).parts)
+
+ # Only include files that are one or two levels deep
+ if relative_depth <= MAX_DEPTH and file_path.is_file() and not file_path.name.startswith("__"):
+ file_list.append(str(file_path))
+ return file_list
+
+ def find_menu(self, response, menu_name):
+ """Find and return a menu by its name in the response."""
+ return next(
+ (menu for menu in response["menu"] if menu["name"] == menu_name),
+ None,
+ )
+
+ def _is_type_hint_imported(self, type_hint_name: str, code: str) -> bool:
+ """Check if a specific type hint is imported from the typing module in the given code."""
+ module = ast.parse(code)
+
+ return any(
+ isinstance(node, ast.ImportFrom)
+ and node.module == "typing"
+ and any(alias.name == type_hint_name for alias in node.names)
+ for node in ast.walk(module)
+ )
+
+ def _is_type_hint_used_in_args(self, type_hint_name: str, code: str) -> bool:
+ """Check if a specific type hint is used in the function definitions within the given code."""
+ try:
+ module = ast.parse(code)
+
+ for node in ast.walk(module):
+ if isinstance(node, ast.FunctionDef):
+ for arg in node.args.args:
+ if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
+ return True
+ except SyntaxError:
+ # Returns False if the code is not valid Python
+ return False
+ return False
+
+ def _is_type_hint_in_arg_annotation(self, annotation, type_hint_name: str) -> bool:
+ """Helper function to check if a type hint exists in an annotation."""
+ return (
+ annotation is not None
+ and isinstance(annotation, ast.Subscript)
+ and isinstance(annotation.value, ast.Name)
+ and annotation.value.id == type_hint_name
+ )
+
+ def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
+ """Check if a type hint is used but not imported in the given code."""
+ try:
+ return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
+ type_hint_name, code
+ )
+ except SyntaxError:
+ # Returns True if there's something wrong with the code
+ # TODO : Find a better way to handle this
+ return True
+
+ def process_file(self, file_path):
+ """Process a file by validating its content and returning the result and content/error message."""
+ try:
+ file_content = self.read_file_content(file_path)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error while reading file {file_path}")
+ return False, f"Could not read {file_path}"
+
+ if file_content is None:
+ return False, f"Could not read {file_path}"
+ if self.is_empty_file(file_content):
+ return False, "Empty file"
+ if not self.validate_code(file_content):
+ return False, "Syntax error"
+ if self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
+ "Optional", file_content
+ ):
+ return (
+ False,
+ "Type hint 'Optional' is used but not imported in the code.",
+ )
+ if self.compress_code_field:
+ file_content = str(StringCompressor(file_content).compress_string())
+ return True, file_content
+
+ def build_component_menu_list(self, file_paths):
+ """Build a list of menus with their components from the .py files in the directory."""
+ response = {"menu": []}
+ logger.debug("-------------------- Building component menu list --------------------")
+
+ for file_path in file_paths:
+ file_path_ = Path(file_path)
+ menu_name = file_path_.parent.name
+ filename = file_path_.name
+ validation_result, result_content = self.process_file(file_path)
+ if not validation_result:
+ logger.error(f"Error while processing file {file_path}")
+
+ menu_result = self.find_menu(response, menu_name) or {
+ "name": menu_name,
+ "path": str(file_path_.parent),
+ "components": [],
+ }
+ component_name = filename.split(".")[0]
+ # This is the name of the file which will be displayed in the UI
+ # We need to change it from snake_case to CamelCase
+
+ # first check if it's already CamelCase
+ if "_" in component_name:
+ component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
+ else:
+ component_name_camelcase = component_name
+
+ if validation_result:
+ try:
+ output_types = self.get_output_types_from_code(result_content)
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).debug("Error while getting output types from code")
+ output_types = [component_name_camelcase]
+ else:
+ output_types = [component_name_camelcase]
+
+ component_info = {
+ "name": component_name_camelcase,
+ "output_types": output_types,
+ "file": filename,
+ "code": result_content if validation_result else "",
+ "error": "" if validation_result else result_content,
+ }
+ menu_result["components"].append(component_info)
+
+ if menu_result not in response["menu"]:
+ response["menu"].append(menu_result)
+ logger.debug("-------------------- Component menu list built --------------------")
+ return response
+
+ async def process_file_async(self, file_path):
+ try:
+ file_content = await self.aread_file_content(file_path)
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error while reading file {file_path}")
+ return False, f"Could not read {file_path}"
+
+ if file_content is None:
+ return False, f"Could not read {file_path}"
+ if self.is_empty_file(file_content):
+ return False, "Empty file"
+ if not self.validate_code(file_content):
+ return False, "Syntax error"
+ if self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
+ "Optional", file_content
+ ):
+ return (
+ False,
+ "Type hint 'Optional' is used but not imported in the code.",
+ )
+ if self.compress_code_field:
+ file_content = str(StringCompressor(file_content).compress_string())
+ return True, file_content
+
+ async def abuild_component_menu_list(self, file_paths):
+ response = {"menu": []}
+ logger.debug("-------------------- Async Building component menu list --------------------")
+
+ tasks = [self.process_file_async(file_path) for file_path in file_paths]
+ results = await asyncio.gather(*tasks)
+
+ for file_path, (validation_result, result_content) in zip(file_paths, results, strict=True):
+ file_path_ = Path(file_path)
+ menu_name = file_path_.parent.name
+ filename = file_path_.name
+
+ if not validation_result:
+ logger.error(f"Error while processing file {file_path}")
+
+ menu_result = self.find_menu(response, menu_name) or {
+ "name": menu_name,
+ "path": str(file_path_.parent),
+ "components": [],
+ }
+ component_name = filename.split(".")[0]
+
+ if "_" in component_name:
+ component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
+ else:
+ component_name_camelcase = component_name
+
+ if validation_result:
+ try:
+ output_types = await asyncio.to_thread(self.get_output_types_from_code, result_content)
+ except Exception: # noqa: BLE001
+ logger.exception("Error while getting output types from code")
+ output_types = [component_name_camelcase]
+ else:
+ output_types = [component_name_camelcase]
+
+ component_info = {
+ "name": component_name_camelcase,
+ "output_types": output_types,
+ "file": filename,
+ "code": result_content if validation_result else "",
+ "error": "" if validation_result else result_content,
+ }
+ menu_result["components"].append(component_info)
+
+ if menu_result not in response["menu"]:
+ response["menu"].append(menu_result)
+
+ logger.debug("-------------------- Component menu list built --------------------")
+ return response
+
+ @staticmethod
+ def get_output_types_from_code(code: str) -> list:
+ """Get the output types from the code."""
+ custom_component = Component(_code=code)
+ types_list = custom_component._get_function_entrypoint_return_type
+
+ # Get the name of types classes
+ return [type_.__name__ for type_ in types_list if hasattr(type_, "__name__")]
diff --git a/langflow/src/backend/base/langflow/custom/directory_reader/utils.py b/langflow/src/backend/base/langflow/custom/directory_reader/utils.py
new file mode 100644
index 0000000..e9c3357
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/directory_reader/utils.py
@@ -0,0 +1,172 @@
+import asyncio
+
+from loguru import logger
+
+from langflow.custom.directory_reader import DirectoryReader
+from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
+
+
+def merge_nested_dicts_with_renaming(dict1, dict2):
+ for key, value in dict2.items():
+ if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict):
+ for sub_key, sub_value in value.items():
+ # if sub_key in dict1[key]:
+ # new_key = get_new_key(dict1[key], sub_key)
+ # dict1[key][new_key] = sub_value
+ # else:
+ dict1[key][sub_key] = sub_value
+ else:
+ dict1[key] = value
+ return dict1
+
+
+def build_invalid_menu(invalid_components):
+ """Build the invalid menu."""
+ if not invalid_components.get("menu"):
+ return {}
+
+ logger.debug("------------------- INVALID COMPONENTS -------------------")
+ invalid_menu = {}
+ for menu_item in invalid_components["menu"]:
+ menu_name = menu_item["name"]
+ invalid_menu[menu_name] = build_invalid_menu_items(menu_item)
+ return invalid_menu
+
+
+def build_valid_menu(valid_components):
+ """Build the valid menu."""
+ valid_menu = {}
+ logger.debug("------------------- VALID COMPONENTS -------------------")
+ for menu_item in valid_components["menu"]:
+ menu_name = menu_item["name"]
+ valid_menu[menu_name] = build_menu_items(menu_item)
+ return valid_menu
+
+
+def build_and_validate_all_files(reader: DirectoryReader, file_list):
+ """Build and validate all files."""
+ data = reader.build_component_menu_list(file_list)
+
+ valid_components = reader.filter_loaded_components(data=data, with_errors=False)
+ invalid_components = reader.filter_loaded_components(data=data, with_errors=True)
+
+ return valid_components, invalid_components
+
+
+async def abuild_and_validate_all_files(reader: DirectoryReader, file_list):
+ """Build and validate all files."""
+ data = await reader.abuild_component_menu_list(file_list)
+
+ valid_components = reader.filter_loaded_components(data=data, with_errors=False)
+ invalid_components = reader.filter_loaded_components(data=data, with_errors=True)
+
+ return valid_components, invalid_components
+
+
+def load_files_from_path(path: str):
+ """Load all files from a given path."""
+ reader = DirectoryReader(path, compress_code_field=False)
+
+ return reader.get_files()
+
+
+def build_custom_component_list_from_path(path: str):
+ """Build a list of custom components for the langchain from a given path."""
+ file_list = load_files_from_path(path)
+ reader = DirectoryReader(path, compress_code_field=False)
+
+ valid_components, invalid_components = build_and_validate_all_files(reader, file_list)
+
+ valid_menu = build_valid_menu(valid_components)
+ invalid_menu = build_invalid_menu(invalid_components)
+
+ return merge_nested_dicts_with_renaming(valid_menu, invalid_menu)
+
+
+async def abuild_custom_component_list_from_path(path: str):
+ """Build a list of custom components for the langchain from a given path."""
+ file_list = await asyncio.to_thread(load_files_from_path, path)
+ reader = DirectoryReader(path, compress_code_field=False)
+
+ valid_components, invalid_components = await abuild_and_validate_all_files(reader, file_list)
+
+ valid_menu = build_valid_menu(valid_components)
+ invalid_menu = build_invalid_menu(invalid_components)
+
+ return merge_nested_dicts_with_renaming(valid_menu, invalid_menu)
+
+
+def create_invalid_component_template(component, component_name):
+ """Create a template for an invalid component."""
+ component_code = component["code"]
+ component_frontend_node = CustomComponentFrontendNode(
+ description="ERROR - Check your Python Code",
+ display_name=f"ERROR - {component_name}",
+ )
+
+ component_frontend_node.error = component.get("error", None)
+ field = component_frontend_node.template.get_field("code")
+ field.value = component_code
+ component_frontend_node.template.update_field("code", field)
+ return component_frontend_node.model_dump(by_alias=True, exclude_none=True)
+
+
+def log_invalid_component_details(component) -> None:
+ """Log details of an invalid component."""
+ logger.debug(component)
+ logger.debug(f"Component Path: {component.get('path', None)}")
+ logger.debug(f"Component Error: {component.get('error', None)}")
+
+
+def build_invalid_component(component):
+ """Build a single invalid component."""
+ component_name = component["name"]
+ component_template = create_invalid_component_template(component, component_name)
+ log_invalid_component_details(component)
+ return component_name, component_template
+
+
+def build_invalid_menu_items(menu_item):
+ """Build invalid menu items for a given menu."""
+ menu_items = {}
+ for component in menu_item["components"]:
+ try:
+ component_name, component_template = build_invalid_component(component)
+ menu_items[component_name] = component_template
+ logger.debug(f"Added {component_name} to invalid menu.")
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error while creating custom component [{component_name}]")
+ return menu_items
+
+
+def get_new_key(dictionary, original_key):
+ counter = 1
+ new_key = original_key + " (" + str(counter) + ")"
+ while new_key in dictionary:
+ counter += 1
+ new_key = original_key + " (" + str(counter) + ")"
+ return new_key
+
+
+def determine_component_name(component):
+ """Determine the name of the component."""
+ # component_output_types = component["output_types"]
+ # if len(component_output_types) == 1:
+ # return component_output_types[0]
+ # else:
+ # file_name = component.get("file").split(".")[0]
+ # return "".join(word.capitalize() for word in file_name.split("_")) if "_" in file_name else file_name
+ return component["name"]
+
+
+def build_menu_items(menu_item):
+ """Build menu items for a given menu."""
+ menu_items = {}
+ logger.debug(f"Building menu items for {menu_item['name']}")
+ logger.debug(f"Loading {len(menu_item['components'])} components")
+ for component_name, component_template, component in menu_item["components"]:
+ try:
+ menu_items[component_name] = component_template
+ except Exception: # noqa: BLE001
+ logger.exception(f"Error while building custom component {component['output_types']}")
+ return menu_items
diff --git a/langflow/src/backend/base/langflow/custom/eval.py b/langflow/src/backend/base/langflow/custom/eval.py
new file mode 100644
index 0000000..b163e8e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/eval.py
@@ -0,0 +1,12 @@
+from typing import TYPE_CHECKING
+
+from langflow.utils import validate
+
+if TYPE_CHECKING:
+ from langflow.custom import CustomComponent
+
+
+def eval_custom_component_code(code: str) -> type["CustomComponent"]:
+ """Evaluate custom component code."""
+ class_name = validate.extract_class_name(code)
+ return validate.create_class(code, class_name)
diff --git a/langflow/src/backend/base/langflow/custom/schema.py b/langflow/src/backend/base/langflow/custom/schema.py
new file mode 100644
index 0000000..5c90356
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/schema.py
@@ -0,0 +1,32 @@
+from typing import Any
+
+from pydantic import BaseModel, Field
+
+
+class ClassCodeDetails(BaseModel):
+ """A dataclass for storing details about a class."""
+
+ name: str
+ doc: str | None = None
+ bases: list
+ attributes: list
+ methods: list
+ init: dict | None = Field(default_factory=dict)
+
+
+class CallableCodeDetails(BaseModel):
+ """A dataclass for storing details about a callable."""
+
+ name: str
+ doc: str | None = None
+ args: list
+ body: list
+ return_type: Any | None = None
+ has_return: bool = False
+
+
+class MissingDefault:
+ """A class to represent a missing default value."""
+
+ def __repr__(self) -> str:
+ return "MISSING"
diff --git a/langflow/src/backend/base/langflow/custom/tree_visitor.py b/langflow/src/backend/base/langflow/custom/tree_visitor.py
new file mode 100644
index 0000000..57579a5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/tree_visitor.py
@@ -0,0 +1,21 @@
+import ast
+from typing import Any
+
+from typing_extensions import override
+
+
+class RequiredInputsVisitor(ast.NodeVisitor):
+ def __init__(self, inputs: dict[str, Any]):
+ self.inputs: dict[str, Any] = inputs
+ self.required_inputs: set[str] = set()
+
+ @override
+ def visit_Attribute(self, node) -> None:
+ if (
+ isinstance(node.value, ast.Name)
+ and node.value.id == "self"
+ and node.attr in self.inputs
+ and self.inputs[node.attr].required
+ ):
+ self.required_inputs.add(node.attr)
+ self.generic_visit(node)
diff --git a/langflow/src/backend/base/langflow/custom/utils.py b/langflow/src/backend/base/langflow/custom/utils.py
new file mode 100644
index 0000000..13afaaf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/custom/utils.py
@@ -0,0 +1,562 @@
+import ast
+import asyncio
+import contextlib
+import inspect
+import re
+import traceback
+from typing import Any
+from uuid import UUID
+
+from fastapi import HTTPException
+from loguru import logger
+from pydantic import BaseModel
+
+from langflow.custom import CustomComponent
+from langflow.custom.custom_component.component import Component
+from langflow.custom.directory_reader.utils import (
+ abuild_custom_component_list_from_path,
+ build_custom_component_list_from_path,
+ merge_nested_dicts_with_renaming,
+)
+from langflow.custom.eval import eval_custom_component_code
+from langflow.custom.schema import MissingDefault
+from langflow.field_typing.range_spec import RangeSpec
+from langflow.helpers.custom import format_type
+from langflow.schema import dotdict
+from langflow.template.field.base import Input
+from langflow.template.frontend_node.custom_components import ComponentFrontendNode, CustomComponentFrontendNode
+from langflow.type_extraction.type_extraction import extract_inner_type
+from langflow.utils import validate
+from langflow.utils.util import get_base_classes
+
+
+class UpdateBuildConfigError(Exception):
+ pass
+
+
+def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: list[str]) -> None:
+ """Add output types to the frontend node."""
+ for return_type in return_types:
+ if return_type is None:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": ("Invalid return type. Please check your code and try again."),
+ "traceback": traceback.format_exc(),
+ },
+ )
+ if return_type is str:
+ return_type_ = "Text"
+ elif hasattr(return_type, "__name__"):
+ return_type_ = return_type.__name__
+ elif hasattr(return_type, "__class__"):
+ return_type_ = return_type.__class__.__name__
+ else:
+ return_type_ = str(return_type)
+
+ frontend_node.add_output_type(return_type_)
+
+
+def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: list[str]) -> None:
+ """Reorder fields in the frontend node based on the specified field_order."""
+ if not field_order:
+ return
+
+ # Create a dictionary for O(1) lookup time.
+ field_dict = {field.name: field for field in frontend_node.template.fields}
+ reordered_fields = [field_dict[name] for name in field_order if name in field_dict]
+ # Add any fields that are not in the field_order list
+ reordered_fields.extend(field for field in frontend_node.template.fields if field.name not in field_order)
+ frontend_node.template.fields = reordered_fields
+ frontend_node.field_order = field_order
+
+
+def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: list[str]) -> None:
+ """Add base classes to the frontend node."""
+ for return_type_instance in return_types:
+ if return_type_instance is None:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": ("Invalid return type. Please check your code and try again."),
+ "traceback": traceback.format_exc(),
+ },
+ )
+
+ base_classes = get_base_classes(return_type_instance)
+ if return_type_instance is str:
+ base_classes.append("Text")
+
+ for base_class in base_classes:
+ frontend_node.add_base_class(base_class)
+
+
+def extract_type_from_optional(field_type):
+ """Extract the type from a string formatted as "Optional[]".
+
+ Parameters:
+ field_type (str): The string from which to extract the type.
+
+ Returns:
+ str: The extracted type, or an empty string if no type was found.
+ """
+ if "optional" not in field_type.lower():
+ return field_type
+ match = re.search(r"\[(.*?)\]$", field_type)
+ return match[1] if match else field_type
+
+
+def get_field_properties(extra_field):
+ """Get the properties of an extra field."""
+ field_name = extra_field["name"]
+ field_type = extra_field.get("type", "str")
+ field_value = extra_field.get("default", "")
+ # a required field is a field that does not contain
+ # optional in field_type
+ # and a field that does not have a default value
+ field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault)
+ field_value = field_value if not isinstance(field_value, MissingDefault) else None
+
+ if not field_required:
+ field_type = extract_type_from_optional(field_type)
+ if field_value is not None:
+ with contextlib.suppress(Exception):
+ field_value = ast.literal_eval(field_value)
+ return field_name, field_type, field_value, field_required
+
+
+def process_type(field_type: str):
+ if field_type.startswith(("list", "List")):
+ return extract_inner_type(field_type)
+
+ # field_type is a string can be Prompt or Code too
+ # so we just need to lower if it is the case
+ lowercase_type = field_type.lower()
+ if lowercase_type in {"prompt", "code"}:
+ return lowercase_type
+ return field_type
+
+
+def add_new_custom_field(
+ *,
+ frontend_node: CustomComponentFrontendNode,
+ field_name: str,
+ field_type: str,
+ field_value: Any,
+ field_required: bool,
+ field_config: dict,
+):
+ # Check field_config if any of the keys are in it
+ # if it is, update the value
+ display_name = field_config.pop("display_name", None)
+ if not field_type:
+ if "type" in field_config and field_config["type"] is not None:
+ field_type = field_config.pop("type")
+ elif "field_type" in field_config and field_config["field_type"] is not None:
+ field_type = field_config.pop("field_type")
+ field_contains_list = "list" in field_type.lower()
+ field_type = process_type(field_type)
+ field_value = field_config.pop("value", field_value)
+ field_advanced = field_config.pop("advanced", False)
+
+ if field_type == "Dict":
+ field_type = "dict"
+
+ if field_type == "bool" and field_value is None:
+ field_value = False
+
+ if field_type == "SecretStr":
+ field_config["password"] = True
+ field_config["load_from_db"] = True
+ field_config["input_types"] = ["Text"]
+
+ # If options is a list, then it's a dropdown or multiselect
+ # If options is None, then it's a list of strings
+ is_list = isinstance(field_config.get("options"), list)
+ field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list
+
+ if "name" in field_config:
+ logger.warning("The 'name' key in field_config is used to build the object and can't be changed.")
+ required = field_config.pop("required", field_required)
+ placeholder = field_config.pop("placeholder", "")
+
+ new_field = Input(
+ name=field_name,
+ field_type=field_type,
+ value=field_value,
+ show=True,
+ required=required,
+ advanced=field_advanced,
+ placeholder=placeholder,
+ display_name=display_name,
+ **sanitize_field_config(field_config),
+ )
+ frontend_node.template.upsert_field(field_name, new_field)
+ if isinstance(frontend_node.custom_fields, dict):
+ frontend_node.custom_fields[field_name] = None
+
+ return frontend_node
+
+
+def add_extra_fields(frontend_node, field_config, function_args) -> None:
+ """Add extra fields to the frontend node."""
+ if not function_args:
+ return
+ field_config_ = field_config.copy()
+ function_args_names = [arg["name"] for arg in function_args]
+ # If kwargs is in the function_args and not all field_config keys are in function_args
+ # then we need to add the extra fields
+
+ for extra_field in function_args:
+ if "name" not in extra_field or extra_field["name"] in {
+ "self",
+ "kwargs",
+ "args",
+ }:
+ continue
+
+ field_name, field_type, field_value, field_required = get_field_properties(extra_field)
+ config = field_config_.pop(field_name, {})
+ frontend_node = add_new_custom_field(
+ frontend_node=frontend_node,
+ field_name=field_name,
+ field_type=field_type,
+ field_value=field_value,
+ field_required=field_required,
+ field_config=config,
+ )
+ if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config):
+ for field_name, config in field_config_.items():
+ if "name" not in config or field_name == "code":
+ continue
+ config_ = config.model_dump() if isinstance(config, BaseModel) else config
+ field_name_, field_type, field_value, field_required = get_field_properties(extra_field=config_)
+ frontend_node = add_new_custom_field(
+ frontend_node=frontend_node,
+ field_name=field_name_,
+ field_type=field_type,
+ field_value=field_value,
+ field_required=field_required,
+ field_config=config_,
+ )
+
+
+def get_field_dict(field: Input | dict):
+ """Get the field dictionary from a Input or a dict."""
+ if isinstance(field, Input):
+ return dotdict(field.model_dump(by_alias=True, exclude_none=True))
+ return field
+
+
+def run_build_inputs(
+ custom_component: Component,
+):
+ """Run the build inputs of a custom component."""
+ try:
+ return custom_component.build_inputs()
+ # add_extra_fields(frontend_node, field_config, field_config.values())
+ except Exception as exc:
+ logger.exception("Error running build inputs")
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+
+
+def get_component_instance(custom_component: CustomComponent, user_id: str | UUID | None = None):
+ if custom_component._code is None:
+ error = "Code is None"
+ elif not isinstance(custom_component._code, str):
+ error = "Invalid code type"
+ else:
+ try:
+ custom_class = eval_custom_component_code(custom_component._code)
+ except Exception as exc:
+ logger.exception("Error while evaluating custom component code")
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": ("Invalid type conversion. Please check your code and try again."),
+ "traceback": traceback.format_exc(),
+ },
+ ) from exc
+
+ try:
+ return custom_class(_user_id=user_id, _code=custom_component._code)
+ except Exception as exc:
+ logger.exception("Error while instantiating custom component")
+ if hasattr(exc, "detail") and "traceback" in exc.detail:
+ logger.error(exc.detail["traceback"])
+
+ raise
+
+ msg = f"Invalid type conversion: {error}. Please check your code and try again."
+ logger.error(msg)
+ raise HTTPException(
+ status_code=400,
+ detail={"error": msg},
+ )
+
+
+def run_build_config(
+ custom_component: CustomComponent,
+ user_id: str | UUID | None = None,
+) -> tuple[dict, CustomComponent]:
+ """Build the field configuration for a custom component."""
+ if custom_component._code is None:
+ error = "Code is None"
+ elif not isinstance(custom_component._code, str):
+ error = "Invalid code type"
+ else:
+ try:
+ custom_class = eval_custom_component_code(custom_component._code)
+ except Exception as exc:
+ logger.exception("Error while evaluating custom component code")
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": ("Invalid type conversion. Please check your code and try again."),
+ "traceback": traceback.format_exc(),
+ },
+ ) from exc
+
+ try:
+ custom_instance = custom_class(_user_id=user_id)
+ build_config: dict = custom_instance.build_config()
+
+ for field_name, field in build_config.copy().items():
+ # Allow user to build Input as well
+ # as a dict with the same keys as Input
+ field_dict = get_field_dict(field)
+ # Let's check if "rangeSpec" is a RangeSpec object
+ if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec):
+ field_dict["rangeSpec"] = field_dict["rangeSpec"].model_dump()
+ build_config[field_name] = field_dict
+
+ except Exception as exc:
+ logger.exception("Error while building field config")
+ if hasattr(exc, "detail") and "traceback" in exc.detail:
+ logger.error(exc.detail["traceback"])
+ raise
+ return build_config, custom_instance
+
+ msg = f"Invalid type conversion: {error}. Please check your code and try again."
+ logger.error(msg)
+ raise HTTPException(
+ status_code=400,
+ detail={"error": msg},
+ )
+
+
+def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code):
+ code_field = Input(
+ dynamic=True,
+ required=True,
+ placeholder="",
+ multiline=True,
+ value=raw_code,
+ password=False,
+ name="code",
+ advanced=True,
+ field_type="code",
+ is_list=False,
+ )
+ frontend_node.template.add_field(code_field)
+
+ return frontend_node
+
+
+def build_custom_component_template_from_inputs(
+ custom_component: Component | CustomComponent, user_id: str | UUID | None = None
+):
+ # The List of Inputs fills the role of the build_config and the entrypoint_args
+ cc_instance = get_component_instance(custom_component, user_id=user_id)
+ field_config = cc_instance.get_template_config(cc_instance)
+ frontend_node = ComponentFrontendNode.from_inputs(**field_config)
+ frontend_node = add_code_field(frontend_node, custom_component._code)
+ # But we now need to calculate the return_type of the methods in the outputs
+ for output in frontend_node.outputs:
+ if output.types:
+ continue
+ return_types = cc_instance.get_method_return_type(output.method)
+ return_types = [format_type(return_type) for return_type in return_types]
+ output.add_types(return_types)
+ output.set_selected()
+ # Validate that there is not name overlap between inputs and outputs
+ frontend_node.validate_component()
+ # ! This should be removed when we have a better way to handle this
+ frontend_node.set_base_classes_from_outputs()
+ reorder_fields(frontend_node, cc_instance._get_field_order())
+
+ return frontend_node.to_dict(keep_name=False), cc_instance
+
+
+def build_custom_component_template(
+ custom_component: CustomComponent,
+ user_id: str | UUID | None = None,
+) -> tuple[dict[str, Any], CustomComponent | Component]:
+ """Build a custom component template."""
+ try:
+ has_template_config = hasattr(custom_component, "template_config")
+ except Exception as exc:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": (f"Error building Component: {exc}"),
+ "traceback": traceback.format_exc(),
+ },
+ ) from exc
+ if not has_template_config:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": ("Error building Component. Please check if you are importing Component correctly."),
+ },
+ )
+ try:
+ if "inputs" in custom_component.template_config:
+ return build_custom_component_template_from_inputs(custom_component, user_id=user_id)
+ frontend_node = CustomComponentFrontendNode(**custom_component.template_config)
+
+ field_config, custom_instance = run_build_config(
+ custom_component,
+ user_id=user_id,
+ )
+
+ entrypoint_args = custom_component.get_function_entrypoint_args
+
+ add_extra_fields(frontend_node, field_config, entrypoint_args)
+
+ frontend_node = add_code_field(frontend_node, custom_component._code)
+
+ add_base_classes(frontend_node, custom_component._get_function_entrypoint_return_type)
+ add_output_types(frontend_node, custom_component._get_function_entrypoint_return_type)
+
+ reorder_fields(frontend_node, custom_instance._get_field_order())
+
+ return frontend_node.to_dict(keep_name=False), custom_instance
+ except Exception as exc:
+ if isinstance(exc, HTTPException):
+ raise
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": (f"Error building Component: {exc}"),
+ "traceback": traceback.format_exc(),
+ },
+ ) from exc
+
+
+def create_component_template(component):
+ """Create a template for a component."""
+ component_code = component["code"]
+ component_output_types = component["output_types"]
+
+ component_extractor = Component(_code=component_code)
+
+ component_template, component_instance = build_custom_component_template(component_extractor)
+ if not component_template["output_types"] and component_output_types:
+ component_template["output_types"] = component_output_types
+
+ return component_template, component_instance
+
+
+def build_custom_components(components_paths: list[str]):
+ """Build custom components from the specified paths."""
+ if not components_paths:
+ return {}
+
+ logger.info(f"Building custom components from {components_paths}")
+ custom_components_from_file: dict = {}
+ processed_paths = set()
+ for path in components_paths:
+ path_str = str(path)
+ if path_str in processed_paths:
+ continue
+
+ custom_component_dict = build_custom_component_list_from_path(path_str)
+ if custom_component_dict:
+ category = next(iter(custom_component_dict))
+ logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
+ custom_components_from_file = merge_nested_dicts_with_renaming(
+ custom_components_from_file, custom_component_dict
+ )
+ processed_paths.add(path_str)
+
+ return custom_components_from_file
+
+
+async def abuild_custom_components(components_paths: list[str]):
+ """Build custom components from the specified paths."""
+ if not components_paths:
+ return {}
+
+ logger.info(f"Building custom components from {components_paths}")
+ custom_components_from_file: dict = {}
+ processed_paths = set()
+ for path in components_paths:
+ path_str = str(path)
+ if path_str in processed_paths:
+ continue
+
+ custom_component_dict = await abuild_custom_component_list_from_path(path_str)
+ if custom_component_dict:
+ category = next(iter(custom_component_dict))
+ logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
+ custom_components_from_file = merge_nested_dicts_with_renaming(
+ custom_components_from_file, custom_component_dict
+ )
+ processed_paths.add(path_str)
+
+ return custom_components_from_file
+
+
+def sanitize_field_config(field_config: dict | Input):
+ # If any of the already existing keys are in field_config, remove them
+ field_dict = field_config.to_dict() if isinstance(field_config, Input) else field_config
+ for key in [
+ "name",
+ "field_type",
+ "value",
+ "required",
+ "placeholder",
+ "display_name",
+ "advanced",
+ "show",
+ ]:
+ field_dict.pop(key, None)
+
+ # Remove field_type and type because they were extracted already
+ field_dict.pop("field_type", None)
+ field_dict.pop("type", None)
+
+ return field_dict
+
+
+def build_component(component):
+ """Build a single component."""
+ component_template, component_instance = create_component_template(component)
+ component_name = get_instance_name(component_instance)
+ return component_name, component_template
+
+
+def get_function(code):
+ """Get the function."""
+ function_name = validate.extract_function_name(code)
+
+ return validate.create_function(code, function_name)
+
+
+def get_instance_name(instance):
+ name = instance.__class__.__name__
+ if hasattr(instance, "name") and instance.name:
+ name = instance.name
+ return name
+
+
+async def update_component_build_config(
+ component: CustomComponent,
+ build_config: dotdict,
+ field_value: Any,
+ field_name: str | None = None,
+):
+ if inspect.iscoroutinefunction(component.update_build_config):
+ return await component.update_build_config(build_config, field_value, field_name)
+ return await asyncio.to_thread(component.update_build_config, build_config, field_value, field_name)
diff --git a/langflow/src/backend/base/langflow/events/__init__.py b/langflow/src/backend/base/langflow/events/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/events/event_manager.py b/langflow/src/backend/base/langflow/events/event_manager.py
new file mode 100644
index 0000000..e499030
--- /dev/null
+++ b/langflow/src/backend/base/langflow/events/event_manager.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+import inspect
+import json
+import time
+import uuid
+from functools import partial
+from typing import TYPE_CHECKING, Literal
+
+from fastapi.encoders import jsonable_encoder
+from loguru import logger
+from typing_extensions import Protocol
+
+from langflow.schema.playground_events import create_event_by_type
+
+if TYPE_CHECKING:
+ import asyncio
+
+ from langflow.schema.log import LoggableType
+
+
+class EventCallback(Protocol):
+ def __call__(self, *, manager: EventManager, event_type: str, data: LoggableType): ...
+
+
+class PartialEventCallback(Protocol):
+ def __call__(self, *, data: LoggableType): ...
+
+
+class EventManager:
+ def __init__(self, queue: asyncio.Queue):
+ self.queue = queue
+ self.events: dict[str, PartialEventCallback] = {}
+
+ @staticmethod
+ def _validate_callback(callback: EventCallback) -> None:
+ if not callable(callback):
+ msg = "Callback must be callable"
+ raise TypeError(msg)
+ # Check if it has `self, event_type and data`
+ sig = inspect.signature(callback)
+ parameters = ["manager", "event_type", "data"]
+ if len(sig.parameters) != len(parameters):
+ msg = "Callback must have exactly 3 parameters"
+ raise ValueError(msg)
+ if not all(param.name in parameters for param in sig.parameters.values()):
+ msg = "Callback must have exactly 3 parameters: manager, event_type, and data"
+ raise ValueError(msg)
+
+ def register_event(
+ self,
+ name: str,
+ event_type: Literal["message", "error", "warning", "info", "token"],
+ callback: EventCallback | None = None,
+ ) -> None:
+ if not name:
+ msg = "Event name cannot be empty"
+ raise ValueError(msg)
+ if not name.startswith("on_"):
+ msg = "Event name must start with 'on_'"
+ raise ValueError(msg)
+ if callback is None:
+ callback_ = partial(self.send_event, event_type=event_type)
+ else:
+ callback_ = partial(callback, manager=self, event_type=event_type)
+ self.events[name] = callback_
+
+ def send_event(self, *, event_type: Literal["message", "error", "warning", "info", "token"], data: LoggableType):
+ try:
+ if isinstance(data, dict) and event_type in {"message", "error", "warning", "info", "token"}:
+ data = create_event_by_type(event_type, **data)
+ except TypeError as e:
+ logger.debug(f"Error creating playground event: {e}")
+ except Exception:
+ raise
+ jsonable_data = jsonable_encoder(data)
+ json_data = {"event": event_type, "data": jsonable_data}
+ event_id = f"{event_type}-{uuid.uuid4()}"
+ str_data = json.dumps(json_data) + "\n\n"
+ self.queue.put_nowait((event_id, str_data.encode("utf-8"), time.time()))
+
+ def noop(self, *, data: LoggableType) -> None:
+ pass
+
+ def __getattr__(self, name: str) -> PartialEventCallback:
+ return self.events.get(name, self.noop)
+
+
+def create_default_event_manager(queue):
+ manager = EventManager(queue)
+ manager.register_event("on_token", "token")
+ manager.register_event("on_vertices_sorted", "vertices_sorted")
+ manager.register_event("on_error", "error")
+ manager.register_event("on_end", "end")
+ manager.register_event("on_message", "add_message")
+ manager.register_event("on_remove_message", "remove_message")
+ manager.register_event("on_end_vertex", "end_vertex")
+ manager.register_event("on_build_start", "build_start")
+ manager.register_event("on_build_end", "build_end")
+ return manager
+
+
+def create_stream_tokens_event_manager(queue):
+ manager = EventManager(queue)
+ manager.register_event("on_message", "add_message")
+ manager.register_event("on_token", "token")
+ manager.register_event("on_end", "end")
+ return manager
diff --git a/langflow/src/backend/base/langflow/exceptions/__init__.py b/langflow/src/backend/base/langflow/exceptions/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/exceptions/api.py b/langflow/src/backend/base/langflow/exceptions/api.py
new file mode 100644
index 0000000..1997ad1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/exceptions/api.py
@@ -0,0 +1,34 @@
+from fastapi import HTTPException
+from pydantic import BaseModel
+
+from langflow.api.utils import get_suggestion_message
+from langflow.services.database.models.flow.model import Flow
+from langflow.services.database.models.flow.utils import get_outdated_components
+
+
+class InvalidChatInputError(Exception):
+ pass
+
+
+# create a pidantic documentation for this class
+class ExceptionBody(BaseModel):
+ message: str | list[str]
+ traceback: str | list[str] | None = None
+ description: str | list[str] | None = None
+ code: str | None = None
+ suggestion: str | list[str] | None = None
+
+
+class APIException(HTTPException):
+ def __init__(self, exception: Exception, flow: Flow | None = None, status_code: int = 500):
+ body = self.build_exception_body(exception, flow)
+ super().__init__(status_code=status_code, detail=body.model_dump_json())
+
+ @staticmethod
+ def build_exception_body(exc: str | list[str] | Exception, flow: Flow | None) -> ExceptionBody:
+ body = {"message": str(exc)}
+ if flow:
+ outdated_components = get_outdated_components(flow)
+ if outdated_components:
+ body["suggestion"] = get_suggestion_message(outdated_components)
+ return ExceptionBody(**body)
diff --git a/langflow/src/backend/base/langflow/exceptions/component.py b/langflow/src/backend/base/langflow/exceptions/component.py
new file mode 100644
index 0000000..1f61ab9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/exceptions/component.py
@@ -0,0 +1,17 @@
+# Create an exception class that receives the message and the formatted traceback
+
+from langflow.schema.properties import Source
+
+
+class ComponentBuildError(Exception):
+ def __init__(self, message: str, formatted_traceback: str):
+ self.message = message
+ self.formatted_traceback = formatted_traceback
+ super().__init__(message)
+
+
+class StreamingError(Exception):
+ def __init__(self, cause: Exception, source: Source):
+ self.cause = cause
+ self.source = source
+ super().__init__(cause)
diff --git a/langflow/src/backend/base/langflow/exceptions/serialization.py b/langflow/src/backend/base/langflow/exceptions/serialization.py
new file mode 100644
index 0000000..54bc0fd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/exceptions/serialization.py
@@ -0,0 +1,56 @@
+from typing import Any
+
+from fastapi import HTTPException, status
+
+
+class SerializationError(HTTPException):
+ """Exception raised when there are errors serializing data to JSON."""
+
+ def __init__(
+ self,
+ detail: str,
+ original_error: Exception | None = None,
+ data: Any = None,
+ status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
+ ) -> None:
+ super().__init__(status_code=status_code, detail=detail)
+ self.original_error = original_error
+ self.data = data
+
+ @classmethod
+ def from_exception(cls, exc: Exception, data: Any = None) -> "SerializationError":
+ """Create a SerializationError from an existing exception."""
+ errors = exc.args[0] if exc.args else []
+
+ if isinstance(errors, list):
+ for error in errors:
+ if isinstance(error, TypeError):
+ if "'coroutine'" in str(error):
+ return cls(
+ detail=(
+ "The component contains async functions that need to be awaited. Please add 'await' "
+ "before any async function calls in your component code."
+ ),
+ original_error=exc,
+ data=data,
+ )
+ if "vars()" in str(error):
+ return cls(
+ detail=(
+ "The component contains objects that cannot be converted to JSON. Please ensure all "
+ "properties and return values in your component are basic Python types like strings, "
+ "numbers, lists, or dictionaries."
+ ),
+ original_error=exc,
+ data=data,
+ )
+
+ # Generic error for other cases
+ return cls(
+ detail=(
+ "The component returned invalid data. Please check that all values in your component (properties, "
+ "return values, etc.) are basic Python types that can be converted to JSON."
+ ),
+ original_error=exc,
+ data=data,
+ )
diff --git a/langflow/src/backend/base/langflow/field_typing/__init__.py b/langflow/src/backend/base/langflow/field_typing/__init__.py
new file mode 100644
index 0000000..8eb1591
--- /dev/null
+++ b/langflow/src/backend/base/langflow/field_typing/__init__.py
@@ -0,0 +1,91 @@
+from typing import Any
+
+from .constants import (
+ AgentExecutor,
+ BaseChatMemory,
+ BaseChatModel,
+ BaseDocumentCompressor,
+ BaseLanguageModel,
+ BaseLLM,
+ BaseLoader,
+ BaseMemory,
+ BaseOutputParser,
+ BasePromptTemplate,
+ BaseRetriever,
+ Callable,
+ Chain,
+ ChatPromptTemplate,
+ Code,
+ Data,
+ Document,
+ Embeddings,
+ LanguageModel,
+ NestedDict,
+ Object,
+ PromptTemplate,
+ Retriever,
+ Text,
+ TextSplitter,
+ Tool,
+ VectorStore,
+)
+from .range_spec import RangeSpec
+
+
+def _import_input_class():
+ from langflow.template.field.base import Input
+
+ return Input
+
+
+def _import_output_class():
+ from langflow.template.field.base import Output
+
+ return Output
+
+
+def __getattr__(name: str) -> Any:
+ # This is to avoid circular imports
+ if name == "Input":
+ return _import_input_class()
+ return RangeSpec
+ if name == "Output":
+ return _import_output_class()
+ # The other names should work as if they were imported from constants
+ # Import the constants module langflow.field_typing.constants
+ from . import constants
+
+ return getattr(constants, name)
+
+
+__all__ = [
+ "AgentExecutor",
+ "BaseChatMemory",
+ "BaseChatModel",
+ "BaseDocumentCompressor",
+ "BaseLLM",
+ "BaseLanguageModel",
+ "BaseLoader",
+ "BaseMemory",
+ "BaseOutputParser",
+ "BasePromptTemplate",
+ "BaseRetriever",
+ "Callable",
+ "Chain",
+ "ChatPromptTemplate",
+ "Code",
+ "Data",
+ "Document",
+ "Embeddings",
+ "Input",
+ "LanguageModel",
+ "NestedDict",
+ "Object",
+ "PromptTemplate",
+ "RangeSpec",
+ "Retriever",
+ "Text",
+ "TextSplitter",
+ "Tool",
+ "VectorStore",
+]
diff --git a/langflow/src/backend/base/langflow/field_typing/constants.py b/langflow/src/backend/base/langflow/field_typing/constants.py
new file mode 100644
index 0000000..1ec8187
--- /dev/null
+++ b/langflow/src/backend/base/langflow/field_typing/constants.py
@@ -0,0 +1,135 @@
+from collections.abc import Callable
+from typing import Text, TypeAlias, TypeVar
+
+from langchain.agents.agent import AgentExecutor
+from langchain.chains.base import Chain
+from langchain.memory.chat_memory import BaseChatMemory
+from langchain_core.chat_history import BaseChatMessageHistory
+from langchain_core.document_loaders import BaseLoader
+from langchain_core.documents import Document
+from langchain_core.documents.compressor import BaseDocumentCompressor
+from langchain_core.embeddings import Embeddings
+from langchain_core.language_models import BaseLanguageModel, BaseLLM
+from langchain_core.language_models.chat_models import BaseChatModel
+from langchain_core.memory import BaseMemory
+from langchain_core.output_parsers import BaseLLMOutputParser, BaseOutputParser
+from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
+from langchain_core.retrievers import BaseRetriever
+from langchain_core.tools import BaseTool, Tool
+from langchain_core.vectorstores import VectorStore, VectorStoreRetriever
+from langchain_text_splitters import TextSplitter
+
+from langflow.schema.data import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.message import Message
+
+NestedDict: TypeAlias = dict[str, str | dict]
+LanguageModel = TypeVar("LanguageModel", BaseLanguageModel, BaseLLM, BaseChatModel)
+ToolEnabledLanguageModel = TypeVar("ToolEnabledLanguageModel", BaseLanguageModel, BaseLLM, BaseChatModel)
+Memory = TypeVar("Memory", bound=BaseChatMessageHistory)
+
+Retriever = TypeVar(
+ "Retriever",
+ BaseRetriever,
+ VectorStoreRetriever,
+)
+OutputParser = TypeVar(
+ "OutputParser",
+ BaseOutputParser,
+ BaseLLMOutputParser,
+)
+
+
+class Object:
+ pass
+
+
+class Code:
+ pass
+
+
+LANGCHAIN_BASE_TYPES = {
+ "Chain": Chain,
+ "AgentExecutor": AgentExecutor,
+ "BaseTool": BaseTool,
+ "Tool": Tool,
+ "BaseLLM": BaseLLM,
+ "BaseLanguageModel": BaseLanguageModel,
+ "PromptTemplate": PromptTemplate,
+ "ChatPromptTemplate": ChatPromptTemplate,
+ "BasePromptTemplate": BasePromptTemplate,
+ "BaseLoader": BaseLoader,
+ "Document": Document,
+ "TextSplitter": TextSplitter,
+ "VectorStore": VectorStore,
+ "Embeddings": Embeddings,
+ "BaseRetriever": BaseRetriever,
+ "BaseOutputParser": BaseOutputParser,
+ "BaseMemory": BaseMemory,
+ "BaseChatMemory": BaseChatMemory,
+ "BaseChatModel": BaseChatModel,
+ "Memory": Memory,
+ "BaseDocumentCompressor": BaseDocumentCompressor,
+}
+# Langchain base types plus Python base types
+CUSTOM_COMPONENT_SUPPORTED_TYPES = {
+ **LANGCHAIN_BASE_TYPES,
+ "NestedDict": NestedDict,
+ "Data": Data,
+ "Message": Message,
+ "Text": Text, # noqa: UP019
+ "Object": Object,
+ "Callable": Callable,
+ "LanguageModel": LanguageModel,
+ "Retriever": Retriever,
+ "DataFrame": DataFrame,
+}
+
+DEFAULT_IMPORT_STRING = """from langchain.agents.agent import AgentExecutor
+from langchain.chains.base import Chain
+from langchain.memory.chat_memory import BaseChatMemory
+from langchain_core.chat_history import BaseChatMessageHistory
+from langchain_core.document_loaders import BaseLoader
+from langchain_core.documents import Document
+from langchain_core.embeddings import Embeddings
+from langchain_core.language_models import BaseLanguageModel, BaseLLM
+from langchain_core.language_models.chat_models import BaseChatModel
+from langchain_core.memory import BaseMemory
+from langchain_core.output_parsers import BaseLLMOutputParser, BaseOutputParser
+from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
+from langchain_core.retrievers import BaseRetriever
+from langchain_core.documents.compressor import BaseDocumentCompressor
+from langchain_core.tools import BaseTool, Tool
+from langchain_core.vectorstores import VectorStore, VectorStoreRetriever
+from langchain_text_splitters import TextSplitter
+
+from langflow.io import (
+ BoolInput,
+ CodeInput,
+ DataFrameInput,
+ DataInput,
+ DefaultPromptField,
+ DictInput,
+ DropdownInput,
+ FileInput,
+ FloatInput,
+ HandleInput,
+ IntInput,
+ LinkInput,
+ MessageInput,
+ MessageTextInput,
+ MultilineInput,
+ MultilineSecretInput,
+ MultiselectInput,
+ NestedDictInput,
+ Output,
+ PromptInput,
+ SecretStrInput,
+ SliderInput,
+ StrInput,
+ TableInput,
+)
+from langflow.schema.data import Data
+from langflow.schema.dataframe import DataFrame
+from langflow.schema.message import Message
+"""
diff --git a/langflow/src/backend/base/langflow/field_typing/range_spec.py b/langflow/src/backend/base/langflow/field_typing/range_spec.py
new file mode 100644
index 0000000..eabd1c0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/field_typing/range_spec.py
@@ -0,0 +1,33 @@
+from typing import Literal
+
+from pydantic import BaseModel, field_validator
+
+
+class RangeSpec(BaseModel):
+ step_type: Literal["int", "float"] = "float"
+ min: float = -1.0
+ max: float = 1.0
+ step: float = 0.1
+
+ @field_validator("max")
+ @classmethod
+ def max_must_be_greater_than_min(cls, v, values):
+ if "min" in values.data and v <= values.data["min"]:
+ msg = "Max must be greater than min"
+ raise ValueError(msg)
+ return v
+
+ @field_validator("step")
+ @classmethod
+ def step_must_be_positive(cls, v, values):
+ if v <= 0:
+ msg = "Step must be positive"
+ raise ValueError(msg)
+ if values.data["step_type"] == "int" and isinstance(v, float) and not v.is_integer():
+ msg = "When step_type is int, step must be an integer"
+ raise ValueError(msg)
+ return v
+
+ @classmethod
+ def set_step_type(cls, step_type: Literal["int", "float"], range_spec: "RangeSpec") -> "RangeSpec":
+ return cls(min=range_spec.min, max=range_spec.max, step=range_spec.step, step_type=step_type)
diff --git a/langflow/src/backend/base/langflow/graph/__init__.py b/langflow/src/backend/base/langflow/graph/__init__.py
new file mode 100644
index 0000000..d68fd43
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/__init__.py
@@ -0,0 +1,6 @@
+from langflow.graph.edge.base import Edge
+from langflow.graph.graph.base import Graph
+from langflow.graph.vertex.base import Vertex
+from langflow.graph.vertex.vertex_types import CustomComponentVertex, InterfaceVertex, StateVertex
+
+__all__ = ["CustomComponentVertex", "Edge", "Graph", "InterfaceVertex", "StateVertex", "Vertex"]
diff --git a/langflow/src/backend/base/langflow/graph/edge/__init__.py b/langflow/src/backend/base/langflow/graph/edge/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/edge/base.py b/langflow/src/backend/base/langflow/graph/edge/base.py
new file mode 100644
index 0000000..013662e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/edge/base.py
@@ -0,0 +1,276 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, cast
+
+from loguru import logger
+
+from langflow.graph.edge.schema import EdgeData, SourceHandle, TargetHandle, TargetHandleDict
+from langflow.schema.schema import INPUT_FIELD_NAME
+
+if TYPE_CHECKING:
+ from langflow.graph.vertex.base import Vertex
+
+
+class Edge:
+ def __init__(self, source: Vertex, target: Vertex, edge: EdgeData):
+ self.source_id: str = source.id if source else ""
+ self.target_id: str = target.id if target else ""
+ self.valid_handles: bool = False
+ self.target_param: str | None = None
+ self._target_handle: TargetHandleDict | str | None = None
+ self._data = edge.copy()
+ self.is_cycle = False
+ if data := edge.get("data", {}):
+ self._source_handle = data.get("sourceHandle", {})
+ self._target_handle = cast("TargetHandleDict", data.get("targetHandle", {}))
+ self.source_handle: SourceHandle = SourceHandle(**self._source_handle)
+ if isinstance(self._target_handle, dict):
+ try:
+ if "name" in self._target_handle:
+ self.target_handle: TargetHandle = TargetHandle.from_loop_target_handle(self._target_handle)
+ else:
+ self.target_handle = TargetHandle(**self._target_handle)
+ except Exception as e:
+ if "inputTypes" in self._target_handle and self._target_handle["inputTypes"] is None:
+ # Check if self._target_handle['fieldName']
+ if hasattr(target, "custom_component"):
+ display_name = getattr(target.custom_component, "display_name", "")
+ msg = (
+ f"Component {display_name} field '{self._target_handle['fieldName']}' "
+ "might not be a valid input."
+ )
+ raise ValueError(msg) from e
+ msg = (
+ f"Field '{self._target_handle['fieldName']}' on {target.display_name} "
+ "might not be a valid input."
+ )
+ raise ValueError(msg) from e
+ raise
+
+ else:
+ msg = "Target handle is not a dictionary"
+ raise ValueError(msg)
+ self.target_param = self.target_handle.field_name
+ # validate handles
+ self.validate_handles(source, target)
+ else:
+ # Logging here because this is a breaking change
+ logger.error("Edge data is empty")
+ self._source_handle = edge.get("sourceHandle", "") # type: ignore[assignment]
+ self._target_handle = edge.get("targetHandle", "") # type: ignore[assignment]
+ # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
+ # target_param is documents
+ if isinstance(self._target_handle, str):
+ self.target_param = self._target_handle.split("|")[1]
+ self.source_handle = None
+ self.target_handle = None
+ else:
+ msg = "Target handle is not a string"
+ raise ValueError(msg)
+ # Validate in __init__ to fail fast
+ self.validate_edge(source, target)
+
+ def to_data(self):
+ return self._data
+
+ def validate_handles(self, source, target) -> None:
+ if isinstance(self._source_handle, str) or self.source_handle.base_classes:
+ self._legacy_validate_handles(source, target)
+ else:
+ self._validate_handles(source, target)
+
+ def _validate_handles(self, source, target) -> None:
+ if self.target_handle.input_types is None:
+ self.valid_handles = self.target_handle.type in self.source_handle.output_types
+ elif self.target_handle.type is None:
+ # ! This is not a good solution
+ # This is a loop edge
+ # If the target_handle.type is None, it means it's a loop edge
+ # and we should check if the source_handle.output_types is not empty
+ # and if the target_handle.input_types is empty or if any of the source_handle.output_types
+ # is in the target_handle.input_types
+ self.valid_handles = bool(self.source_handle.output_types) and (
+ not self.target_handle.input_types
+ or any(output_type in self.target_handle.input_types for output_type in self.source_handle.output_types)
+ )
+
+ elif self.source_handle.output_types is not None:
+ self.valid_handles = (
+ any(output_type in self.target_handle.input_types for output_type in self.source_handle.output_types)
+ or self.target_handle.type in self.source_handle.output_types
+ )
+
+ if not self.valid_handles:
+ logger.debug(self.source_handle)
+ logger.debug(self.target_handle)
+ msg = f"Edge between {source.display_name} and {target.display_name} has invalid handles"
+ raise ValueError(msg)
+
+ def _legacy_validate_handles(self, source, target) -> None:
+ if self.target_handle.input_types is None:
+ self.valid_handles = self.target_handle.type in self.source_handle.base_classes
+ else:
+ self.valid_handles = (
+ any(baseClass in self.target_handle.input_types for baseClass in self.source_handle.base_classes)
+ or self.target_handle.type in self.source_handle.base_classes
+ )
+ if not self.valid_handles:
+ logger.debug(self.source_handle)
+ logger.debug(self.target_handle)
+ msg = f"Edge between {source.vertex_type} and {target.vertex_type} has invalid handles"
+ raise ValueError(msg)
+
+ def __setstate__(self, state):
+ self.source_id = state["source_id"]
+ self.target_id = state["target_id"]
+ self.target_param = state["target_param"]
+ self.source_handle = state.get("source_handle")
+ self.target_handle = state.get("target_handle")
+ self._source_handle = state.get("_source_handle")
+ self._target_handle = state.get("_target_handle")
+ self._data = state.get("_data")
+ self.valid_handles = state.get("valid_handles")
+ self.source_types = state.get("source_types")
+ self.target_reqs = state.get("target_reqs")
+ self.matched_type = state.get("matched_type")
+
+ def validate_edge(self, source, target) -> None:
+ # If the self.source_handle has base_classes, then we are using the legacy
+ # way of defining the source and target handles
+ if isinstance(self._source_handle, str) or self.source_handle.base_classes:
+ self._legacy_validate_edge(source, target)
+ else:
+ self._validate_edge(source, target)
+
+ def _validate_edge(self, source, target) -> None:
+ # Validate that the outputs of the source node are valid inputs
+ # for the target node
+ # .outputs is a list of Output objects as dictionaries
+ # meaning: check for "types" key in each dictionary
+ self.source_types = [output for output in source.outputs if output["name"] == self.source_handle.name]
+ self.target_reqs = target.required_inputs + target.optional_inputs
+ # Both lists contain strings and sometimes a string contains the value we are
+ # looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
+ # so we need to check if any of the strings in source_types is in target_reqs
+ self.valid = any(
+ any(output_type in target_req for output_type in output["types"])
+ for output in self.source_types
+ for target_req in self.target_reqs
+ )
+ # Get what type of input the target node is expecting
+
+ # Update the matched type to be the first found match
+ self.matched_type = next(
+ (
+ output_type
+ for output in self.source_types
+ for output_type in output["types"]
+ for target_req in self.target_reqs
+ if output_type in target_req
+ ),
+ None,
+ )
+ no_matched_type = self.matched_type is None
+ if no_matched_type:
+ logger.debug(self.source_types)
+ logger.debug(self.target_reqs)
+ msg = f"Edge between {source.vertex_type} and {target.vertex_type} has no matched type."
+ raise ValueError(msg)
+
+ def _legacy_validate_edge(self, source, target) -> None:
+ # Validate that the outputs of the source node are valid inputs
+ # for the target node
+ self.source_types = source.output
+ self.target_reqs = target.required_inputs + target.optional_inputs
+ # Both lists contain strings and sometimes a string contains the value we are
+ # looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
+ # so we need to check if any of the strings in source_types is in target_reqs
+ self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
+ # Get what type of input the target node is expecting
+
+ self.matched_type = next(
+ (output for output in self.source_types if output in self.target_reqs),
+ None,
+ )
+ no_matched_type = self.matched_type is None
+ if no_matched_type:
+ logger.debug(self.source_types)
+ logger.debug(self.target_reqs)
+ msg = f"Edge between {source.vertex_type} and {target.vertex_type} has no matched type"
+ raise ValueError(msg)
+
+ def __repr__(self) -> str:
+ if (hasattr(self, "source_handle") and self.source_handle) and (
+ hasattr(self, "target_handle") and self.target_handle
+ ):
+ return f"{self.source_id} -[{self.source_handle.name}->{self.target_handle.field_name}]-> {self.target_id}"
+ return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
+
+ def __hash__(self) -> int:
+ return hash(self.__repr__())
+
+ def __eq__(self, /, other: object) -> bool:
+ if not isinstance(other, Edge):
+ return False
+ return (
+ self._source_handle == other._source_handle
+ and self._target_handle == other._target_handle
+ and self.target_param == other.target_param
+ )
+
+ def __str__(self) -> str:
+ return self.__repr__()
+
+
+class CycleEdge(Edge):
+ def __init__(self, source: Vertex, target: Vertex, raw_edge: EdgeData):
+ super().__init__(source, target, raw_edge)
+ self.is_fulfilled = False # Whether the contract has been fulfilled.
+ self.result: Any = None
+ self.is_cycle = True
+ source.has_cycle_edges = True
+ target.has_cycle_edges = True
+
+ async def honor(self, source: Vertex, target: Vertex) -> None:
+ """Fulfills the contract by setting the result of the source vertex to the target vertex's parameter.
+
+ If the edge is runnable, the source vertex is run with the message text and the target vertex's
+ root_field param is set to the
+ result. If the edge is not runnable, the target vertex's parameter is set to the result.
+ :param message: The message object to be processed if the edge is runnable.
+ """
+ if self.is_fulfilled:
+ return
+
+ if not source.built:
+ # The system should be read-only, so we should not be building vertices
+ # that are not already built.
+ msg = f"Source vertex {source.id} is not built."
+ raise ValueError(msg)
+
+ if self.matched_type == "Text":
+ self.result = source.built_result
+ else:
+ self.result = source.built_object
+
+ target.params[self.target_param] = self.result
+ self.is_fulfilled = True
+
+ async def get_result_from_source(self, source: Vertex, target: Vertex):
+ # Fulfill the contract if it has not been fulfilled.
+ if not self.is_fulfilled:
+ await self.honor(source, target)
+
+ # If the target vertex is a power component we log messages
+ if (
+ target.vertex_type == "ChatOutput"
+ and isinstance(target.params.get(INPUT_FIELD_NAME), str | dict)
+ and target.params.get("message") == ""
+ ):
+ return self.result
+ return self.result
+
+ def __repr__(self) -> str:
+ str_repr = super().__repr__()
+ # Add a symbol to show this is a cycle edge
+ return f"{str_repr} 🔄"
diff --git a/langflow/src/backend/base/langflow/graph/edge/schema.py b/langflow/src/backend/base/langflow/graph/edge/schema.py
new file mode 100644
index 0000000..72efd86
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/edge/schema.py
@@ -0,0 +1,112 @@
+from typing import Any
+
+from pydantic import ConfigDict, Field, field_validator
+from typing_extensions import TypedDict
+
+from langflow.helpers.base_model import BaseModel
+
+
+class SourceHandleDict(TypedDict, total=False):
+ baseClasses: list[str]
+ dataType: str
+ id: str
+ name: str | None
+ output_types: list[str]
+
+
+class TargetHandleDict(TypedDict):
+ fieldName: str
+ id: str
+ inputTypes: list[str] | None
+ type: str
+
+
+class EdgeDataDetails(TypedDict):
+ sourceHandle: SourceHandleDict
+ targetHandle: TargetHandleDict
+
+
+class EdgeData(TypedDict, total=False):
+ source: str
+ target: str
+ data: EdgeDataDetails
+
+
+class ResultPair(BaseModel):
+ result: Any
+ extra: Any
+
+
+class Payload(BaseModel):
+ result_pairs: list[ResultPair] = []
+
+ def __iter__(self):
+ return iter(self.result_pairs)
+
+ def add_result_pair(self, result: Any, extra: Any | None = None) -> None:
+ self.result_pairs.append(ResultPair(result=result, extra=extra))
+
+ def get_last_result_pair(self) -> ResultPair:
+ return self.result_pairs[-1]
+
+ # format all but the last result pair
+ # into a string
+ def format(self, sep: str = "\n") -> str:
+ # Result: the result
+ # Extra: the extra if it exists don't show if it doesn't
+ return sep.join(
+ [
+ f"Result: {result_pair.result}\nExtra: {result_pair.extra}"
+ if result_pair.extra is not None
+ else f"Result: {result_pair.result}"
+ for result_pair in self.result_pairs[:-1]
+ ]
+ )
+
+
+class TargetHandle(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+ field_name: str = Field(..., alias="fieldName", description="Field name for the target handle.")
+ id: str = Field(..., description="Unique identifier for the target handle.")
+ input_types: list[str] = Field(
+ default_factory=list, alias="inputTypes", description="List of input types for the target handle."
+ )
+ type: str = Field(None, description="Type of the target handle.")
+
+ @classmethod
+ def from_loop_target_handle(cls, target_handle: TargetHandleDict) -> "TargetHandle":
+ # The target handle is a loop edge
+ # The target handle is a dict with the following keys:
+ # - name: str
+ # - id: str
+ # - inputTypes: list[str]
+ # - type: str
+ # It is built from an Output, which is why it has a different structure
+ return cls(
+ field_name=target_handle.get("name"),
+ id=target_handle.get("id"),
+ input_types=target_handle.get("output_types"),
+ )
+
+
+class SourceHandle(BaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+ base_classes: list[str] = Field(
+ default_factory=list, alias="baseClasses", description="List of base classes for the source handle."
+ )
+ data_type: str = Field(..., alias="dataType", description="Data type for the source handle.")
+ id: str = Field(..., description="Unique identifier for the source handle.")
+ name: str | None = Field(None, description="Name of the source handle.")
+ output_types: list[str] = Field(default_factory=list, description="List of output types for the source handle.")
+
+ @field_validator("name", mode="before")
+ @classmethod
+ def validate_name(cls, v, info):
+ if info.data["data_type"] == "GroupNode":
+ # 'OpenAIModel-u4iGV_text_output'
+ splits = v.split("_", 1)
+ if len(splits) != 2: # noqa: PLR2004
+ msg = f"Invalid source handle name {v}"
+ raise ValueError(msg)
+ v = splits[1]
+ return v
diff --git a/langflow/src/backend/base/langflow/graph/edge/utils.py b/langflow/src/backend/base/langflow/graph/edge/utils.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/graph/__init__.py b/langflow/src/backend/base/langflow/graph/graph/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/graph/ascii.py b/langflow/src/backend/base/langflow/graph/graph/ascii.py
new file mode 100644
index 0000000..2d7e0f1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/ascii.py
@@ -0,0 +1,202 @@
+"""This code is adapted from the DVC project.
+
+Original source:
+https://github.com/iterative/dvc/blob/c5bac1c8cfdb2c0f54d52ac61ff754e6f583822a/dvc/dagascii.py
+
+The original code has been modified to focus on drawing a Directed Acyclic Graph (DAG) in ASCII
+using the `grandalf` library. Non-essential parts have been removed, and the code has been
+refactored to suit this specific use case.
+
+
+"""
+
+import math
+
+from grandalf.graphs import Edge as GrandalfEdge
+from grandalf.graphs import Graph as GrandalfGraph
+from grandalf.graphs import Vertex as GrandalfVertex
+from grandalf.layouts import SugiyamaLayout
+from grandalf.routing import EdgeViewer, route_with_lines
+
+MINIMUM_EDGE_VIEW_POINTS = 2
+
+
+class VertexViewer:
+ """Class to define vertex box boundaries that will be accounted for during graph building by grandalf."""
+
+ HEIGHT = 3 # top and bottom box edges + text
+
+ def __init__(self, name) -> None:
+ self._h = self.HEIGHT # top and bottom box edges + text
+ self._w = len(name) + 2 # right and left bottom edges + text
+
+ @property
+ def h(self):
+ return self._h
+
+ @property
+ def w(self):
+ return self._w
+
+
+class AsciiCanvas:
+ """Class for drawing in ASCII."""
+
+ def __init__(self, cols, lines) -> None:
+ if cols <= 1:
+ msg = "cols must be greater than 1"
+ raise ValueError(msg)
+ if lines <= 1:
+ msg = "lines must be greater than 1"
+ raise ValueError(msg)
+ self.cols = cols
+ self.lines = lines
+ self.canvas = [[" "] * cols for _ in range(lines)]
+
+ def get_lines(self):
+ return map("".join, self.canvas)
+
+ def draws(self):
+ return "\n".join(self.get_lines())
+
+ def draw(self) -> None:
+ """Draws ASCII canvas on the screen."""
+ lines = self.get_lines()
+ print("\n".join(lines)) # noqa: T201
+
+ def point(self, x, y, char) -> None:
+ """Create a point on ASCII canvas."""
+ if len(char) != 1:
+ msg = "char must be a single character"
+ raise ValueError(msg)
+ if x < 0 or x >= self.cols:
+ msg = "x is out of bounds"
+ raise ValueError(msg)
+ if y < 0 or y >= self.lines:
+ msg = "y is out of bounds"
+ raise ValueError(msg)
+ self.canvas[y][x] = char
+
+ def line(self, x0, y0, x1, y1, char) -> None:
+ """Create a line on ASCII canvas."""
+ if x0 > x1:
+ x1, x0 = x0, x1
+ y1, y0 = y0, y1
+
+ dx = x1 - x0
+ dy = y1 - y0
+
+ if dx == 0 and dy == 0:
+ self.point(x0, y0, char)
+ elif abs(dx) >= abs(dy):
+ for x in range(x0, x1 + 1):
+ y = y0 + round((x - x0) * dy / float(dx)) if dx else y0
+ self.point(x, y, char)
+ else:
+ for y in range(min(y0, y1), max(y0, y1) + 1):
+ x = x0 + round((y - y0) * dx / float(dy)) if dy else x0
+ self.point(x, y, char)
+
+ def text(self, x, y, text) -> None:
+ """Print a text on ASCII canvas."""
+ for i, char in enumerate(text):
+ self.point(x + i, y, char)
+
+ def box(self, x0, y0, width, height) -> None:
+ """Create a box on ASCII canvas."""
+ if width <= 1:
+ msg = "width must be greater than 1"
+ raise ValueError(msg)
+ if height <= 1:
+ msg = "height must be greater than 1"
+ raise ValueError(msg)
+ width -= 1
+ height -= 1
+
+ for x in range(x0, x0 + width):
+ self.point(x, y0, "-")
+ self.point(x, y0 + height, "-")
+ for y in range(y0, y0 + height):
+ self.point(x0, y, "|")
+ self.point(x0 + width, y, "|")
+ self.point(x0, y0, "+")
+ self.point(x0 + width, y0, "+")
+ self.point(x0, y0 + height, "+")
+ self.point(x0 + width, y0 + height, "+")
+
+
+def build_sugiyama_layout(vertexes, edges):
+ vertexes = {v: GrandalfVertex(v) for v in vertexes}
+ edges = [GrandalfEdge(vertexes[s], vertexes[e]) for s, e in edges]
+ graph = GrandalfGraph(vertexes.values(), edges)
+
+ for vertex in vertexes.values():
+ vertex.view = VertexViewer(vertex.data)
+
+ minw = min(v.view.w for v in vertexes.values())
+
+ for edge in edges:
+ edge.view = EdgeViewer()
+
+ sug = SugiyamaLayout(graph.C[0])
+ roots = [v for v in sug.g.sV if len(v.e_in()) == 0]
+ sug.init_all(roots=roots, optimize=True)
+
+ sug.yspace = VertexViewer.HEIGHT
+ sug.xspace = minw
+ sug.route_edge = route_with_lines
+
+ sug.draw()
+ return sug
+
+
+def draw_graph(vertexes, edges, *, return_ascii=True):
+ """Build a DAG and draw it in ASCII."""
+ sug = build_sugiyama_layout(vertexes, edges)
+
+ xlist = []
+ ylist = []
+
+ for vertex in sug.g.sV:
+ xlist.extend([vertex.view.xy[0] - vertex.view.w / 2.0, vertex.view.xy[0] + vertex.view.w / 2.0])
+ ylist.extend([vertex.view.xy[1], vertex.view.xy[1] + vertex.view.h])
+
+ for edge in sug.g.sE:
+ for x, y in edge.view._pts:
+ xlist.append(x)
+ ylist.append(y)
+
+ minx = min(xlist)
+ miny = min(ylist)
+ maxx = max(xlist)
+ maxy = max(ylist)
+
+ canvas_cols = math.ceil(maxx - minx) + 1
+ canvas_lines = round(maxy - miny)
+
+ canvas = AsciiCanvas(canvas_cols, canvas_lines)
+
+ for edge in sug.g.sE:
+ if len(edge.view._pts) < MINIMUM_EDGE_VIEW_POINTS:
+ msg = "edge.view._pts must have at least 2 points"
+ raise ValueError(msg)
+ for index in range(1, len(edge.view._pts)):
+ start = edge.view._pts[index - 1]
+ end = edge.view._pts[index]
+ canvas.line(
+ round(start[0] - minx),
+ round(start[1] - miny),
+ round(end[0] - minx),
+ round(end[1] - miny),
+ "*",
+ )
+
+ for vertex in sug.g.sV:
+ x = vertex.view.xy[0] - vertex.view.w / 2.0
+ y = vertex.view.xy[1]
+ canvas.box(round(x - minx), round(y - miny), vertex.view.w, vertex.view.h)
+ canvas.text(round(x - minx) + 1, round(y - miny) + 1, vertex.data)
+ if return_ascii:
+ return canvas.draws()
+ canvas.draw()
+ return None
diff --git a/langflow/src/backend/base/langflow/graph/graph/base.py b/langflow/src/backend/base/langflow/graph/graph/base.py
new file mode 100644
index 0000000..6a6a4b4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/base.py
@@ -0,0 +1,2065 @@
+from __future__ import annotations
+
+import asyncio
+import contextlib
+import copy
+import json
+import queue
+import threading
+import uuid
+from collections import defaultdict, deque
+from datetime import datetime, timezone
+from functools import partial
+from itertools import chain
+from typing import TYPE_CHECKING, Any, cast
+
+from loguru import logger
+
+from langflow.exceptions.component import ComponentBuildError
+from langflow.graph.edge.base import CycleEdge, Edge
+from langflow.graph.graph.constants import Finish, lazy_load_vertex_dict
+from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager
+from langflow.graph.graph.schema import GraphData, GraphDump, StartConfigDict, VertexBuildResult
+from langflow.graph.graph.state_manager import GraphStateManager
+from langflow.graph.graph.state_model import create_state_model_from_graph
+from langflow.graph.graph.utils import (
+ find_all_cycle_edges,
+ find_cycle_vertices,
+ find_start_component_id,
+ get_sorted_vertices,
+ process_flow,
+ should_continue,
+)
+from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
+from langflow.graph.vertex.base import Vertex, VertexStates
+from langflow.graph.vertex.schema import NodeData, NodeTypeEnum
+from langflow.graph.vertex.vertex_types import ComponentVertex, InterfaceVertex, StateVertex
+from langflow.logging.logger import LogConfig, configure
+from langflow.schema.dotdict import dotdict
+from langflow.schema.schema import INPUT_FIELD_NAME, InputType
+from langflow.services.cache.utils import CacheMiss
+from langflow.services.deps import get_chat_service, get_tracing_service
+from langflow.utils.async_helpers import run_until_complete
+
+if TYPE_CHECKING:
+ from collections.abc import Generator, Iterable
+
+ from langflow.api.v1.schemas import InputValueRequest
+ from langflow.custom.custom_component.component import Component
+ from langflow.events.event_manager import EventManager
+ from langflow.graph.edge.schema import EdgeData
+ from langflow.graph.schema import ResultData
+ from langflow.schema import Data
+ from langflow.services.chat.schema import GetCache, SetCache
+ from langflow.services.tracing.service import TracingService
+
+
+class Graph:
+ """A class representing a graph of vertices and edges."""
+
+ def __init__(
+ self,
+ start: Component | None = None,
+ end: Component | None = None,
+ flow_id: str | None = None,
+ flow_name: str | None = None,
+ description: str | None = None,
+ user_id: str | None = None,
+ log_config: LogConfig | None = None,
+ context: dict[str, Any] | None = None,
+ ) -> None:
+ """Initializes a new instance of the Graph class.
+
+ Args:
+ start: The start component.
+ end: The end component.
+ flow_id: The ID of the flow. Defaults to None.
+ flow_name: The flow name.
+ description: The graph description.
+ user_id: The user ID.
+ log_config: The log configuration.
+ context: Additional context for the graph. Defaults to None.
+ """
+ if log_config:
+ configure(**log_config)
+
+ self._start = start
+ self._state_model = None
+ self._end = end
+ self._prepared = False
+ self._runs = 0
+ self._updates = 0
+ self.flow_id = flow_id
+ self.flow_name = flow_name
+ self.description = description
+ self.user_id = user_id
+ self._is_input_vertices: list[str] = []
+ self._is_output_vertices: list[str] = []
+ self._is_state_vertices: list[str] = []
+ self.has_session_id_vertices: list[str] = []
+ self._sorted_vertices_layers: list[list[str]] = []
+ self._run_id = ""
+ self._session_id = ""
+ self._start_time = datetime.now(timezone.utc)
+ self.inactivated_vertices: set = set()
+ self.activated_vertices: list[str] = []
+ self.vertices_layers: list[list[str]] = []
+ self.vertices_to_run: set[str] = set()
+ self.stop_vertex: str | None = None
+ self.inactive_vertices: set = set()
+ self.edges: list[CycleEdge] = []
+ self.vertices: list[Vertex] = []
+ self.run_manager = RunnableVerticesManager()
+ self.state_manager = GraphStateManager()
+ self._vertices: list[NodeData] = []
+ self._edges: list[EdgeData] = []
+
+ self.top_level_vertices: list[str] = []
+ self.vertex_map: dict[str, Vertex] = {}
+ self.predecessor_map: dict[str, list[str]] = defaultdict(list)
+ self.successor_map: dict[str, list[str]] = defaultdict(list)
+ self.in_degree_map: dict[str, int] = defaultdict(int)
+ self.parent_child_map: dict[str, list[str]] = defaultdict(list)
+ self._run_queue: deque[str] = deque()
+ self._first_layer: list[str] = []
+ self._lock = asyncio.Lock()
+ self.raw_graph_data: GraphData = {"nodes": [], "edges": []}
+ self._is_cyclic: bool | None = None
+ self._cycles: list[tuple[str, str]] | None = None
+ self._cycle_vertices: set[str] | None = None
+ self._call_order: list[str] = []
+ self._snapshots: list[dict[str, Any]] = []
+ self._end_trace_tasks: set[asyncio.Task] = set()
+
+ if context and not isinstance(context, dict):
+ msg = "Context must be a dictionary"
+ raise TypeError(msg)
+ self._context = dotdict(context or {})
+ try:
+ self.tracing_service: TracingService | None = get_tracing_service()
+ except Exception: # noqa: BLE001
+ logger.exception("Error getting tracing service")
+ self.tracing_service = None
+ if start is not None and end is not None:
+ self._set_start_and_end(start, end)
+ self.prepare(start_component_id=start._id)
+ if (start is not None and end is None) or (start is None and end is not None):
+ msg = "You must provide both input and output components"
+ raise ValueError(msg)
+
+ @property
+ def context(self) -> dotdict:
+ if isinstance(self._context, dotdict):
+ return self._context
+ return dotdict(self._context)
+
+ @context.setter
+ def context(self, value: dict[str, Any]):
+ if not isinstance(value, dict):
+ msg = "Context must be a dictionary"
+ raise TypeError(msg)
+ if isinstance(value, dict):
+ value = dotdict(value)
+ self._context = value
+
+ @property
+ def session_id(self):
+ return self._session_id
+
+ @session_id.setter
+ def session_id(self, value: str):
+ self._session_id = value
+
+ @property
+ def state_model(self):
+ if not self._state_model:
+ self._state_model = create_state_model_from_graph(self)
+ return self._state_model
+
+ def __add__(self, other):
+ if not isinstance(other, Graph):
+ msg = "Can only add Graph objects"
+ raise TypeError(msg)
+ # Add the vertices and edges from the other graph to this graph
+ new_instance = copy.deepcopy(self)
+ for vertex in other.vertices:
+ # This updates the edges as well
+ new_instance.add_vertex(vertex)
+ new_instance.build_graph_maps(new_instance.edges)
+ new_instance.define_vertices_lists()
+ return new_instance
+
+ def __iadd__(self, other):
+ if not isinstance(other, Graph):
+ msg = "Can only add Graph objects"
+ raise TypeError(msg)
+ # Add the vertices and edges from the other graph to this graph
+ for vertex in other.vertices:
+ # This updates the edges as well
+ self.add_vertex(vertex)
+ self.build_graph_maps(self.edges)
+ self.define_vertices_lists()
+ return self
+
+ def dumps(
+ self,
+ name: str | None = None,
+ description: str | None = None,
+ endpoint_name: str | None = None,
+ ) -> str:
+ graph_dict = self.dump(name, description, endpoint_name)
+ return json.dumps(graph_dict, indent=4, sort_keys=True)
+
+ def dump(
+ self, name: str | None = None, description: str | None = None, endpoint_name: str | None = None
+ ) -> GraphDump:
+ if self.raw_graph_data != {"nodes": [], "edges": []}:
+ data_dict = self.raw_graph_data
+ else:
+ # we need to convert the vertices and edges to json
+ nodes = [node.to_data() for node in self.vertices]
+ edges = [edge.to_data() for edge in self.edges]
+ self.raw_graph_data = {"nodes": nodes, "edges": edges}
+ data_dict = self.raw_graph_data
+ graph_dict: GraphDump = {
+ "data": data_dict,
+ "is_component": len(data_dict.get("nodes", [])) == 1 and data_dict["edges"] == [],
+ }
+ if name:
+ graph_dict["name"] = name
+ elif name is None and self.flow_name:
+ graph_dict["name"] = self.flow_name
+ if description:
+ graph_dict["description"] = description
+ elif description is None and self.description:
+ graph_dict["description"] = self.description
+ graph_dict["endpoint_name"] = str(endpoint_name)
+ return graph_dict
+
+ def add_nodes_and_edges(self, nodes: list[NodeData], edges: list[EdgeData]) -> None:
+ self._vertices = nodes
+ self._edges = edges
+ self.raw_graph_data = {"nodes": nodes, "edges": edges}
+ self.top_level_vertices = []
+ for vertex in self._vertices:
+ if vertex_id := vertex.get("id"):
+ self.top_level_vertices.append(vertex_id)
+ if vertex_id in self.cycle_vertices:
+ self.run_manager.add_to_cycle_vertices(vertex_id)
+ self._graph_data = process_flow(self.raw_graph_data)
+
+ self._vertices = self._graph_data["nodes"]
+ self._edges = self._graph_data["edges"]
+ self.initialize()
+
+ def add_component(self, component: Component, component_id: str | None = None) -> str:
+ component_id = component_id or component._id
+ if component_id in self.vertex_map:
+ return component_id
+ component._id = component_id
+ if component_id in self.vertex_map:
+ msg = f"Component ID {component_id} already exists"
+ raise ValueError(msg)
+ frontend_node = component.to_frontend_node()
+ self._vertices.append(frontend_node)
+ vertex = self._create_vertex(frontend_node)
+ vertex.add_component_instance(component)
+ self._add_vertex(vertex)
+ if component._edges:
+ for edge in component._edges:
+ self._add_edge(edge)
+
+ if component._components:
+ for _component in component._components:
+ self.add_component(_component)
+
+ return component_id
+
+ def _set_start_and_end(self, start: Component, end: Component) -> None:
+ if not hasattr(start, "to_frontend_node"):
+ msg = f"start must be a Component. Got {type(start)}"
+ raise TypeError(msg)
+ if not hasattr(end, "to_frontend_node"):
+ msg = f"end must be a Component. Got {type(end)}"
+ raise TypeError(msg)
+ self.add_component(start, start._id)
+ self.add_component(end, end._id)
+
+ def add_component_edge(self, source_id: str, output_input_tuple: tuple[str, str], target_id: str) -> None:
+ source_vertex = self.get_vertex(source_id)
+ if not isinstance(source_vertex, ComponentVertex):
+ msg = f"Source vertex {source_id} is not a component vertex."
+ raise TypeError(msg)
+ target_vertex = self.get_vertex(target_id)
+ if not isinstance(target_vertex, ComponentVertex):
+ msg = f"Target vertex {target_id} is not a component vertex."
+ raise TypeError(msg)
+ output_name, input_name = output_input_tuple
+ if source_vertex.custom_component is None:
+ msg = f"Source vertex {source_id} does not have a custom component."
+ raise ValueError(msg)
+ if target_vertex.custom_component is None:
+ msg = f"Target vertex {target_id} does not have a custom component."
+ raise ValueError(msg)
+
+ try:
+ input_field = target_vertex.get_input(input_name)
+ input_types = input_field.input_types
+ input_field_type = str(input_field.field_type)
+ except ValueError as e:
+ input_field = target_vertex.data.get("node", {}).get("template", {}).get(input_name)
+ if not input_field:
+ msg = f"Input field {input_name} not found in target vertex {target_id}"
+ raise ValueError(msg) from e
+ input_types = input_field.get("input_types", [])
+ input_field_type = input_field.get("type", "")
+
+ edge_data: EdgeData = {
+ "source": source_id,
+ "target": target_id,
+ "data": {
+ "sourceHandle": {
+ "dataType": source_vertex.custom_component.name
+ or source_vertex.custom_component.__class__.__name__,
+ "id": source_vertex.id,
+ "name": output_name,
+ "output_types": source_vertex.get_output(output_name).types,
+ },
+ "targetHandle": {
+ "fieldName": input_name,
+ "id": target_vertex.id,
+ "inputTypes": input_types,
+ "type": input_field_type,
+ },
+ },
+ }
+ self._add_edge(edge_data)
+
+ async def async_start(
+ self,
+ inputs: list[dict] | None = None,
+ max_iterations: int | None = None,
+ event_manager: EventManager | None = None,
+ ):
+ if not self._prepared:
+ msg = "Graph not prepared. Call prepare() first."
+ raise ValueError(msg)
+ # The idea is for this to return a generator that yields the result of
+ # each step call and raise StopIteration when the graph is done
+ for _input in inputs or []:
+ for key, value in _input.items():
+ vertex = self.get_vertex(key)
+ vertex.set_input_value(key, value)
+ # I want to keep a counter of how many tyimes result.vertex.id
+ # has been yielded
+ yielded_counts: dict[str, int] = defaultdict(int)
+
+ while should_continue(yielded_counts, max_iterations):
+ result = await self.astep(event_manager=event_manager)
+ yield result
+ if hasattr(result, "vertex"):
+ yielded_counts[result.vertex.id] += 1
+ if isinstance(result, Finish):
+ return
+
+ msg = "Max iterations reached"
+ raise ValueError(msg)
+
+ def _snapshot(self):
+ return {
+ "_run_queue": self._run_queue.copy(),
+ "_first_layer": self._first_layer.copy(),
+ "vertices_layers": copy.deepcopy(self.vertices_layers),
+ "vertices_to_run": copy.deepcopy(self.vertices_to_run),
+ "run_manager": copy.deepcopy(self.run_manager.to_dict()),
+ }
+
+ def __apply_config(self, config: StartConfigDict) -> None:
+ for vertex in self.vertices:
+ if vertex.custom_component is None:
+ continue
+ for output in vertex.custom_component._outputs_map.values():
+ for key, value in config["output"].items():
+ setattr(output, key, value)
+
+ def start(
+ self,
+ inputs: list[dict] | None = None,
+ max_iterations: int | None = None,
+ config: StartConfigDict | None = None,
+ event_manager: EventManager | None = None,
+ ) -> Generator:
+ """Starts the graph execution synchronously by creating a new event loop in a separate thread.
+
+ Args:
+ inputs: Optional list of input dictionaries
+ max_iterations: Optional maximum number of iterations
+ config: Optional configuration dictionary
+ event_manager: Optional event manager
+
+ Returns:
+ Generator yielding results from graph execution
+ """
+ if self.is_cyclic and max_iterations is None:
+ msg = "You must specify a max_iterations if the graph is cyclic"
+ raise ValueError(msg)
+
+ if config is not None:
+ self.__apply_config(config)
+
+ # Create a queue for passing results and errors between threads
+ result_queue: queue.Queue[VertexBuildResult | Exception | None] = queue.Queue()
+
+ # Function to run async code in separate thread
+ def run_async_code():
+ # Create new event loop for this thread
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ try:
+ # Run the async generator
+ async_gen = self.async_start(inputs, max_iterations, event_manager)
+
+ while True:
+ try:
+ # Get next result from async generator
+ result = loop.run_until_complete(anext(async_gen))
+ result_queue.put(result)
+
+ if isinstance(result, Finish):
+ break
+
+ except StopAsyncIteration:
+ break
+ except ValueError as e:
+ # Put the exception in the queue
+ result_queue.put(e)
+ break
+
+ finally:
+ # Ensure all pending tasks are completed
+ pending = asyncio.all_tasks(loop)
+ if pending:
+ # Create a future to gather all pending tasks
+ cleanup_future = asyncio.gather(*pending, return_exceptions=True)
+ loop.run_until_complete(cleanup_future)
+
+ # Close the loop
+ loop.close()
+ # Signal completion
+ result_queue.put(None)
+
+ # Start thread for async execution
+ thread = threading.Thread(target=run_async_code)
+ thread.start()
+
+ # Yield results from queue
+ while True:
+ result = result_queue.get()
+ if result is None:
+ break
+ if isinstance(result, Exception):
+ raise result
+ yield result
+
+ # Wait for thread to complete
+ thread.join()
+
+ def _add_edge(self, edge: EdgeData) -> None:
+ self.add_edge(edge)
+ source_id = edge["data"]["sourceHandle"]["id"]
+ target_id = edge["data"]["targetHandle"]["id"]
+ self.predecessor_map[target_id].append(source_id)
+ self.successor_map[source_id].append(target_id)
+ self.in_degree_map[target_id] += 1
+ self.parent_child_map[source_id].append(target_id)
+
+ def add_node(self, node: NodeData) -> None:
+ self._vertices.append(node)
+
+ def add_edge(self, edge: EdgeData) -> None:
+ # Check if the edge already exists
+ if edge in self._edges:
+ return
+ self._edges.append(edge)
+
+ def initialize(self) -> None:
+ self._build_graph()
+ self.build_graph_maps(self.edges)
+ self.define_vertices_lists()
+
+ def get_state(self, name: str) -> Data | None:
+ """Returns the state of the graph with the given name.
+
+ Args:
+ name (str): The name of the state.
+
+ Returns:
+ Optional[Data]: The state record, or None if the state does not exist.
+ """
+ return self.state_manager.get_state(name, run_id=self._run_id)
+
+ def update_state(self, name: str, record: str | Data, caller: str | None = None) -> None:
+ """Updates the state of the graph with the given name.
+
+ Args:
+ name (str): The name of the state.
+ record (Union[str, Data]): The new state record.
+ caller (Optional[str], optional): The ID of the vertex that is updating the state. Defaults to None.
+ """
+ if caller:
+ # If there is a caller which is a vertex_id, I want to activate
+ # all StateVertex in self.vertices that are not the caller
+ # essentially notifying all the other vertices that the state has changed
+ # This also has to activate their successors
+ self.activate_state_vertices(name, caller)
+
+ self.state_manager.update_state(name, record, run_id=self._run_id)
+
+ def activate_state_vertices(self, name: str, caller: str) -> None:
+ """Activates the state vertices in the graph with the given name and caller.
+
+ Args:
+ name (str): The name of the state.
+ caller (str): The ID of the vertex that is updating the state.
+ """
+ vertices_ids = set()
+ new_predecessor_map = {}
+ for vertex_id in self._is_state_vertices:
+ caller_vertex = self.get_vertex(caller)
+ vertex = self.get_vertex(vertex_id)
+ if vertex_id == caller or vertex.display_name == caller_vertex.display_name:
+ continue
+ if (
+ isinstance(vertex.raw_params["name"], str)
+ and name in vertex.raw_params["name"]
+ and vertex_id != caller
+ and isinstance(vertex, StateVertex)
+ ):
+ vertices_ids.add(vertex_id)
+ successors = self.get_all_successors(vertex, flat=True)
+ # Update run_manager.run_predecessors because we are activating vertices
+ # The run_prdecessors is the predecessor map of the vertices
+ # we remove the vertex_id from the predecessor map whenever we run a vertex
+ # So we need to get all edges of the vertex and successors
+ # and run self.build_adjacency_maps(edges) to get the new predecessor map
+ # that is not complete but we can use to update the run_predecessors
+ edges_set = set()
+ for _vertex in [vertex, *successors]:
+ edges_set.update(_vertex.edges)
+ if _vertex.state == VertexStates.INACTIVE:
+ _vertex.set_state("ACTIVE")
+
+ vertices_ids.add(_vertex.id)
+ edges = list(edges_set)
+ predecessor_map, _ = self.build_adjacency_maps(edges)
+ new_predecessor_map.update(predecessor_map)
+
+ vertices_ids.update(new_predecessor_map.keys())
+ vertices_ids.update(v_id for value_list in new_predecessor_map.values() for v_id in value_list)
+
+ self.activated_vertices = list(vertices_ids)
+ self.vertices_to_run.update(vertices_ids)
+ self.run_manager.update_run_state(
+ run_predecessors=new_predecessor_map,
+ vertices_to_run=self.vertices_to_run,
+ )
+
+ def reset_activated_vertices(self) -> None:
+ """Resets the activated vertices in the graph."""
+ self.activated_vertices = []
+
+ def append_state(self, name: str, record: str | Data, caller: str | None = None) -> None:
+ """Appends the state of the graph with the given name.
+
+ Args:
+ name (str): The name of the state.
+ record (Union[str, Data]): The state record to append.
+ caller (Optional[str], optional): The ID of the vertex that is updating the state. Defaults to None.
+ """
+ if caller:
+ self.activate_state_vertices(name, caller)
+
+ self.state_manager.append_state(name, record, run_id=self._run_id)
+
+ def validate_stream(self) -> None:
+ """Validates the stream configuration of the graph.
+
+ If there are two vertices in the same graph (connected by edges)
+ that have `stream=True` or `streaming=True`, raises a `ValueError`.
+
+ Raises:
+ ValueError: If two connected vertices have `stream=True` or `streaming=True`.
+ """
+ for vertex in self.vertices:
+ if vertex.params.get("stream") or vertex.params.get("streaming"):
+ successors = self.get_all_successors(vertex)
+ for successor in successors:
+ if successor.params.get("stream") or successor.params.get("streaming"):
+ msg = (
+ f"Components {vertex.display_name} and {successor.display_name} "
+ "are connected and both have stream or streaming set to True"
+ )
+ raise ValueError(msg)
+
+ @property
+ def first_layer(self):
+ if self._first_layer is None:
+ msg = "Graph not prepared. Call prepare() first."
+ raise ValueError(msg)
+ return self._first_layer
+
+ @property
+ def is_cyclic(self):
+ """Check if the graph has any cycles.
+
+ Returns:
+ bool: True if the graph has any cycles, False otherwise.
+ """
+ if self._is_cyclic is None:
+ self._is_cyclic = bool(self.cycle_vertices)
+ return self._is_cyclic
+
+ @property
+ def run_id(self):
+ """The ID of the current run.
+
+ Returns:
+ str: The run ID.
+
+ Raises:
+ ValueError: If the run ID is not set.
+ """
+ if not self._run_id:
+ msg = "Run ID not set"
+ raise ValueError(msg)
+ return self._run_id
+
+ def set_tracing_session_id(self) -> None:
+ """Sets the ID of the current session.
+
+ Args:
+ session_id (str): The session ID.
+ """
+ if self.tracing_service:
+ self.tracing_service.set_session_id(self._session_id)
+
+ def set_run_id(self, run_id: uuid.UUID | None = None) -> None:
+ """Sets the ID of the current run.
+
+ Args:
+ run_id (str): The run ID.
+ """
+ if run_id is None:
+ run_id = uuid.uuid4()
+
+ self._run_id = str(run_id)
+ if self.tracing_service:
+ self.tracing_service.set_run_id(run_id)
+ if self._session_id and self.tracing_service is not None:
+ self.tracing_service.set_session_id(self.session_id)
+
+ def set_run_name(self) -> None:
+ # Given a flow name, flow_id
+ if not self.tracing_service:
+ return
+ name = f"{self.flow_name} - {self.flow_id}"
+
+ self.set_run_id()
+ self.tracing_service.set_run_name(name)
+
+ async def initialize_run(self) -> None:
+ if self.tracing_service:
+ await self.tracing_service.initialize_tracers()
+
+ def _end_all_traces_async(self, outputs: dict[str, Any] | None = None, error: Exception | None = None) -> None:
+ task = asyncio.create_task(self.end_all_traces(outputs, error))
+ self._end_trace_tasks.add(task)
+ task.add_done_callback(self._end_trace_tasks.discard)
+
+ async def end_all_traces(self, outputs: dict[str, Any] | None = None, error: Exception | None = None) -> None:
+ if not self.tracing_service:
+ return
+ self._end_time = datetime.now(timezone.utc)
+ if outputs is None:
+ outputs = {}
+ outputs |= self.metadata
+ await self.tracing_service.end(outputs, error)
+
+ @property
+ def sorted_vertices_layers(self) -> list[list[str]]:
+ """The sorted layers of vertices in the graph.
+
+ Returns:
+ List[List[str]]: The sorted layers of vertices.
+ """
+ if not self._sorted_vertices_layers:
+ self.sort_vertices()
+ return self._sorted_vertices_layers
+
+ def define_vertices_lists(self) -> None:
+ """Defines the lists of vertices that are inputs, outputs, and have session_id."""
+ for vertex in self.vertices:
+ if vertex.is_input:
+ self._is_input_vertices.append(vertex.id)
+ if vertex.is_output:
+ self._is_output_vertices.append(vertex.id)
+ if vertex.has_session_id:
+ self.has_session_id_vertices.append(vertex.id)
+ if vertex.is_state:
+ self._is_state_vertices.append(vertex.id)
+
+ def _set_inputs(self, input_components: list[str], inputs: dict[str, str], input_type: InputType | None) -> None:
+ for vertex_id in self._is_input_vertices:
+ vertex = self.get_vertex(vertex_id)
+ # If the vertex is not in the input_components list
+ if input_components and (vertex_id not in input_components and vertex.display_name not in input_components):
+ continue
+ # If the input_type is not any and the input_type is not in the vertex id
+ # Example: input_type = "chat" and vertex.id = "OpenAI-19ddn"
+ if input_type is not None and input_type != "any" and input_type not in vertex.id.lower():
+ continue
+ if vertex is None:
+ msg = f"Vertex {vertex_id} not found"
+ raise ValueError(msg)
+ vertex.update_raw_params(inputs, overwrite=True)
+
+ async def _run(
+ self,
+ *,
+ inputs: dict[str, str],
+ input_components: list[str],
+ input_type: InputType | None,
+ outputs: list[str],
+ stream: bool,
+ session_id: str,
+ fallback_to_env_vars: bool,
+ event_manager: EventManager | None = None,
+ ) -> list[ResultData | None]:
+ """Runs the graph with the given inputs.
+
+ Args:
+ inputs (Dict[str, str]): The input values for the graph.
+ input_components (list[str]): The components to run for the inputs.
+ input_type: (Optional[InputType]): The input type.
+ outputs (list[str]): The outputs to retrieve from the graph.
+ stream (bool): Whether to stream the results or not.
+ session_id (str): The session ID for the graph.
+ fallback_to_env_vars (bool): Whether to fallback to environment variables.
+ event_manager (EventManager | None): The event manager for the graph.
+
+ Returns:
+ List[Optional["ResultData"]]: The outputs of the graph.
+ """
+ if input_components and not isinstance(input_components, list):
+ msg = f"Invalid components value: {input_components}. Expected list"
+ raise ValueError(msg)
+ if input_components is None:
+ input_components = []
+
+ if not isinstance(inputs.get(INPUT_FIELD_NAME, ""), str):
+ msg = f"Invalid input value: {inputs.get(INPUT_FIELD_NAME)}. Expected string"
+ raise TypeError(msg)
+ if inputs:
+ self._set_inputs(input_components, inputs, input_type)
+ # Update all the vertices with the session_id
+ for vertex_id in self.has_session_id_vertices:
+ vertex = self.get_vertex(vertex_id)
+ if vertex is None:
+ msg = f"Vertex {vertex_id} not found"
+ raise ValueError(msg)
+ vertex.update_raw_params({"session_id": session_id})
+ # Process the graph
+ try:
+ cache_service = get_chat_service()
+ if self.flow_id:
+ await cache_service.set_cache(self.flow_id, self)
+ except Exception: # noqa: BLE001
+ logger.exception("Error setting cache")
+
+ try:
+ # Prioritize the webhook component if it exists
+ start_component_id = find_start_component_id(self._is_input_vertices)
+ await self.process(
+ start_component_id=start_component_id,
+ fallback_to_env_vars=fallback_to_env_vars,
+ event_manager=event_manager,
+ )
+ self.increment_run_count()
+ except Exception as exc:
+ self._end_all_traces_async(error=exc)
+ msg = f"Error running graph: {exc}"
+ raise ValueError(msg) from exc
+
+ self._end_all_traces_async()
+ # Get the outputs
+ vertex_outputs = []
+ for vertex in self.vertices:
+ if not vertex.built:
+ continue
+ if vertex is None:
+ msg = f"Vertex {vertex_id} not found"
+ raise ValueError(msg)
+
+ if not vertex.result and not stream and hasattr(vertex, "consume_async_generator"):
+ await vertex.consume_async_generator()
+ if (not outputs and vertex.is_output) or (vertex.display_name in outputs or vertex.id in outputs):
+ vertex_outputs.append(vertex.result)
+
+ return vertex_outputs
+
+ async def arun(
+ self,
+ inputs: list[dict[str, str]],
+ *,
+ inputs_components: list[list[str]] | None = None,
+ types: list[InputType | None] | None = None,
+ outputs: list[str] | None = None,
+ session_id: str | None = None,
+ stream: bool = False,
+ fallback_to_env_vars: bool = False,
+ event_manager: EventManager | None = None,
+ ) -> list[RunOutputs]:
+ """Runs the graph with the given inputs.
+
+ Args:
+ inputs (list[Dict[str, str]]): The input values for the graph.
+ inputs_components (Optional[list[list[str]]], optional): Components to run for the inputs. Defaults to None.
+ types (Optional[list[Optional[InputType]]], optional): The types of the inputs. Defaults to None.
+ outputs (Optional[list[str]], optional): The outputs to retrieve from the graph. Defaults to None.
+ session_id (Optional[str], optional): The session ID for the graph. Defaults to None.
+ stream (bool, optional): Whether to stream the results or not. Defaults to False.
+ fallback_to_env_vars (bool, optional): Whether to fallback to environment variables. Defaults to False.
+ event_manager (EventManager | None): The event manager for the graph.
+
+ Returns:
+ List[RunOutputs]: The outputs of the graph.
+ """
+ # inputs is {"message": "Hello, world!"}
+ # we need to go through self.inputs and update the self.raw_params
+ # of the vertices that are inputs
+ # if the value is a list, we need to run multiple times
+ vertex_outputs = []
+ if not isinstance(inputs, list):
+ inputs = [inputs]
+ elif not inputs:
+ inputs = [{}]
+ # Length of all should be the as inputs length
+ # just add empty lists to complete the length
+ if inputs_components is None:
+ inputs_components = []
+ for _ in range(len(inputs) - len(inputs_components)):
+ inputs_components.append([])
+ if types is None:
+ types = []
+ for _ in range(len(inputs) - len(types)):
+ types.append("chat") # default to chat
+ for run_inputs, components, input_type in zip(inputs, inputs_components, types, strict=True):
+ run_outputs = await self._run(
+ inputs=run_inputs,
+ input_components=components,
+ input_type=input_type,
+ outputs=outputs or [],
+ stream=stream,
+ session_id=session_id or "",
+ fallback_to_env_vars=fallback_to_env_vars,
+ event_manager=event_manager,
+ )
+ run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
+ logger.debug(f"Run outputs: {run_output_object}")
+ vertex_outputs.append(run_output_object)
+ return vertex_outputs
+
+ def next_vertex_to_build(self):
+ """Returns the next vertex to be built.
+
+ Yields:
+ str: The ID of the next vertex to be built.
+ """
+ yield from chain.from_iterable(self.vertices_layers)
+
+ @property
+ def metadata(self):
+ """The metadata of the graph.
+
+ Returns:
+ dict: The metadata of the graph.
+ """
+ time_format = "%Y-%m-%d %H:%M:%S %Z"
+ return {
+ "start_time": self._start_time.strftime(time_format),
+ "end_time": self._end_time.strftime(time_format),
+ "time_elapsed": f"{(self._end_time - self._start_time).total_seconds()} seconds",
+ "flow_id": self.flow_id,
+ "flow_name": self.flow_name,
+ }
+
+ def build_graph_maps(self, edges: list[CycleEdge] | None = None, vertices: list[Vertex] | None = None) -> None:
+ """Builds the adjacency maps for the graph."""
+ if edges is None:
+ edges = self.edges
+
+ if vertices is None:
+ vertices = self.vertices
+
+ self.predecessor_map, self.successor_map = self.build_adjacency_maps(edges)
+
+ self.in_degree_map = self.build_in_degree(edges)
+ self.parent_child_map = self.build_parent_child_map(vertices)
+
+ def reset_inactivated_vertices(self) -> None:
+ """Resets the inactivated vertices in the graph."""
+ for vertex_id in self.inactivated_vertices.copy():
+ self.mark_vertex(vertex_id, "ACTIVE")
+ self.inactivated_vertices = set()
+ self.inactivated_vertices = set()
+
+ def mark_all_vertices(self, state: str) -> None:
+ """Marks all vertices in the graph."""
+ for vertex in self.vertices:
+ vertex.set_state(state)
+
+ def mark_vertex(self, vertex_id: str, state: str) -> None:
+ """Marks a vertex in the graph."""
+ vertex = self.get_vertex(vertex_id)
+ vertex.set_state(state)
+ if state == VertexStates.INACTIVE:
+ self.run_manager.remove_from_predecessors(vertex_id)
+
+ def _mark_branch(
+ self, vertex_id: str, state: str, visited: set | None = None, output_name: str | None = None
+ ) -> None:
+ """Marks a branch of the graph."""
+ if visited is None:
+ visited = set()
+ else:
+ self.mark_vertex(vertex_id, state)
+ if vertex_id in visited:
+ return
+ visited.add(vertex_id)
+
+ for child_id in self.parent_child_map[vertex_id]:
+ # Only child_id that have an edge with the vertex_id through the output_name
+ # should be marked
+ if output_name:
+ edge = self.get_edge(vertex_id, child_id)
+ if edge and edge.source_handle.name != output_name:
+ continue
+ self._mark_branch(child_id, state, visited)
+
+ def mark_branch(self, vertex_id: str, state: str, output_name: str | None = None) -> None:
+ self._mark_branch(vertex_id=vertex_id, state=state, output_name=output_name)
+ new_predecessor_map, _ = self.build_adjacency_maps(self.edges)
+ self.run_manager.update_run_state(
+ run_predecessors=new_predecessor_map,
+ vertices_to_run=self.vertices_to_run,
+ )
+
+ def get_edge(self, source_id: str, target_id: str) -> CycleEdge | None:
+ """Returns the edge between two vertices."""
+ for edge in self.edges:
+ if edge.source_id == source_id and edge.target_id == target_id:
+ return edge
+ return None
+
+ def build_parent_child_map(self, vertices: list[Vertex]):
+ parent_child_map = defaultdict(list)
+ for vertex in vertices:
+ parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
+ return parent_child_map
+
+ def increment_run_count(self) -> None:
+ self._runs += 1
+
+ def increment_update_count(self) -> None:
+ self._updates += 1
+
+ def __getstate__(self):
+ # Get all attributes that are useful in runs.
+ # We don't need to save the state_manager because it is
+ # a singleton and it is not necessary to save it
+ return {
+ "vertices": self.vertices,
+ "edges": self.edges,
+ "flow_id": self.flow_id,
+ "flow_name": self.flow_name,
+ "description": self.description,
+ "user_id": self.user_id,
+ "raw_graph_data": self.raw_graph_data,
+ "top_level_vertices": self.top_level_vertices,
+ "inactivated_vertices": self.inactivated_vertices,
+ "run_manager": self.run_manager.to_dict(),
+ "_run_id": self._run_id,
+ "in_degree_map": self.in_degree_map,
+ "parent_child_map": self.parent_child_map,
+ "predecessor_map": self.predecessor_map,
+ "successor_map": self.successor_map,
+ "activated_vertices": self.activated_vertices,
+ "vertices_layers": self.vertices_layers,
+ "vertices_to_run": self.vertices_to_run,
+ "stop_vertex": self.stop_vertex,
+ "_run_queue": self._run_queue,
+ "_first_layer": self._first_layer,
+ "_vertices": self._vertices,
+ "_edges": self._edges,
+ "_is_input_vertices": self._is_input_vertices,
+ "_is_output_vertices": self._is_output_vertices,
+ "has_session_id_vertices": self.has_session_id_vertices,
+ "_sorted_vertices_layers": self._sorted_vertices_layers,
+ }
+
+ def __deepcopy__(self, memo):
+ # Check if we've already copied this instance
+ if id(self) in memo:
+ return memo[id(self)]
+
+ if self._start is not None and self._end is not None:
+ # Deep copy start and end components
+ start_copy = copy.deepcopy(self._start, memo)
+ end_copy = copy.deepcopy(self._end, memo)
+ new_graph = type(self)(
+ start_copy,
+ end_copy,
+ copy.deepcopy(self.flow_id, memo),
+ copy.deepcopy(self.flow_name, memo),
+ copy.deepcopy(self.user_id, memo),
+ )
+ else:
+ # Create a new graph without start and end, but copy flow_id, flow_name, and user_id
+ new_graph = type(self)(
+ None,
+ None,
+ copy.deepcopy(self.flow_id, memo),
+ copy.deepcopy(self.flow_name, memo),
+ copy.deepcopy(self.user_id, memo),
+ )
+ # Deep copy vertices and edges
+ new_graph.add_nodes_and_edges(copy.deepcopy(self._vertices, memo), copy.deepcopy(self._edges, memo))
+
+ # Store the newly created object in memo
+ memo[id(self)] = new_graph
+
+ return new_graph
+
+ def __setstate__(self, state):
+ run_manager = state["run_manager"]
+ if isinstance(run_manager, RunnableVerticesManager):
+ state["run_manager"] = run_manager
+ else:
+ state["run_manager"] = RunnableVerticesManager.from_dict(run_manager)
+ self.__dict__.update(state)
+ self.vertex_map = {vertex.id: vertex for vertex in self.vertices}
+ self.state_manager = GraphStateManager()
+ self.tracing_service = get_tracing_service()
+ self.set_run_id(self._run_id)
+ self.set_run_name()
+
+ @classmethod
+ def from_payload(
+ cls,
+ payload: dict,
+ flow_id: str | None = None,
+ flow_name: str | None = None,
+ user_id: str | None = None,
+ ) -> Graph:
+ """Creates a graph from a payload.
+
+ Args:
+ payload: The payload to create the graph from.
+ flow_id: The ID of the flow.
+ flow_name: The flow name.
+ user_id: The user ID.
+
+ Returns:
+ Graph: The created graph.
+ """
+ if "data" in payload:
+ payload = payload["data"]
+ try:
+ vertices = payload["nodes"]
+ edges = payload["edges"]
+ graph = cls(flow_id=flow_id, flow_name=flow_name, user_id=user_id)
+ graph.add_nodes_and_edges(vertices, edges)
+ except KeyError as exc:
+ logger.exception(exc)
+ if "nodes" not in payload and "edges" not in payload:
+ msg = f"Invalid payload. Expected keys 'nodes' and 'edges'. Found {list(payload.keys())}"
+ raise ValueError(msg) from exc
+
+ msg = f"Error while creating graph from payload: {exc}"
+ raise ValueError(msg) from exc
+ else:
+ return graph
+
+ def __eq__(self, /, other: object) -> bool:
+ if not isinstance(other, Graph):
+ return False
+ return self.__repr__() == other.__repr__()
+
+ # update this graph with another graph by comparing the __repr__ of each vertex
+ # and if the __repr__ of a vertex is not the same as the other
+ # then update the .data of the vertex to the self
+ # both graphs have the same vertices and edges
+ # but the data of the vertices might be different
+
+ def update_edges_from_vertex(self, other_vertex: Vertex) -> None:
+ """Updates the edges of a vertex in the Graph."""
+ new_edges = []
+ for edge in self.edges:
+ if other_vertex.id in {edge.source_id, edge.target_id}:
+ continue
+ new_edges.append(edge)
+ new_edges += other_vertex.edges
+ self.edges = new_edges
+
+ def vertex_data_is_identical(self, vertex: Vertex, other_vertex: Vertex) -> bool:
+ data_is_equivalent = vertex == other_vertex
+ if not data_is_equivalent:
+ return False
+ return self.vertex_edges_are_identical(vertex, other_vertex)
+
+ @staticmethod
+ def vertex_edges_are_identical(vertex: Vertex, other_vertex: Vertex) -> bool:
+ same_length = len(vertex.edges) == len(other_vertex.edges)
+ if not same_length:
+ return False
+ return all(edge in other_vertex.edges for edge in vertex.edges)
+
+ def update(self, other: Graph) -> Graph:
+ # Existing vertices in self graph
+ existing_vertex_ids = {vertex.id for vertex in self.vertices}
+ # Vertex IDs in the other graph
+ other_vertex_ids = set(other.vertex_map.keys())
+
+ # Find vertices that are in other but not in self (new vertices)
+ new_vertex_ids = other_vertex_ids - existing_vertex_ids
+
+ # Find vertices that are in self but not in other (removed vertices)
+ removed_vertex_ids = existing_vertex_ids - other_vertex_ids
+
+ # Remove vertices that are not in the other graph
+ for vertex_id in removed_vertex_ids:
+ with contextlib.suppress(ValueError):
+ self.remove_vertex(vertex_id)
+
+ # The order here matters because adding the vertex is required
+ # if any of them have edges that point to any of the new vertices
+ # By adding them first, them adding the edges we ensure that the
+ # edges have valid vertices to point to
+
+ # Add new vertices
+ for vertex_id in new_vertex_ids:
+ new_vertex = other.get_vertex(vertex_id)
+ self._add_vertex(new_vertex)
+
+ # Now update the edges
+ for vertex_id in new_vertex_ids:
+ new_vertex = other.get_vertex(vertex_id)
+ self._update_edges(new_vertex)
+ # Graph is set at the end because the edges come from the graph
+ # and the other graph is where the new edges and vertices come from
+ new_vertex.graph = self
+
+ # Update existing vertices that have changed
+ for vertex_id in existing_vertex_ids.intersection(other_vertex_ids):
+ self_vertex = self.get_vertex(vertex_id)
+ other_vertex = other.get_vertex(vertex_id)
+ # If the vertices are not identical, update the vertex
+ if not self.vertex_data_is_identical(self_vertex, other_vertex):
+ self.update_vertex_from_another(self_vertex, other_vertex)
+
+ self.build_graph_maps()
+ self.define_vertices_lists()
+ self.increment_update_count()
+ return self
+
+ def update_vertex_from_another(self, vertex: Vertex, other_vertex: Vertex) -> None:
+ """Updates a vertex from another vertex.
+
+ Args:
+ vertex (Vertex): The vertex to be updated.
+ other_vertex (Vertex): The vertex to update from.
+ """
+ vertex.full_data = other_vertex.full_data
+ vertex.parse_data()
+ # Now we update the edges of the vertex
+ self.update_edges_from_vertex(other_vertex)
+ vertex.params = {}
+ vertex.build_params()
+ vertex.graph = self
+ # If the vertex is frozen, we don't want
+ # to reset the results nor the built attribute
+ if not vertex.frozen:
+ vertex.built = False
+ vertex.result = None
+ vertex.artifacts = {}
+ vertex.set_top_level(self.top_level_vertices)
+ self.reset_all_edges_of_vertex(vertex)
+
+ def reset_all_edges_of_vertex(self, vertex: Vertex) -> None:
+ """Resets all the edges of a vertex."""
+ for edge in vertex.edges:
+ for vid in [edge.source_id, edge.target_id]:
+ if vid in self.vertex_map:
+ vertex_ = self.vertex_map[vid]
+ if not vertex_.frozen:
+ vertex_.build_params()
+
+ def _add_vertex(self, vertex: Vertex) -> None:
+ """Adds a vertex to the graph."""
+ self.vertices.append(vertex)
+ self.vertex_map[vertex.id] = vertex
+
+ def add_vertex(self, vertex: Vertex) -> None:
+ """Adds a new vertex to the graph."""
+ self._add_vertex(vertex)
+ self._update_edges(vertex)
+
+ def _update_edges(self, vertex: Vertex) -> None:
+ """Updates the edges of a vertex."""
+ # Vertex has edges, so we need to update the edges
+ for edge in vertex.edges:
+ if edge not in self.edges and edge.source_id in self.vertex_map and edge.target_id in self.vertex_map:
+ self.edges.append(edge)
+
+ def _build_graph(self) -> None:
+ """Builds the graph from the vertices and edges."""
+ self.vertices = self._build_vertices()
+ self.vertex_map = {vertex.id: vertex for vertex in self.vertices}
+ self.edges = self._build_edges()
+
+ # This is a hack to make sure that the LLM vertex is sent to
+ # the toolkit vertex
+ self._build_vertex_params()
+ self._instantiate_components_in_vertices()
+ self._set_cache_to_vertices_in_cycle()
+ for vertex in self.vertices:
+ if vertex.id in self.cycle_vertices:
+ self.run_manager.add_to_cycle_vertices(vertex.id)
+
+ self.assert_streaming_sequence()
+
+ def _get_edges_as_list_of_tuples(self) -> list[tuple[str, str]]:
+ """Returns the edges of the graph as a list of tuples."""
+ return [(e["data"]["sourceHandle"]["id"], e["data"]["targetHandle"]["id"]) for e in self._edges]
+
+ def _set_cache_to_vertices_in_cycle(self) -> None:
+ """Sets the cache to the vertices in cycle."""
+ edges = self._get_edges_as_list_of_tuples()
+ cycle_vertices = set(find_cycle_vertices(edges))
+ for vertex in self.vertices:
+ if vertex.id in cycle_vertices:
+ vertex.apply_on_outputs(lambda output_object: setattr(output_object, "cache", False))
+
+ def _instantiate_components_in_vertices(self) -> None:
+ """Instantiates the components in the vertices."""
+ for vertex in self.vertices:
+ vertex.instantiate_component(self.user_id)
+
+ def remove_vertex(self, vertex_id: str) -> None:
+ """Removes a vertex from the graph."""
+ vertex = self.get_vertex(vertex_id)
+ if vertex is None:
+ return
+ self.vertices.remove(vertex)
+ self.vertex_map.pop(vertex_id)
+ self.edges = [edge for edge in self.edges if vertex_id not in {edge.source_id, edge.target_id}]
+
+ def _build_vertex_params(self) -> None:
+ """Identifies and handles the LLM vertex within the graph."""
+ for vertex in self.vertices:
+ vertex.build_params()
+
+ def _validate_vertex(self, vertex: Vertex) -> bool:
+ """Validates a vertex."""
+ # All vertices that do not have edges are invalid
+ return len(self.get_vertex_edges(vertex.id)) > 0
+
+ def get_vertex(self, vertex_id: str) -> Vertex:
+ """Returns a vertex by id."""
+ try:
+ return self.vertex_map[vertex_id]
+ except KeyError as e:
+ msg = f"Vertex {vertex_id} not found"
+ raise ValueError(msg) from e
+
+ def get_root_of_group_node(self, vertex_id: str) -> Vertex:
+ """Returns the root of a group node."""
+ if vertex_id in self.top_level_vertices:
+ # Get all vertices with vertex_id as .parent_node_id
+ # then get the one at the top
+ vertices = [vertex for vertex in self.vertices if vertex.parent_node_id == vertex_id]
+ # Now go through successors of the vertices
+ # and get the one that none of its successors is in vertices
+ for vertex in vertices:
+ successors = self.get_all_successors(vertex, recursive=False)
+ if not any(successor in vertices for successor in successors):
+ return vertex
+ msg = f"Vertex {vertex_id} is not a top level vertex or no root vertex found"
+ raise ValueError(msg)
+
+ def get_next_in_queue(self):
+ if not self._run_queue:
+ return None
+ return self._run_queue.popleft()
+
+ def extend_run_queue(self, vertices: list[str]) -> None:
+ self._run_queue.extend(vertices)
+
+ async def astep(
+ self,
+ inputs: InputValueRequest | None = None,
+ files: list[str] | None = None,
+ user_id: str | None = None,
+ event_manager: EventManager | None = None,
+ ):
+ if not self._prepared:
+ msg = "Graph not prepared. Call prepare() first."
+ raise ValueError(msg)
+ if not self._run_queue:
+ self._end_all_traces_async()
+ return Finish()
+ vertex_id = self.get_next_in_queue()
+ chat_service = get_chat_service()
+ vertex_build_result = await self.build_vertex(
+ vertex_id=vertex_id,
+ user_id=user_id,
+ inputs_dict=inputs.model_dump() if inputs else {},
+ files=files,
+ get_cache=chat_service.get_cache,
+ set_cache=chat_service.set_cache,
+ event_manager=event_manager,
+ )
+
+ next_runnable_vertices = await self.get_next_runnable_vertices(
+ self._lock, vertex=vertex_build_result.vertex, cache=False
+ )
+ if self.stop_vertex and self.stop_vertex in next_runnable_vertices:
+ next_runnable_vertices = [self.stop_vertex]
+ self.extend_run_queue(next_runnable_vertices)
+ self.reset_inactivated_vertices()
+ self.reset_activated_vertices()
+
+ await chat_service.set_cache(str(self.flow_id or self._run_id), self)
+ self._record_snapshot(vertex_id)
+ return vertex_build_result
+
+ def get_snapshot(self):
+ return copy.deepcopy(
+ {
+ "run_manager": self.run_manager.to_dict(),
+ "run_queue": self._run_queue,
+ "vertices_layers": self.vertices_layers,
+ "first_layer": self.first_layer,
+ "inactive_vertices": self.inactive_vertices,
+ "activated_vertices": self.activated_vertices,
+ }
+ )
+
+ def _record_snapshot(self, vertex_id: str | None = None) -> None:
+ self._snapshots.append(self.get_snapshot())
+ if vertex_id:
+ self._call_order.append(vertex_id)
+
+ def step(
+ self,
+ inputs: InputValueRequest | None = None,
+ files: list[str] | None = None,
+ user_id: str | None = None,
+ ):
+ """Runs the next vertex in the graph.
+
+ Note:
+ This function is a synchronous wrapper around `astep`.
+ It creates an event loop if one does not exist.
+
+ Args:
+ inputs: The inputs for the vertex. Defaults to None.
+ files: The files for the vertex. Defaults to None.
+ user_id: The user ID. Defaults to None.
+ """
+ return run_until_complete(self.astep(inputs, files, user_id))
+
+ async def build_vertex(
+ self,
+ vertex_id: str,
+ *,
+ get_cache: GetCache | None = None,
+ set_cache: SetCache | None = None,
+ inputs_dict: dict[str, str] | None = None,
+ files: list[str] | None = None,
+ user_id: str | None = None,
+ fallback_to_env_vars: bool = False,
+ event_manager: EventManager | None = None,
+ ) -> VertexBuildResult:
+ """Builds a vertex in the graph.
+
+ Args:
+ vertex_id (str): The ID of the vertex to build.
+ get_cache (GetCache): A coroutine to get the cache.
+ set_cache (SetCache): A coroutine to set the cache.
+ inputs_dict (Optional[Dict[str, str]]): Optional dictionary of inputs for the vertex. Defaults to None.
+ files: (Optional[List[str]]): Optional list of files. Defaults to None.
+ user_id (Optional[str]): Optional user ID. Defaults to None.
+ fallback_to_env_vars (bool): Whether to fallback to environment variables. Defaults to False.
+ event_manager (Optional[EventManager]): Optional event manager. Defaults to None.
+
+ Returns:
+ Tuple: A tuple containing the next runnable vertices, top level vertices, result dictionary,
+ parameters, validity flag, artifacts, and the built vertex.
+
+ Raises:
+ ValueError: If no result is found for the vertex.
+ """
+ vertex = self.get_vertex(vertex_id)
+ self.run_manager.add_to_vertices_being_run(vertex_id)
+ try:
+ params = ""
+ should_build = False
+ if not vertex.frozen:
+ should_build = True
+ else:
+ # Check the cache for the vertex
+ if get_cache is not None:
+ cached_result = await get_cache(key=vertex.id)
+ else:
+ cached_result = CacheMiss()
+ if isinstance(cached_result, CacheMiss):
+ should_build = True
+ else:
+ try:
+ cached_vertex_dict = cached_result["result"]
+ # Now set update the vertex with the cached vertex
+ vertex.built = cached_vertex_dict["built"]
+ vertex.artifacts = cached_vertex_dict["artifacts"]
+ vertex.built_object = cached_vertex_dict["built_object"]
+ vertex.built_result = cached_vertex_dict["built_result"]
+ vertex.full_data = cached_vertex_dict["full_data"]
+ vertex.results = cached_vertex_dict["results"]
+ try:
+ vertex.finalize_build()
+
+ if vertex.result is not None:
+ vertex.result.used_frozen_result = True
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).debug("Error finalizing build")
+ should_build = True
+ except KeyError:
+ should_build = True
+
+ if should_build:
+ await vertex.build(
+ user_id=user_id,
+ inputs=inputs_dict,
+ fallback_to_env_vars=fallback_to_env_vars,
+ files=files,
+ event_manager=event_manager,
+ )
+ if set_cache is not None:
+ vertex_dict = {
+ "built": vertex.built,
+ "results": vertex.results,
+ "artifacts": vertex.artifacts,
+ "built_object": vertex.built_object,
+ "built_result": vertex.built_result,
+ "full_data": vertex.full_data,
+ }
+
+ await set_cache(key=vertex.id, data=vertex_dict)
+
+ except Exception as exc:
+ if not isinstance(exc, ComponentBuildError):
+ logger.exception("Error building Component")
+ raise
+
+ if vertex.result is not None:
+ params = f"{vertex.built_object_repr()}{params}"
+ valid = True
+ result_dict = vertex.result
+ artifacts = vertex.artifacts
+ else:
+ msg = f"Error building Component: no result found for vertex {vertex_id}"
+ raise ValueError(msg)
+
+ return VertexBuildResult(
+ result_dict=result_dict, params=params, valid=valid, artifacts=artifacts, vertex=vertex
+ )
+
+ def get_vertex_edges(
+ self,
+ vertex_id: str,
+ *,
+ is_target: bool | None = None,
+ is_source: bool | None = None,
+ ) -> list[CycleEdge]:
+ """Returns a list of edges for a given vertex."""
+ # The idea here is to return the edges that have the vertex_id as source or target
+ # or both
+ return [
+ edge
+ for edge in self.edges
+ if (edge.source_id == vertex_id and is_source is not False)
+ or (edge.target_id == vertex_id and is_target is not False)
+ ]
+
+ def get_vertices_with_target(self, vertex_id: str) -> list[Vertex]:
+ """Returns the vertices connected to a vertex."""
+ vertices: list[Vertex] = []
+ for edge in self.edges:
+ if edge.target_id == vertex_id:
+ vertex = self.get_vertex(edge.source_id)
+ if vertex is None:
+ continue
+ vertices.append(vertex)
+ return vertices
+
+ async def process(
+ self,
+ *,
+ fallback_to_env_vars: bool,
+ start_component_id: str | None = None,
+ event_manager: EventManager | None = None,
+ ) -> Graph:
+ """Processes the graph with vertices in each layer run in parallel."""
+ first_layer = self.sort_vertices(start_component_id=start_component_id)
+ vertex_task_run_count: dict[str, int] = {}
+ to_process = deque(first_layer)
+ layer_index = 0
+ chat_service = get_chat_service()
+ self.set_run_id()
+ self.set_run_name()
+ await self.initialize_run()
+ lock = asyncio.Lock()
+ while to_process:
+ current_batch = list(to_process) # Copy current deque items to a list
+ to_process.clear() # Clear the deque for new items
+ tasks = []
+ for vertex_id in current_batch:
+ vertex = self.get_vertex(vertex_id)
+ task = asyncio.create_task(
+ self.build_vertex(
+ vertex_id=vertex_id,
+ user_id=self.user_id,
+ inputs_dict={},
+ fallback_to_env_vars=fallback_to_env_vars,
+ get_cache=chat_service.get_cache,
+ set_cache=chat_service.set_cache,
+ event_manager=event_manager,
+ ),
+ name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
+ )
+ tasks.append(task)
+ vertex_task_run_count[vertex_id] = vertex_task_run_count.get(vertex_id, 0) + 1
+
+ logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks, {current_batch}")
+ try:
+ next_runnable_vertices = await self._execute_tasks(tasks, lock=lock)
+ except Exception:
+ logger.exception(f"Error executing tasks in layer {layer_index}")
+ raise
+ if not next_runnable_vertices:
+ break
+ to_process.extend(next_runnable_vertices)
+ layer_index += 1
+
+ logger.debug("Graph processing complete")
+ return self
+
+ def find_next_runnable_vertices(self, vertex_successors_ids: list[str]) -> list[str]:
+ next_runnable_vertices = set()
+ for v_id in sorted(vertex_successors_ids):
+ if not self.is_vertex_runnable(v_id):
+ next_runnable_vertices.update(self.find_runnable_predecessors_for_successor(v_id))
+ else:
+ next_runnable_vertices.add(v_id)
+
+ return sorted(next_runnable_vertices)
+
+ async def get_next_runnable_vertices(self, lock: asyncio.Lock, vertex: Vertex, *, cache: bool = True) -> list[str]:
+ v_id = vertex.id
+ v_successors_ids = vertex.successors_ids
+ async with lock:
+ self.run_manager.remove_vertex_from_runnables(v_id)
+ next_runnable_vertices = self.find_next_runnable_vertices(v_successors_ids)
+
+ for next_v_id in set(next_runnable_vertices): # Use set to avoid duplicates
+ if next_v_id == v_id:
+ next_runnable_vertices.remove(v_id)
+ else:
+ self.run_manager.add_to_vertices_being_run(next_v_id)
+ if cache and self.flow_id is not None:
+ set_cache_coro = partial(get_chat_service().set_cache, key=self.flow_id)
+ await set_cache_coro(data=self, lock=lock)
+ return next_runnable_vertices
+
+ async def _execute_tasks(self, tasks: list[asyncio.Task], lock: asyncio.Lock) -> list[str]:
+ """Executes tasks in parallel, handling exceptions for each task."""
+ results = []
+ completed_tasks = await asyncio.gather(*tasks, return_exceptions=True)
+ vertices: list[Vertex] = []
+
+ for i, result in enumerate(completed_tasks):
+ task_name = tasks[i].get_name()
+ if isinstance(result, Exception):
+ logger.error(f"Task {task_name} failed with exception: {result}")
+ # Cancel all remaining tasks
+ for t in tasks[i + 1 :]:
+ t.cancel()
+ raise result
+ if isinstance(result, VertexBuildResult):
+ vertices.append(result.vertex)
+ else:
+ msg = f"Invalid result from task {task_name}: {result}"
+ raise TypeError(msg)
+
+ for v in vertices:
+ # set all executed vertices as non-runnable to not run them again.
+ # they could be calculated as predecessor or successors of parallel vertices
+ # This could usually happen with input vertices like ChatInput
+ self.run_manager.remove_vertex_from_runnables(v.id)
+
+ logger.debug(f"Vertex {v.id}, result: {v.built_result}, object: {v.built_object}")
+
+ for v in vertices:
+ next_runnable_vertices = await self.get_next_runnable_vertices(lock, vertex=v, cache=False)
+ results.extend(next_runnable_vertices)
+ return list(set(results))
+
+ def topological_sort(self) -> list[Vertex]:
+ """Performs a topological sort of the vertices in the graph.
+
+ Returns:
+ List[Vertex]: A list of vertices in topological order.
+
+ Raises:
+ ValueError: If the graph contains a cycle.
+ """
+ # States: 0 = unvisited, 1 = visiting, 2 = visited
+ state = dict.fromkeys(self.vertices, 0)
+ sorted_vertices = []
+
+ def dfs(vertex) -> None:
+ if state[vertex] == 1:
+ # We have a cycle
+ msg = "Graph contains a cycle, cannot perform topological sort"
+ raise ValueError(msg)
+ if state[vertex] == 0:
+ state[vertex] = 1
+ for edge in vertex.edges:
+ if edge.source_id == vertex.id:
+ dfs(self.get_vertex(edge.target_id))
+ state[vertex] = 2
+ sorted_vertices.append(vertex)
+
+ # Visit each vertex
+ for vertex in self.vertices:
+ if state[vertex] == 0:
+ dfs(vertex)
+
+ return list(reversed(sorted_vertices))
+
+ def generator_build(self) -> Generator[Vertex, None, None]:
+ """Builds each vertex in the graph and yields it."""
+ sorted_vertices = self.topological_sort()
+ logger.debug("There are %s vertices in the graph", len(sorted_vertices))
+ yield from sorted_vertices
+
+ def get_predecessors(self, vertex):
+ """Returns the predecessors of a vertex."""
+ return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
+
+ def get_all_successors(self, vertex: Vertex, *, recursive=True, flat=True, visited=None):
+ if visited is None:
+ visited = set()
+
+ # Prevent revisiting vertices to avoid infinite loops in cyclic graphs
+ if vertex in visited:
+ return []
+
+ visited.add(vertex)
+
+ successors = vertex.successors
+ if not successors:
+ return []
+
+ successors_result = []
+
+ for successor in successors:
+ if recursive:
+ next_successors = self.get_all_successors(successor, recursive=recursive, flat=flat, visited=visited)
+ if flat:
+ successors_result.extend(next_successors)
+ else:
+ successors_result.append(next_successors)
+ if flat:
+ successors_result.append(successor)
+ else:
+ successors_result.append([successor])
+
+ if not flat and successors_result:
+ return [successors, *successors_result]
+
+ return successors_result
+
+ def get_successors(self, vertex: Vertex) -> list[Vertex]:
+ """Returns the successors of a vertex."""
+ return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, set())]
+
+ def get_vertex_neighbors(self, vertex: Vertex) -> dict[Vertex, int]:
+ """Returns the neighbors of a vertex."""
+ neighbors: dict[Vertex, int] = {}
+ for edge in self.edges:
+ if edge.source_id == vertex.id:
+ neighbor = self.get_vertex(edge.target_id)
+ if neighbor is None:
+ continue
+ if neighbor not in neighbors:
+ neighbors[neighbor] = 0
+ neighbors[neighbor] += 1
+ elif edge.target_id == vertex.id:
+ neighbor = self.get_vertex(edge.source_id)
+ if neighbor is None:
+ continue
+ if neighbor not in neighbors:
+ neighbors[neighbor] = 0
+ neighbors[neighbor] += 1
+ return neighbors
+
+ @property
+ def cycles(self):
+ if self._cycles is None:
+ if self._start is None:
+ self._cycles = []
+ else:
+ entry_vertex = self._start._id
+ edges = [(e["data"]["sourceHandle"]["id"], e["data"]["targetHandle"]["id"]) for e in self._edges]
+ self._cycles = find_all_cycle_edges(entry_vertex, edges)
+ return self._cycles
+
+ @property
+ def cycle_vertices(self):
+ if self._cycle_vertices is None:
+ edges = self._get_edges_as_list_of_tuples()
+ self._cycle_vertices = set(find_cycle_vertices(edges))
+ return self._cycle_vertices
+
+ def _build_edges(self) -> list[CycleEdge]:
+ """Builds the edges of the graph."""
+ # Edge takes two vertices as arguments, so we need to build the vertices first
+ # and then build the edges
+ # if we can't find a vertex, we raise an error
+ edges: set[CycleEdge | Edge] = set()
+ for edge in self._edges:
+ new_edge = self.build_edge(edge)
+ edges.add(new_edge)
+ if self.vertices and not edges:
+ logger.warning("Graph has vertices but no edges")
+ return list(cast("Iterable[CycleEdge]", edges))
+
+ def build_edge(self, edge: EdgeData) -> CycleEdge | Edge:
+ source = self.get_vertex(edge["source"])
+ target = self.get_vertex(edge["target"])
+
+ if source is None:
+ msg = f"Source vertex {edge['source']} not found"
+ raise ValueError(msg)
+ if target is None:
+ msg = f"Target vertex {edge['target']} not found"
+ raise ValueError(msg)
+ if any(v in self.cycle_vertices for v in [source.id, target.id]):
+ new_edge: CycleEdge | Edge = CycleEdge(source, target, edge)
+ else:
+ new_edge = Edge(source, target, edge)
+ return new_edge
+
+ @staticmethod
+ def _get_vertex_class(node_type: str, node_base_type: str, node_id: str) -> type[Vertex]:
+ """Returns the node class based on the node type."""
+ # First we check for the node_base_type
+ node_name = node_id.split("-")[0]
+ if node_name in InterfaceComponentTypes:
+ return InterfaceVertex
+ if node_name in {"SharedState", "Notify", "Listen"}:
+ return StateVertex
+ if node_base_type in lazy_load_vertex_dict.vertex_type_map:
+ return lazy_load_vertex_dict.vertex_type_map[node_base_type]
+ if node_name in lazy_load_vertex_dict.vertex_type_map:
+ return lazy_load_vertex_dict.vertex_type_map[node_name]
+
+ if node_type in lazy_load_vertex_dict.vertex_type_map:
+ return lazy_load_vertex_dict.vertex_type_map[node_type]
+ return Vertex
+
+ def _build_vertices(self) -> list[Vertex]:
+ """Builds the vertices of the graph."""
+ vertices: list[Vertex] = []
+ for frontend_data in self._vertices:
+ if frontend_data.get("type") == NodeTypeEnum.NoteNode:
+ continue
+ try:
+ vertex_instance = self.get_vertex(frontend_data["id"])
+ except ValueError:
+ vertex_instance = self._create_vertex(frontend_data)
+ vertices.append(vertex_instance)
+
+ return vertices
+
+ def _create_vertex(self, frontend_data: NodeData):
+ vertex_data = frontend_data["data"]
+ vertex_type: str = vertex_data["type"]
+ vertex_base_type: str = vertex_data["node"]["template"]["_type"]
+ if "id" not in vertex_data:
+ msg = f"Vertex data for {vertex_data['display_name']} does not contain an id"
+ raise ValueError(msg)
+
+ vertex_class = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
+
+ vertex_instance = vertex_class(frontend_data, graph=self)
+ vertex_instance.set_top_level(self.top_level_vertices)
+ return vertex_instance
+
+ def assert_streaming_sequence(self) -> None:
+ for i in self.edges:
+ source = self.get_vertex(i.source_id)
+ if "stream" in source.params and source.params["stream"] is True:
+ target = self.get_vertex(i.target_id)
+ if target.vertex_type != "ChatOutput":
+ msg = (
+ "Error: A 'streaming' vertex cannot be followed by a non-'chat output' vertex."
+ "Disable streaming to run the flow."
+ )
+ raise Exception(msg) # noqa: TRY002
+
+ def prepare(self, stop_component_id: str | None = None, start_component_id: str | None = None):
+ self.initialize()
+ if stop_component_id and start_component_id:
+ msg = "You can only provide one of stop_component_id or start_component_id"
+ raise ValueError(msg)
+ self.validate_stream()
+
+ if stop_component_id or start_component_id:
+ try:
+ first_layer = self.sort_vertices(stop_component_id, start_component_id)
+ except Exception: # noqa: BLE001
+ logger.exception("Error sorting vertices")
+ first_layer = self.sort_vertices()
+ else:
+ first_layer = self.sort_vertices()
+
+ for vertex_id in first_layer:
+ self.run_manager.add_to_vertices_being_run(vertex_id)
+ if vertex_id in self.cycle_vertices:
+ self.run_manager.add_to_cycle_vertices(vertex_id)
+ self._first_layer = sorted(first_layer)
+ self._run_queue = deque(self._first_layer)
+ self._prepared = True
+ self._record_snapshot()
+ return self
+
+ @staticmethod
+ def get_children_by_vertex_type(vertex: Vertex, vertex_type: str) -> list[Vertex]:
+ """Returns the children of a vertex based on the vertex type."""
+ children = []
+ vertex_types = [vertex.data["type"]]
+ if "node" in vertex.data:
+ vertex_types += vertex.data["node"]["base_classes"]
+ if vertex_type in vertex_types:
+ children.append(vertex)
+ return children
+
+ def __repr__(self) -> str:
+ vertex_ids = [vertex.id for vertex in self.vertices]
+ edges_repr = "\n".join([f" {edge.source_id} --> {edge.target_id}" for edge in self.edges])
+
+ return (
+ f"Graph Representation:\n"
+ f"----------------------\n"
+ f"Vertices ({len(vertex_ids)}):\n"
+ f" {', '.join(map(str, vertex_ids))}\n\n"
+ f"Edges ({len(self.edges)}):\n"
+ f"{edges_repr}"
+ )
+
+ def get_vertex_predecessors_ids(self, vertex_id: str) -> list[str]:
+ """Get the predecessor IDs of a vertex."""
+ return [v.id for v in self.get_predecessors(self.get_vertex(vertex_id))]
+
+ def get_vertex_successors_ids(self, vertex_id: str) -> list[str]:
+ """Get the successor IDs of a vertex."""
+ return [v.id for v in self.get_vertex(vertex_id).successors]
+
+ def get_vertex_input_status(self, vertex_id: str) -> bool:
+ """Check if a vertex is an input vertex."""
+ return self.get_vertex(vertex_id).is_input
+
+ def get_parent_map(self) -> dict[str, str | None]:
+ """Get the parent node map for all vertices."""
+ return {vertex.id: vertex.parent_node_id for vertex in self.vertices}
+
+ def get_vertex_ids(self) -> list[str]:
+ """Get all vertex IDs in the graph."""
+ return [vertex.id for vertex in self.vertices]
+
+ def sort_vertices(
+ self,
+ stop_component_id: str | None = None,
+ start_component_id: str | None = None,
+ ) -> list[str]:
+ """Sorts the vertices in the graph."""
+ self.mark_all_vertices("ACTIVE")
+
+ first_layer, remaining_layers = get_sorted_vertices(
+ vertices_ids=self.get_vertex_ids(),
+ cycle_vertices=self.cycle_vertices,
+ stop_component_id=stop_component_id,
+ start_component_id=start_component_id,
+ graph_dict=self.__to_dict(),
+ in_degree_map=self.in_degree_map,
+ successor_map=self.successor_map,
+ predecessor_map=self.predecessor_map,
+ is_input_vertex=self.get_vertex_input_status,
+ get_vertex_predecessors=self.get_vertex_predecessors_ids,
+ get_vertex_successors=self.get_vertex_successors_ids,
+ is_cyclic=self.is_cyclic,
+ )
+
+ self.increment_run_count()
+ self._sorted_vertices_layers = [first_layer, *remaining_layers]
+ self.vertices_layers = remaining_layers
+ self.vertices_to_run = set(chain.from_iterable([first_layer, *remaining_layers]))
+ self.build_run_map()
+ self._first_layer = first_layer
+ return first_layer
+
+ @staticmethod
+ def sort_interface_components_first(vertices_layers: list[list[str]]) -> list[list[str]]:
+ """Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
+
+ def contains_interface_component(vertex):
+ return any(component.value in vertex for component in InterfaceComponentTypes)
+
+ # Sort each inner list so that vertices containing ChatInput or ChatOutput come first
+ return [
+ sorted(
+ inner_list,
+ key=lambda vertex: not contains_interface_component(vertex),
+ )
+ for inner_list in vertices_layers
+ ]
+
+ def sort_by_avg_build_time(self, vertices_layers: list[list[str]]) -> list[list[str]]:
+ """Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
+
+ def sort_layer_by_avg_build_time(vertices_ids: list[str]) -> list[str]:
+ """Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
+ if len(vertices_ids) == 1:
+ return vertices_ids
+ vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
+
+ return vertices_ids
+
+ return [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
+
+ def is_vertex_runnable(self, vertex_id: str) -> bool:
+ """Returns whether a vertex is runnable."""
+ is_active = self.get_vertex(vertex_id).is_active()
+ is_loop = self.get_vertex(vertex_id).is_loop
+ return self.run_manager.is_vertex_runnable(vertex_id, is_active=is_active, is_loop=is_loop)
+
+ def build_run_map(self) -> None:
+ """Builds the run map for the graph.
+
+ This method is responsible for building the run map for the graph,
+ which maps each node in the graph to its corresponding run function.
+ """
+ self.run_manager.build_run_map(predecessor_map=self.predecessor_map, vertices_to_run=self.vertices_to_run)
+
+ def find_runnable_predecessors_for_successors(self, vertex_id: str) -> list[str]:
+ """For each successor of the current vertex, find runnable predecessors if any.
+
+ This checks the direct predecessors of each successor to identify any that are
+ immediately runnable, expanding the search to ensure progress can be made.
+ """
+ runnable_vertices = []
+ for successor_id in self.run_manager.run_map.get(vertex_id, []):
+ runnable_vertices.extend(self.find_runnable_predecessors_for_successor(successor_id))
+
+ return sorted(runnable_vertices)
+
+ def find_runnable_predecessors_for_successor(self, vertex_id: str) -> list[str]:
+ runnable_vertices = []
+ visited = set()
+
+ def find_runnable_predecessors(predecessor_id: str) -> None:
+ if predecessor_id in visited:
+ return
+ visited.add(predecessor_id)
+ predecessor_vertex = self.get_vertex(predecessor_id)
+ is_active = predecessor_vertex.is_active()
+ is_loop = predecessor_vertex.is_loop
+ if self.run_manager.is_vertex_runnable(predecessor_id, is_active=is_active, is_loop=is_loop):
+ runnable_vertices.append(predecessor_id)
+ else:
+ for pred_pred_id in self.run_manager.run_predecessors.get(predecessor_id, []):
+ find_runnable_predecessors(pred_pred_id)
+
+ for predecessor_id in self.run_manager.run_predecessors.get(vertex_id, []):
+ find_runnable_predecessors(predecessor_id)
+ return runnable_vertices
+
+ def remove_from_predecessors(self, vertex_id: str) -> None:
+ self.run_manager.remove_from_predecessors(vertex_id)
+
+ def remove_vertex_from_runnables(self, vertex_id: str) -> None:
+ self.run_manager.remove_vertex_from_runnables(vertex_id)
+
+ def get_top_level_vertices(self, vertices_ids):
+ """Retrieves the top-level vertices from the given graph based on the provided vertex IDs.
+
+ Args:
+ vertices_ids (list): A list of vertex IDs.
+
+ Returns:
+ list: A list of top-level vertex IDs.
+
+ """
+ top_level_vertices = []
+ for vertex_id in vertices_ids:
+ vertex = self.get_vertex(vertex_id)
+ if vertex.parent_is_top_level:
+ top_level_vertices.append(vertex.parent_node_id)
+ else:
+ top_level_vertices.append(vertex_id)
+ return top_level_vertices
+
+ def build_in_degree(self, edges: list[CycleEdge]) -> dict[str, int]:
+ in_degree: dict[str, int] = defaultdict(int)
+
+ for edge in edges:
+ # We don't need to count if a Component connects more than one
+ # time to the same vertex.
+ in_degree[edge.target_id] += 1
+ for vertex in self.vertices:
+ if vertex.id not in in_degree:
+ in_degree[vertex.id] = 0
+ return in_degree
+
+ @staticmethod
+ def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]:
+ """Returns the adjacency maps for the graph."""
+ predecessor_map: dict[str, list[str]] = defaultdict(list)
+ successor_map: dict[str, list[str]] = defaultdict(list)
+ for edge in edges:
+ predecessor_map[edge.target_id].append(edge.source_id)
+ successor_map[edge.source_id].append(edge.target_id)
+ return predecessor_map, successor_map
+
+ def __to_dict(self) -> dict[str, dict[str, list[str]]]:
+ """Converts the graph to a dictionary."""
+ result: dict = {}
+ for vertex in self.vertices:
+ vertex_id = vertex.id
+ sucessors = [i.id for i in self.get_all_successors(vertex)]
+ predecessors = [i.id for i in self.get_predecessors(vertex)]
+ result |= {vertex_id: {"successors": sucessors, "predecessors": predecessors}}
+ return result
diff --git a/langflow/src/backend/base/langflow/graph/graph/constants.py b/langflow/src/backend/base/langflow/graph/graph/constants.py
new file mode 100644
index 0000000..127c055
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/constants.py
@@ -0,0 +1,55 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from langflow.graph.schema import CHAT_COMPONENTS
+from langflow.utils.lazy_load import LazyLoadDictBase
+
+if TYPE_CHECKING:
+ from langflow.graph.vertex.base import Vertex
+ from langflow.graph.vertex.vertex_types import CustomComponentVertex
+
+
+class Finish:
+ def __bool__(self) -> bool:
+ return True
+
+ def __eq__(self, /, other):
+ return isinstance(other, Finish)
+
+
+def _import_vertex_types():
+ from langflow.graph.vertex import vertex_types
+
+ return vertex_types
+
+
+class VertexTypesDict(LazyLoadDictBase):
+ def __init__(self) -> None:
+ self._all_types_dict = None
+ self._types = _import_vertex_types
+
+ @property
+ def vertex_type_map(self) -> dict[str, type[Vertex]]:
+ return self.all_types_dict
+
+ def _build_dict(self):
+ langchain_types_dict = self.get_type_dict()
+ return {
+ **langchain_types_dict,
+ "Custom": ["Custom Tool", "Python Function"],
+ }
+
+ def get_type_dict(self) -> dict[str, type[Vertex]]:
+ types = self._types()
+ return {
+ "CustomComponent": types.CustomComponentVertex,
+ "Component": types.ComponentVertex,
+ **dict.fromkeys(CHAT_COMPONENTS, types.InterfaceVertex),
+ }
+
+ def get_custom_component_vertex_type(self) -> type[CustomComponentVertex]:
+ return self._types().CustomComponentVertex
+
+
+lazy_load_vertex_dict = VertexTypesDict()
diff --git a/langflow/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py b/langflow/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py
new file mode 100644
index 0000000..92edbc7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py
@@ -0,0 +1,119 @@
+from collections import defaultdict
+
+
+class RunnableVerticesManager:
+ def __init__(self) -> None:
+ self.run_map: dict[str, list[str]] = defaultdict(list) # Tracks successors of each vertex
+ self.run_predecessors: dict[str, list[str]] = defaultdict(list) # Tracks predecessors for each vertex
+ self.vertices_to_run: set[str] = set() # Set of vertices that are ready to run
+ self.vertices_being_run: set[str] = set() # Set of vertices that are currently running
+ self.cycle_vertices: set[str] = set() # Set of vertices that are in a cycle
+
+ def to_dict(self) -> dict:
+ return {
+ "run_map": self.run_map,
+ "run_predecessors": self.run_predecessors,
+ "vertices_to_run": self.vertices_to_run,
+ "vertices_being_run": self.vertices_being_run,
+ }
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "RunnableVerticesManager":
+ instance = cls()
+ instance.run_map = data["run_map"]
+ instance.run_predecessors = data["run_predecessors"]
+ instance.vertices_to_run = data["vertices_to_run"]
+ instance.vertices_being_run = data["vertices_being_run"]
+ return instance
+
+ def __getstate__(self) -> object:
+ return {
+ "run_map": self.run_map,
+ "run_predecessors": self.run_predecessors,
+ "vertices_to_run": self.vertices_to_run,
+ "vertices_being_run": self.vertices_being_run,
+ }
+
+ def __setstate__(self, state: dict) -> None:
+ self.run_map = state["run_map"]
+ self.run_predecessors = state["run_predecessors"]
+ self.vertices_to_run = state["vertices_to_run"]
+ self.vertices_being_run = state["vertices_being_run"]
+
+ def all_predecessors_are_fulfilled(self) -> bool:
+ return all(not value for value in self.run_predecessors.values())
+
+ def update_run_state(self, run_predecessors: dict, vertices_to_run: set) -> None:
+ self.run_predecessors.update(run_predecessors)
+ self.vertices_to_run.update(vertices_to_run)
+ self.build_run_map(self.run_predecessors, self.vertices_to_run)
+
+ def is_vertex_runnable(self, vertex_id: str, *, is_active: bool, is_loop: bool = False) -> bool:
+ """Determines if a vertex is runnable based on its active state and predecessor fulfillment."""
+ if not is_active:
+ return False
+ if vertex_id in self.vertices_being_run:
+ return False
+ if vertex_id not in self.vertices_to_run:
+ return False
+
+ return self.are_all_predecessors_fulfilled(vertex_id, is_loop=is_loop)
+
+ def are_all_predecessors_fulfilled(self, vertex_id: str, *, is_loop: bool) -> bool:
+ """Determines if all predecessors for a vertex have been fulfilled.
+
+ This method checks if a vertex is ready to run by verifying that either:
+ 1. It has no pending predecessors that need to complete first
+ 2. For vertices in cycles, none of its pending predecessors are also cycle vertices
+ (which would create a circular dependency)
+
+ Args:
+ vertex_id (str): The ID of the vertex to check
+ is_loop (bool): Whether the vertex is a loop
+ Returns:
+ bool: True if all predecessor conditions are met, False otherwise
+ """
+ # Get pending predecessors, return True if none exist
+ pending = self.run_predecessors.get(vertex_id, [])
+ if not pending:
+ return True
+
+ # For cycle vertices, check if any pending predecessors are also in cycle
+ # Using set intersection is faster than iteration
+ if vertex_id in self.cycle_vertices:
+ return is_loop or not bool(set(pending) & self.cycle_vertices)
+
+ return False
+
+ def remove_from_predecessors(self, vertex_id: str) -> None:
+ """Removes a vertex from the predecessor list of its successors."""
+ predecessors = self.run_map.get(vertex_id, [])
+ for predecessor in predecessors:
+ if vertex_id in self.run_predecessors[predecessor]:
+ self.run_predecessors[predecessor].remove(vertex_id)
+
+ def build_run_map(self, predecessor_map, vertices_to_run) -> None:
+ """Builds a map of vertices and their runnable successors."""
+ self.run_map = defaultdict(list)
+ for vertex_id, predecessors in predecessor_map.items():
+ for predecessor in predecessors:
+ self.run_map[predecessor].append(vertex_id)
+ self.run_predecessors = predecessor_map.copy()
+ self.vertices_to_run = vertices_to_run
+
+ def update_vertex_run_state(self, vertex_id: str, *, is_runnable: bool) -> None:
+ """Updates the runnable state of a vertex."""
+ if is_runnable:
+ self.vertices_to_run.add(vertex_id)
+ else:
+ self.vertices_being_run.discard(vertex_id)
+
+ def remove_vertex_from_runnables(self, v_id) -> None:
+ self.update_vertex_run_state(v_id, is_runnable=False)
+ self.remove_from_predecessors(v_id)
+
+ def add_to_vertices_being_run(self, v_id) -> None:
+ self.vertices_being_run.add(v_id)
+
+ def add_to_cycle_vertices(self, v_id):
+ self.cycle_vertices.add(v_id)
diff --git a/langflow/src/backend/base/langflow/graph/graph/schema.py b/langflow/src/backend/base/langflow/graph/graph/schema.py
new file mode 100644
index 0000000..4777abe
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/schema.py
@@ -0,0 +1,53 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, NamedTuple, Protocol
+
+from typing_extensions import NotRequired, TypedDict
+
+from langflow.graph.edge.schema import EdgeData
+from langflow.graph.vertex.schema import NodeData
+
+if TYPE_CHECKING:
+ from langflow.graph.schema import ResultData
+ from langflow.graph.vertex.base import Vertex
+ from langflow.schema.log import LoggableType
+
+
+class ViewPort(TypedDict):
+ x: float
+ y: float
+ zoom: float
+
+
+class GraphData(TypedDict):
+ nodes: list[NodeData]
+ edges: list[EdgeData]
+ viewport: NotRequired[ViewPort]
+
+
+class GraphDump(TypedDict, total=False):
+ data: GraphData
+ is_component: bool
+ name: str
+ description: str
+ endpoint_name: str
+
+
+class VertexBuildResult(NamedTuple):
+ result_dict: ResultData
+ params: str
+ valid: bool
+ artifacts: dict
+ vertex: Vertex
+
+
+class OutputConfigDict(TypedDict):
+ cache: bool
+
+
+class StartConfigDict(TypedDict):
+ output: OutputConfigDict
+
+
+class LogCallbackFunction(Protocol):
+ def __call__(self, event_name: str, log: LoggableType) -> None: ...
diff --git a/langflow/src/backend/base/langflow/graph/graph/state_manager.py b/langflow/src/backend/base/langflow/graph/graph/state_manager.py
new file mode 100644
index 0000000..06e2c43
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/state_manager.py
@@ -0,0 +1,38 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from loguru import logger
+
+from langflow.services.deps import get_settings_service, get_state_service
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from langflow.services.state.service import StateService
+
+
+class GraphStateManager:
+ def __init__(self) -> None:
+ try:
+ self.state_service: StateService = get_state_service()
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).debug("Error getting state service. Defaulting to InMemoryStateService")
+ from langflow.services.state.service import InMemoryStateService
+
+ self.state_service = InMemoryStateService(get_settings_service())
+
+ def append_state(self, key, new_state, run_id: str) -> None:
+ self.state_service.append_state(key, new_state, run_id)
+
+ def update_state(self, key, new_state, run_id: str) -> None:
+ self.state_service.update_state(key, new_state, run_id)
+
+ def get_state(self, key, run_id: str):
+ return self.state_service.get_state(key, run_id)
+
+ def subscribe(self, key, observer: Callable) -> None:
+ self.state_service.subscribe(key, observer)
+
+ def unsubscribe(self, key, observer: Callable) -> None:
+ self.state_service.unsubscribe(key, observer)
diff --git a/langflow/src/backend/base/langflow/graph/graph/state_model.py b/langflow/src/backend/base/langflow/graph/graph/state_model.py
new file mode 100644
index 0000000..5792fbf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/state_model.py
@@ -0,0 +1,66 @@
+import re
+
+from langflow.graph.state.model import create_state_model
+from langflow.helpers.base_model import BaseModel
+
+
+def camel_to_snake(camel_str: str) -> str:
+ return re.sub(r"(? type[BaseModel]:
+ """Create a Pydantic state model from a graph representation.
+
+ This function generates a Pydantic model that represents the state of an entire graph.
+ It creates getter methods for each vertex in the graph, allowing access to the state
+ of individual components within the graph structure.
+
+ Args:
+ graph (BaseModel): The graph object from which to create the state model.
+ This should be a Pydantic model representing the graph structure,
+ with a 'vertices' attribute containing all graph vertices.
+
+ Returns:
+ type[BaseModel]: A dynamically created Pydantic model class representing
+ the state of the entire graph. This model will have properties
+ corresponding to each vertex in the graph, with names converted from
+ the vertex IDs to snake case.
+
+ Raises:
+ ValueError: If any vertex in the graph does not have a properly initialized
+ component instance (i.e., if vertex.custom_component is None).
+
+ Notes:
+ - Each vertex in the graph must have a 'custom_component' attribute.
+ - The 'custom_component' must have a 'get_state_model_instance_getter' method.
+ - Vertex IDs are converted from camel case to snake case for the resulting model's field names.
+ - The resulting model uses the 'create_state_model' function with validation disabled.
+
+ Example:
+ >>> class Vertex(BaseModel):
+ ... id: str
+ ... custom_component: Any
+ >>> class Graph(BaseModel):
+ ... vertices: List[Vertex]
+ >>> # Assume proper setup of vertices and components
+ >>> graph = Graph(vertices=[...])
+ >>> GraphStateModel = create_state_model_from_graph(graph)
+ >>> graph_state = GraphStateModel()
+ >>> # Access component states, e.g.:
+ >>> print(graph_state.some_component_name)
+ """
+ for vertex in graph.vertices:
+ if hasattr(vertex, "custom_component") and vertex.custom_component is None:
+ msg = f"Vertex {vertex.id} does not have a component instance."
+ raise ValueError(msg)
+
+ state_model_getters = [
+ vertex.custom_component.get_state_model_instance_getter()
+ for vertex in graph.vertices
+ if hasattr(vertex, "custom_component") and hasattr(vertex.custom_component, "get_state_model_instance_getter")
+ ]
+ fields = {
+ camel_to_snake(vertex.id): state_model_getter
+ for vertex, state_model_getter in zip(graph.vertices, state_model_getters, strict=False)
+ }
+ return create_state_model(model_name="GraphStateModel", validate=False, **fields)
diff --git a/langflow/src/backend/base/langflow/graph/graph/utils.py b/langflow/src/backend/base/langflow/graph/graph/utils.py
new file mode 100644
index 0000000..d29dbc8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/graph/utils.py
@@ -0,0 +1,1024 @@
+import copy
+from collections import defaultdict, deque
+from collections.abc import Callable
+from typing import Any
+
+import networkx as nx
+
+PRIORITY_LIST_OF_INPUTS = ["webhook", "chat"]
+MAX_CYCLE_APPEARANCES = 2
+
+
+def find_start_component_id(vertices, *, is_webhook: bool = False):
+ """Finds the component ID from a list of vertices based on a priority list of input types.
+
+ Args:
+ vertices (list): A list of vertex IDs.
+ is_webhook (bool, optional): Whether the flow is being run as a webhook. Defaults to False.
+
+ Returns:
+ str or None: The component ID that matches the highest priority input type, or None if no match is found.
+ """
+ # Set priority list based on whether this is a webhook flow
+ priority_inputs = ["webhook"] if is_webhook else PRIORITY_LIST_OF_INPUTS
+
+ # Check input types in priority order
+ for input_type_str in priority_inputs:
+ component_id = next((vertex_id for vertex_id in vertices if input_type_str in vertex_id.lower()), None)
+ if component_id:
+ return component_id
+ return None
+
+
+def find_last_node(nodes, edges):
+ """This function receives a flow and returns the last node."""
+ source_ids = {edge["source"] for edge in edges}
+ for node in nodes:
+ if node["id"] not in source_ids:
+ return node
+ return None
+
+
+def add_parent_node_id(nodes, parent_node_id) -> None:
+ """This function receives a list of nodes and adds a parent_node_id to each node."""
+ for node in nodes:
+ node["parent_node_id"] = parent_node_id
+
+
+def add_frozen(nodes, frozen) -> None:
+ """This function receives a list of nodes and adds a frozen to each node."""
+ for node in nodes:
+ node["data"]["node"]["frozen"] = frozen
+
+
+def ungroup_node(group_node_data, base_flow):
+ template, flow, frozen = (
+ group_node_data["node"]["template"],
+ group_node_data["node"]["flow"],
+ group_node_data["node"].get("frozen", False),
+ )
+ parent_node_id = group_node_data["id"]
+
+ g_nodes = flow["data"]["nodes"]
+ add_parent_node_id(g_nodes, parent_node_id)
+ add_frozen(g_nodes, frozen)
+ g_edges = flow["data"]["edges"]
+
+ # Redirect edges to the correct proxy node
+ updated_edges = get_updated_edges(base_flow, g_nodes, g_edges, group_node_data["id"])
+
+ # Update template values
+ update_template(template, g_nodes)
+
+ nodes = [n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]] + g_nodes
+ edges = (
+ [e for e in base_flow["edges"] if e["target"] != group_node_data["id"] and e["source"] != group_node_data["id"]]
+ + g_edges
+ + updated_edges
+ )
+
+ base_flow["nodes"] = nodes
+ base_flow["edges"] = edges
+
+ return nodes
+
+
+def process_flow(flow_object):
+ cloned_flow = copy.deepcopy(flow_object)
+ processed_nodes = set() # To keep track of processed nodes
+
+ def process_node(node) -> None:
+ node_id = node.get("id")
+
+ # If node already processed, skip
+ if node_id in processed_nodes:
+ return
+
+ if node.get("data") and node["data"].get("node") and node["data"]["node"].get("flow"):
+ process_flow(node["data"]["node"]["flow"]["data"])
+ new_nodes = ungroup_node(node["data"], cloned_flow)
+ # Add new nodes to the queue for future processing
+ nodes_to_process.extend(new_nodes)
+
+ # Mark node as processed
+ processed_nodes.add(node_id)
+
+ nodes_to_process = deque(cloned_flow["nodes"])
+
+ while nodes_to_process:
+ node = nodes_to_process.popleft()
+ process_node(node)
+
+ return cloned_flow
+
+
+def update_template(template, g_nodes) -> None:
+ """Updates the template of a node in a graph with the given template.
+
+ Args:
+ template (dict): The new template to update the node with.
+ g_nodes (list): The list of nodes in the graph.
+ """
+ for value in template.values():
+ if not value.get("proxy"):
+ continue
+ proxy_dict = value["proxy"]
+ field, id_ = proxy_dict["field"], proxy_dict["id"]
+ node_index = next((i for i, n in enumerate(g_nodes) if n["id"] == id_), -1)
+ if node_index != -1:
+ display_name = None
+ show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
+ advanced = g_nodes[node_index]["data"]["node"]["template"][field]["advanced"]
+ if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
+ display_name = g_nodes[node_index]["data"]["node"]["template"][field]["display_name"]
+ else:
+ display_name = g_nodes[node_index]["data"]["node"]["template"][field]["name"]
+
+ g_nodes[node_index]["data"]["node"]["template"][field] = value
+ g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
+ g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] = advanced
+ g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] = display_name
+
+
+def update_target_handle(new_edge, g_nodes):
+ """Updates the target handle of a given edge if it is a proxy node.
+
+ Args:
+ new_edge (dict): The edge to update.
+ g_nodes (list): The list of nodes in the graph.
+
+ Returns:
+ dict: The updated edge.
+ """
+ target_handle = new_edge["data"]["targetHandle"]
+ if proxy := target_handle.get("proxy"):
+ proxy_id = proxy["id"]
+ for node in g_nodes:
+ if node["id"] == proxy_id:
+ set_new_target_handle(proxy_id, new_edge, target_handle, node)
+ break
+
+ return new_edge
+
+
+def set_new_target_handle(proxy_id, new_edge, target_handle, node) -> None:
+ """Sets a new target handle for a given edge.
+
+ Args:
+ proxy_id (str): The ID of the proxy.
+ new_edge (dict): The new edge to be created.
+ target_handle (dict): The target handle of the edge.
+ node (dict): The node containing the edge.
+ """
+ new_edge["target"] = proxy_id
+ type_ = target_handle.get("type")
+ if type_ is None:
+ msg = "The 'type' key must be present in target_handle."
+ raise KeyError(msg)
+
+ field = target_handle["proxy"]["field"]
+ new_target_handle = {
+ "fieldName": field,
+ "type": type_,
+ "id": proxy_id,
+ }
+
+ node_data = node["data"]["node"]
+ if node_data.get("flow"):
+ field_template_proxy = node_data["template"][field]["proxy"]
+ new_target_handle["proxy"] = {
+ "field": field_template_proxy["field"],
+ "id": field_template_proxy["id"],
+ }
+
+ if input_types := target_handle.get("inputTypes"):
+ new_target_handle["inputTypes"] = input_types
+
+ new_edge["data"]["targetHandle"] = new_target_handle
+
+
+def update_source_handle(new_edge, g_nodes, g_edges):
+ """Updates the source handle of a given edge to the last node in the flow data.
+
+ Args:
+ new_edge (dict): The edge to update.
+ g_nodes: The graph nodes.
+ g_edges: The graph edges.
+
+ Returns:
+ dict: The updated edge with the new source handle.
+ """
+ last_node = copy.deepcopy(find_last_node(g_nodes, g_edges))
+ new_edge["source"] = last_node["id"]
+ new_source_handle = new_edge["data"]["sourceHandle"]
+ new_source_handle["id"] = last_node["id"]
+ new_edge["data"]["sourceHandle"] = new_source_handle
+ return new_edge
+
+
+def get_updated_edges(base_flow, g_nodes, g_edges, group_node_id):
+ """Get updated edges.
+
+ Given a base flow, a list of graph nodes and a group node id, returns a list of updated edges.
+ An updated edge is an edge that has its target or source handle updated based on the group node id.
+
+ Args:
+ base_flow (dict): The base flow containing a list of edges.
+ g_nodes (list): A list of graph nodes.
+ g_edges (list): A list of graph edges.
+ group_node_id (str): The id of the group node.
+
+ Returns:
+ list: A list of updated edges.
+ """
+ updated_edges = []
+ for edge in base_flow["edges"]:
+ new_edge = copy.deepcopy(edge)
+ if new_edge["target"] == group_node_id:
+ new_edge = update_target_handle(new_edge, g_nodes)
+
+ if new_edge["source"] == group_node_id:
+ new_edge = update_source_handle(new_edge, g_nodes, g_edges)
+
+ if group_node_id in {edge["target"], edge["source"]}:
+ updated_edges.append(new_edge)
+ return updated_edges
+
+
+def get_successors(graph: dict[str, dict[str, list[str]]], vertex_id: str) -> list[str]:
+ successors_result = []
+ stack = [vertex_id]
+ visited = set()
+ while stack:
+ current_id = stack.pop()
+ if current_id in visited:
+ continue
+ visited.add(current_id)
+ if current_id != vertex_id:
+ successors_result.append(current_id)
+ stack.extend(graph[current_id]["successors"])
+ return successors_result
+
+
+def get_root_of_group_node(
+ graph: dict[str, dict[str, list[str]]], vertex_id: str, parent_node_map: dict[str, str | None]
+) -> str:
+ """Returns the root of a group node."""
+ if vertex_id in parent_node_map.values():
+ # Get all vertices with vertex_id as their parent node
+ child_vertices = [v_id for v_id, parent_id in parent_node_map.items() if parent_id == vertex_id]
+
+ # Now go through successors of the child vertices
+ # and get the one that none of its successors is in child_vertices
+ for child_id in child_vertices:
+ successors = get_successors(graph, child_id)
+ if not any(successor in child_vertices for successor in successors):
+ return child_id
+
+ msg = f"Vertex {vertex_id} is not a top level vertex or no root vertex found"
+ raise ValueError(msg)
+
+
+def sort_up_to_vertex(
+ graph: dict[str, dict[str, list[str]]],
+ vertex_id: str,
+ *,
+ parent_node_map: dict[str, str | None] | None = None,
+ is_start: bool = False,
+) -> list[str]:
+ """Cuts the graph up to a given vertex and sorts the resulting subgraph."""
+ try:
+ stop_or_start_vertex = graph[vertex_id]
+ except KeyError as e:
+ if parent_node_map is None:
+ msg = "Parent node map is required to find the root of a group node"
+ raise ValueError(msg) from e
+ vertex_id = get_root_of_group_node(graph=graph, vertex_id=vertex_id, parent_node_map=parent_node_map)
+ if vertex_id not in graph:
+ msg = f"Vertex {vertex_id} not found into graph"
+ raise ValueError(msg) from e
+ stop_or_start_vertex = graph[vertex_id]
+
+ visited, excluded = set(), set()
+ stack = [vertex_id]
+ stop_predecessors = set(stop_or_start_vertex["predecessors"])
+
+ while stack:
+ current_id = stack.pop()
+ if current_id in visited or current_id in excluded:
+ continue
+
+ visited.add(current_id)
+ current_vertex = graph[current_id]
+
+ stack.extend(current_vertex["predecessors"])
+
+ if current_id == vertex_id or (current_id not in stop_predecessors and is_start):
+ for successor_id in current_vertex["successors"]:
+ if is_start:
+ stack.append(successor_id)
+ else:
+ excluded.add(successor_id)
+ for succ_id in get_successors(graph, successor_id):
+ if is_start:
+ stack.append(succ_id)
+ else:
+ excluded.add(succ_id)
+
+ return list(visited)
+
+
+def has_cycle(vertex_ids: list[str], edges: list[tuple[str, str]]) -> bool:
+ """Determines whether a directed graph represented by a list of vertices and edges contains a cycle.
+
+ Args:
+ vertex_ids (list[str]): A list of vertex IDs.
+ edges (list[tuple[str, str]]): A list of tuples representing directed edges between vertices.
+
+ Returns:
+ bool: True if the graph contains a cycle, False otherwise.
+ """
+ # Build the graph as an adjacency list
+ graph = defaultdict(list)
+ for u, v in edges:
+ graph[u].append(v)
+
+ # Utility function to perform DFS
+ def dfs(v, visited, rec_stack) -> bool:
+ visited.add(v)
+ rec_stack.add(v)
+
+ for neighbor in graph[v]:
+ if neighbor not in visited:
+ if dfs(neighbor, visited, rec_stack):
+ return True
+ elif neighbor in rec_stack:
+ return True
+
+ rec_stack.remove(v)
+ return False
+
+ visited: set[str] = set()
+ rec_stack: set[str] = set()
+
+ return any(vertex not in visited and dfs(vertex, visited, rec_stack) for vertex in vertex_ids)
+
+
+def find_cycle_edge(entry_point: str, edges: list[tuple[str, str]]) -> tuple[str, str]:
+ """Find the edge that causes a cycle in a directed graph starting from a given entry point.
+
+ Args:
+ entry_point (str): The vertex ID from which to start the search.
+ edges (list[tuple[str, str]]): A list of tuples representing directed edges between vertices.
+
+ Returns:
+ tuple[str, str]: A tuple representing the edge that causes a cycle, or None if no cycle is found.
+ """
+ # Build the graph as an adjacency list
+ graph = defaultdict(list)
+ for u, v in edges:
+ graph[u].append(v)
+
+ # Utility function to perform DFS
+ def dfs(v, visited, rec_stack):
+ visited.add(v)
+ rec_stack.add(v)
+
+ for neighbor in graph[v]:
+ if neighbor not in visited:
+ result = dfs(neighbor, visited, rec_stack)
+ if result:
+ return result
+ elif neighbor in rec_stack:
+ return (v, neighbor) # This edge causes the cycle
+
+ rec_stack.remove(v)
+ return None
+
+ visited: set[str] = set()
+ rec_stack: set[str] = set()
+
+ return dfs(entry_point, visited, rec_stack)
+
+
+def find_all_cycle_edges(entry_point: str, edges: list[tuple[str, str]]) -> list[tuple[str, str]]:
+ """Find all edges that cause cycles in a directed graph starting from a given entry point.
+
+ Args:
+ entry_point (str): The vertex ID from which to start the search.
+ edges (list[tuple[str, str]]): A list of tuples representing directed edges between vertices.
+
+ Returns:
+ list[tuple[str, str]]: A list of tuples representing edges that cause cycles.
+ """
+ # Build the graph as an adjacency list
+ graph = defaultdict(list)
+ for u, v in edges:
+ graph[u].append(v)
+
+ # Utility function to perform DFS
+ def dfs(v, visited, rec_stack, cycle_edges):
+ visited.add(v)
+ rec_stack.add(v)
+
+ for neighbor in graph[v]:
+ if neighbor not in visited:
+ dfs(neighbor, visited, rec_stack, cycle_edges)
+ elif neighbor in rec_stack:
+ cycle_edges.append((v, neighbor)) # This edge causes a cycle
+
+ rec_stack.remove(v)
+
+ visited: set[str] = set()
+ rec_stack: set[str] = set()
+ cycle_edges: list[tuple[str, str]] = []
+
+ dfs(entry_point, visited, rec_stack, cycle_edges)
+
+ return cycle_edges
+
+
+def should_continue(yielded_counts: dict[str, int], max_iterations: int | None) -> bool:
+ if max_iterations is None:
+ return True
+ return max(yielded_counts.values(), default=0) <= max_iterations
+
+
+def find_cycle_vertices(edges):
+ graph = nx.DiGraph(edges)
+
+ # Initialize a set to collect vertices part of any cycle
+ cycle_vertices = set()
+
+ # Utilize the strong component feature in NetworkX to find cycles
+ for component in nx.strongly_connected_components(graph):
+ if len(component) > 1 or graph.has_edge(tuple(component)[0], tuple(component)[0]): # noqa: RUF015
+ cycle_vertices.update(component)
+
+ return sorted(cycle_vertices)
+
+
+def layered_topological_sort(
+ vertices_ids: set[str],
+ in_degree_map: dict[str, int],
+ successor_map: dict[str, list[str]],
+ predecessor_map: dict[str, list[str]],
+ start_id: str | None = None,
+ cycle_vertices: set[str] | None = None,
+ is_input_vertex: Callable[[str], bool] | None = None, # noqa: ARG001
+ *,
+ is_cyclic: bool = False,
+) -> list[list[str]]:
+ """Performs a layered topological sort of the vertices in the graph.
+
+ Args:
+ vertices_ids: Set of vertex IDs to sort
+ in_degree_map: Map of vertex IDs to their in-degree
+ successor_map: Map of vertex IDs to their successors
+ predecessor_map: Map of vertex IDs to their predecessors
+ is_cyclic: Whether the graph is cyclic
+ start_id: ID of the start vertex (if any)
+ cycle_vertices: Set of vertices that form a cycle
+ is_input_vertex: Function to check if a vertex is an input vertex
+
+ Returns:
+ List of layers, where each layer is a list of vertex IDs
+ """
+ # Queue for vertices with no incoming edges
+ cycle_vertices = cycle_vertices or set()
+ in_degree_map = in_degree_map.copy()
+
+ if is_cyclic and all(in_degree_map.values()):
+ # This means we have a cycle because all vertex have in_degree_map > 0
+ # because of this we set the queue to start on the start_id if it exists
+ if start_id is not None:
+ queue = deque([start_id])
+ # Reset in_degree for start_id to allow cycle traversal
+ in_degree_map[start_id] = 0
+ else:
+ # Find the chat input component
+ chat_input = find_start_component_id(vertices_ids)
+ if chat_input is None:
+ # If no input component is found, start with any vertex
+ queue = deque([next(iter(vertices_ids))])
+ in_degree_map[next(iter(vertices_ids))] = 0
+ else:
+ queue = deque([chat_input])
+ # Reset in_degree for chat_input to allow cycle traversal
+ in_degree_map[chat_input] = 0
+ else:
+ # Start with vertices that have no incoming edges or are input vertices
+ queue = deque(
+ vertex_id
+ for vertex_id in vertices_ids
+ if in_degree_map[vertex_id] == 0
+ # We checked if it is input but that caused the TextInput to be at the start
+ # or (is_input_vertex and is_input_vertex(vertex_id))
+ )
+
+ layers: list[list[str]] = []
+ visited = set()
+ cycle_counts = dict.fromkeys(vertices_ids, 0)
+ current_layer = 0
+
+ # Process the first layer separately to avoid duplicates
+ if queue:
+ layers.append([]) # Start the first layer
+ first_layer_vertices = set()
+ layer_size = len(queue)
+ for _ in range(layer_size):
+ vertex_id = queue.popleft()
+ if vertex_id not in first_layer_vertices:
+ first_layer_vertices.add(vertex_id)
+ visited.add(vertex_id)
+ cycle_counts[vertex_id] += 1
+ layers[current_layer].append(vertex_id)
+
+ for neighbor in successor_map[vertex_id]:
+ # only vertices in `vertices_ids` should be considered
+ # because vertices by have been filtered out
+ # in a previous step. All dependencies of theirs
+ # will be built automatically if required
+ if neighbor not in vertices_ids:
+ continue
+
+ in_degree_map[neighbor] -= 1 # 'remove' edge
+ if in_degree_map[neighbor] == 0:
+ queue.append(neighbor)
+
+ # if > 0 it might mean not all predecessors have added to the queue
+ # so we should process the neighbors predecessors
+ elif in_degree_map[neighbor] > 0:
+ for predecessor in predecessor_map[neighbor]:
+ if (
+ predecessor not in queue
+ and predecessor not in first_layer_vertices
+ and (in_degree_map[predecessor] == 0 or predecessor in cycle_vertices)
+ ):
+ queue.append(predecessor)
+
+ current_layer += 1 # Next layer
+
+ # Process remaining layers normally, allowing cycle vertices to appear multiple times
+ while queue:
+ layers.append([]) # Start a new layer
+ layer_size = len(queue)
+ for _ in range(layer_size):
+ vertex_id = queue.popleft()
+ if vertex_id not in visited or (is_cyclic and cycle_counts[vertex_id] < MAX_CYCLE_APPEARANCES):
+ if vertex_id not in visited:
+ visited.add(vertex_id)
+ cycle_counts[vertex_id] += 1
+ layers[current_layer].append(vertex_id)
+
+ for neighbor in successor_map[vertex_id]:
+ # only vertices in `vertices_ids` should be considered
+ # because vertices by have been filtered out
+ # in a previous step. All dependencies of theirs
+ # will be built automatically if required
+ if neighbor not in vertices_ids:
+ continue
+
+ in_degree_map[neighbor] -= 1 # 'remove' edge
+ if in_degree_map[neighbor] == 0 and neighbor not in visited:
+ queue.append(neighbor)
+ # # If this is a cycle vertex, reset its in_degree to allow it to appear again
+ # if neighbor in cycle_vertices and neighbor in visited:
+ # in_degree_map[neighbor] = len(predecessor_map[neighbor])
+
+ # if > 0 it might mean not all predecessors have added to the queue
+ # so we should process the neighbors predecessors
+ elif in_degree_map[neighbor] > 0:
+ for predecessor in predecessor_map[neighbor]:
+ if predecessor not in queue and (
+ predecessor not in visited
+ or (is_cyclic and cycle_counts[predecessor] < MAX_CYCLE_APPEARANCES)
+ ):
+ queue.append(predecessor)
+
+ current_layer += 1 # Next layer
+
+ # Remove empty layers
+ return [layer for layer in layers if layer]
+
+
+def refine_layers(
+ initial_layers: list[list[str]],
+ successor_map: dict[str, list[str]],
+) -> list[list[str]]:
+ """Refines the layers of vertices to ensure proper dependency ordering.
+
+ Args:
+ initial_layers: Initial layers of vertices
+ successor_map: Map of vertex IDs to their successors
+
+ Returns:
+ Refined layers with proper dependency ordering
+ """
+ # Map each vertex to its current layer
+ vertex_to_layer: dict[str, int] = {}
+ for layer_index, layer in enumerate(initial_layers):
+ for vertex in layer:
+ vertex_to_layer[vertex] = layer_index
+
+ refined_layers: list[list[str]] = [[] for _ in initial_layers] # Start with empty layers
+ new_layer_index_map = defaultdict(int)
+
+ # Map each vertex to its new layer index
+ # by finding the lowest layer index of its dependencies
+ # and subtracting 1
+ # If a vertex has no dependencies, it will be placed in the first layer
+ # If a vertex has dependencies, it will be placed in the lowest layer index of its dependencies
+ # minus 1
+ for vertex_id, deps in successor_map.items():
+ indexes = [vertex_to_layer[dep] for dep in deps if dep in vertex_to_layer]
+ new_layer_index = max(min(indexes, default=0) - 1, 0)
+ new_layer_index_map[vertex_id] = new_layer_index
+
+ for layer_index, layer in enumerate(initial_layers):
+ for vertex_id in layer:
+ # Place the vertex in the highest possible layer where its dependencies are met
+ new_layer_index = new_layer_index_map[vertex_id]
+ if new_layer_index > layer_index:
+ refined_layers[new_layer_index].append(vertex_id)
+ vertex_to_layer[vertex_id] = new_layer_index
+ else:
+ refined_layers[layer_index].append(vertex_id)
+
+ # Remove empty layers if any
+ return [layer for layer in refined_layers if layer]
+
+
+def _max_dependency_index(
+ vertex_id: str,
+ index_map: dict[str, int],
+ get_vertex_successors: Callable[[str], list[str]],
+) -> int:
+ """Finds the highest index a given vertex's dependencies occupy in the same layer.
+
+ Args:
+ vertex_id: ID of the vertex to check
+ index_map: Map of vertex IDs to their indices in the layer
+ get_vertex_successors: Function to get the successor IDs of a vertex
+
+ Returns:
+ The highest index of the vertex's dependencies
+ """
+ max_index = -1
+ for successor_id in get_vertex_successors(vertex_id):
+ successor_index = index_map.get(successor_id, -1)
+ max_index = max(successor_index, max_index)
+ return max_index
+
+
+def _sort_single_layer_by_dependency(
+ layer: list[str],
+ get_vertex_successors: Callable[[str], list[str]],
+) -> list[str]:
+ """Sorts a single layer by dependency using a stable sorting method.
+
+ Args:
+ layer: List of vertex IDs in the layer
+ get_vertex_successors: Function to get the successor IDs of a vertex
+
+ Returns:
+ Sorted list of vertex IDs
+ """
+ # Build a map of each vertex to its index in the layer for quick lookup.
+ index_map = {vertex: index for index, vertex in enumerate(layer)}
+ dependency_cache: dict[str, int] = {}
+
+ def max_dependency_index(vertex: str) -> int:
+ if vertex in dependency_cache:
+ return dependency_cache[vertex]
+ max_index = index_map[vertex]
+ for successor in get_vertex_successors(vertex):
+ if successor in index_map:
+ max_index = max(max_index, max_dependency_index(successor))
+
+ dependency_cache[vertex] = max_index
+ return max_index
+
+ return sorted(layer, key=max_dependency_index, reverse=True)
+
+
+def sort_layer_by_dependency(
+ vertices_layers: list[list[str]],
+ get_vertex_successors: Callable[[str], list[str]],
+) -> list[list[str]]:
+ """Sorts the vertices in each layer by dependency, ensuring no vertex depends on a subsequent vertex.
+
+ Args:
+ vertices_layers: List of layers, where each layer is a list of vertex IDs
+ get_vertex_successors: Function to get the successor IDs of a vertex
+
+ Returns:
+ Sorted layers
+ """
+ return [_sort_single_layer_by_dependency(layer, get_vertex_successors) for layer in vertices_layers]
+
+
+def sort_chat_inputs_first(
+ vertices_layers: list[list[str]],
+ get_vertex_predecessors: Callable[[str], list[str]],
+) -> list[list[str]]:
+ """Sorts the vertices so that chat inputs come first in the layers.
+
+ Only one chat input is allowed in the entire graph.
+
+ Args:
+ vertices_layers: List of layers, where each layer is a list of vertex IDs
+ get_vertex_predecessors: Function to get the predecessor IDs of a vertex
+
+ Returns:
+ Sorted layers with single chat input first
+
+ Raises:
+ ValueError: If there are multiple chat inputs in the graph
+ """
+ chat_input = None
+ chat_input_layer_idx = None
+
+ # Find chat input and validate only one exists
+ for layer_idx, layer in enumerate(vertices_layers):
+ for vertex_id in layer:
+ if "ChatInput" in vertex_id and get_vertex_predecessors(vertex_id):
+ return vertices_layers
+ if "ChatInput" in vertex_id:
+ if chat_input is not None:
+ msg = "Only one chat input is allowed in the graph"
+ raise ValueError(msg)
+ chat_input = vertex_id
+ chat_input_layer_idx = layer_idx
+
+ if not chat_input:
+ return vertices_layers
+ # If chat input already in first layer, just move it to index 0
+ if chat_input_layer_idx == 0:
+ # If chat input is alone in first layer, keep as-is
+ if len(vertices_layers[0]) == 1:
+ return vertices_layers
+
+ # Otherwise move chat input to its own layer at the start
+ vertices_layers[0].remove(chat_input)
+ return [[chat_input], *vertices_layers]
+
+ # Otherwise create new layers with chat input first
+ result_layers = []
+ for layer in vertices_layers:
+ layer_vertices = [v for v in layer if v != chat_input]
+ if layer_vertices:
+ result_layers.append(layer_vertices)
+
+ return [[chat_input], *result_layers]
+
+
+def get_sorted_vertices(
+ vertices_ids: list[str],
+ cycle_vertices: set[str],
+ stop_component_id: str | None = None,
+ start_component_id: str | None = None,
+ graph_dict: dict[str, Any] | None = None,
+ in_degree_map: dict[str, int] | None = None,
+ successor_map: dict[str, list[str]] | None = None,
+ predecessor_map: dict[str, list[str]] | None = None,
+ is_input_vertex: Callable[[str], bool] | None = None,
+ get_vertex_predecessors: Callable[[str], list[str]] | None = None,
+ get_vertex_successors: Callable[[str], list[str]] | None = None,
+ *,
+ is_cyclic: bool = False,
+) -> tuple[list[str], list[list[str]]]:
+ """Get sorted vertices in a graph.
+
+ Args:
+ vertices_ids: List of vertex IDs to sort
+ cycle_vertices: Set of vertices that form a cycle
+ stop_component_id: ID of the stop component (if any)
+ start_component_id: ID of the start component (if any)
+ graph_dict: Dictionary containing graph information
+ in_degree_map: Map of vertex IDs to their in-degree
+ successor_map: Map of vertex IDs to their successors
+ predecessor_map: Map of vertex IDs to their predecessors
+ is_input_vertex: Function to check if a vertex is an input vertex
+ get_vertex_predecessors: Function to get predecessors of a vertex
+ get_vertex_successors: Function to get successors of a vertex
+ is_cyclic: Whether the graph is cyclic
+
+ Returns:
+ Tuple of (first layer vertices, remaining layer vertices)
+ """
+ # Handle cycles by converting stop to start
+ if stop_component_id in cycle_vertices:
+ start_component_id = stop_component_id
+ stop_component_id = None
+
+ # Build in_degree_map if not provided
+ if in_degree_map is None:
+ in_degree_map = {}
+ for vertex_id in vertices_ids:
+ if get_vertex_predecessors is not None:
+ in_degree_map[vertex_id] = len(get_vertex_predecessors(vertex_id))
+ else:
+ in_degree_map[vertex_id] = 0
+
+ # Build successor_map if not provided
+ if successor_map is None:
+ successor_map = {}
+ for vertex_id in vertices_ids:
+ if get_vertex_successors is not None:
+ successor_map[vertex_id] = get_vertex_successors(vertex_id)
+ else:
+ successor_map[vertex_id] = []
+
+ # Build predecessor_map if not provided
+ if predecessor_map is None:
+ predecessor_map = {}
+ for vertex_id in vertices_ids:
+ if get_vertex_predecessors is not None:
+ predecessor_map[vertex_id] = get_vertex_predecessors(vertex_id)
+ else:
+ predecessor_map[vertex_id] = []
+
+ # If we have a stop component, we need to filter out all vertices
+ # that are not predecessors of the stop component
+ if stop_component_id is not None:
+ filtered_vertices = filter_vertices_up_to_vertex(
+ vertices_ids,
+ stop_component_id,
+ get_vertex_predecessors=get_vertex_predecessors,
+ get_vertex_successors=get_vertex_successors,
+ graph_dict=graph_dict,
+ )
+ vertices_ids = list(filtered_vertices)
+
+ # If we have a start component, we need to filter out unconnected vertices
+ # but keep vertices that are connected to the graph even if not reachable from start
+ if start_component_id is not None:
+ # First get all vertices reachable from start
+ reachable_vertices = filter_vertices_from_vertex(
+ vertices_ids,
+ start_component_id,
+ get_vertex_predecessors=get_vertex_predecessors,
+ get_vertex_successors=get_vertex_successors,
+ graph_dict=graph_dict,
+ )
+ # Then get all vertices that can reach any reachable vertex
+ connected_vertices = set()
+ for vertex in reachable_vertices:
+ connected_vertices.update(
+ filter_vertices_up_to_vertex(
+ vertices_ids,
+ vertex,
+ get_vertex_predecessors=get_vertex_predecessors,
+ get_vertex_successors=get_vertex_successors,
+ graph_dict=graph_dict,
+ )
+ )
+ vertices_ids = list(connected_vertices)
+
+ # Get the layers
+ layers = layered_topological_sort(
+ vertices_ids=set(vertices_ids),
+ in_degree_map=in_degree_map,
+ successor_map=successor_map,
+ predecessor_map=predecessor_map,
+ start_id=start_component_id,
+ is_input_vertex=is_input_vertex,
+ cycle_vertices=cycle_vertices,
+ is_cyclic=is_cyclic,
+ )
+
+ # Split into first layer and remaining layers
+ if not layers:
+ return [], []
+
+ first_layer = layers[0]
+ remaining_layers = layers[1:]
+
+ # If we have a stop component, we need to filter out all vertices
+ # that are not predecessors of the stop component
+ if stop_component_id is not None and remaining_layers and stop_component_id not in remaining_layers[-1]:
+ remaining_layers[-1].append(stop_component_id)
+
+ # Sort chat inputs first and sort each layer by dependencies
+ all_layers = [first_layer, *remaining_layers]
+ if get_vertex_predecessors is not None and start_component_id is None:
+ all_layers = sort_chat_inputs_first(all_layers, get_vertex_predecessors)
+ if get_vertex_successors is not None:
+ all_layers = sort_layer_by_dependency(all_layers, get_vertex_successors)
+
+ if not all_layers:
+ return [], []
+
+ return all_layers[0], all_layers[1:]
+
+
+def filter_vertices_up_to_vertex(
+ vertices_ids: list[str],
+ vertex_id: str,
+ get_vertex_predecessors: Callable[[str], list[str]] | None = None,
+ get_vertex_successors: Callable[[str], list[str]] | None = None,
+ graph_dict: dict[str, Any] | None = None,
+) -> set[str]:
+ """Filter vertices up to a given vertex.
+
+ Args:
+ vertices_ids: List of vertex IDs to filter
+ vertex_id: ID of the vertex to filter up to
+ get_vertex_predecessors: Function to get predecessors of a vertex
+ get_vertex_successors: Function to get successors of a vertex
+ graph_dict: Dictionary containing graph information
+ parent_node_map: Map of vertex IDs to their parent node IDs
+
+ Returns:
+ Set of vertex IDs that are predecessors of the given vertex
+ """
+ vertices_set = set(vertices_ids)
+ if vertex_id not in vertices_set:
+ return set()
+
+ # Build predecessor map if not provided
+ if get_vertex_predecessors is None:
+ if graph_dict is None:
+ msg = "Either get_vertex_predecessors or graph_dict must be provided"
+ raise ValueError(msg)
+
+ def get_vertex_predecessors(v):
+ return graph_dict[v]["predecessors"]
+
+ # Build successor map if not provided
+ if get_vertex_successors is None:
+ if graph_dict is None:
+ return set()
+
+ def get_vertex_successors(v):
+ return graph_dict[v]["successors"]
+
+ # Start with the target vertex
+ filtered_vertices = {vertex_id}
+ queue = deque([vertex_id])
+
+ # Process vertices in breadth-first order
+ while queue:
+ current_vertex = queue.popleft()
+ for predecessor in get_vertex_predecessors(current_vertex):
+ if predecessor in vertices_set and predecessor not in filtered_vertices:
+ filtered_vertices.add(predecessor)
+ queue.append(predecessor)
+
+ return filtered_vertices
+
+
+def filter_vertices_from_vertex(
+ vertices_ids: list[str],
+ vertex_id: str,
+ get_vertex_predecessors: Callable[[str], list[str]] | None = None,
+ get_vertex_successors: Callable[[str], list[str]] | None = None,
+ graph_dict: dict[str, Any] | None = None,
+) -> set[str]:
+ """Filter vertices starting from a given vertex.
+
+ Args:
+ vertices_ids: List of vertex IDs to filter
+ vertex_id: ID of the vertex to start filtering from
+ get_vertex_predecessors: Function to get predecessors of a vertex
+ get_vertex_successors: Function to get successors of a vertex
+ graph_dict: Dictionary containing graph information
+
+ Returns:
+ Set of vertex IDs that are successors of the given vertex
+ """
+ vertices_set = set(vertices_ids)
+ if vertex_id not in vertices_set:
+ return set()
+
+ # Build predecessor map if not provided
+ if get_vertex_predecessors is None:
+ if graph_dict is None:
+ msg = "Either get_vertex_predecessors or graph_dict must be provided"
+ raise ValueError(msg)
+
+ def get_vertex_predecessors(v):
+ return graph_dict[v]["predecessors"]
+
+ # Build successor map if not provided
+ if get_vertex_successors is None:
+ if graph_dict is None:
+ return set()
+
+ def get_vertex_successors(v):
+ return graph_dict[v]["successors"]
+
+ # Start with the target vertex
+ filtered_vertices = {vertex_id}
+ queue = deque([vertex_id])
+
+ # Process vertices in breadth-first order
+ while queue:
+ current_vertex = queue.popleft()
+ for successor in get_vertex_successors(current_vertex):
+ if successor in vertices_set and successor not in filtered_vertices:
+ filtered_vertices.add(successor)
+ queue.append(successor)
+
+ return filtered_vertices
diff --git a/langflow/src/backend/base/langflow/graph/schema.py b/langflow/src/backend/base/langflow/graph/schema.py
new file mode 100644
index 0000000..407b306
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/schema.py
@@ -0,0 +1,75 @@
+from enum import Enum
+from typing import Any
+
+from pydantic import BaseModel, Field, field_serializer, model_validator
+
+from langflow.schema.schema import OutputValue, StreamURL
+from langflow.serialization import serialize
+from langflow.utils.schemas import ChatOutputResponse, ContainsEnumMeta
+
+
+class ResultData(BaseModel):
+ results: Any | None = Field(default_factory=dict)
+ artifacts: Any | None = Field(default_factory=dict)
+ outputs: dict | None = Field(default_factory=dict)
+ logs: dict | None = Field(default_factory=dict)
+ messages: list[ChatOutputResponse] | None = Field(default_factory=list)
+ timedelta: float | None = None
+ duration: str | None = None
+ component_display_name: str | None = None
+ component_id: str | None = None
+ used_frozen_result: bool | None = False
+
+ @field_serializer("results")
+ def serialize_results(self, value):
+ if isinstance(value, dict):
+ return {key: serialize(val) for key, val in value.items()}
+ return serialize(value)
+
+ @model_validator(mode="before")
+ @classmethod
+ def validate_model(cls, values):
+ if not values.get("outputs") and values.get("artifacts"):
+ # Build the log from the artifacts
+
+ for key in values["artifacts"]:
+ message = values["artifacts"][key]
+
+ # ! Temporary fix
+ if message is None:
+ continue
+
+ if "stream_url" in message and "type" in message:
+ stream_url = StreamURL(location=message["stream_url"])
+ values["outputs"].update({key: OutputValue(message=stream_url, type=message["type"])})
+ elif "type" in message:
+ values["outputs"].update({key: OutputValue(message=message, type=message["type"])})
+ return values
+
+
+class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
+ ChatInput = "ChatInput"
+ ChatOutput = "ChatOutput"
+ TextInput = "TextInput"
+ TextOutput = "TextOutput"
+ DataOutput = "DataOutput"
+ WebhookInput = "Webhook"
+
+
+CHAT_COMPONENTS = [InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatOutput]
+RECORDS_COMPONENTS = [InterfaceComponentTypes.DataOutput]
+INPUT_COMPONENTS = [
+ InterfaceComponentTypes.ChatInput,
+ InterfaceComponentTypes.WebhookInput,
+ InterfaceComponentTypes.TextInput,
+]
+OUTPUT_COMPONENTS = [
+ InterfaceComponentTypes.ChatOutput,
+ InterfaceComponentTypes.DataOutput,
+ InterfaceComponentTypes.TextOutput,
+]
+
+
+class RunOutputs(BaseModel):
+ inputs: dict = Field(default_factory=dict)
+ outputs: list[ResultData | None] = Field(default_factory=list)
diff --git a/langflow/src/backend/base/langflow/graph/state/__init__.py b/langflow/src/backend/base/langflow/graph/state/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/state/model.py b/langflow/src/backend/base/langflow/graph/state/model.py
new file mode 100644
index 0000000..b56e886
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/state/model.py
@@ -0,0 +1,237 @@
+from collections.abc import Callable
+from typing import Any, get_type_hints
+
+from pydantic import ConfigDict, computed_field, create_model
+from pydantic.fields import FieldInfo
+
+
+def __validate_method(method: Callable) -> None:
+ """Validates a method by checking if it has the required attributes.
+
+ This function ensures that the given method belongs to a class with the necessary
+ structure for output handling. It checks for the presence of a __self__ attribute
+ on the method and a get_output_by_method attribute on the method's class.
+
+ Args:
+ method (Callable): The method to be validated.
+
+ Raises:
+ ValueError: If the method does not have a __self__ attribute or if the method's
+ class does not have a get_output_by_method attribute.
+
+ Example:
+ >>> class ValidClass:
+ ... def get_output_by_method(self):
+ ... pass
+ ... def valid_method(self):
+ ... pass
+ >>> __validate_method(ValidClass().valid_method) # This will pass
+ >>> __validate_method(lambda x: x) # This will raise a ValueError
+ """
+ if not hasattr(method, "__self__"):
+ msg = f"Method {method} does not have a __self__ attribute."
+ raise ValueError(msg)
+ if not hasattr(method.__self__, "get_output_by_method"):
+ msg = f"Method's class {method.__self__} must have a get_output_by_method attribute."
+ raise ValueError(msg)
+
+
+def build_output_getter(method: Callable, *, validate: bool = True) -> Callable:
+ """Builds an output getter function for a given method in a graph component.
+
+ This function creates a new callable that, when invoked, retrieves the output
+ of the specified method using the get_output_by_method of the method's class.
+ It's used in creating dynamic state models for graph components.
+
+ Args:
+ method (Callable): The method for which to build the output getter.
+ validate (bool, optional): Whether to validate the method before building
+ the getter. Defaults to True.
+
+ Returns:
+ Callable: The output getter function. When called, this function returns
+ the value of the output associated with the original method.
+
+ Raises:
+ ValueError: If the method has no return type annotation or if validation fails.
+
+ Notes:
+ - The getter function returns UNDEFINED if the output has not been set.
+ - When validate is True, the method must belong to a class with a
+ 'get_output_by_method' attribute.
+ - This function is typically used internally by create_state_model.
+
+ Example:
+ >>> class ChatComponent:
+ ... def get_output_by_method(self, method):
+ ... return type('Output', (), {'value': "Hello, World!"})()
+ ... def get_message(self) -> str:
+ ... pass
+ >>> component = ChatComponent()
+ >>> getter = build_output_getter(component.get_message)
+ >>> print(getter(None)) # This will print "Hello, World!"
+ """
+
+ def output_getter(_):
+ if validate:
+ __validate_method(method)
+ methods_class = method.__self__
+ output = methods_class.get_output_by_method(method)
+ return output.value
+
+ return_type = get_type_hints(method).get("return", None)
+
+ if return_type is None:
+ msg = f"Method {method.__name__} has no return type annotation."
+ raise ValueError(msg)
+ output_getter.__annotations__["return"] = return_type
+ return output_getter
+
+
+def build_output_setter(method: Callable, *, validate: bool = True) -> Callable:
+ """Build an output setter function for a given method in a graph component.
+
+ This function creates a new callable that, when invoked, sets the output
+ of the specified method using the get_output_by_method of the method's class.
+ It's used in creating dynamic state models for graph components, allowing
+ for the modification of component states.
+
+ Args:
+ method (Callable): The method for which the output setter is being built.
+ validate (bool, optional): Flag indicating whether to validate the method
+ before building the setter. Defaults to True.
+
+ Returns:
+ Callable: The output setter function. When called with a value, this function
+ sets the output associated with the original method to that value.
+
+ Raises:
+ ValueError: If validation fails when validate is True.
+
+ Notes:
+ - When validate is True, the method must belong to a class with a
+ 'get_output_by_method' attribute.
+ - This function is typically used internally by create_state_model.
+ - The setter allows for dynamic updating of component states in a graph.
+
+ Example:
+ >>> class ChatComponent:
+ ... def get_output_by_method(self, method):
+ ... return type('Output', (), {'value': None})()
+ ... def set_message(self):
+ ... pass
+ >>> component = ChatComponent()
+ >>> setter = build_output_setter(component.set_message)
+ >>> setter(component, "New message")
+ >>> print(component.get_output_by_method(component.set_message).value) # Prints "New message"
+ """
+
+ def output_setter(self, value) -> None: # noqa: ARG001
+ if validate:
+ __validate_method(method)
+ methods_class = method.__self__ # type: ignore[attr-defined]
+ output = methods_class.get_output_by_method(method)
+ output.value = value
+
+ return output_setter
+
+
+def create_state_model(model_name: str = "State", *, validate: bool = True, **kwargs) -> type:
+ """Create a dynamic Pydantic state model based on the provided keyword arguments.
+
+ This function generates a Pydantic model class with fields corresponding to the
+ provided keyword arguments. It can handle various types of field definitions,
+ including callable methods (which are converted to properties), FieldInfo objects,
+ and type-default value tuples.
+
+ Args:
+ model_name (str, optional): The name of the model. Defaults to "State".
+ validate (bool, optional): Whether to validate the methods when converting
+ them to properties. Defaults to True.
+ **kwargs: Keyword arguments representing the fields of the model. Each argument
+ can be a callable method, a FieldInfo object, or a tuple of (type, default).
+
+ Returns:
+ type: The dynamically created Pydantic state model class.
+
+ Raises:
+ ValueError: If the provided field value is invalid or cannot be processed.
+
+ Examples:
+ >>> from langflow.components.inputs import ChatInput
+ >>> from langflow.components.outputs.ChatOutput import ChatOutput
+ >>> from pydantic import Field
+ >>>
+ >>> chat_input = ChatInput()
+ >>> chat_output = ChatOutput()
+ >>>
+ >>> # Create a model with a method from a component
+ >>> StateModel = create_state_model(method_one=chat_input.message_response)
+ >>> state = StateModel()
+ >>> assert state.method_one is UNDEFINED
+ >>> chat_input.set_output_value("message", "test")
+ >>> assert state.method_one == "test"
+ >>>
+ >>> # Create a model with multiple components and a Pydantic Field
+ >>> NewStateModel = create_state_model(
+ ... model_name="NewStateModel",
+ ... first_method=chat_input.message_response,
+ ... second_method=chat_output.message_response,
+ ... my_attribute=Field(None)
+ ... )
+ >>> new_state = NewStateModel()
+ >>> new_state.first_method = "test"
+ >>> new_state.my_attribute = 123
+ >>> assert new_state.first_method == "test"
+ >>> assert new_state.my_attribute == 123
+ >>>
+ >>> # Create a model with tuple-based field definitions
+ >>> TupleStateModel = create_state_model(field_one=(str, "default"), field_two=(int, 123))
+ >>> tuple_state = TupleStateModel()
+ >>> assert tuple_state.field_one == "default"
+ >>> assert tuple_state.field_two == 123
+
+ Notes:
+ - The function handles empty keyword arguments gracefully.
+ - For tuple-based field definitions, the first element must be a valid Python type.
+ - Unsupported value types in keyword arguments will raise a ValueError.
+ - Callable methods must have proper return type annotations and belong to a class
+ with a 'get_output_by_method' attribute when validate is True.
+ """
+ fields = {}
+
+ for name, value in kwargs.items():
+ # Extract the return type from the method's type annotations
+ if callable(value):
+ # Define the field with the return type
+ try:
+ __validate_method(value)
+ getter = build_output_getter(value, validate=validate)
+ setter = build_output_setter(value, validate=validate)
+ property_method = property(getter, setter)
+ except ValueError as e:
+ # If the method is not valid,assume it is already a getter
+ if ("get_output_by_method" not in str(e) and "__self__" not in str(e)) or validate:
+ raise
+ property_method = value
+ fields[name] = computed_field(property_method)
+ elif isinstance(value, FieldInfo):
+ field_tuple = (value.annotation or Any, value)
+ fields[name] = field_tuple
+ elif isinstance(value, tuple) and len(value) == 2: # noqa: PLR2004
+ # Fields are defined by one of the following tuple forms:
+
+ # (, )
+ # (, Field(...))
+ # typing.Annotated[, Field(...)]
+ if not isinstance(value[0], type):
+ msg = f"Invalid type for field {name}: {type(value[0])}"
+ raise TypeError(msg)
+ fields[name] = (value[0], value[1])
+ else:
+ msg = f"Invalid value type {type(value)} for field {name}"
+ raise ValueError(msg)
+
+ # Create the model dynamically
+ config_dict = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True)
+ return create_model(model_name, __config__=config_dict, **fields)
diff --git a/langflow/src/backend/base/langflow/graph/utils.py b/langflow/src/backend/base/langflow/graph/utils.py
new file mode 100644
index 0000000..47ef56d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/utils.py
@@ -0,0 +1,199 @@
+from __future__ import annotations
+
+import json
+from collections.abc import Generator
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+from uuid import UUID
+
+from loguru import logger
+
+from langflow.interface.utils import extract_input_variables_from_prompt
+from langflow.schema.data import Data
+from langflow.schema.message import Message
+from langflow.serialization import serialize
+from langflow.services.database.models.transactions.crud import log_transaction as crud_log_transaction
+from langflow.services.database.models.transactions.model import TransactionBase
+from langflow.services.database.models.vertex_builds.crud import log_vertex_build as crud_log_vertex_build
+from langflow.services.database.models.vertex_builds.model import VertexBuildBase
+from langflow.services.database.utils import session_getter
+from langflow.services.deps import get_db_service, get_settings_service
+
+if TYPE_CHECKING:
+ from langflow.api.v1.schemas import ResultDataResponse
+ from langflow.graph.vertex.base import Vertex
+
+
+class UnbuiltObject:
+ pass
+
+
+class UnbuiltResult:
+ pass
+
+
+class ArtifactType(str, Enum):
+ TEXT = "text"
+ RECORD = "record"
+ OBJECT = "object"
+ ARRAY = "array"
+ STREAM = "stream"
+ UNKNOWN = "unknown"
+ MESSAGE = "message"
+
+
+def validate_prompt(prompt: str):
+ """Validate prompt."""
+ if extract_input_variables_from_prompt(prompt):
+ return prompt
+
+ return fix_prompt(prompt)
+
+
+def fix_prompt(prompt: str):
+ """Fix prompt."""
+ return prompt + " {input}"
+
+
+def flatten_list(list_of_lists: list[list | Any]) -> list:
+ """Flatten list of lists."""
+ new_list = []
+ for item in list_of_lists:
+ if isinstance(item, list):
+ new_list.extend(item)
+ else:
+ new_list.append(item)
+ return new_list
+
+
+def get_artifact_type(value, build_result) -> str:
+ result = ArtifactType.UNKNOWN
+ match value:
+ case Data():
+ result = ArtifactType.RECORD
+
+ case str():
+ result = ArtifactType.TEXT
+
+ case dict():
+ result = ArtifactType.OBJECT
+
+ case list():
+ result = ArtifactType.ARRAY
+
+ case Message():
+ result = ArtifactType.MESSAGE
+
+ if result == ArtifactType.UNKNOWN and (
+ isinstance(build_result, Generator) or (isinstance(value, Message) and isinstance(value.text, Generator))
+ ):
+ result = ArtifactType.STREAM
+
+ return result.value
+
+
+def post_process_raw(raw, artifact_type: str):
+ if artifact_type == ArtifactType.STREAM.value:
+ raw = ""
+
+ return raw
+
+
+def _vertex_to_primitive_dict(target: Vertex) -> dict:
+ """Cleans the parameters of the target vertex."""
+ # Removes all keys that the values aren't python types like str, int, bool, etc.
+ params = {
+ key: value for key, value in target.params.items() if isinstance(value, str | int | bool | float | list | dict)
+ }
+ # if it is a list we need to check if the contents are python types
+ for key, value in params.items():
+ if isinstance(value, list):
+ params[key] = [item for item in value if isinstance(item, str | int | bool | float | list | dict)]
+ return params
+
+
+async def log_transaction(
+ flow_id: str | UUID, source: Vertex, status, target: Vertex | None = None, error=None
+) -> None:
+ try:
+ if not get_settings_service().settings.transactions_storage_enabled:
+ return
+ if not flow_id:
+ if source.graph.flow_id:
+ flow_id = source.graph.flow_id
+ else:
+ return
+ inputs = _vertex_to_primitive_dict(source)
+ transaction = TransactionBase(
+ vertex_id=source.id,
+ target_id=target.id if target else None,
+ inputs=inputs,
+ # ugly hack to get the model dump with weird datatypes
+ outputs=json.loads(source.result.model_dump_json()) if source.result else None,
+ status=status,
+ error=error,
+ flow_id=flow_id if isinstance(flow_id, UUID) else UUID(flow_id),
+ )
+ async with session_getter(get_db_service()) as session:
+ with session.no_autoflush:
+ inserted = await crud_log_transaction(session, transaction)
+ if inserted:
+ logger.debug(f"Logged transaction: {inserted.id}")
+ except Exception: # noqa: BLE001
+ logger.error("Error logging transaction")
+
+
+async def log_vertex_build(
+ *,
+ flow_id: str,
+ vertex_id: str,
+ valid: bool,
+ params: Any,
+ data: ResultDataResponse,
+ artifacts: dict | None = None,
+) -> None:
+ try:
+ if not get_settings_service().settings.vertex_builds_storage_enabled:
+ return
+
+ vertex_build = VertexBuildBase(
+ flow_id=flow_id,
+ id=vertex_id,
+ valid=valid,
+ params=str(params) if params else None,
+ # Serialize data using our custom serializer
+ data=serialize(data),
+ # Serialize artifacts using our custom serializer
+ artifacts=serialize(artifacts) if artifacts else None,
+ )
+ async with session_getter(get_db_service()) as session:
+ inserted = await crud_log_vertex_build(session, vertex_build)
+ logger.debug(f"Logged vertex build: {inserted.build_id}")
+ except Exception: # noqa: BLE001
+ logger.exception("Error logging vertex build")
+
+
+def rewrite_file_path(file_path: str):
+ file_path = file_path.replace("\\", "/")
+
+ if ":" in file_path:
+ file_path = file_path.split(":", 1)[-1]
+
+ file_path_split = [part for part in file_path.split("/") if part]
+
+ if len(file_path_split) > 1:
+ consistent_file_path = f"{file_path_split[-2]}/{file_path_split[-1]}"
+ else:
+ consistent_file_path = "/".join(file_path_split)
+
+ return [consistent_file_path]
+
+
+def has_output_vertex(vertices: dict[Vertex, int]):
+ return any(vertex.is_output for vertex in vertices)
+
+
+def has_chat_output(vertices: dict[Vertex, int]):
+ from langflow.graph.schema import InterfaceComponentTypes
+
+ return any(InterfaceComponentTypes.ChatOutput in vertex.id for vertex in vertices)
diff --git a/langflow/src/backend/base/langflow/graph/vertex/__init__.py b/langflow/src/backend/base/langflow/graph/vertex/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/vertex/base.py b/langflow/src/backend/base/langflow/graph/vertex/base.py
new file mode 100644
index 0000000..a40a771
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/vertex/base.py
@@ -0,0 +1,923 @@
+from __future__ import annotations
+
+import ast
+import asyncio
+import inspect
+import os
+import traceback
+import types
+from collections.abc import AsyncIterator, Callable, Iterator, Mapping
+from enum import Enum
+from typing import TYPE_CHECKING, Any
+
+import pandas as pd
+from loguru import logger
+
+from langflow.exceptions.component import ComponentBuildError
+from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
+from langflow.graph.utils import UnbuiltObject, UnbuiltResult, log_transaction
+from langflow.interface import initialize
+from langflow.interface.listing import lazy_load_dict
+from langflow.schema.artifact import ArtifactType
+from langflow.schema.data import Data
+from langflow.schema.message import Message
+from langflow.schema.schema import INPUT_FIELD_NAME, OutputValue, build_output_logs
+from langflow.services.deps import get_storage_service
+from langflow.utils.constants import DIRECT_TYPES
+from langflow.utils.schemas import ChatOutputResponse
+from langflow.utils.util import sync_to_async, unescape_string
+
+if TYPE_CHECKING:
+ from uuid import UUID
+
+ from langflow.custom import Component
+ from langflow.events.event_manager import EventManager
+ from langflow.graph.edge.base import CycleEdge, Edge
+ from langflow.graph.graph.base import Graph
+ from langflow.graph.vertex.schema import NodeData
+ from langflow.services.tracing.schema import Log
+
+
+class VertexStates(str, Enum):
+ """Vertex are related to it being active, inactive, or in an error state."""
+
+ ACTIVE = "ACTIVE"
+ INACTIVE = "INACTIVE"
+ ERROR = "ERROR"
+
+
+class Vertex:
+ def __init__(
+ self,
+ data: NodeData,
+ graph: Graph,
+ *,
+ base_type: str | None = None,
+ is_task: bool = False,
+ params: dict | None = None,
+ ) -> None:
+ # is_external means that the Vertex send or receives data from
+ # an external source (e.g the chat)
+ self._lock = asyncio.Lock()
+ self.will_stream = False
+ self.updated_raw_params = False
+ self.id: str = data["id"]
+ self.base_name = self.id.split("-")[0]
+ self.is_state = False
+ self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
+ self.is_output = any(output_component_name in self.id for output_component_name in OUTPUT_COMPONENTS)
+ self._is_loop = None
+ self.has_session_id = None
+ self.custom_component = None
+ self.has_external_input = False
+ self.has_external_output = False
+ self.graph = graph
+ self.full_data = data.copy()
+ self.base_type: str | None = base_type
+ self.outputs: list[dict] = []
+ self.parse_data()
+ self.built_object: Any = UnbuiltObject()
+ self.built_result: Any = None
+ self.built = False
+ self._successors_ids: list[str] | None = None
+ self.artifacts: dict[str, Any] = {}
+ self.artifacts_raw: dict[str, Any] = {}
+ self.artifacts_type: dict[str, str] = {}
+ self.steps: list[Callable] = [self._build]
+ self.steps_ran: list[Callable] = []
+ self.task_id: str | None = None
+ self.is_task = is_task
+ self.params = params or {}
+ self.parent_node_id: str | None = self.full_data.get("parent_node_id")
+ self.load_from_db_fields: list[str] = []
+ self.parent_is_top_level = False
+ self.layer = None
+ self.result: ResultData | None = None
+ self.results: dict[str, Any] = {}
+ self.outputs_logs: dict[str, OutputValue] = {}
+ self.logs: dict[str, list[Log]] = {}
+ self.has_cycle_edges = False
+ try:
+ self.is_interface_component = self.vertex_type in InterfaceComponentTypes
+ except ValueError:
+ self.is_interface_component = False
+
+ self.use_result = False
+ self.build_times: list[float] = []
+ self.state = VertexStates.ACTIVE
+ self.log_transaction_tasks: set[asyncio.Task] = set()
+ self.output_names: list[str] = [
+ output["name"] for output in self.outputs if isinstance(output, dict) and "name" in output
+ ]
+
+ @property
+ def is_loop(self) -> bool:
+ """Check if any output allows looping."""
+ if self._is_loop is None:
+ self._is_loop = any(output.get("allows_loop", False) for output in self.outputs)
+ return self._is_loop
+
+ def set_input_value(self, name: str, value: Any) -> None:
+ if self.custom_component is None:
+ msg = f"Vertex {self.id} does not have a component instance."
+ raise ValueError(msg)
+ self.custom_component._set_input_value(name, value)
+
+ def to_data(self):
+ return self.full_data
+
+ def add_component_instance(self, component_instance: Component) -> None:
+ component_instance.set_vertex(self)
+ self.custom_component = component_instance
+
+ def add_result(self, name: str, result: Any) -> None:
+ self.results[name] = result
+
+ def update_graph_state(self, key, new_state, *, append: bool) -> None:
+ if append:
+ self.graph.append_state(key, new_state, caller=self.id)
+ else:
+ self.graph.update_state(key, new_state, caller=self.id)
+
+ def set_state(self, state: str) -> None:
+ self.state = VertexStates[state]
+ if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] <= 1:
+ # If the vertex is inactive and has only one in degree
+ # it means that it is not a merge point in the graph
+ self.graph.inactivated_vertices.add(self.id)
+ elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
+ self.graph.inactivated_vertices.remove(self.id)
+
+ def is_active(self):
+ return self.state == VertexStates.ACTIVE
+
+ @property
+ def avg_build_time(self):
+ return sum(self.build_times) / len(self.build_times) if self.build_times else 0
+
+ def add_build_time(self, time) -> None:
+ self.build_times.append(time)
+
+ def set_result(self, result: ResultData) -> None:
+ self.result = result
+
+ def get_built_result(self):
+ # If the Vertex.type is a power component
+ # then we need to return the built object
+ # instead of the result dict
+ if self.is_interface_component and not isinstance(self.built_object, UnbuiltObject):
+ result = self.built_object
+ # if it is not a dict or a string and hasattr model_dump then
+ # return the model_dump
+ if not isinstance(result, dict | str) and hasattr(result, "content"):
+ return result.content
+ return result
+ if isinstance(self.built_object, str):
+ self.built_result = self.built_object
+
+ if isinstance(self.built_result, UnbuiltResult):
+ return {}
+ return self.built_result if isinstance(self.built_result, dict) else {"result": self.built_result}
+
+ def set_artifacts(self) -> None:
+ pass
+
+ @property
+ def edges(self) -> list[CycleEdge]:
+ return self.graph.get_vertex_edges(self.id)
+
+ @property
+ def outgoing_edges(self) -> list[CycleEdge]:
+ return [edge for edge in self.edges if edge.source_id == self.id]
+
+ @property
+ def incoming_edges(self) -> list[CycleEdge]:
+ return [edge for edge in self.edges if edge.target_id == self.id]
+
+ @property
+ def edges_source_names(self) -> set[str | None]:
+ return {edge.source_handle.name for edge in self.edges}
+
+ @property
+ def predecessors(self) -> list[Vertex]:
+ return self.graph.get_predecessors(self)
+
+ @property
+ def successors(self) -> list[Vertex]:
+ return self.graph.get_successors(self)
+
+ @property
+ def successors_ids(self) -> list[str]:
+ return self.graph.successor_map.get(self.id, [])
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state["_lock"] = None # Locks are not serializable
+ state["built_object"] = None if isinstance(self.built_object, UnbuiltObject) else self.built_object
+ state["built_result"] = None if isinstance(self.built_result, UnbuiltResult) else self.built_result
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+ self._lock = asyncio.Lock() # Reinitialize the lock
+ self.built_object = state.get("built_object") or UnbuiltObject()
+ self.built_result = state.get("built_result") or UnbuiltResult()
+
+ def set_top_level(self, top_level_vertices: list[str]) -> None:
+ self.parent_is_top_level = self.parent_node_id in top_level_vertices
+
+ def parse_data(self) -> None:
+ self.data = self.full_data["data"]
+ if self.data["node"]["template"]["_type"] == "Component":
+ if "outputs" not in self.data["node"]:
+ msg = f"Outputs not found for {self.display_name}"
+ raise ValueError(msg)
+ self.outputs = self.data["node"]["outputs"]
+ else:
+ self.outputs = self.data["node"].get("outputs", [])
+ self.output = self.data["node"]["base_classes"]
+
+ self.display_name: str = self.data["node"].get("display_name", self.id.split("-")[0])
+ self.icon: str = self.data["node"].get("icon", self.id.split("-")[0])
+
+ self.description: str = self.data["node"].get("description", "")
+ self.frozen: bool = self.data["node"].get("frozen", False)
+
+ self.is_input = self.data["node"].get("is_input") or self.is_input
+ self.is_output = self.data["node"].get("is_output") or self.is_output
+ template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
+
+ self.has_session_id = "session_id" in template_dicts
+
+ self.required_inputs: list[str] = []
+ self.optional_inputs: list[str] = []
+ for value_dict in template_dicts.values():
+ list_to_append = self.required_inputs if value_dict.get("required") else self.optional_inputs
+
+ if "type" in value_dict:
+ list_to_append.append(value_dict["type"])
+ if "input_types" in value_dict:
+ list_to_append.extend(value_dict["input_types"])
+
+ template_dict = self.data["node"]["template"]
+ self.vertex_type = (
+ self.data["type"]
+ if "Tool" not in [type_ for out in self.outputs for type_ in out["types"]]
+ or template_dict["_type"].islower()
+ else template_dict["_type"]
+ )
+
+ if self.base_type is None:
+ for base_type, value in lazy_load_dict.all_types_dict.items():
+ if self.vertex_type in value:
+ self.base_type = base_type
+ break
+
+ def get_value_from_output_names(self, key: str):
+ if key in self.output_names:
+ return self.graph.get_vertex(key)
+ return None
+
+ def get_value_from_template_dict(self, key: str):
+ template_dict = self.data.get("node", {}).get("template", {})
+
+ if key not in template_dict:
+ msg = f"Key {key} not found in template dict"
+ raise ValueError(msg)
+ return template_dict.get(key, {}).get("value")
+
+ def _set_params_from_normal_edge(self, params: dict, edge: Edge, template_dict: dict):
+ param_key = edge.target_param
+
+ # If the param_key is in the template_dict and the edge.target_id is the current node
+ # We check this to make sure params with the same name but different target_id
+ # don't get overwritten
+ if param_key in template_dict and edge.target_id == self.id:
+ if template_dict[param_key].get("list"):
+ if param_key not in params:
+ params[param_key] = []
+ params[param_key].append(self.graph.get_vertex(edge.source_id))
+ elif edge.target_id == self.id:
+ if isinstance(template_dict[param_key].get("value"), dict):
+ # we don't know the key of the dict but we need to set the value
+ # to the vertex that is the source of the edge
+ param_dict = template_dict[param_key]["value"]
+ if not param_dict or len(param_dict) != 1:
+ params[param_key] = self.graph.get_vertex(edge.source_id)
+ else:
+ params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict}
+
+ else:
+ params[param_key] = self.graph.get_vertex(edge.source_id)
+ elif param_key in self.output_names:
+ params[param_key] = self.graph.get_vertex(edge.source_id)
+ return params
+
+ def build_params(self) -> None:
+ # sourcery skip: merge-list-append, remove-redundant-if
+ # Some params are required, some are optional
+ # but most importantly, some params are python base classes
+ # like str and others are LangChain objects like LLMChain, BasePromptTemplate
+ # so we need to be able to distinguish between the two
+
+ # The dicts with "type" == "str" are the ones that are python base classes
+ # and most likely have a "value" key
+
+ # So for each key besides "_type" in the template dict, we have a dict
+ # with a "type" key. If the type is not "str", then we need to get the
+ # edge that connects to that node and get the Node with the required data
+ # and use that as the value for the param
+ # If the type is "str", then we need to get the value of the "value" key
+ # and use that as the value for the param
+
+ if self.graph is None:
+ msg = "Graph not found"
+ raise ValueError(msg)
+
+ if self.updated_raw_params:
+ self.updated_raw_params = False
+ return
+
+ template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
+ params: dict = {}
+
+ for edge in self.edges:
+ if not hasattr(edge, "target_param"):
+ continue
+ params = self._set_params_from_normal_edge(params, edge, template_dict)
+
+ load_from_db_fields = []
+ for field_name, field in template_dict.items():
+ if field_name in params:
+ continue
+ # Skip _type and any value that has show == False and is not code
+ # If we don't want to show code but we want to use it
+ if field_name == "_type" or (not field.get("show") and field_name != "code"):
+ continue
+ # If the type is not transformable to a python base class
+ # then we need to get the edge that connects to this node
+ if field.get("type") == "file":
+ # Load the type in value.get('fileTypes') using
+ # what is inside value.get('content')
+ # value.get('value') is the file name
+ if file_path := field.get("file_path"):
+ storage_service = get_storage_service()
+ try:
+ full_path: str | list[str] = ""
+ if field.get("list"):
+ full_path = []
+ if isinstance(file_path, str):
+ file_path = [file_path]
+ for p in file_path:
+ flow_id, file_name = os.path.split(p)
+ path = storage_service.build_full_path(flow_id, file_name)
+ full_path.append(path)
+ else:
+ flow_id, file_name = os.path.split(file_path)
+ full_path = storage_service.build_full_path(flow_id, file_name)
+ except ValueError as e:
+ if "too many values to unpack" in str(e):
+ full_path = file_path
+ else:
+ raise
+ params[field_name] = full_path
+ elif field.get("required"):
+ field_display_name = field.get("display_name")
+ logger.warning(
+ f"File path not found for {field_display_name} in component {self.display_name}. "
+ "Setting to None."
+ )
+ params[field_name] = None
+ elif field["list"]:
+ params[field_name] = []
+ else:
+ params[field_name] = None
+
+ elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None:
+ val = field.get("value")
+ if field.get("type") == "code":
+ try:
+ if field_name == "code":
+ params[field_name] = val
+ else:
+ params[field_name] = ast.literal_eval(val) if val else None
+ except Exception: # noqa: BLE001
+ logger.debug(f"Error evaluating code for {field_name}")
+ params[field_name] = val
+ elif field.get("type") in {"dict", "NestedDict"}:
+ # When dict comes from the frontend it comes as a
+ # list of dicts, so we need to convert it to a dict
+ # before passing it to the build method
+ if isinstance(val, list):
+ params[field_name] = {k: v for item in field.get("value", []) for k, v in item.items()}
+ elif isinstance(val, dict):
+ params[field_name] = val
+ elif field.get("type") == "int" and val is not None:
+ try:
+ params[field_name] = int(val)
+ except ValueError:
+ params[field_name] = val
+ elif field.get("type") in {"float", "slider"} and val is not None:
+ try:
+ params[field_name] = float(val)
+ except ValueError:
+ params[field_name] = val
+ params[field_name] = val
+ elif field.get("type") == "str" and val is not None:
+ # val may contain escaped \n, \t, etc.
+ # so we need to unescape it
+ if isinstance(val, list):
+ params[field_name] = [unescape_string(v) for v in val]
+ elif isinstance(val, str):
+ params[field_name] = unescape_string(val)
+ elif isinstance(val, Data):
+ params[field_name] = unescape_string(val.get_text())
+ elif field.get("type") == "bool" and val is not None:
+ if isinstance(val, bool):
+ params[field_name] = val
+ elif isinstance(val, str):
+ params[field_name] = bool(val)
+ elif field.get("type") == "table" and val is not None:
+ # check if the value is a list of dicts
+ # if it is, create a pandas dataframe from it
+ if isinstance(val, list) and all(isinstance(item, dict) for item in val):
+ params[field_name] = pd.DataFrame(val)
+ else:
+ msg = f"Invalid value type {type(val)} for field {field_name}"
+ raise ValueError(msg)
+ elif val:
+ params[field_name] = val
+
+ if field.get("load_from_db"):
+ load_from_db_fields.append(field_name)
+
+ if not field.get("required") and params.get(field_name) is None:
+ if field.get("default"):
+ params[field_name] = field.get("default")
+ else:
+ params.pop(field_name, None)
+ # Add _type to params
+ self.params = params
+ self.load_from_db_fields = load_from_db_fields
+ self.raw_params = params.copy()
+
+ def update_raw_params(self, new_params: Mapping[str, str | list[str]], *, overwrite: bool = False) -> None:
+ """Update the raw parameters of the vertex with the given new parameters.
+
+ Args:
+ new_params (Dict[str, Any]): The new parameters to update.
+ overwrite (bool, optional): Whether to overwrite the existing parameters.
+ Defaults to False.
+
+ Raises:
+ ValueError: If any key in new_params is not found in self.raw_params.
+ """
+ # First check if the input_value in raw_params is not a vertex
+ if not new_params:
+ return
+ if any(isinstance(self.raw_params.get(key), Vertex) for key in new_params):
+ return
+ if not overwrite:
+ for key in new_params.copy(): # type: ignore[attr-defined]
+ if key not in self.raw_params:
+ new_params.pop(key) # type: ignore[attr-defined]
+ self.raw_params.update(new_params)
+ self.params = self.raw_params.copy()
+ self.updated_raw_params = True
+
+ def instantiate_component(self, user_id=None) -> None:
+ if not self.custom_component:
+ self.custom_component, _ = initialize.loading.instantiate_class(
+ user_id=user_id,
+ vertex=self,
+ )
+
+ async def _build(
+ self,
+ fallback_to_env_vars,
+ user_id=None,
+ event_manager: EventManager | None = None,
+ ) -> None:
+ """Initiate the build process."""
+ logger.debug(f"Building {self.display_name}")
+ await self._build_each_vertex_in_params_dict()
+
+ if self.base_type is None:
+ msg = f"Base type for vertex {self.display_name} not found"
+ raise ValueError(msg)
+
+ if not self.custom_component:
+ custom_component, custom_params = initialize.loading.instantiate_class(
+ user_id=user_id, vertex=self, event_manager=event_manager
+ )
+ else:
+ custom_component = self.custom_component
+ if hasattr(self.custom_component, "set_event_manager"):
+ self.custom_component.set_event_manager(event_manager)
+ custom_params = initialize.loading.get_params(self.params)
+
+ await self._build_results(
+ custom_component=custom_component,
+ custom_params=custom_params,
+ fallback_to_env_vars=fallback_to_env_vars,
+ base_type=self.base_type,
+ )
+
+ self._validate_built_object()
+
+ self.built = True
+
+ def extract_messages_from_artifacts(self, artifacts: dict[str, Any]) -> list[dict]:
+ """Extracts messages from the artifacts.
+
+ Args:
+ artifacts (Dict[str, Any]): The artifacts to extract messages from.
+
+ Returns:
+ List[str]: The extracted messages.
+ """
+ try:
+ text = artifacts["text"]
+ sender = artifacts.get("sender")
+ sender_name = artifacts.get("sender_name")
+ session_id = artifacts.get("session_id")
+ stream_url = artifacts.get("stream_url")
+ files = [{"path": file} if isinstance(file, str) else file for file in artifacts.get("files", [])]
+ component_id = self.id
+ type_ = self.artifacts_type
+
+ if isinstance(sender_name, Data | Message):
+ sender_name = sender_name.get_text()
+
+ messages = [
+ ChatOutputResponse(
+ message=text,
+ sender=sender,
+ sender_name=sender_name,
+ session_id=session_id,
+ stream_url=stream_url,
+ files=files,
+ component_id=component_id,
+ type=type_,
+ ).model_dump(exclude_none=True)
+ ]
+ except KeyError:
+ messages = []
+
+ return messages
+
+ def finalize_build(self) -> None:
+ result_dict = self.get_built_result()
+ # We need to set the artifacts to pass information
+ # to the frontend
+ self.set_artifacts()
+ artifacts = self.artifacts_raw
+ messages = self.extract_messages_from_artifacts(artifacts) if isinstance(artifacts, dict) else []
+ result_dict = ResultData(
+ results=result_dict,
+ artifacts=artifacts,
+ outputs=self.outputs_logs,
+ logs=self.logs,
+ messages=messages,
+ component_display_name=self.display_name,
+ component_id=self.id,
+ )
+ self.set_result(result_dict)
+
+ async def _build_each_vertex_in_params_dict(self) -> None:
+ """Iterates over each vertex in the params dictionary and builds it."""
+ for key, value in self.raw_params.items():
+ if self._is_vertex(value):
+ if value == self:
+ del self.params[key]
+ continue
+ await self._build_vertex_and_update_params(
+ key,
+ value,
+ )
+ elif isinstance(value, list) and self._is_list_of_vertices(value):
+ await self._build_list_of_vertices_and_update_params(key, value)
+ elif isinstance(value, dict):
+ await self._build_dict_and_update_params(
+ key,
+ value,
+ )
+ elif key not in self.params or self.updated_raw_params:
+ self.params[key] = value
+
+ async def _build_dict_and_update_params(
+ self,
+ key,
+ vertices_dict: dict[str, Vertex],
+ ) -> None:
+ """Iterates over a dictionary of vertices, builds each and updates the params dictionary."""
+ for sub_key, value in vertices_dict.items():
+ if not self._is_vertex(value):
+ self.params[key][sub_key] = value
+ else:
+ result = await value.get_result(self, target_handle_name=key)
+ self.params[key][sub_key] = result
+
+ @staticmethod
+ def _is_vertex(value):
+ """Checks if the provided value is an instance of Vertex."""
+ return isinstance(value, Vertex)
+
+ def _is_list_of_vertices(self, value):
+ """Checks if the provided value is a list of Vertex instances."""
+ return all(self._is_vertex(vertex) for vertex in value)
+
+ async def get_result(self, requester: Vertex, target_handle_name: str | None = None) -> Any:
+ """Retrieves the result of the vertex.
+
+ This is a read-only method so it raises an error if the vertex has not been built yet.
+
+ Returns:
+ The result of the vertex.
+ """
+ async with self._lock:
+ return await self._get_result(requester, target_handle_name)
+
+ async def _log_transaction_async(
+ self,
+ flow_id: str | UUID,
+ source: Vertex,
+ status,
+ target: Vertex | None = None,
+ error=None,
+ ) -> None:
+ """Log a transaction asynchronously with proper task handling and cancellation.
+
+ Args:
+ flow_id: The ID of the flow
+ source: Source vertex
+ status: Transaction status
+ target: Optional target vertex
+ error: Optional error information
+ """
+ if self.log_transaction_tasks:
+ # Safely await and remove completed tasks
+ task = self.log_transaction_tasks.pop()
+ await task
+
+ # Create and track new task
+ task = asyncio.create_task(log_transaction(flow_id, source, status, target, error))
+ self.log_transaction_tasks.add(task)
+ task.add_done_callback(self.log_transaction_tasks.discard)
+
+ async def _get_result(
+ self,
+ requester: Vertex,
+ target_handle_name: str | None = None, # noqa: ARG002
+ ) -> Any:
+ """Retrieves the result of the built component.
+
+ If the component has not been built yet, a ValueError is raised.
+
+ Returns:
+ The built result if use_result is True, else the built object.
+ """
+ flow_id = self.graph.flow_id
+ if not self.built:
+ if flow_id:
+ await self._log_transaction_async(str(flow_id), source=self, target=requester, status="error")
+ msg = f"Component {self.display_name} has not been built yet"
+ raise ValueError(msg)
+
+ result = self.built_result if self.use_result else self.built_object
+ if flow_id:
+ await self._log_transaction_async(str(flow_id), source=self, target=requester, status="success")
+ return result
+
+ async def _build_vertex_and_update_params(self, key, vertex: Vertex) -> None:
+ """Builds a given vertex and updates the params dictionary accordingly."""
+ result = await vertex.get_result(self, target_handle_name=key)
+ self._handle_func(key, result)
+ if isinstance(result, list):
+ self._extend_params_list_with_result(key, result)
+ self.params[key] = result
+
+ async def _build_list_of_vertices_and_update_params(
+ self,
+ key,
+ vertices: list[Vertex],
+ ) -> None:
+ """Iterates over a list of vertices, builds each and updates the params dictionary."""
+ self.params[key] = []
+ for vertex in vertices:
+ result = await vertex.get_result(self, target_handle_name=key)
+ # Weird check to see if the params[key] is a list
+ # because sometimes it is a Data and breaks the code
+ if not isinstance(self.params[key], list):
+ self.params[key] = [self.params[key]]
+
+ if isinstance(result, list):
+ self.params[key].extend(result)
+ else:
+ try:
+ if self.params[key] == result:
+ continue
+
+ self.params[key].append(result)
+ except AttributeError as e:
+ logger.exception(e)
+ msg = (
+ f"Params {key} ({self.params[key]}) is not a list and cannot be extended with {result}"
+ f"Error building Component {self.display_name}: \n\n{e}"
+ )
+ raise ValueError(msg) from e
+
+ def _handle_func(self, key, result) -> None:
+ """Handles 'func' key by checking if the result is a function and setting it as coroutine."""
+ if key == "func":
+ if not isinstance(result, types.FunctionType):
+ if hasattr(result, "run"):
+ result = result.run
+ elif hasattr(result, "get_function"):
+ result = result.get_function()
+ elif inspect.iscoroutinefunction(result):
+ self.params["coroutine"] = result
+ else:
+ self.params["coroutine"] = sync_to_async(result)
+
+ def _extend_params_list_with_result(self, key, result) -> None:
+ """Extends a list in the params dictionary with the given result if it exists."""
+ if isinstance(self.params[key], list):
+ self.params[key].extend(result)
+
+ async def _build_results(
+ self,
+ custom_component,
+ custom_params,
+ base_type: str,
+ *,
+ fallback_to_env_vars=False,
+ ) -> None:
+ try:
+ result = await initialize.loading.get_instance_results(
+ custom_component=custom_component,
+ custom_params=custom_params,
+ vertex=self,
+ fallback_to_env_vars=fallback_to_env_vars,
+ base_type=base_type,
+ )
+
+ self.outputs_logs = build_output_logs(self, result)
+
+ self._update_built_object_and_artifacts(result)
+ except Exception as exc:
+ tb = traceback.format_exc()
+ logger.exception(exc)
+ msg = f"Error building Component {self.display_name}: \n\n{exc}"
+ raise ComponentBuildError(msg, tb) from exc
+
+ def _update_built_object_and_artifacts(self, result: Any | tuple[Any, dict] | tuple[Component, Any, dict]) -> None:
+ """Updates the built object and its artifacts."""
+ if isinstance(result, tuple):
+ if len(result) == 2: # noqa: PLR2004
+ self.built_object, self.artifacts = result
+ elif len(result) == 3: # noqa: PLR2004
+ self.custom_component, self.built_object, self.artifacts = result
+ self.logs = self.custom_component._output_logs
+ self.artifacts_raw = self.artifacts.get("raw", None)
+ self.artifacts_type = {
+ self.outputs[0]["name"]: self.artifacts.get("type", None) or ArtifactType.UNKNOWN.value
+ }
+ self.artifacts = {self.outputs[0]["name"]: self.artifacts}
+ else:
+ self.built_object = result
+
+ def _validate_built_object(self) -> None:
+ """Checks if the built object is None and raises a ValueError if so."""
+ if isinstance(self.built_object, UnbuiltObject):
+ msg = f"{self.display_name}: {self.built_object_repr()}"
+ raise TypeError(msg)
+ if self.built_object is None:
+ message = f"{self.display_name} returned None."
+ if self.base_type == "custom_components":
+ message += " Make sure your build method returns a component."
+
+ logger.warning(message)
+ elif isinstance(self.built_object, Iterator | AsyncIterator):
+ if self.display_name == "Text Output":
+ msg = f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
+ raise ValueError(msg)
+
+ def _reset(self) -> None:
+ self.built = False
+ self.built_object = UnbuiltObject()
+ self.built_result = UnbuiltResult()
+ self.artifacts = {}
+ self.steps_ran = []
+ self.build_params()
+
+ def _is_chat_input(self) -> bool:
+ return False
+
+ def build_inactive(self) -> None:
+ # Just set the results to None
+ self.built = True
+ self.built_object = None
+ self.built_result = None
+
+ async def build(
+ self,
+ user_id=None,
+ inputs: dict[str, Any] | None = None,
+ files: list[str] | None = None,
+ requester: Vertex | None = None,
+ event_manager: EventManager | None = None,
+ **kwargs,
+ ) -> Any:
+ async with self._lock:
+ if self.state == VertexStates.INACTIVE:
+ # If the vertex is inactive, return None
+ self.build_inactive()
+ return None
+
+ if self.frozen and self.built:
+ return await self.get_requester_result(requester)
+ if self.built and requester is not None:
+ # This means that the vertex has already been built
+ # and we are just getting the result for the requester
+ return await self.get_requester_result(requester)
+ self._reset()
+ # inject session_id if it is not None
+ if inputs is not None and "session" in inputs and inputs["session"] is not None and self.has_session_id:
+ session_id_value = self.get_value_from_template_dict("session_id")
+ if session_id_value == "":
+ self.update_raw_params({"session_id": inputs["session"]}, overwrite=True)
+ if self._is_chat_input() and (inputs or files):
+ chat_input = {}
+ if (
+ inputs
+ and isinstance(inputs, dict)
+ and "input_value" in inputs
+ and inputs.get("input_value") is not None
+ ):
+ chat_input.update({"input_value": inputs.get(INPUT_FIELD_NAME, "")})
+ if files:
+ chat_input.update({"files": files})
+
+ self.update_raw_params(chat_input, overwrite=True)
+
+ # Run steps
+ for step in self.steps:
+ if step not in self.steps_ran:
+ await step(user_id=user_id, event_manager=event_manager, **kwargs)
+ self.steps_ran.append(step)
+
+ self.finalize_build()
+
+ return await self.get_requester_result(requester)
+
+ async def get_requester_result(self, requester: Vertex | None):
+ # If the requester is None, this means that
+ # the Vertex is the root of the graph
+ if requester is None:
+ return self.built_object
+
+ # Get the requester edge
+ requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
+ # Return the result of the requester edge
+ return (
+ None
+ if requester_edge is None
+ else await requester_edge.get_result_from_source(source=self, target=requester)
+ )
+
+ def add_edge(self, edge: CycleEdge) -> None:
+ if edge not in self.edges:
+ self.edges.append(edge)
+
+ def __repr__(self) -> str:
+ return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
+
+ def __eq__(self, /, other: object) -> bool:
+ try:
+ if not isinstance(other, Vertex):
+ return False
+ # We should create a more robust comparison
+ # for the Vertex class
+ ids_are_equal = self.id == other.id
+ # self.data is a dict and we need to compare them
+ # to check if they are equal
+ data_are_equal = self.data == other.data
+ except AttributeError:
+ return False
+ else:
+ return ids_are_equal and data_are_equal
+
+ def __hash__(self) -> int:
+ return id(self)
+
+ def built_object_repr(self) -> str:
+ # Add a message with an emoji, stars for success,
+ return "Built successfully ✨" if self.built_object is not None else "Failed to build 😵💫"
+
+ def apply_on_outputs(self, func: Callable[[Any], Any]) -> None:
+ """Applies a function to the outputs of the vertex."""
+ if not self.custom_component or not self.custom_component.outputs:
+ return
+ # Apply the function to each output
+ [func(output) for output in self.custom_component._outputs_map.values()]
diff --git a/langflow/src/backend/base/langflow/graph/vertex/constants.py b/langflow/src/backend/base/langflow/graph/vertex/constants.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/graph/vertex/exceptions.py b/langflow/src/backend/base/langflow/graph/vertex/exceptions.py
new file mode 100644
index 0000000..5b773ba
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/vertex/exceptions.py
@@ -0,0 +1,4 @@
+class NoComponentInstanceError(Exception):
+ def __init__(self, vertex_id: str):
+ message = f"Vertex {vertex_id} does not have a component instance."
+ super().__init__(message)
diff --git a/langflow/src/backend/base/langflow/graph/vertex/schema.py b/langflow/src/backend/base/langflow/graph/vertex/schema.py
new file mode 100644
index 0000000..5a52cbd
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/vertex/schema.py
@@ -0,0 +1,26 @@
+from enum import Enum
+
+from typing_extensions import NotRequired, TypedDict
+
+
+class NodeTypeEnum(str, Enum):
+ NoteNode = "noteNode"
+ GenericNode = "genericNode"
+
+
+class Position(TypedDict):
+ x: float
+ y: float
+
+
+class NodeData(TypedDict):
+ id: str
+ data: dict
+ dragging: NotRequired[bool]
+ height: NotRequired[int]
+ width: NotRequired[int]
+ position: NotRequired[Position]
+ positionAbsolute: NotRequired[Position]
+ selected: NotRequired[bool]
+ parent_node_id: NotRequired[str]
+ type: NotRequired[NodeTypeEnum]
diff --git a/langflow/src/backend/base/langflow/graph/vertex/utils.py b/langflow/src/backend/base/langflow/graph/vertex/utils.py
new file mode 100644
index 0000000..4844ad8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/vertex/utils.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from langflow.graph.vertex.base import Vertex
+
+
+def build_clean_params(target: Vertex) -> dict:
+ """Cleans the parameters of the target vertex."""
+ # Removes all keys that the values aren't python types like str, int, bool, etc.
+ params = {
+ key: value for key, value in target.params.items() if isinstance(value, str | int | bool | float | list | dict)
+ }
+ # if it is a list we need to check if the contents are python types
+ for key, value in params.items():
+ if isinstance(value, list):
+ params[key] = [item for item in value if isinstance(item, str | int | bool | float | list | dict)]
+ return params
diff --git a/langflow/src/backend/base/langflow/graph/vertex/vertex_types.py b/langflow/src/backend/base/langflow/graph/vertex/vertex_types.py
new file mode 100644
index 0000000..cebc0cb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/graph/vertex/vertex_types.py
@@ -0,0 +1,484 @@
+from __future__ import annotations
+
+import contextlib
+import json
+from collections.abc import AsyncIterator, Generator, Iterator
+from typing import TYPE_CHECKING, Any, cast
+
+import yaml
+from langchain_core.messages import AIMessage, AIMessageChunk
+from loguru import logger
+
+from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData
+from langflow.graph.utils import UnbuiltObject, log_vertex_build, rewrite_file_path
+from langflow.graph.vertex.base import Vertex
+from langflow.graph.vertex.exceptions import NoComponentInstanceError
+from langflow.schema import Data
+from langflow.schema.artifact import ArtifactType
+from langflow.schema.message import Message
+from langflow.schema.schema import INPUT_FIELD_NAME
+from langflow.serialization import serialize
+from langflow.template.field.base import UNDEFINED, Output
+from langflow.utils.schemas import ChatOutputResponse, DataOutputResponse
+from langflow.utils.util import unescape_string
+
+if TYPE_CHECKING:
+ from langflow.graph.edge.base import CycleEdge
+ from langflow.graph.vertex.schema import NodeData
+ from langflow.inputs.inputs import InputTypes
+
+
+class CustomComponentVertex(Vertex):
+ def __init__(self, data: NodeData, graph):
+ super().__init__(data, graph=graph, base_type="custom_components")
+
+ def built_object_repr(self):
+ if self.artifacts and "repr" in self.artifacts:
+ return self.artifacts["repr"] or super().built_object_repr()
+ return None
+
+
+class ComponentVertex(Vertex):
+ def __init__(self, data: NodeData, graph):
+ super().__init__(data, graph=graph, base_type="component")
+
+ def get_input(self, name: str) -> InputTypes:
+ if self.custom_component is None:
+ msg = f"Vertex {self.id} does not have a component instance."
+ raise ValueError(msg)
+ return self.custom_component.get_input(name)
+
+ def get_output(self, name: str) -> Output:
+ if self.custom_component is None:
+ raise NoComponentInstanceError(self.id)
+ return self.custom_component.get_output(name)
+
+ def built_object_repr(self):
+ if self.artifacts and "repr" in self.artifacts:
+ return self.artifacts["repr"] or super().built_object_repr()
+ return None
+
+ def _update_built_object_and_artifacts(self, result) -> None:
+ """Updates the built object and its artifacts."""
+ if isinstance(result, tuple):
+ if len(result) == 2: # noqa: PLR2004
+ self.built_object, self.artifacts = result
+ elif len(result) == 3: # noqa: PLR2004
+ self.custom_component, self.built_object, self.artifacts = result
+ self.logs = self.custom_component._output_logs
+ for key in self.artifacts:
+ self.artifacts_raw[key] = self.artifacts[key].get("raw", None)
+ self.artifacts_type[key] = self.artifacts[key].get("type", None) or ArtifactType.UNKNOWN.value
+ else:
+ self.built_object = result
+
+ for key, value in self.built_object.items():
+ self.add_result(key, value)
+
+ def get_edge_with_target(self, target_id: str) -> Generator[CycleEdge, None, None]:
+ """Get the edge with the target id.
+
+ Args:
+ target_id: The target id of the edge.
+
+ Returns:
+ The edge with the target id.
+ """
+ for edge in self.edges:
+ if edge.target_id == target_id:
+ yield edge
+
+ async def _get_result(self, requester: Vertex, target_handle_name: str | None = None) -> Any:
+ """Retrieves the result of the built component.
+
+ If the component has not been built yet, a ValueError is raised.
+
+ Returns:
+ The built result if use_result is True, else the built object.
+ """
+ flow_id = self.graph.flow_id
+ if not self.built:
+ default_value: Any = UNDEFINED
+ for edge in self.get_edge_with_target(requester.id):
+ # We need to check if the edge is a normal edge
+ if edge.is_cycle and edge.target_param:
+ if edge.target_param in requester.output_names:
+ default_value = None
+ else:
+ default_value = requester.get_value_from_template_dict(edge.target_param)
+
+ if flow_id:
+ await self._log_transaction_async(source=self, target=requester, flow_id=str(flow_id), status="error")
+ if default_value is not UNDEFINED:
+ return default_value
+ msg = f"Component {self.display_name} has not been built yet"
+ raise ValueError(msg)
+
+ if requester is None:
+ msg = "Requester Vertex is None"
+ raise ValueError(msg)
+
+ edges = self.get_edge_with_target(requester.id)
+ result = UNDEFINED
+ for edge in edges:
+ if (
+ edge is not None
+ and edge.source_handle.name in self.results
+ and edge.target_handle.field_name == target_handle_name
+ ):
+ # Get the result from the output instead of the results dict
+ try:
+ output = self.get_output(edge.source_handle.name)
+
+ if output.value is UNDEFINED:
+ result = self.results[edge.source_handle.name]
+ else:
+ result = cast("Any", output.value)
+ except NoComponentInstanceError:
+ result = self.results[edge.source_handle.name]
+ break
+ if result is UNDEFINED:
+ if edge is None:
+ msg = f"Edge not found between {self.display_name} and {requester.display_name}"
+ raise ValueError(msg)
+ if edge.source_handle.name not in self.results:
+ msg = f"Result not found for {edge.source_handle.name}. Results: {self.results}"
+ raise ValueError(msg)
+ msg = f"Result not found for {edge.source_handle.name} in {edge}"
+ raise ValueError(msg)
+ if flow_id:
+ await self._log_transaction_async(source=self, target=requester, flow_id=str(flow_id), status="success")
+ return result
+
+ def extract_messages_from_artifacts(self, artifacts: dict[str, Any]) -> list[dict]:
+ """Extracts messages from the artifacts.
+
+ Args:
+ artifacts (Dict[str, Any]): The artifacts to extract messages from.
+
+ Returns:
+ List[str]: The extracted messages.
+ """
+ messages = []
+ for key, artifact in artifacts.items():
+ if any(
+ k not in artifact for k in ["text", "sender", "sender_name", "session_id", "stream_url"]
+ ) and not isinstance(artifact, Message):
+ continue
+ message_dict = artifact if isinstance(artifact, dict) else artifact.model_dump()
+ if not message_dict.get("text"):
+ continue
+ with contextlib.suppress(KeyError):
+ messages.append(
+ ChatOutputResponse(
+ message=message_dict["text"],
+ sender=message_dict.get("sender"),
+ sender_name=message_dict.get("sender_name"),
+ session_id=message_dict.get("session_id"),
+ stream_url=message_dict.get("stream_url"),
+ files=[
+ {"path": file} if isinstance(file, str) else file for file in message_dict.get("files", [])
+ ],
+ component_id=self.id,
+ type=self.artifacts_type[key],
+ ).model_dump(exclude_none=True)
+ )
+ return messages
+
+ def finalize_build(self) -> None:
+ result_dict = self.get_built_result()
+ # We need to set the artifacts to pass information
+ # to the frontend
+ messages = self.extract_messages_from_artifacts(result_dict)
+ result_dict = ResultData(
+ results=result_dict,
+ artifacts=self.artifacts,
+ outputs=self.outputs_logs,
+ logs=self.logs,
+ messages=messages,
+ component_display_name=self.display_name,
+ component_id=self.id,
+ )
+ self.set_result(result_dict)
+
+
+class InterfaceVertex(ComponentVertex):
+ def __init__(self, data: NodeData, graph):
+ super().__init__(data, graph=graph)
+ self.added_message = None
+ self.steps = [self._build, self._run]
+ self.is_interface_component = True
+
+ def build_stream_url(self) -> str:
+ return f"/api/v1/build/{self.graph.flow_id}/{self.id}/stream"
+
+ def built_object_repr(self):
+ if self.task_id and self.is_task:
+ if task := self.get_task():
+ return str(task.info)
+ return f"Task {self.task_id} is not running"
+ if self.artifacts:
+ # dump as a yaml string
+ if isinstance(self.artifacts, dict):
+ artifacts_ = [self.artifacts]
+ elif hasattr(self.artifacts, "data"):
+ artifacts_ = self.artifacts.data
+ else:
+ artifacts_ = self.artifacts
+ artifacts = []
+ for artifact in artifacts_:
+ # artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None}
+ artifact_ = {k.title().replace("_", " "): v for k, v in artifact.items() if v is not None}
+ artifacts.append(artifact_)
+ return yaml.dump(artifacts, default_flow_style=False, allow_unicode=True)
+ return super().built_object_repr()
+
+ def _process_chat_component(self):
+ """Process the chat component and return the message.
+
+ This method processes the chat component by extracting the necessary parameters
+ such as sender, sender_name, and message from the `params` dictionary. It then
+ performs additional operations based on the type of the `built_object` attribute.
+ If `built_object` is an instance of `AIMessage`, it creates a `ChatOutputResponse`
+ object using the `from_message` method. If `built_object` is not an instance of
+ `UnbuiltObject`, it checks the type of `built_object` and performs specific
+ operations accordingly. If `built_object` is a dictionary, it converts it into a
+ code block. If `built_object` is an instance of `Data`, it assigns the `text`
+ attribute to the `message` variable. If `message` is an instance of `AsyncIterator`
+ or `Iterator`, it builds a stream URL and sets `message` to an empty string. If
+ `built_object` is not a string, it converts it to a string. If `message` is a
+ generator or iterator, it assigns it to the `message` variable. Finally, it creates
+ a `ChatOutputResponse` object using the extracted parameters and assigns it to the
+ `artifacts` attribute. If `artifacts` is not None, it calls the `model_dump` method
+ on it and assigns the result to the `artifacts` attribute. It then returns the
+ `message` variable.
+
+ Returns:
+ str: The processed message.
+ """
+ artifacts = None
+ sender = self.params.get("sender", None)
+ sender_name = self.params.get("sender_name", None)
+ message = self.params.get(INPUT_FIELD_NAME, None)
+ files = self.params.get("files", [])
+ treat_file_path = files is not None and not isinstance(files, list) and isinstance(files, str)
+ if treat_file_path:
+ self.params["files"] = rewrite_file_path(files)
+ files = [{"path": file} if isinstance(file, str) else file for file in self.params.get("files", [])]
+ if isinstance(message, str):
+ message = unescape_string(message)
+ stream_url = None
+ if "text" in self.results:
+ text_output = self.results["text"]
+ elif "message" in self.results:
+ text_output = self.results["message"].text
+ else:
+ text_output = message
+ if isinstance(text_output, AIMessage | AIMessageChunk):
+ artifacts = ChatOutputResponse.from_message(
+ text_output,
+ sender=sender,
+ sender_name=sender_name,
+ )
+ elif not isinstance(text_output, UnbuiltObject):
+ if isinstance(text_output, dict):
+ # Turn the dict into a pleasing to
+ # read JSON inside a code block
+ message = dict_to_codeblock(text_output)
+ elif isinstance(text_output, Data):
+ message = text_output.text
+ elif isinstance(message, AsyncIterator | Iterator):
+ stream_url = self.build_stream_url()
+ message = ""
+ self.results["text"] = message
+ self.results["message"].text = message
+ self.built_object = self.results
+ elif not isinstance(text_output, str):
+ message = str(text_output)
+ # if the message is a generator or iterator
+ # it means that it is a stream of messages
+
+ else:
+ message = text_output
+
+ if hasattr(sender_name, "get_text"):
+ sender_name = sender_name.get_text()
+
+ artifact_type = ArtifactType.STREAM if stream_url is not None else ArtifactType.OBJECT
+ artifacts = ChatOutputResponse(
+ message=message,
+ sender=sender,
+ sender_name=sender_name,
+ stream_url=stream_url,
+ files=files,
+ type=artifact_type,
+ )
+
+ self.will_stream = stream_url is not None
+ if artifacts:
+ self.artifacts = artifacts.model_dump(exclude_none=True)
+
+ return message
+
+ def _process_data_component(self):
+ """Process the record component of the vertex.
+
+ If the built object is an instance of `Data`, it calls the `model_dump` method
+ and assigns the result to the `artifacts` attribute.
+
+ If the built object is a list, it iterates over each element and checks if it is
+ an instance of `Data`. If it is, it calls the `model_dump` method and appends
+ the result to the `artifacts` list. If it is not, it raises a `ValueError` if the
+ `ignore_errors` parameter is set to `False`, or logs an error message if it is set
+ to `True`.
+
+ Returns:
+ The built object.
+
+ Raises:
+ ValueError: If an element in the list is not an instance of `Data` and
+ `ignore_errors` is set to `False`.
+ """
+ if isinstance(self.built_object, Data):
+ artifacts = [self.built_object.data]
+ elif isinstance(self.built_object, list):
+ artifacts = []
+ ignore_errors = self.params.get("ignore_errors", False)
+ for value in self.built_object:
+ if isinstance(value, Data):
+ artifacts.append(value.data)
+ elif ignore_errors:
+ logger.error(f"Data expected, but got {value} of type {type(value)}")
+ else:
+ msg = f"Data expected, but got {value} of type {type(value)}"
+ raise ValueError(msg)
+ self.artifacts = DataOutputResponse(data=artifacts)
+ return self.built_object
+
+ async def _run(self, *args, **kwargs) -> None: # noqa: ARG002
+ if self.vertex_type in CHAT_COMPONENTS:
+ message = self._process_chat_component()
+ elif self.vertex_type in RECORDS_COMPONENTS:
+ message = self._process_data_component()
+ if isinstance(self.built_object, AsyncIterator | Iterator):
+ if self.params.get("return_data", False):
+ self.built_object = Data(text=message, data=self.artifacts)
+ else:
+ self.built_object = message
+ self.built_result = self.built_object
+
+ async def stream(self):
+ iterator = self.params.get(INPUT_FIELD_NAME, None)
+ if not isinstance(iterator, AsyncIterator | Iterator):
+ msg = "The message must be an iterator or an async iterator."
+ raise TypeError(msg)
+ is_async = isinstance(iterator, AsyncIterator)
+ complete_message = ""
+ if is_async:
+ async for message in iterator:
+ message_ = message.content if hasattr(message, "content") else message
+ message_ = message_.text if hasattr(message_, "text") else message_
+ yield message_
+ complete_message += message_
+ else:
+ for message in iterator:
+ message_ = message.content if hasattr(message, "content") else message
+ message_ = message_.text if hasattr(message_, "text") else message_
+ yield message_
+ complete_message += message_
+
+ files = self.params.get("files", [])
+
+ treat_file_path = files is not None and not isinstance(files, list) and isinstance(files, str)
+ if treat_file_path:
+ self.params["files"] = rewrite_file_path(files)
+
+ if hasattr(self.params.get("sender_name"), "get_text"):
+ sender_name = self.params.get("sender_name").get_text()
+ else:
+ sender_name = self.params.get("sender_name")
+ self.artifacts = ChatOutputResponse(
+ message=complete_message,
+ sender=self.params.get("sender", ""),
+ sender_name=sender_name,
+ files=[{"path": file} if isinstance(file, str) else file for file in self.params.get("files", [])],
+ type=ArtifactType.OBJECT.value,
+ ).model_dump()
+
+ message = await Message.create(
+ text=complete_message,
+ sender=self.params.get("sender", ""),
+ sender_name=self.params.get("sender_name", ""),
+ files=self.params.get("files", []),
+ flow_id=self.graph.flow_id,
+ session_id=self.params.get("session_id", ""),
+ )
+ self.params[INPUT_FIELD_NAME] = complete_message
+ if isinstance(self.built_object, dict):
+ for key, value in self.built_object.items():
+ if hasattr(value, "text") and (isinstance(value.text, AsyncIterator | Iterator) or value.text == ""):
+ self.built_object[key] = message
+ else:
+ self.built_object = message
+ self.artifacts_type = ArtifactType.MESSAGE
+
+ # Update artifacts with the message
+ # and remove the stream_url
+ self.finalize_build()
+ logger.debug(f"Streamed message: {complete_message}")
+ # Set the result in the vertex of origin
+ edges = self.get_edge_with_target(self.id)
+ for edge in edges:
+ origin_vertex = self.graph.get_vertex(edge.source_id)
+ for key, value in origin_vertex.results.items():
+ if isinstance(value, AsyncIterator | Iterator):
+ origin_vertex.results[key] = complete_message
+ if (
+ self.custom_component
+ and hasattr(self.custom_component, "should_store_message")
+ and hasattr(self.custom_component, "store_message")
+ ):
+ await self.custom_component.store_message(message)
+ await log_vertex_build(
+ flow_id=self.graph.flow_id,
+ vertex_id=self.id,
+ valid=True,
+ params=self.built_object_repr(),
+ data=self.result,
+ artifacts=self.artifacts,
+ )
+
+ self._validate_built_object()
+ self.built = True
+
+ async def consume_async_generator(self) -> None:
+ async for _ in self.stream():
+ pass
+
+ def _is_chat_input(self):
+ return self.vertex_type == InterfaceComponentTypes.ChatInput and self.is_input
+
+
+class StateVertex(ComponentVertex):
+ def __init__(self, data: NodeData, graph):
+ super().__init__(data, graph=graph)
+ self.steps = [self._build]
+ self.is_state = False
+
+ @property
+ def successors_ids(self) -> list[str]:
+ if self._successors_ids is None:
+ self.is_state = False
+ return super().successors_ids
+ return self._successors_ids
+
+ def built_object_repr(self):
+ if self.artifacts and "repr" in self.artifacts:
+ return self.artifacts["repr"] or super().built_object_repr()
+ return None
+
+
+def dict_to_codeblock(d: dict) -> str:
+ serialized = {key: serialize(val) for key, val in d.items()}
+ json_str = json.dumps(serialized, indent=4)
+ return f"```json\n{json_str}\n```"
diff --git a/langflow/src/backend/base/langflow/helpers/__init__.py b/langflow/src/backend/base/langflow/helpers/__init__.py
new file mode 100644
index 0000000..02f9d73
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/__init__.py
@@ -0,0 +1,3 @@
+from .data import data_to_text, docs_to_data, messages_to_text
+
+__all__ = ["data_to_text", "docs_to_data", "messages_to_text"]
diff --git a/langflow/src/backend/base/langflow/helpers/base_model.py b/langflow/src/backend/base/langflow/helpers/base_model.py
new file mode 100644
index 0000000..87b4d64
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/base_model.py
@@ -0,0 +1,71 @@
+from typing import Any, TypedDict
+
+from pydantic import BaseModel as PydanticBaseModel
+from pydantic import ConfigDict, Field, create_model
+
+TRUE_VALUES = ["true", "1", "t", "y", "yes"]
+
+
+class SchemaField(TypedDict):
+ name: str
+ type: str
+ description: str
+ multiple: bool
+
+
+class BaseModel(PydanticBaseModel):
+ model_config = ConfigDict(populate_by_name=True)
+
+
+def _get_type_annotation(type_str: str, *, multiple: bool) -> type:
+ type_mapping = {
+ "str": str,
+ "int": int,
+ "float": float,
+ "bool": bool,
+ "boolean": bool,
+ "list": list[Any],
+ "dict": dict[str, Any],
+ "number": float,
+ "text": str,
+ }
+ try:
+ base_type = type_mapping[type_str]
+ except KeyError as e:
+ msg = f"Invalid type: {type_str}"
+ raise ValueError(msg) from e
+ if multiple:
+ return list[base_type] # type: ignore[valid-type]
+ return base_type # type: ignore[return-value]
+
+
+def build_model_from_schema(schema: list[SchemaField]) -> type[PydanticBaseModel]:
+ fields = {}
+ for field in schema:
+ field_name = field["name"]
+ field_type_str = field["type"]
+ description = field.get("description", "")
+ multiple = field.get("multiple", False)
+ multiple = coalesce_bool(multiple)
+ field_type_annotation = _get_type_annotation(field_type_str, multiple=multiple)
+ fields[field_name] = (field_type_annotation, Field(description=description))
+ return create_model("OutputModel", **fields)
+
+
+def coalesce_bool(value: Any) -> bool:
+ """Coalesces the given value into a boolean.
+
+ Args:
+ value (Any): The value to be coalesced.
+
+ Returns:
+ bool: The coalesced boolean value.
+
+ """
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, str):
+ return value.lower() in TRUE_VALUES
+ if isinstance(value, int):
+ return bool(value)
+ return False
diff --git a/langflow/src/backend/base/langflow/helpers/custom.py b/langflow/src/backend/base/langflow/helpers/custom.py
new file mode 100644
index 0000000..225c124
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/custom.py
@@ -0,0 +1,13 @@
+from typing import Any
+
+
+def format_type(type_: Any) -> str:
+ if type_ is str:
+ type_ = "Text"
+ elif hasattr(type_, "__name__"):
+ type_ = type_.__name__
+ elif hasattr(type_, "__class__"):
+ type_ = type_.__class__.__name__
+ else:
+ type_ = str(type_)
+ return type_
diff --git a/langflow/src/backend/base/langflow/helpers/data.py b/langflow/src/backend/base/langflow/helpers/data.py
new file mode 100644
index 0000000..6482ebb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/data.py
@@ -0,0 +1,141 @@
+from collections import defaultdict
+
+from langchain_core.documents import Document
+
+from langflow.schema import Data
+from langflow.schema.message import Message
+
+
+def docs_to_data(documents: list[Document]) -> list[Data]:
+ """Converts a list of Documents to a list of Data.
+
+ Args:
+ documents (list[Document]): The list of Documents to convert.
+
+ Returns:
+ list[Data]: The converted list of Data.
+ """
+ return [Data.from_document(document) for document in documents]
+
+
+def data_to_text_list(template: str, data: Data | list[Data]) -> tuple[list[str], list[Data]]:
+ """Format text from Data objects using a template string.
+
+ This function processes Data objects and formats their content using a template string.
+ It handles various data structures and ensures consistent text formatting across different
+ input types.
+
+ Key Features:
+ - Supports single Data object or list of Data objects
+ - Handles nested dictionaries and extracts text from various locations
+ - Uses safe string formatting with fallback for missing keys
+ - Preserves original Data objects in output
+
+ Args:
+ template: Format string with placeholders (e.g., "Hello {text}")
+ Placeholders are replaced with values from Data objects
+ data: Either a single Data object or a list of Data objects to format
+ Each object can contain text, dictionaries, or nested data
+
+ Returns:
+ A tuple containing:
+ - List[str]: Formatted strings based on the template
+ - List[Data]: Original Data objects in the same order
+
+ Raises:
+ ValueError: If template is None
+ TypeError: If template is not a string
+
+ Examples:
+ >>> result = data_to_text_list("Hello {text}", Data(text="world"))
+ >>> assert result == (["Hello world"], [Data(text="world")])
+
+ >>> result = data_to_text_list(
+ ... "{name} is {age}",
+ ... Data(data={"name": "Alice", "age": 25})
+ ... )
+ >>> assert result == (["Alice is 25"], [Data(data={"name": "Alice", "age": 25})])
+ """
+ if data is None:
+ return [], []
+
+ if template is None:
+ msg = "Template must be a string, but got None."
+ raise ValueError(msg)
+
+ if not isinstance(template, str):
+ msg = f"Template must be a string, but got {type(template)}"
+ raise TypeError(msg)
+
+ formatted_text: list[str] = []
+ processed_data: list[Data] = []
+
+ data_list = [data] if isinstance(data, Data) else data
+
+ data_objects = [item if isinstance(item, Data) else Data(text=str(item)) for item in data_list]
+
+ for data_obj in data_objects:
+ format_dict = {}
+
+ if isinstance(data_obj.data, dict):
+ format_dict.update(data_obj.data)
+
+ if isinstance(data_obj.data.get("data"), dict):
+ format_dict.update(data_obj.data["data"])
+
+ elif format_dict.get("error"):
+ format_dict["text"] = format_dict["error"]
+
+ format_dict["data"] = data_obj.data
+
+ safe_dict = defaultdict(str, format_dict)
+
+ try:
+ formatted_text.append(template.format_map(safe_dict))
+ processed_data.append(data_obj)
+ except ValueError as e:
+ msg = f"Error formatting template: {e!s}"
+ raise ValueError(msg) from e
+
+ return formatted_text, processed_data
+
+
+def data_to_text(template: str, data: Data | list[Data], sep: str = "\n") -> str:
+ r"""Converts data into a formatted text string based on a given template.
+
+ Args:
+ template (str): The template string used to format each data item.
+ data (Data | list[Data]): A single data item or a list of data items to be formatted.
+ sep (str, optional): The separator to use between formatted data items. Defaults to "\n".
+
+ Returns:
+ str: A string containing the formatted data items separated by the specified separator.
+ """
+ formatted_text, _ = data_to_text_list(template, data)
+ sep = "\n" if sep is None else sep
+ return sep.join(formatted_text)
+
+
+def messages_to_text(template: str, messages: Message | list[Message]) -> str:
+ """Converts a list of Messages to a list of texts.
+
+ Args:
+ template (str): The template to use for the conversion.
+ messages (list[Message]): The list of Messages to convert.
+
+ Returns:
+ list[str]: The converted list of texts.
+ """
+ if isinstance(messages, (Message)):
+ messages = [messages]
+ # Check if there are any format strings in the template
+ messages_ = []
+ for message in messages:
+ # If it is not a message, create one with the key "text"
+ if not isinstance(message, Message):
+ msg = "All elements in the list must be of type Message."
+ raise TypeError(msg)
+ messages_.append(message)
+
+ formated_messages = [template.format(data=message.model_dump(), **message.model_dump()) for message in messages_]
+ return "\n".join(formated_messages)
diff --git a/langflow/src/backend/base/langflow/helpers/flow.py b/langflow/src/backend/base/langflow/helpers/flow.py
new file mode 100644
index 0000000..6846c34
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/flow.py
@@ -0,0 +1,360 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, cast
+from uuid import UUID
+
+from fastapi import HTTPException
+from loguru import logger
+from pydantic.v1 import BaseModel, Field, create_model
+from sqlmodel import select
+
+from langflow.schema.schema import INPUT_FIELD_NAME
+from langflow.services.database.models.flow import Flow
+from langflow.services.database.models.flow.model import FlowRead
+from langflow.services.deps import get_settings_service, session_scope
+
+if TYPE_CHECKING:
+ from collections.abc import Awaitable, Callable
+
+ from langflow.graph.graph.base import Graph
+ from langflow.graph.schema import RunOutputs
+ from langflow.graph.vertex.base import Vertex
+ from langflow.schema import Data
+
+INPUT_TYPE_MAP = {
+ "ChatInput": {"type_hint": "Optional[str]", "default": '""'},
+ "TextInput": {"type_hint": "Optional[str]", "default": '""'},
+ "JSONInput": {"type_hint": "Optional[dict]", "default": "{}"},
+}
+
+
+async def list_flows(*, user_id: str | None = None) -> list[Data]:
+ if not user_id:
+ msg = "Session is invalid"
+ raise ValueError(msg)
+ try:
+ async with session_scope() as session:
+ uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id
+ stmt = select(Flow).where(Flow.user_id == uuid_user_id).where(Flow.is_component == False) # noqa: E712
+ flows = (await session.exec(stmt)).all()
+
+ return [flow.to_data() for flow in flows]
+ except Exception as e:
+ msg = f"Error listing flows: {e}"
+ raise ValueError(msg) from e
+
+
+async def load_flow(
+ user_id: str, flow_id: str | None = None, flow_name: str | None = None, tweaks: dict | None = None
+) -> Graph:
+ from langflow.graph.graph.base import Graph
+ from langflow.processing.process import process_tweaks
+
+ if not flow_id and not flow_name:
+ msg = "Flow ID or Flow Name is required"
+ raise ValueError(msg)
+ if not flow_id and flow_name:
+ flow_id = await find_flow(flow_name, user_id)
+ if not flow_id:
+ msg = f"Flow {flow_name} not found"
+ raise ValueError(msg)
+
+ async with session_scope() as session:
+ graph_data = flow.data if (flow := await session.get(Flow, flow_id)) else None
+ if not graph_data:
+ msg = f"Flow {flow_id} not found"
+ raise ValueError(msg)
+ if tweaks:
+ graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks)
+ return Graph.from_payload(graph_data, flow_id=flow_id, user_id=user_id)
+
+
+async def find_flow(flow_name: str, user_id: str) -> str | None:
+ async with session_scope() as session:
+ uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id
+ stmt = select(Flow).where(Flow.name == flow_name).where(Flow.user_id == uuid_user_id)
+ flow = (await session.exec(stmt)).first()
+ return flow.id if flow else None
+
+
+async def run_flow(
+ inputs: dict | list[dict] | None = None,
+ tweaks: dict | None = None,
+ flow_id: str | None = None,
+ flow_name: str | None = None,
+ output_type: str | None = "chat",
+ user_id: str | None = None,
+ run_id: str | None = None,
+ session_id: str | None = None,
+ graph: Graph | None = None,
+) -> list[RunOutputs]:
+ if user_id is None:
+ msg = "Session is invalid"
+ raise ValueError(msg)
+ if graph is None:
+ graph = await load_flow(user_id, flow_id, flow_name, tweaks)
+ if run_id:
+ graph.set_run_id(UUID(run_id))
+ if session_id:
+ graph.session_id = session_id
+ if user_id:
+ graph.user_id = user_id
+
+ if inputs is None:
+ inputs = []
+ if isinstance(inputs, dict):
+ inputs = [inputs]
+ inputs_list = []
+ inputs_components = []
+ types = []
+ for input_dict in inputs:
+ inputs_list.append({INPUT_FIELD_NAME: cast("str", input_dict.get("input_value"))})
+ inputs_components.append(input_dict.get("components", []))
+ types.append(input_dict.get("type", "chat"))
+
+ outputs = [
+ vertex.id
+ for vertex in graph.vertices
+ if output_type == "debug"
+ or (
+ vertex.is_output and (output_type == "any" or output_type in vertex.id.lower()) # type: ignore[operator]
+ )
+ ]
+
+ fallback_to_env_vars = get_settings_service().settings.fallback_to_env_var
+
+ return await graph.arun(
+ inputs_list,
+ outputs=outputs,
+ inputs_components=inputs_components,
+ types=types,
+ fallback_to_env_vars=fallback_to_env_vars,
+ )
+
+
+def generate_function_for_flow(
+ inputs: list[Vertex], flow_id: str, user_id: str | UUID | None
+) -> Callable[..., Awaitable[Any]]:
+ """Generate a dynamic flow function based on the given inputs and flow ID.
+
+ Args:
+ inputs (List[Vertex]): The list of input vertices for the flow.
+ flow_id (str): The ID of the flow.
+ user_id (str | UUID | None): The user ID associated with the flow.
+
+ Returns:
+ Coroutine: The dynamic flow function.
+
+ Raises:
+ None
+
+ Example:
+ inputs = [vertex1, vertex2]
+ flow_id = "my_flow"
+ function = generate_function_for_flow(inputs, flow_id)
+ result = function(input1, input2)
+ """
+ # Prepare function arguments with type hints and default values
+ args = [
+ (
+ f"{input_.display_name.lower().replace(' ', '_')}: {INPUT_TYPE_MAP[input_.base_name]['type_hint']} = "
+ f"{INPUT_TYPE_MAP[input_.base_name]['default']}"
+ )
+ for input_ in inputs
+ ]
+
+ # Maintain original argument names for constructing the tweaks dictionary
+ original_arg_names = [input_.display_name for input_ in inputs]
+
+ # Prepare a Pythonic, valid function argument string
+ func_args = ", ".join(args)
+
+ # Map original argument names to their corresponding Pythonic variable names in the function
+ arg_mappings = ", ".join(
+ f'"{original_name}": {name}'
+ for original_name, name in zip(original_arg_names, [arg.split(":")[0] for arg in args], strict=True)
+ )
+
+ func_body = f"""
+from typing import Optional
+async def flow_function({func_args}):
+ tweaks = {{ {arg_mappings} }}
+ from langflow.helpers.flow import run_flow
+ from langchain_core.tools import ToolException
+ from langflow.base.flow_processing.utils import build_data_from_result_data, format_flow_output_data
+ try:
+ run_outputs = await run_flow(
+ tweaks={{key: {{'input_value': value}} for key, value in tweaks.items()}},
+ flow_id="{flow_id}",
+ user_id="{user_id}"
+ )
+ if not run_outputs:
+ return []
+ run_output = run_outputs[0]
+
+ data = []
+ if run_output is not None:
+ for output in run_output.outputs:
+ if output:
+ data.extend(build_data_from_result_data(output))
+ return format_flow_output_data(data)
+ except Exception as e:
+ raise ToolException(f'Error running flow: ' + e)
+"""
+
+ compiled_func = compile(func_body, "", "exec")
+ local_scope: dict = {}
+ exec(compiled_func, globals(), local_scope) # noqa: S102
+ return local_scope["flow_function"]
+
+
+def build_function_and_schema(
+ flow_data: Data, graph: Graph, user_id: str | UUID | None
+) -> tuple[Callable[..., Awaitable[Any]], type[BaseModel]]:
+ """Builds a dynamic function and schema for a given flow.
+
+ Args:
+ flow_data (Data): The flow record containing information about the flow.
+ graph (Graph): The graph representing the flow.
+ user_id (str): The user ID associated with the flow.
+
+ Returns:
+ Tuple[Callable, BaseModel]: A tuple containing the dynamic function and the schema.
+ """
+ flow_id = flow_data.id
+ inputs = get_flow_inputs(graph)
+ dynamic_flow_function = generate_function_for_flow(inputs, flow_id, user_id=user_id)
+ schema = build_schema_from_inputs(flow_data.name, inputs)
+ return dynamic_flow_function, schema
+
+
+def get_flow_inputs(graph: Graph) -> list[Vertex]:
+ """Retrieves the flow inputs from the given graph.
+
+ Args:
+ graph (Graph): The graph object representing the flow.
+
+ Returns:
+ List[Data]: A list of input data, where each record contains the ID, name, and description of the input vertex.
+ """
+ return [vertex for vertex in graph.vertices if vertex.is_input]
+
+
+def build_schema_from_inputs(name: str, inputs: list[Vertex]) -> type[BaseModel]:
+ """Builds a schema from the given inputs.
+
+ Args:
+ name (str): The name of the schema.
+ inputs (List[tuple[str, str, str]]): A list of tuples representing the inputs.
+ Each tuple contains three elements: the input name, the input type, and the input description.
+
+ Returns:
+ BaseModel: The schema model.
+
+ """
+ fields = {}
+ for input_ in inputs:
+ field_name = input_.display_name.lower().replace(" ", "_")
+ description = input_.description
+ fields[field_name] = (str, Field(default="", description=description))
+ return create_model(name, **fields)
+
+
+def get_arg_names(inputs: list[Vertex]) -> list[dict[str, str]]:
+ """Returns a list of dictionaries containing the component name and its corresponding argument name.
+
+ Args:
+ inputs (List[Vertex]): A list of Vertex objects representing the inputs.
+
+ Returns:
+ List[dict[str, str]]: A list of dictionaries, where each dictionary contains the component name and its
+ argument name.
+ """
+ return [
+ {"component_name": input_.display_name, "arg_name": input_.display_name.lower().replace(" ", "_")}
+ for input_ in inputs
+ ]
+
+
+async def get_flow_by_id_or_endpoint_name(flow_id_or_name: str, user_id: str | UUID | None = None) -> FlowRead | None:
+ async with session_scope() as session:
+ endpoint_name = None
+ try:
+ flow_id = UUID(flow_id_or_name)
+ flow = await session.get(Flow, flow_id)
+ except ValueError:
+ endpoint_name = flow_id_or_name
+ stmt = select(Flow).where(Flow.endpoint_name == endpoint_name)
+ if user_id:
+ uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id
+ stmt = stmt.where(Flow.user_id == uuid_user_id)
+ flow = (await session.exec(stmt)).first()
+ if flow is None:
+ raise HTTPException(status_code=404, detail=f"Flow identifier {flow_id_or_name} not found")
+ return FlowRead.model_validate(flow, from_attributes=True)
+
+
+async def generate_unique_flow_name(flow_name, user_id, session):
+ original_name = flow_name
+ n = 1
+ while True:
+ # Check if a flow with the given name exists
+ existing_flow = (
+ await session.exec(
+ select(Flow).where(
+ Flow.name == flow_name,
+ Flow.user_id == user_id,
+ )
+ )
+ ).first()
+
+ # If no flow with the given name exists, return the name
+ if not existing_flow:
+ return flow_name
+
+ # If a flow with the name already exists, append (n) to the name and increment n
+ flow_name = f"{original_name} ({n})"
+ n += 1
+
+
+def json_schema_from_flow(flow: Flow) -> dict:
+ """Generate JSON schema from flow input nodes."""
+ from langflow.graph.graph.base import Graph
+
+ # Get the flow's data which contains the nodes and their configurations
+ flow_data = flow.data or {}
+
+ graph = Graph.from_payload(flow_data)
+ input_nodes = [vertex for vertex in graph.vertices if vertex.is_input]
+
+ properties = {}
+ required = []
+ for node in input_nodes:
+ node_data = node.data["node"]
+ template = node_data["template"]
+
+ for field_name, field_data in template.items():
+ if field_data != "Component" and field_data.get("show", False) and not field_data.get("advanced", False):
+ field_type = field_data.get("type", "string")
+ properties[field_name] = {
+ "type": field_type,
+ "description": field_data.get("info", f"Input for {field_name}"),
+ }
+ # Update field_type in properties after determining the JSON Schema type
+ if field_type == "str":
+ field_type = "string"
+ elif field_type == "int":
+ field_type = "integer"
+ elif field_type == "float":
+ field_type = "number"
+ elif field_type == "bool":
+ field_type = "boolean"
+ else:
+ logger.warning(f"Unknown field type: {field_type} defaulting to string")
+ field_type = "string"
+ properties[field_name]["type"] = field_type
+
+ if field_data.get("required", False):
+ required.append(field_name)
+
+ return {"type": "object", "properties": properties, "required": required}
diff --git a/langflow/src/backend/base/langflow/helpers/folders.py b/langflow/src/backend/base/langflow/helpers/folders.py
new file mode 100644
index 0000000..be13b0d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/folders.py
@@ -0,0 +1,26 @@
+from sqlalchemy import select
+
+from langflow.services.database.models.folder.model import Folder
+
+
+async def generate_unique_folder_name(folder_name, user_id, session):
+ original_name = folder_name
+ n = 1
+ while True:
+ # Check if a folder with the given name exists
+ existing_folder = (
+ await session.exec(
+ select(Folder).where(
+ Folder.name == folder_name,
+ Folder.user_id == user_id,
+ )
+ )
+ ).first()
+
+ # If no folder with the given name exists, return the name
+ if not existing_folder:
+ return folder_name
+
+ # If a folder with the name already exists, append (n) to the name and increment n
+ folder_name = f"{original_name} ({n})"
+ n += 1
diff --git a/langflow/src/backend/base/langflow/helpers/user.py b/langflow/src/backend/base/langflow/helpers/user.py
new file mode 100644
index 0000000..df0e3a1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/helpers/user.py
@@ -0,0 +1,27 @@
+from uuid import UUID
+
+from fastapi import HTTPException
+from sqlmodel import select
+
+from langflow.services.database.models.flow.model import Flow
+from langflow.services.database.models.user.model import User, UserRead
+from langflow.services.deps import get_db_service
+
+
+async def get_user_by_flow_id_or_endpoint_name(flow_id_or_name: str) -> UserRead | None:
+ async with get_db_service().with_session() as session:
+ try:
+ flow_id = UUID(flow_id_or_name)
+ flow = await session.get(Flow, flow_id)
+ except ValueError:
+ stmt = select(Flow).where(Flow.endpoint_name == flow_id_or_name)
+ flow = (await session.exec(stmt)).first()
+
+ if flow is None:
+ raise HTTPException(status_code=404, detail=f"Flow identifier {flow_id_or_name} not found")
+
+ user = await session.get(User, flow.user_id)
+ if user is None:
+ raise HTTPException(status_code=404, detail=f"User for flow {flow_id_or_name} not found")
+
+ return UserRead.model_validate(user, from_attributes=True)
diff --git a/langflow/src/backend/base/langflow/initial_setup/__init__.py b/langflow/src/backend/base/langflow/initial_setup/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/backend/base/langflow/initial_setup/constants.py b/langflow/src/backend/base/langflow/initial_setup/constants.py
new file mode 100644
index 0000000..b84cb90
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/constants.py
@@ -0,0 +1,2 @@
+STARTER_FOLDER_NAME = "Starter Projects"
+STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
diff --git a/langflow/src/backend/base/langflow/initial_setup/load.py b/langflow/src/backend/base/langflow/initial_setup/load.py
new file mode 100644
index 0000000..081453f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/load.py
@@ -0,0 +1,25 @@
+from .starter_projects import (
+ basic_prompting_graph,
+ blog_writer_graph,
+ document_qa_graph,
+ hierarchical_tasks_agent_graph,
+ memory_chatbot_graph,
+ sequential_tasks_agent_graph,
+ vector_store_rag_graph,
+)
+
+
+def get_starter_projects_graphs():
+ return [
+ basic_prompting_graph(),
+ blog_writer_graph(),
+ document_qa_graph(),
+ memory_chatbot_graph(),
+ vector_store_rag_graph(),
+ sequential_tasks_agent_graph(),
+ hierarchical_tasks_agent_graph(),
+ ]
+
+
+def get_starter_projects_dump():
+ return [g.dump() for g in get_starter_projects_graphs()]
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-01.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-01.svg
new file mode 100644
index 0000000..62f3c1d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-01.svg
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-02.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-02.svg
new file mode 100644
index 0000000..6a1e32a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-02.svg
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-03.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-03.svg
new file mode 100644
index 0000000..df46762
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-03.svg
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-04.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-04.svg
new file mode 100644
index 0000000..bd4dcdc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-04.svg
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-05.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-05.svg
new file mode 100644
index 0000000..22fdcd4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-05.svg
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-06.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-06.svg
new file mode 100644
index 0000000..9d45fb8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-06.svg
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-07.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-07.svg
new file mode 100644
index 0000000..d9800b2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-07.svg
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-08.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-08.svg
new file mode 100644
index 0000000..58e755d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-08.svg
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-09.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-09.svg
new file mode 100644
index 0000000..a498bfb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-09.svg
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-10.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-10.svg
new file mode 100644
index 0000000..93ced75
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-10.svg
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-11.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-11.svg
new file mode 100644
index 0000000..578c244
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-11.svg
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-12.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-12.svg
new file mode 100644
index 0000000..373031e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-12.svg
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-13.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-13.svg
new file mode 100644
index 0000000..0a0523d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-13.svg
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-14.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-14.svg
new file mode 100644
index 0000000..5c0672f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-14.svg
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-15.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-15.svg
new file mode 100644
index 0000000..7c6159c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-15.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-16.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-16.svg
new file mode 100644
index 0000000..9638967
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-16.svg
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-17.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-17.svg
new file mode 100644
index 0000000..dc8aa54
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-17.svg
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-18.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-18.svg
new file mode 100644
index 0000000..33daef8
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-18.svg
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-19.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-19.svg
new file mode 100644
index 0000000..f3a36ec
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-19.svg
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-20.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-20.svg
new file mode 100644
index 0000000..a53a56a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-20.svg
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-21.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-21.svg
new file mode 100644
index 0000000..2cb2fcb
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-21.svg
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-22.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-22.svg
new file mode 100644
index 0000000..9c2923e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-22.svg
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-23.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-23.svg
new file mode 100644
index 0000000..111f30e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-23.svg
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-24.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-24.svg
new file mode 100644
index 0000000..fd3b0ed
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-24.svg
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-25.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-25.svg
new file mode 100644
index 0000000..f5df7a6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-25.svg
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-26.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-26.svg
new file mode 100644
index 0000000..5d2d1d2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-26.svg
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-27.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-27.svg
new file mode 100644
index 0000000..805d92a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-01-27.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-01.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-01.svg
new file mode 100644
index 0000000..6c52670
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-01.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-02.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-02.svg
new file mode 100644
index 0000000..812919c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-02.svg
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-03.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-03.svg
new file mode 100644
index 0000000..c228bec
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-03.svg
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-04.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-04.svg
new file mode 100644
index 0000000..2446760
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-04.svg
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-05.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-05.svg
new file mode 100644
index 0000000..ffe3ec4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-05.svg
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-06.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-06.svg
new file mode 100644
index 0000000..48ab569
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-06.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-07.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-07.svg
new file mode 100644
index 0000000..4aa9f92
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-07.svg
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-08.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-08.svg
new file mode 100644
index 0000000..4cb7cba
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-08.svg
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-09.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-09.svg
new file mode 100644
index 0000000..9274750
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-09.svg
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-10.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-10.svg
new file mode 100644
index 0000000..5edf770
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-10.svg
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-11.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-11.svg
new file mode 100644
index 0000000..2973381
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-11.svg
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-12.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-12.svg
new file mode 100644
index 0000000..07ee182
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-12.svg
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-13.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-13.svg
new file mode 100644
index 0000000..9ab2fd5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-13.svg
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-14.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-14.svg
new file mode 100644
index 0000000..99453ce
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-14.svg
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-15.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-15.svg
new file mode 100644
index 0000000..7e65d9d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-15.svg
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-16.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-16.svg
new file mode 100644
index 0000000..add07c2
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-16.svg
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-17.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-17.svg
new file mode 100644
index 0000000..d069c00
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-17.svg
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-18.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-18.svg
new file mode 100644
index 0000000..8ca1898
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-18.svg
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-19.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-19.svg
new file mode 100644
index 0000000..1da4dd5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-19.svg
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-20.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-20.svg
new file mode 100644
index 0000000..f3cadd3
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-20.svg
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-21.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-21.svg
new file mode 100644
index 0000000..72d24ed
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-21.svg
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-22.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-22.svg
new file mode 100644
index 0000000..31976af
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-22.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-23.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-23.svg
new file mode 100644
index 0000000..8ca26c7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-23.svg
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-24.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-24.svg
new file mode 100644
index 0000000..70c5f9a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-24.svg
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-25.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-25.svg
new file mode 100644
index 0000000..0b06acf
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-25.svg
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-26.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-26.svg
new file mode 100644
index 0000000..efe31a6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-26.svg
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-27.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-27.svg
new file mode 100644
index 0000000..6be7092
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/People/People Avatar-02-27.svg
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/026-alien.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/026-alien.svg
new file mode 100644
index 0000000..a897a78
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/026-alien.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/027-satellite.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/027-satellite.svg
new file mode 100644
index 0000000..2b3d755
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/027-satellite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/028-alien.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/028-alien.svg
new file mode 100644
index 0000000..d9de6b1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/028-alien.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/029-telescope.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/029-telescope.svg
new file mode 100644
index 0000000..9fdb0bc
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/029-telescope.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/030-books.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/030-books.svg
new file mode 100644
index 0000000..4cb73ec
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/030-books.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/031-planet.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/031-planet.svg
new file mode 100644
index 0000000..243c6a4
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/031-planet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/032-constellation.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/032-constellation.svg
new file mode 100644
index 0000000..57b370a
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/032-constellation.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/033-planet.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/033-planet.svg
new file mode 100644
index 0000000..9b9b8c9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/033-planet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/034-alien.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/034-alien.svg
new file mode 100644
index 0000000..c1f232e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/034-alien.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/035-globe.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/035-globe.svg
new file mode 100644
index 0000000..de37ab0
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/035-globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/036-eclipse.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/036-eclipse.svg
new file mode 100644
index 0000000..b3a217c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/036-eclipse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/037-meteor.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/037-meteor.svg
new file mode 100644
index 0000000..57bb861
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/037-meteor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/038-eclipse.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/038-eclipse.svg
new file mode 100644
index 0000000..400ecd7
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/038-eclipse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/039-Asteroid.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/039-Asteroid.svg
new file mode 100644
index 0000000..fd89424
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/039-Asteroid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/040-mission.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/040-mission.svg
new file mode 100644
index 0000000..003e0ef
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/040-mission.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/041-spaceship.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/041-spaceship.svg
new file mode 100644
index 0000000..d1164b1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/041-spaceship.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/042-space shuttle.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/042-space shuttle.svg
new file mode 100644
index 0000000..03a5894
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/042-space shuttle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/043-space shuttle.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/043-space shuttle.svg
new file mode 100644
index 0000000..841d226
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/043-space shuttle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/044-rocket.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/044-rocket.svg
new file mode 100644
index 0000000..7a069e5
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/044-rocket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/045-astronaut.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/045-astronaut.svg
new file mode 100644
index 0000000..c69214e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/045-astronaut.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/046-rocket.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/046-rocket.svg
new file mode 100644
index 0000000..65e523f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/046-rocket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/047-computer.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/047-computer.svg
new file mode 100644
index 0000000..912c33f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/047-computer.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/048-satellite.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/048-satellite.svg
new file mode 100644
index 0000000..1f0bfb9
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/048-satellite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/049-astronaut.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/049-astronaut.svg
new file mode 100644
index 0000000..c9c582d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/049-astronaut.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/050-space robot.svg b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/050-space robot.svg
new file mode 100644
index 0000000..13d034c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/profile_pictures/Space/050-space robot.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/setup.py b/langflow/src/backend/base/langflow/initial_setup/setup.py
new file mode 100644
index 0000000..b0ea404
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/setup.py
@@ -0,0 +1,980 @@
+import asyncio
+import copy
+import io
+import json
+import os
+import re
+import shutil
+import zipfile
+from collections import defaultdict
+from copy import deepcopy
+from datetime import datetime, timezone
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from typing import AnyStr
+from uuid import UUID
+
+import anyio
+import httpx
+import orjson
+import sqlalchemy as sa
+from aiofile import async_open
+from emoji import demojize, purely_emoji
+from loguru import logger
+from sqlalchemy.exc import NoResultFound
+from sqlalchemy.orm import selectinload
+from sqlmodel import select
+from sqlmodel.ext.asyncio.session import AsyncSession
+
+from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES, ORJSON_OPTIONS
+from langflow.initial_setup.constants import STARTER_FOLDER_DESCRIPTION, STARTER_FOLDER_NAME
+from langflow.services.auth.utils import create_super_user
+from langflow.services.database.models.flow.model import Flow, FlowCreate
+from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
+from langflow.services.database.models.folder.model import Folder, FolderCreate, FolderRead
+from langflow.services.database.models.user.crud import get_user_by_username
+from langflow.services.deps import get_settings_service, get_storage_service, get_variable_service, session_scope
+from langflow.template.field.prompt import DEFAULT_PROMPT_INTUT_TYPES
+from langflow.utils.util import escape_json_dump
+
+# In the folder ./starter_projects we have a few JSON files that represent
+# starter projects. We want to load these into the database so that users
+# can use them as a starting point for their own projects.
+
+
+def update_projects_components_with_latest_component_versions(project_data, all_types_dict):
+ # Flatten the all_types_dict for easy access
+ all_types_dict_flat = {}
+ for category in all_types_dict.values():
+ for key, component in category.items():
+ all_types_dict_flat[key] = component # noqa: PERF403
+
+ node_changes_log = defaultdict(list)
+ project_data_copy = deepcopy(project_data)
+
+ for node in project_data_copy.get("nodes", []):
+ node_data = node.get("data").get("node")
+ node_type = node.get("data").get("type")
+
+ # Skip updating if tool_mode is True
+ if node_data.get("tool_mode", False):
+ continue
+
+ # Skip nodes with outputs of the specified format
+ # NOTE: to account for the fact that the Simple Agent has dynamic outputs
+ if any(output.get("types") == ["Tool"] for output in node_data.get("outputs", [])):
+ continue
+
+ if node_type in all_types_dict_flat:
+ latest_node = all_types_dict_flat.get(node_type)
+ latest_template = latest_node.get("template")
+ node_data["template"]["code"] = latest_template["code"]
+
+ if "outputs" in latest_node:
+ node_data["outputs"] = latest_node["outputs"]
+ if node_data["template"]["_type"] != latest_template["_type"]:
+ node_data["template"]["_type"] = latest_template["_type"]
+ if node_type != "Prompt":
+ node_data["template"] = latest_template
+ else:
+ for key, value in latest_template.items():
+ if key not in node_data["template"]:
+ node_changes_log[node_type].append(
+ {
+ "attr": key,
+ "old_value": None,
+ "new_value": value,
+ }
+ )
+ node_data["template"][key] = value
+ elif isinstance(value, dict) and value.get("value"):
+ node_changes_log[node_type].append(
+ {
+ "attr": key,
+ "old_value": node_data["template"][key],
+ "new_value": value,
+ }
+ )
+ node_data["template"][key]["value"] = value["value"]
+ for key in node_data["template"]:
+ if key not in latest_template:
+ node_data["template"][key]["input_types"] = DEFAULT_PROMPT_INTUT_TYPES
+ node_changes_log[node_type].append(
+ {
+ "attr": "_type",
+ "old_value": node_data["template"]["_type"],
+ "new_value": latest_template["_type"],
+ }
+ )
+ else:
+ for attr in NODE_FORMAT_ATTRIBUTES:
+ if (
+ attr in latest_node
+ # Check if it needs to be updated
+ and latest_node[attr] != node_data.get(attr)
+ ):
+ node_changes_log[node_type].append(
+ {
+ "attr": attr,
+ "old_value": node_data.get(attr),
+ "new_value": latest_node[attr],
+ }
+ )
+ node_data[attr] = latest_node[attr]
+
+ for field_name, field_dict in latest_template.items():
+ if field_name not in node_data["template"]:
+ node_data["template"][field_name] = field_dict
+ continue
+ # The idea here is to update some attributes of the field
+ to_check_attributes = FIELD_FORMAT_ATTRIBUTES
+ for attr in to_check_attributes:
+ if (
+ attr in field_dict
+ and attr in node_data["template"].get(field_name)
+ # Check if it needs to be updated
+ and field_dict[attr] != node_data["template"][field_name][attr]
+ ):
+ node_changes_log[node_type].append(
+ {
+ "attr": f"{field_name}.{attr}",
+ "old_value": node_data["template"][field_name][attr],
+ "new_value": field_dict[attr],
+ }
+ )
+ node_data["template"][field_name][attr] = field_dict[attr]
+ # Remove fields that are not in the latest template
+ if node_type != "Prompt":
+ for field_name in list(node_data["template"].keys()):
+ if field_name not in latest_template:
+ node_data["template"].pop(field_name)
+ log_node_changes(node_changes_log)
+ return project_data_copy
+
+
+def scape_json_parse(json_string: str) -> dict:
+ if isinstance(json_string, dict):
+ return json_string
+ parsed_string = json_string.replace("œ", '"')
+ return json.loads(parsed_string)
+
+
+def update_new_output(data):
+ nodes = copy.deepcopy(data["nodes"])
+ edges = copy.deepcopy(data["edges"])
+
+ for edge in edges:
+ if "sourceHandle" in edge and "targetHandle" in edge:
+ new_source_handle = scape_json_parse(edge["sourceHandle"])
+ new_target_handle = scape_json_parse(edge["targetHandle"])
+ id_ = new_source_handle["id"]
+ source_node_index = next((index for (index, d) in enumerate(nodes) if d["id"] == id_), -1)
+ source_node = nodes[source_node_index] if source_node_index != -1 else None
+
+ if "baseClasses" in new_source_handle:
+ if "output_types" not in new_source_handle:
+ if source_node and "node" in source_node["data"] and "output_types" in source_node["data"]["node"]:
+ new_source_handle["output_types"] = source_node["data"]["node"]["output_types"]
+ else:
+ new_source_handle["output_types"] = new_source_handle["baseClasses"]
+ del new_source_handle["baseClasses"]
+
+ if new_target_handle.get("inputTypes"):
+ intersection = [
+ type_ for type_ in new_source_handle["output_types"] if type_ in new_target_handle["inputTypes"]
+ ]
+ else:
+ intersection = [
+ type_ for type_ in new_source_handle["output_types"] if type_ == new_target_handle["type"]
+ ]
+
+ selected = intersection[0] if intersection else None
+ if "name" not in new_source_handle:
+ new_source_handle["name"] = " | ".join(new_source_handle["output_types"])
+ new_source_handle["output_types"] = [selected] if selected else []
+
+ if source_node and not source_node["data"]["node"].get("outputs"):
+ if "outputs" not in source_node["data"]["node"]:
+ source_node["data"]["node"]["outputs"] = []
+ types = source_node["data"]["node"].get(
+ "output_types", source_node["data"]["node"].get("base_classes", [])
+ )
+ if not any(output.get("selected") == selected for output in source_node["data"]["node"]["outputs"]):
+ source_node["data"]["node"]["outputs"].append(
+ {
+ "types": types,
+ "selected": selected,
+ "name": " | ".join(types),
+ "display_name": " | ".join(types),
+ }
+ )
+ deduplicated_outputs = []
+ if source_node is None:
+ source_node = {"data": {"node": {"outputs": []}}}
+
+ for output in source_node["data"]["node"]["outputs"]:
+ if output["name"] not in [d["name"] for d in deduplicated_outputs]:
+ deduplicated_outputs.append(output)
+ source_node["data"]["node"]["outputs"] = deduplicated_outputs
+
+ edge["sourceHandle"] = escape_json_dump(new_source_handle)
+ edge["data"]["sourceHandle"] = new_source_handle
+ edge["data"]["targetHandle"] = new_target_handle
+ # The above sets the edges but some of the sourceHandles do not have valid name
+ # which can be found in the nodes. We need to update the sourceHandle with the
+ # name from node['data']['node']['outputs']
+ for node in nodes:
+ if "outputs" in node["data"]["node"]:
+ for output in node["data"]["node"]["outputs"]:
+ for edge in edges:
+ if node["id"] != edge["source"] or output.get("method") is None:
+ continue
+ source_handle = scape_json_parse(edge["sourceHandle"])
+ if source_handle["output_types"] == output.get("types") and source_handle["name"] != output["name"]:
+ source_handle["name"] = output["name"]
+ if isinstance(source_handle, str):
+ source_handle = scape_json_parse(source_handle)
+ edge["sourceHandle"] = escape_json_dump(source_handle)
+ edge["data"]["sourceHandle"] = source_handle
+
+ data_copy = copy.deepcopy(data)
+ data_copy["nodes"] = nodes
+ data_copy["edges"] = edges
+ return data_copy
+
+
+def update_edges_with_latest_component_versions(project_data):
+ """Update edges in a project with the latest component versions.
+
+ This function processes each edge in the project data and ensures that the source and target handles
+ are updated to match the latest component versions. It tracks all changes made to edges in a log
+ for debugging purposes.
+
+ Args:
+ project_data (dict): The project data containing nodes and edges to be updated.
+
+ Returns:
+ dict: A deep copy of the project data with updated edges.
+
+ The function performs the following operations:
+ 1. Creates a deep copy of the project data to avoid modifying the original
+ 2. For each edge, extracts and parses the source and target handles
+ 3. Finds the corresponding source and target nodes
+ 4. Updates output types in the source handle based on the node's outputs
+ 5. Updates input types in the target handle based on the node's template
+ 6. Escapes and updates the handles in the edge data
+ 7. Logs all changes made to the edges
+ """
+ # Initialize a dictionary to track changes for logging
+ edge_changes_log = defaultdict(list)
+ # Create a deep copy to avoid modifying the original data
+ project_data_copy = deepcopy(project_data)
+
+ # Create a mapping of node types to node IDs for node reconciliation
+ node_type_map = {}
+ for node in project_data_copy.get("nodes", []):
+ node_type = node.get("data", {}).get("type", "")
+ if node_type:
+ if node_type not in node_type_map:
+ node_type_map[node_type] = []
+ node_type_map[node_type].append(node.get("id"))
+
+ # Process each edge in the project
+ for edge in project_data_copy.get("edges", []):
+ # Extract and parse source and target handles
+ source_handle = edge.get("data", {}).get("sourceHandle")
+ source_handle = scape_json_parse(source_handle)
+ target_handle = edge.get("data", {}).get("targetHandle")
+ target_handle = scape_json_parse(target_handle)
+
+ # Find the corresponding source and target nodes
+ source_node = next(
+ (node for node in project_data.get("nodes", []) if node.get("id") == edge.get("source")),
+ None,
+ )
+ target_node = next(
+ (node for node in project_data.get("nodes", []) if node.get("id") == edge.get("target")),
+ None,
+ )
+
+ # Try to reconcile missing nodes by type
+ if source_node is None and source_handle and "dataType" in source_handle:
+ node_type = source_handle.get("dataType")
+ if node_type_map.get(node_type):
+ # Use the first node of matching type as replacement
+ new_node_id = node_type_map[node_type][0]
+ logger.info(f"Reconciling missing source node: replacing {edge.get('source')} with {new_node_id}")
+
+ # Update edge source
+ edge["source"] = new_node_id
+
+ # Update source handle ID
+ source_handle["id"] = new_node_id
+
+ # Find the new source node
+ source_node = next(
+ (node for node in project_data.get("nodes", []) if node.get("id") == new_node_id),
+ None,
+ )
+
+ # Update edge ID (complex as it contains encoded handles)
+ # This is a simplified approach - in production you'd need to parse and rebuild the ID
+ old_id_prefix = edge.get("id", "").split("{")[0]
+ if old_id_prefix:
+ new_id_prefix = old_id_prefix.replace(edge.get("source"), new_node_id)
+ edge["id"] = edge.get("id", "").replace(old_id_prefix, new_id_prefix)
+
+ if target_node is None and target_handle and "id" in target_handle:
+ # Extract node type from target handle ID (e.g., "AstraDBGraph-jr8pY" -> "AstraDBGraph")
+ id_parts = target_handle.get("id", "").split("-")
+ if len(id_parts) > 0:
+ node_type = id_parts[0]
+ if node_type_map.get(node_type):
+ # Use the first node of matching type as replacement
+ new_node_id = node_type_map[node_type][0]
+ logger.info(f"Reconciling missing target node: replacing {edge.get('target')} with {new_node_id}")
+
+ # Update edge target
+ edge["target"] = new_node_id
+
+ # Update target handle ID
+ target_handle["id"] = new_node_id
+
+ # Find the new target node
+ target_node = next(
+ (node for node in project_data.get("nodes", []) if node.get("id") == new_node_id),
+ None,
+ )
+
+ # Update edge ID (simplified approach)
+ old_id_suffix = edge.get("id", "").split("}-")[1] if "}-" in edge.get("id", "") else ""
+ if old_id_suffix:
+ new_id_suffix = old_id_suffix.replace(edge.get("target"), new_node_id)
+ edge["id"] = edge.get("id", "").replace(old_id_suffix, new_id_suffix)
+
+ if source_node and target_node:
+ # Extract node data for easier access
+ source_node_data = source_node.get("data", {}).get("node", {})
+ target_node_data = target_node.get("data", {}).get("node", {})
+
+ # Find the output data that matches the source handle name
+ output_data = next(
+ (
+ output
+ for output in source_node_data.get("outputs", [])
+ if output.get("name") == source_handle.get("name")
+ ),
+ None,
+ )
+
+ # If not found by name, try to find by display_name
+ if not output_data:
+ output_data = next(
+ (
+ output
+ for output in source_node_data.get("outputs", [])
+ if output.get("display_name") == source_handle.get("name")
+ ),
+ None,
+ )
+ # Update source handle name if found by display_name
+ if output_data:
+ source_handle["name"] = output_data.get("name")
+
+ # Determine the new output types based on the output data
+ if output_data:
+ if len(output_data.get("types", [])) == 1:
+ new_output_types = output_data.get("types", [])
+ elif output_data.get("selected"):
+ new_output_types = [output_data.get("selected")]
+ else:
+ new_output_types = []
+ else:
+ new_output_types = []
+
+ # Update output types if they've changed and log the change
+ if source_handle.get("output_types", []) != new_output_types:
+ edge_changes_log[source_node_data.get("display_name", "unknown")].append(
+ {
+ "attr": "output_types",
+ "old_value": source_handle.get("output_types", []),
+ "new_value": new_output_types,
+ }
+ )
+ source_handle["output_types"] = new_output_types
+
+ # Update input types if they've changed and log the change
+ field_name = target_handle.get("fieldName")
+ if field_name in target_node_data.get("template", {}) and target_handle.get(
+ "inputTypes", []
+ ) != target_node_data.get("template", {}).get(field_name, {}).get("input_types", []):
+ edge_changes_log[target_node_data.get("display_name", "unknown")].append(
+ {
+ "attr": "inputTypes",
+ "old_value": target_handle.get("inputTypes", []),
+ "new_value": target_node_data.get("template", {}).get(field_name, {}).get("input_types", []),
+ }
+ )
+ target_handle["inputTypes"] = (
+ target_node_data.get("template", {}).get(field_name, {}).get("input_types", [])
+ )
+
+ # Escape the updated handles for JSON storage
+ escaped_source_handle = escape_json_dump(source_handle)
+ escaped_target_handle = escape_json_dump(target_handle)
+
+ # Try to parse and escape the old handles for comparison
+ try:
+ old_escape_source_handle = escape_json_dump(json.loads(edge.get("sourceHandle", "{}")))
+ except (json.JSONDecodeError, TypeError):
+ old_escape_source_handle = edge.get("sourceHandle", "")
+
+ try:
+ old_escape_target_handle = escape_json_dump(json.loads(edge.get("targetHandle", "{}")))
+ except (json.JSONDecodeError, TypeError):
+ old_escape_target_handle = edge.get("targetHandle", "")
+
+ # Update source handle if it's changed and log the change
+ if old_escape_source_handle != escaped_source_handle:
+ edge_changes_log[source_node_data.get("display_name", "unknown")].append(
+ {
+ "attr": "sourceHandle",
+ "old_value": old_escape_source_handle,
+ "new_value": escaped_source_handle,
+ }
+ )
+ edge["sourceHandle"] = escaped_source_handle
+ if "data" in edge:
+ edge["data"]["sourceHandle"] = source_handle
+
+ # Update target handle if it's changed and log the change
+ if old_escape_target_handle != escaped_target_handle:
+ edge_changes_log[target_node_data.get("display_name", "unknown")].append(
+ {
+ "attr": "targetHandle",
+ "old_value": old_escape_target_handle,
+ "new_value": escaped_target_handle,
+ }
+ )
+ edge["targetHandle"] = escaped_target_handle
+ if "data" in edge:
+ edge["data"]["targetHandle"] = target_handle
+
+ else:
+ # Log an error if source or target node is not found after reconciliation attempt
+ logger.error(f"Source or target node not found for edge: {edge}")
+
+ # Log all the changes that were made
+ log_node_changes(edge_changes_log)
+ return project_data_copy
+
+
+def log_node_changes(node_changes_log) -> None:
+ # The idea here is to log the changes that were made to the nodes in debug
+ # Something like:
+ # Node: "Node Name" was updated with the following changes:
+ # attr_name: old_value -> new_value
+ # let's create one log per node
+ formatted_messages = []
+ for node_name, changes in node_changes_log.items():
+ message = f"\nNode: {node_name} was updated with the following changes:"
+ for change in changes:
+ message += f"\n- {change['attr']}: {change['old_value']} -> {change['new_value']}"
+ formatted_messages.append(message)
+ if formatted_messages:
+ logger.debug("\n".join(formatted_messages))
+
+
+async def load_starter_projects(retries=3, delay=1) -> list[tuple[anyio.Path, dict]]:
+ starter_projects = []
+ folder = anyio.Path(__file__).parent / "starter_projects"
+ async for file in folder.glob("*.json"):
+ attempt = 0
+ while attempt < retries:
+ async with async_open(str(file), "r", encoding="utf-8") as f:
+ content = await f.read()
+ try:
+ project = orjson.loads(content)
+ starter_projects.append((file, project))
+ logger.info(f"Loaded starter project {file}")
+ break # Break if load is successful
+ except orjson.JSONDecodeError as e:
+ attempt += 1
+ if attempt >= retries:
+ msg = f"Error loading starter project {file}: {e}"
+ raise ValueError(msg) from e
+ await asyncio.sleep(delay) # Wait before retrying
+ return starter_projects
+
+
+async def copy_profile_pictures() -> None:
+ """Asynchronously copies profile pictures from the source directory to the target configuration directory.
+
+ This function copies profile pictures while optimizing I/O operations by:
+ 1. Using a set to track existing files and avoid redundant filesystem checks
+ 2. Performing bulk copy operations concurrently using asyncio.gather
+ 3. Offloading blocking I/O to threads
+
+ The directory structure is:
+ profile_pictures/
+ ├── People/
+ │ └── [profile images]
+ └── Space/
+ └── [profile images]
+ """
+ # Get config directory from settings
+ config_dir = get_storage_service().settings_service.settings.config_dir
+ if config_dir is None:
+ msg = "Config dir is not set in the settings"
+ raise ValueError(msg)
+
+ # Setup source and target paths
+ origin = anyio.Path(__file__).parent / "profile_pictures"
+ target = anyio.Path(config_dir) / "profile_pictures"
+
+ if not await origin.exists():
+ msg = f"The source folder '{origin}' does not exist."
+ raise ValueError(msg)
+
+ # Create target dir if needed
+ if not await target.exists():
+ await target.mkdir(parents=True, exist_ok=True)
+
+ try:
+ # Get set of existing files in target to avoid redundant checks
+ target_files = {str(f.relative_to(target)) async for f in target.rglob("*") if await f.is_file()}
+
+ # Define a helper coroutine to copy a single file concurrently
+ async def copy_file(src_file, dst_file, rel_path):
+ # Create parent directories if needed
+ await dst_file.parent.mkdir(parents=True, exist_ok=True)
+ # Offload blocking I/O to a thread
+ await asyncio.to_thread(shutil.copy2, str(src_file), str(dst_file))
+ logger.debug(f"Copied file '{rel_path}'")
+
+ tasks = []
+ async for src_file in origin.rglob("*"):
+ if not await src_file.is_file():
+ continue
+
+ rel_path = src_file.relative_to(origin)
+ if str(rel_path) not in target_files:
+ dst_file = target / rel_path
+ tasks.append(copy_file(src_file, dst_file, rel_path))
+ else:
+ logger.debug(f"Skipped existing file: '{rel_path}'")
+
+ if tasks:
+ await asyncio.gather(*tasks)
+
+ except Exception as exc:
+ logger.exception("Error copying profile pictures")
+ msg = "An error occurred while copying profile pictures."
+ raise RuntimeError(msg) from exc
+
+
+def get_project_data(project):
+ project_name = project.get("name")
+ project_description = project.get("description")
+ project_is_component = project.get("is_component")
+ project_updated_at = project.get("updated_at")
+ if not project_updated_at:
+ updated_at_datetime = datetime.now(tz=timezone.utc)
+ else:
+ updated_at_datetime = datetime.fromisoformat(project_updated_at)
+ project_data = project.get("data")
+ project_icon = project.get("icon")
+ project_icon = demojize(project_icon) if project_icon and purely_emoji(project_icon) else project_icon
+ project_icon_bg_color = project.get("icon_bg_color")
+ project_gradient = project.get("gradient")
+ project_tags = project.get("tags")
+ return (
+ project_name,
+ project_description,
+ project_is_component,
+ updated_at_datetime,
+ project_data,
+ project_icon,
+ project_icon_bg_color,
+ project_gradient,
+ project_tags,
+ )
+
+
+async def update_project_file(project_path: anyio.Path, project: dict, updated_project_data) -> None:
+ project["data"] = updated_project_data
+ async with async_open(str(project_path), "w", encoding="utf-8") as f:
+ await f.write(orjson.dumps(project, option=ORJSON_OPTIONS).decode())
+ logger.info(f"Updated starter project {project['name']} file")
+
+
+def update_existing_project(
+ existing_project,
+ project_name,
+ project_description,
+ project_is_component,
+ updated_at_datetime,
+ project_data,
+ project_icon,
+ project_icon_bg_color,
+) -> None:
+ logger.info(f"Updating starter project {project_name}")
+ existing_project.data = project_data
+ existing_project.folder = STARTER_FOLDER_NAME
+ existing_project.description = project_description
+ existing_project.is_component = project_is_component
+ existing_project.updated_at = updated_at_datetime
+ existing_project.icon = project_icon
+ existing_project.icon_bg_color = project_icon_bg_color
+
+
+def create_new_project(
+ session,
+ project_name,
+ project_description,
+ project_is_component,
+ updated_at_datetime,
+ project_data,
+ project_gradient,
+ project_tags,
+ project_icon,
+ project_icon_bg_color,
+ new_folder_id,
+) -> None:
+ logger.debug(f"Creating starter project {project_name}")
+ new_project = FlowCreate(
+ name=project_name,
+ description=project_description,
+ icon=project_icon,
+ icon_bg_color=project_icon_bg_color,
+ data=project_data,
+ is_component=project_is_component,
+ updated_at=updated_at_datetime,
+ folder_id=new_folder_id,
+ gradient=project_gradient,
+ tags=project_tags,
+ )
+ db_flow = Flow.model_validate(new_project, from_attributes=True)
+ session.add(db_flow)
+
+
+async def get_all_flows_similar_to_project(session: AsyncSession, folder_id: UUID) -> list[Flow]:
+ stmt = select(Folder).options(selectinload(Folder.flows)).where(Folder.id == folder_id)
+ return list((await session.exec(stmt)).first().flows)
+
+
+async def delete_start_projects(session, folder_id) -> None:
+ flows = await get_all_flows_similar_to_project(session, folder_id)
+ for flow in flows:
+ await session.delete(flow)
+ await session.commit()
+
+
+async def folder_exists(session, folder_name):
+ stmt = select(Folder).where(Folder.name == folder_name)
+ folder = (await session.exec(stmt)).first()
+ return folder is not None
+
+
+async def create_starter_folder(session):
+ if not await folder_exists(session, STARTER_FOLDER_NAME):
+ new_folder = FolderCreate(name=STARTER_FOLDER_NAME, description=STARTER_FOLDER_DESCRIPTION)
+ db_folder = Folder.model_validate(new_folder, from_attributes=True)
+ session.add(db_folder)
+ await session.commit()
+ await session.refresh(db_folder)
+ return db_folder
+ stmt = select(Folder).where(Folder.name == STARTER_FOLDER_NAME)
+ return (await session.exec(stmt)).first()
+
+
+def _is_valid_uuid(val):
+ try:
+ uuid_obj = UUID(val)
+ except ValueError:
+ return False
+ return str(uuid_obj) == val
+
+
+async def load_flows_from_directory() -> None:
+ """On langflow startup, this loads all flows from the directory specified in the settings.
+
+ All flows are uploaded into the default folder for the superuser.
+ Note that this feature currently only works if AUTO_LOGIN is enabled in the settings.
+ """
+ settings_service = get_settings_service()
+ flows_path = settings_service.settings.load_flows_path
+ if not flows_path:
+ return
+ if not settings_service.auth_settings.AUTO_LOGIN:
+ logger.warning("AUTO_LOGIN is disabled, not loading flows from directory")
+ return
+
+ async with session_scope() as session:
+ user = await get_user_by_username(session, settings_service.auth_settings.SUPERUSER)
+ if user is None:
+ msg = "Superuser not found in the database"
+ raise NoResultFound(msg)
+
+ # Ensure that the default folder exists for this user
+ _ = await get_or_create_default_folder(session, user.id)
+
+ for file_path in await asyncio.to_thread(Path(flows_path).iterdir):
+ if not await anyio.Path(file_path).is_file() or file_path.suffix != ".json":
+ continue
+ logger.info(f"Loading flow from file: {file_path.name}")
+ async with async_open(str(file_path), "r", encoding="utf-8") as f:
+ content = await f.read()
+ await upsert_flow_from_file(content, file_path.stem, session, user.id)
+
+
+async def detect_github_url(url: str) -> str:
+ if matched := re.match(r"https?://(?:www\.)?github\.com/([\w.-]+)/([\w.-]+)?/?$", url):
+ owner, repo = matched.groups()
+
+ repo = repo.removesuffix(".git")
+
+ async with httpx.AsyncClient(follow_redirects=True) as client:
+ response = await client.get(f"https://api.github.com/repos/{owner}/{repo}")
+ response.raise_for_status()
+ default_branch = response.json().get("default_branch")
+ return f"https://github.com/{owner}/{repo}/archive/refs/heads/{default_branch}.zip"
+
+ if matched := re.match(r"https?://(?:www\.)?github\.com/([\w.-]+)/([\w.-]+)/tree/([\w\\/.-]+)", url):
+ owner, repo, branch = matched.groups()
+ if branch[-1] == "/":
+ branch = branch[:-1]
+ return f"https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip"
+
+ if matched := re.match(r"https?://(?:www\.)?github\.com/([\w.-]+)/([\w.-]+)/releases/tag/([\w\\/.-]+)", url):
+ owner, repo, tag = matched.groups()
+ if tag[-1] == "/":
+ tag = tag[:-1]
+ return f"https://github.com/{owner}/{repo}/archive/refs/tags/{tag}.zip"
+
+ if matched := re.match(r"https?://(?:www\.)?github\.com/([\w.-]+)/([\w.-]+)/commit/(\w+)/?$", url):
+ owner, repo, commit = matched.groups()
+ return f"https://github.com/{owner}/{repo}/archive/{commit}.zip"
+
+ return url
+
+
+async def load_bundles_from_urls() -> tuple[list[TemporaryDirectory], list[str]]:
+ component_paths: set[str] = set()
+ temp_dirs = []
+ settings_service = get_settings_service()
+ bundle_urls = settings_service.settings.bundle_urls
+ if not bundle_urls:
+ return [], []
+ if not settings_service.auth_settings.AUTO_LOGIN:
+ logger.warning("AUTO_LOGIN is disabled, not loading flows from URLs")
+
+ async with session_scope() as session:
+ user = await get_user_by_username(session, settings_service.auth_settings.SUPERUSER)
+ if user is None:
+ msg = "Superuser not found in the database"
+ raise NoResultFound(msg)
+ user_id = user.id
+
+ for url in bundle_urls:
+ url_ = await detect_github_url(url)
+
+ async with httpx.AsyncClient(follow_redirects=True) as client:
+ response = await client.get(url_)
+ response.raise_for_status()
+
+ with zipfile.ZipFile(io.BytesIO(response.content)) as zfile:
+ dir_names = [f.filename for f in zfile.infolist() if f.is_dir() and "/" not in f.filename[:-1]]
+ temp_dir = None
+ for filename in zfile.namelist():
+ path = Path(filename)
+ for dir_name in dir_names:
+ if (
+ settings_service.auth_settings.AUTO_LOGIN
+ and path.is_relative_to(f"{dir_name}flows/")
+ and path.suffix == ".json"
+ ):
+ file_content = zfile.read(filename)
+ await upsert_flow_from_file(file_content, path.stem, session, user_id)
+ elif path.is_relative_to(f"{dir_name}components/"):
+ if temp_dir is None:
+ temp_dir = await asyncio.to_thread(TemporaryDirectory)
+ temp_dirs.append(temp_dir)
+ component_paths.add(str(Path(temp_dir.name) / f"{dir_name}components"))
+ await asyncio.to_thread(zfile.extract, filename, temp_dir.name)
+
+ return temp_dirs, list(component_paths)
+
+
+async def upsert_flow_from_file(file_content: AnyStr, filename: str, session: AsyncSession, user_id: UUID) -> None:
+ flow = orjson.loads(file_content)
+ flow_endpoint_name = flow.get("endpoint_name")
+ if _is_valid_uuid(filename):
+ flow["id"] = filename
+ flow_id = flow.get("id")
+
+ if isinstance(flow_id, str):
+ try:
+ flow_id = UUID(flow_id)
+ except ValueError:
+ logger.error(f"Invalid UUID string: {flow_id}")
+ return
+
+ existing = await find_existing_flow(session, flow_id, flow_endpoint_name)
+ if existing:
+ logger.debug(f"Found existing flow: {existing.name}")
+ logger.info(f"Updating existing flow: {flow_id} with endpoint name {flow_endpoint_name}")
+ for key, value in flow.items():
+ if hasattr(existing, key):
+ # flow dict from json and db representation are not 100% the same
+ setattr(existing, key, value)
+ existing.updated_at = datetime.now(tz=timezone.utc).astimezone()
+ existing.user_id = user_id
+
+ # Ensure that the flow is associated with an existing default folder
+ if existing.folder_id is None:
+ folder_id = await get_or_create_default_folder(session, user_id)
+ existing.folder_id = folder_id
+
+ if isinstance(existing.id, str):
+ try:
+ existing.id = UUID(existing.id)
+ except ValueError:
+ logger.error(f"Invalid UUID string: {existing.id}")
+ return
+
+ session.add(existing)
+ else:
+ logger.info(f"Creating new flow: {flow_id} with endpoint name {flow_endpoint_name}")
+
+ # Assign the newly created flow to the default folder
+ folder = await get_or_create_default_folder(session, user_id)
+ flow["user_id"] = user_id
+ flow["folder_id"] = folder.id
+ flow = Flow.model_validate(flow)
+ flow.updated_at = datetime.now(tz=timezone.utc).astimezone()
+
+ session.add(flow)
+
+
+async def find_existing_flow(session, flow_id, flow_endpoint_name):
+ if flow_endpoint_name:
+ logger.debug(f"flow_endpoint_name: {flow_endpoint_name}")
+ stmt = select(Flow).where(Flow.endpoint_name == flow_endpoint_name)
+ if existing := (await session.exec(stmt)).first():
+ logger.debug(f"Found existing flow by endpoint name: {existing.name}")
+ return existing
+
+ stmt = select(Flow).where(Flow.id == flow_id)
+ if existing := (await session.exec(stmt)).first():
+ logger.debug(f"Found existing flow by id: {flow_id}")
+ return existing
+ return None
+
+
+async def create_or_update_starter_projects(all_types_dict: dict, *, do_create: bool = True) -> None:
+ """Create or update starter projects.
+
+ Args:
+ all_types_dict (dict): Dictionary containing all component types and their templates
+ do_create (bool, optional): Whether to create new projects. Defaults to True.
+ """
+ async with session_scope() as session:
+ new_folder = await create_starter_folder(session)
+ starter_projects = await load_starter_projects()
+ await delete_start_projects(session, new_folder.id)
+ await copy_profile_pictures()
+ for project_path, project in starter_projects:
+ (
+ project_name,
+ project_description,
+ project_is_component,
+ updated_at_datetime,
+ project_data,
+ project_icon,
+ project_icon_bg_color,
+ project_gradient,
+ project_tags,
+ ) = get_project_data(project)
+ do_update_starter_projects = os.environ.get("LANGFLOW_UPDATE_STARTER_PROJECTS", "true").lower() == "true"
+ if do_update_starter_projects:
+ updated_project_data = update_projects_components_with_latest_component_versions(
+ project_data.copy(), all_types_dict
+ )
+ updated_project_data = update_edges_with_latest_component_versions(updated_project_data)
+ if updated_project_data != project_data:
+ project_data = updated_project_data
+ # We also need to update the project data in the file
+ await update_project_file(project_path, project, updated_project_data)
+ if do_create and project_name and project_data:
+ existing_flows = await get_all_flows_similar_to_project(session, new_folder.id)
+ for existing_project in existing_flows:
+ await session.delete(existing_project)
+
+ create_new_project(
+ session=session,
+ project_name=project_name,
+ project_description=project_description,
+ project_is_component=project_is_component,
+ updated_at_datetime=updated_at_datetime,
+ project_data=project_data,
+ project_icon=project_icon,
+ project_icon_bg_color=project_icon_bg_color,
+ project_gradient=project_gradient,
+ project_tags=project_tags,
+ new_folder_id=new_folder.id,
+ )
+
+
+async def initialize_super_user_if_needed() -> None:
+ settings_service = get_settings_service()
+ if not settings_service.auth_settings.AUTO_LOGIN:
+ return
+ username = settings_service.auth_settings.SUPERUSER
+ password = settings_service.auth_settings.SUPERUSER_PASSWORD
+ if not username or not password:
+ msg = "SUPERUSER and SUPERUSER_PASSWORD must be set in the settings if AUTO_LOGIN is true."
+ raise ValueError(msg)
+
+ async with session_scope() as async_session:
+ super_user = await create_super_user(db=async_session, username=username, password=password)
+ await get_variable_service().initialize_user_variables(super_user.id, async_session)
+ _ = await get_or_create_default_folder(async_session, super_user.id)
+ logger.info("Super user initialized")
+
+
+async def get_or_create_default_folder(session: AsyncSession, user_id: UUID) -> FolderRead:
+ """Ensure the default folder exists for the given user_id. If it doesn't exist, create it.
+
+ Uses an idempotent insertion approach to handle concurrent creation gracefully.
+
+ This implementation avoids an external distributed lock and works with both SQLite and PostgreSQL.
+
+ Args:
+ session (AsyncSession): The active database session.
+ user_id (UUID): The ID of the user who owns the folder.
+
+ Returns:
+ UUID: The ID of the default folder.
+ """
+ stmt = select(Folder).where(Folder.user_id == user_id, Folder.name == DEFAULT_FOLDER_NAME)
+ result = await session.exec(stmt)
+ folder = result.first()
+ if folder:
+ return FolderRead.model_validate(folder, from_attributes=True)
+
+ try:
+ folder_obj = Folder(user_id=user_id, name=DEFAULT_FOLDER_NAME)
+ session.add(folder_obj)
+ await session.commit()
+ await session.refresh(folder_obj)
+ except sa.exc.IntegrityError as e:
+ # Another worker may have created the folder concurrently.
+ await session.rollback()
+ result = await session.exec(stmt)
+ folder = result.first()
+ if folder:
+ return FolderRead.model_validate(folder, from_attributes=True)
+ msg = "Failed to get or create default folder"
+ raise ValueError(msg) from e
+ return FolderRead.model_validate(folder_obj, from_attributes=True)
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json
new file mode 100644
index 0000000..7618106
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompt Chaining.json
@@ -0,0 +1,2414 @@
+{
+ "data": {
+ "edges": [
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-f1f2v",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-lL9HA",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-f1f2v{œdataTypeœ:œPromptœ,œidœ:œPrompt-f1f2vœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-lL9HA{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-lL9HAœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-f1f2v",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-f1f2vœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-lL9HA",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-lL9HAœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-GyBUF",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-lL9HA",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-GyBUF{œdataTypeœ:œChatInputœ,œidœ:œChatInput-GyBUFœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-lL9HA{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-lL9HAœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-GyBUF",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-GyBUFœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-lL9HA",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-lL9HAœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-lL9HA",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-JieGw",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-lL9HA{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-lL9HAœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-JieGw{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-JieGwœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-lL9HA",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-lL9HAœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-JieGw",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-JieGwœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-4IOgm",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-JieGw",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-4IOgm{œdataTypeœ:œPromptœ,œidœ:œPrompt-4IOgmœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-JieGw{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-JieGwœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-4IOgm",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-4IOgmœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-JieGw",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-JieGwœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-JieGw",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-dXMRv",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-JieGw{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-JieGwœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-dXMRv{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-dXMRvœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-JieGw",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-JieGwœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-dXMRv",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-dXMRvœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-FRjO8",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-dXMRv",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-FRjO8{œdataTypeœ:œPromptœ,œidœ:œPrompt-FRjO8œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-dXMRv{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-dXMRvœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-FRjO8",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-FRjO8œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-dXMRv",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-dXMRvœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-dXMRv",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-KXQMh",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-dXMRv{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-dXMRvœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-KXQMh{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-KXQMhœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-dXMRv",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-dXMRvœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-KXQMh",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-KXQMhœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-4IOgm",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": []
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "You are a seasoned business analyst with a strong background in tech product development and market research. Your analytical skills are unparalleled, allowing you to dissect product concepts and evaluate their market viability with precision. You have a keen eye for identifying potential challenges and opportunities that others might overlook. Your insights have been crucial in shaping successful product strategies for numerous tech companies.\n\nYour task is to:\n\n1. Evaluate the concept in terms of market potential and technical feasibility\n2. Identify two potential challenges for developing this product\n3. Suggest one improvement or expansion to the concept\n\n\nPlease structure your response as follows:\n\nConcept Evaluation:\n[concept_evaluation]\n\nPotential Challenges:\n1. [challenge_1]\n2. [challenge_2]\n...\n\nImprovement Suggestion:\n[improvement_suggestion]\n\nProvide an objective and well-founded analysis, considering market and technological factors in your evaluation.\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 260,
+ "id": "Prompt-4IOgm",
+ "measured": {
+ "height": 260,
+ "width": 320
+ },
+ "position": {
+ "x": 1921.9168573384,
+ "y": 1162.4082184281983
+ },
+ "positionAbsolute": {
+ "x": 1921.9168573384,
+ "y": 1162.4082184281983
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "ChatInput-GyBUF",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "inputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatInput",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "The growing demand for personalized, AI-driven mental health support tools that can provide real-time interventions and track long-term emotional well-being."
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-GyBUF",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 1178.0239685549568,
+ "y": 879.9087836229152
+ },
+ "positionAbsolute": {
+ "x": 1178.0239685549568,
+ "y": 879.9087836229152
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-KXQMh",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-KXQMh",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 3389.8335347035913,
+ "y": 1185.8259442119552
+ },
+ "positionAbsolute": {
+ "x": 3363.868906129255,
+ "y": 1189.5351768654318
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-FRjO8",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": []
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "You are an accomplished product manager with a track record of bringing innovative tech products from concept to market. Your strategic thinking and ability to balance technical feasibility with market demands have resulted in several successful product launches. You excel at distilling complex ideas into clear, actionable plans and have a talent for identifying the most critical features that will drive product adoption and success.\n\nBased on the analysis of the innovative product, create a simplified development plan that includes:\n\n1. Product overview (1-2 sentences)\n2. Three main features to be developed\n3. A basic market launch strategy\n\n\nPlease structure your plan as follows:\n\nProduct Overview:\n[product_overview]\n\nMain Features:\n1. [feature_1]\n2. [feature_2]\n3. [feature_3]\n...\n\nLaunch Strategy:\n[launch_strategy]\n\nYour plan should be concise, realistic, and aligned with the information provided in the previous steps.\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 260,
+ "id": "Prompt-FRjO8",
+ "measured": {
+ "height": 260,
+ "width": 320
+ },
+ "position": {
+ "x": 2647.8305106628454,
+ "y": 1161.2328062686402
+ },
+ "positionAbsolute": {
+ "x": 2647.8305106628454,
+ "y": 1161.2328062686402
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-f1f2v",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": []
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "You are a visionary product innovator at a cutting-edge tech startup. Your expertise lies in identifying emerging market trends and translating them into groundbreaking product concepts. Your creative thinking and deep understanding of technology allow you to envision products that not only meet current needs but also anticipate future demands. Your ideas often challenge conventional thinking and push the boundaries of what's possible with current technology.\n\nPlease create a product concept, providing:\n\n1. Product name\n2. Brief description (2-3 sentences)\n3. Main innovative feature\n4. Target audience\n\nStructure your response like this:\n\nProduct Name: [product_name]\n\nDescription: [product_description]\n\nMain Innovation: [main_innovation]\n\nTarget Audience: [target_audience]\n\nBe creative and bold in your idea, but keep it realistic and aligned with the provided market trend."
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 260,
+ "id": "Prompt-f1f2v",
+ "measured": {
+ "height": 260,
+ "width": 320
+ },
+ "position": {
+ "x": 1178.7099500302636,
+ "y": 1167.8586867404465
+ },
+ "positionAbsolute": {
+ "x": 1178.7099500302636,
+ "y": 1167.8586867404465
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-UUVIc",
+ "node": {
+ "description": "### Input Examples\n1.\n \"The growing demand for personalized, AI-driven mental health support tools that can provide real-time interventions and track long-term emotional well-being.\"\n\n\n2. \n \"The increasing need for secure and user-friendly decentralized finance (DeFi) platforms that make cryptocurrency investments accessible to non-tech-savvy users.\"\n \n\n3. \n \"The rising popularity of immersive, augmented reality (AR) experiences for remote collaboration and virtual team-building in distributed workforces.\"\n\n\n4. \n \"The expanding market for smart, IoT-enabled urban farming solutions that allow city dwellers to grow their own food efficiently in small spaces.\"\n\n\n5. \n \"The emerging demand for AI-powered personal styling and shopping assistants that consider sustainability, body positivity, and individual style preferences.\"\n\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "emerald"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-UUVIc",
+ "measured": {
+ "height": 324,
+ "width": 324
+ },
+ "position": {
+ "x": 528.0392006831054,
+ "y": 973.781986567496
+ },
+ "positionAbsolute": {
+ "x": 528.0392006831054,
+ "y": 973.781986567496
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-DqpAx",
+ "node": {
+ "description": "### Prompt Chaining\n\nThis flow demonstrates fundamental prompt chaining principles:\n\n1. **Chain Structure**\n • User input → First Prompt → LLM\n • First output → Second Prompt → LLM\n • Second output → Final Prompt → LLM\n • Final output\n\n2. **Key Technique Elements**\n • Each prompt is specifically designed to process previous output\n • Output formatting ensures clean handoff between stages\n • Context flows naturally through the chain\n • Each LLM call builds upon previous results\n\n3. **Technical Implementation**\n • Multiple prompt templates working in sequence\n • Strategic input/output connections\n • Consistent message handling between stages\n • Progressive refinement through the chain\n\nThis pattern can be adapted for any use case by modifying the prompt templates while keeping the same chaining structure.",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-DqpAx",
+ "measured": {
+ "height": 324,
+ "width": 324
+ },
+ "position": {
+ "x": 892.4280059782889,
+ "y": 406.2411111617474
+ },
+ "positionAbsolute": {
+ "x": 892.4280059782889,
+ "y": 406.2411111617474
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-lL9HA",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-lL9HA",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 1563.1591714154865,
+ "y": 811.7524000203542
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-JieGw",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-JieGw",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 2289.2925714867265,
+ "y": 849.5992204058788
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-dXMRv",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-dXMRv",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 3018.8917918926063,
+ "y": 844.6876489700377
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -659.8399794180846,
+ "y": -252.56300712780512,
+ "zoom": 0.5391950807198421
+ }
+ },
+ "description": "Connect multiple prompts in sequence where each output becomes the next stage's input, enabling step-by-step text processing.",
+ "endpoint_name": null,
+ "gradient": "0",
+ "icon": "Link",
+ "id": "b5b0c252-95ae-4b07-8211-67c8b12ea60e",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Prompt Chaining",
+ "tags": [
+ "chatbots"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json
new file mode 100644
index 0000000..abcc3c1
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting.json
@@ -0,0 +1,1271 @@
+{
+ "data": {
+ "edges": [
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-jFwUm",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-OcXkl",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-jFwUm{œdataTypeœ:œChatInputœ,œidœ:œChatInput-jFwUmœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-OcXkl{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-OcXklœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ChatInput-jFwUm",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-jFwUmœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-OcXkl",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-OcXklœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-3SM2g",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-OcXkl",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-3SM2g{œdataTypeœ:œPromptœ,œidœ:œPrompt-3SM2gœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-OcXkl{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-OcXklœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-3SM2g",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-3SM2gœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-OcXkl",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-OcXklœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-OcXkl",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-gDYiJ",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-OcXkl{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-OcXklœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-gDYiJ{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-gDYiJœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "OpenAIModel-OcXkl",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-OcXklœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-gDYiJ",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-gDYiJœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "id": "ChatInput-jFwUm",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Hello"
+ },
+ "sender": {
+ "advanced": true,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-jFwUm",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 689.5720422421635,
+ "y": 765.155834131403
+ },
+ "positionAbsolute": {
+ "x": 689.5720422421635,
+ "y": 765.155834131403
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-3SM2g",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": []
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Answer the user as if you were a GenAI expert, enthusiastic about helping them get started building something fresh."
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 260,
+ "id": "Prompt-3SM2g",
+ "measured": {
+ "height": 260,
+ "width": 360
+ },
+ "position": {
+ "x": 690.2015147036818,
+ "y": 1040.6625705470924
+ },
+ "positionAbsolute": {
+ "x": 690.2015147036818,
+ "y": 1018.5443911764344
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "undefined-kVLkG",
+ "node": {
+ "description": "## 📖 README\n\nPerform basic prompting with an OpenAI model.\n\n#### Quick Start\n- Add your **OpenAI API key** to the **OpenAI Model**\n- Open the **Playground** to chat with your bot.\n\n#### Next steps:\n Experiment by changing the prompt and the OpenAI model temperature to see how the bot's responses change.",
+ "display_name": "Read Me",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "neutral"
+ }
+ }
+ },
+ "dragging": false,
+ "height": 332,
+ "id": "undefined-kVLkG",
+ "measured": {
+ "height": 332,
+ "width": 328
+ },
+ "position": {
+ "x": 133.95771636602308,
+ "y": 753.6499167055161
+ },
+ "positionAbsolute": {
+ "x": 66.38770028934243,
+ "y": 749.744424427066
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 250,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-WrEGM",
+ "node": {
+ "description": "### 💡 Add your OpenAI API key here 👇",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-WrEGM",
+ "measured": {
+ "height": 324,
+ "width": 326
+ },
+ "position": {
+ "x": 1075.829573520873,
+ "y": 657.2057655038416
+ },
+ "positionAbsolute": {
+ "x": 1075.829573520873,
+ "y": 657.2057655038416
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "ChatOutput-gDYiJ",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-gDYiJ",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 1460.070372772908,
+ "y": 872.7273956769025
+ },
+ "positionAbsolute": {
+ "x": 1444.936881624563,
+ "y": 872.7273956769025
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-OcXkl",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "load_from_db": false,
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-OcXkl",
+ "measured": {
+ "height": 734,
+ "width": 360
+ },
+ "position": {
+ "x": 1071.3015591664102,
+ "y": 724.6650363109242
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -37.61270157375441,
+ "y": -155.91266341888854,
+ "zoom": 0.7575251406952855
+ }
+ },
+ "description": "Perform basic prompting with an OpenAI model.",
+ "endpoint_name": null,
+ "gradient": "2",
+ "icon": "Braces",
+ "id": "1511c230-d446-43a7-bfc3-539e69ce05b8",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Basic Prompting",
+ "tags": [
+ "chatbots"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json
new file mode 100644
index 0000000..e3d2001
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json
@@ -0,0 +1,1535 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-4Sckw",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "references",
+ "id": "Prompt-65R68",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-4Sckw{œdataTypeœ:œParseDataœ,œidœ:œParseData-4Sckwœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-65R68{œfieldNameœ:œreferencesœ,œidœ:œPrompt-65R68œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "ParseData-4Sckw",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-4Sckwœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-65R68",
+ "targetHandle": "{œfieldNameœ: œreferencesœ, œidœ: œPrompt-65R68œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "TextInput",
+ "id": "TextInput-t88FI",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "instructions",
+ "id": "Prompt-65R68",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-TextInput-t88FI{œdataTypeœ:œTextInputœ,œidœ:œTextInput-t88FIœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-65R68{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-65R68œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "TextInput-t88FI",
+ "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-t88FIœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-65R68",
+ "targetHandle": "{œfieldNameœ: œinstructionsœ, œidœ: œPrompt-65R68œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-65R68",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-MyAsQ",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-65R68{œdataTypeœ:œPromptœ,œidœ:œPrompt-65R68œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-MyAsQ{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-MyAsQœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-65R68",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-65R68œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-MyAsQ",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-MyAsQœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-MyAsQ",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-BE4YI",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-MyAsQ{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-MyAsQœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-BE4YI{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-BE4YIœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "OpenAIModel-MyAsQ",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-MyAsQœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-BE4YI",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BE4YIœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "URL",
+ "id": "URL-EPEnt",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-4Sckw",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-URL-EPEnt{œdataTypeœ:œURLœ,œidœ:œURL-EPEntœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-4Sckw{œfieldNameœ:œdataœ,œidœ:œParseData-4Sckwœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "URL-EPEnt",
+ "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-EPEntœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-4Sckw",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-4Sckwœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "id": "ParseData-4Sckw",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ }
+ }
+ },
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "height": 302,
+ "id": "ParseData-4Sckw",
+ "measured": {
+ "height": 302,
+ "width": 320
+ },
+ "position": {
+ "x": 955.6736985046297,
+ "y": 702.7003891105396
+ },
+ "positionAbsolute": {
+ "x": 955.6736985046297,
+ "y": 702.7003891105396
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-65R68",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "references",
+ "instructions"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "instructions": {
+ "advanced": false,
+ "display_name": "instructions",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "instructions",
+ "password": false,
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "references": {
+ "advanced": false,
+ "display_name": "references",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "references",
+ "password": false,
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Reference 1:\n\n{references}\n\n---\n\n{instructions}\n\nBlog: \n\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 433,
+ "id": "Prompt-65R68",
+ "measured": {
+ "height": 433,
+ "width": 320
+ },
+ "position": {
+ "x": 1341.1018009526915,
+ "y": 456.4098573354365
+ },
+ "positionAbsolute": {
+ "x": 1341.1018009526915,
+ "y": 456.4098573354365
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Get text inputs from the Playground.",
+ "display_name": "Instructions",
+ "id": "TextInput-t88FI",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get text inputs from the Playground.",
+ "display_name": "Instructions",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value"
+ ],
+ "frozen": false,
+ "icon": "type",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Playground.\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n return Message(\n text=self.input_value,\n )\n"
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Text to be passed as input.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Use the references above for style to write a new blog/tutorial about Langflow and AI. Suggest non-covered topics."
+ }
+ }
+ },
+ "type": "TextInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "TextInput-t88FI",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 955.8314364398983,
+ "y": 402.24423846638155
+ },
+ "positionAbsolute": {
+ "x": 955.8314364398983,
+ "y": 402.24423846638155
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-BE4YI",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "advanced": true,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-BE4YI",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 2097.489047349972,
+ "y": 603.355618581002
+ },
+ "positionAbsolute": {
+ "x": 2113.228183852361,
+ "y": 594.6116538574528
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-lriuW",
+ "node": {
+ "description": "## URL Component Setup\n\n**Purpose:**\nFetch and process content from the web to use as reference material for creating a blog post.\n\n**Instructions:**\n1. **Input URLs**: List the URLs of web pages whose content you want to fetch. Ensure the URLs start with `http://` or `https://`.\n2. **Select Output Format**:\n - **Text**: To extract plain text from the pages.\n - **Raw HTML**: To retrieve the raw HTML content for advanced uses.\n\n**Tips**:\n- Double-check URL formats to prevent any data fetching errors.\n- Use the '+' button to add multiple URLs as needed for comprehensive references.\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-lriuW",
+ "measured": {
+ "height": 324,
+ "width": 325
+ },
+ "position": {
+ "x": 484.73635938598477,
+ "y": 153.29803159918163
+ },
+ "positionAbsolute": {
+ "x": 484.73635938598477,
+ "y": 153.29803159918163
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 329,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-1oPzi",
+ "node": {
+ "description": "# Blog Writing Flow Overview\n\n**Workflow Description:**\nThis flow assists in creating a blog post by using content fetched from URLs and user-provided instructions. It combines external references and user inputs to generate coherent and context-rich text.\n\n**Components**:\n1. **URL Component**: Fetches reference content from specified web pages.\n2. **Parse Data**: Converts the fetched content into a text format.\n3. **Text Input**: Accepts user-specific instructions for the blog post.\n4. **Prompt with Variables**: Merges references and instructions into a dynamic writing prompt.\n5. **OpenAI Model**: Generates the blog post using an AI language model.\n6. **Chat Output**: Displays the final blog text for user review and further refinement.\n\n**Steps to Execute**:\n1. Enter the relevant URLs and specify the output format in the **URL Component**.\n2. Provide detailed writing **Instructions** for AI to follow.\n3. Run the flow to generate the blog and view the result in **Chat Output**.\n\n**Benefits**:\n- Simplifies blog creation by using AI to structure and write content.\n- Incorporates comprehensive reference material to enhance post depth and accuracy.",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 486,
+ "id": "note-1oPzi",
+ "measured": {
+ "height": 486,
+ "width": 325
+ },
+ "position": {
+ "x": -78.41970365609802,
+ "y": 405.04114164010207
+ },
+ "positionAbsolute": {
+ "x": -78.41970365609802,
+ "y": 405.04114164010207
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 509,
+ "width": 562
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-qhsOB",
+ "node": {
+ "description": "## Get Your OpenAI API Key\n**Steps**:\n1. **Visit** [OpenAI's API Key Page](https://platform.openai.com/api-keys).\n\n2. **Log In/Sign Up**:\n - Log in or create a new OpenAI account.\n\n3. **Generate API Key**:\n - Click \"Create New Secret Key\" to obtain your key.\n\n4. **Store Your Key Securely**:\n - Note it down as it will only display once.\n\n5. **Enter API Key**:\n - Input your key in the OpenAI API Key field within the component setup.\n\nKeep your key safe and manage it responsibly!",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "rose"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-qhsOB",
+ "measured": {
+ "height": 324,
+ "width": 325
+ },
+ "position": {
+ "x": 1702.078126574358,
+ "y": 121.36154223045867
+ },
+ "positionAbsolute": {
+ "x": 1703.974189852056,
+ "y": 125.15366878585462
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 343
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-MyAsQ",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.001,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-MyAsQ",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 1715.4141756503298,
+ "y": 434.13478201304184
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "URL-EPEnt",
+ "node": {
+ "base_classes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "beta": false,
+ "category": "data",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Load and retrive data from specified URLs.",
+ "display_name": "URL",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "urls",
+ "format"
+ ],
+ "frozen": false,
+ "icon": "layout-template",
+ "key": "URL",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "fetch_content",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "fetch_content_text",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 2.220446049250313e-16,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import re\n\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Load and retrive data from specified URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.\",\n options=[\"Text\", \"Raw HTML\"],\n value=\"Text\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Message\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n msg = f\"Invalid URL: {string}\"\n raise ValueError(msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.fetch_content())\n"
+ },
+ "format": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Output Format",
+ "dynamic": false,
+ "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ "name": "format",
+ "options": [
+ "Text",
+ "Raw HTML"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Text"
+ },
+ "urls": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URLs",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": true,
+ "list_add_label": "Add URL",
+ "load_from_db": false,
+ "name": "urls",
+ "placeholder": "Enter a URL...",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "https://langflow.org/",
+ "https://docs.langflow.org/"
+ ]
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "URL"
+ },
+ "dragging": false,
+ "id": "URL-EPEnt",
+ "measured": {
+ "height": 465,
+ "width": 320
+ },
+ "position": {
+ "x": 498.72695054312635,
+ "y": 554.4485732587549
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": 107.17740467666556,
+ "y": 281.84175643433355,
+ "zoom": 0.5274085584390961
+ }
+ },
+ "description": "Auto-generate a customized blog post from instructions and referenced articles.",
+ "endpoint_name": null,
+ "gradient": "4",
+ "icon": "NotebookPen",
+ "id": "8b12aa0f-8b59-4806-a01f-5e545b5b1688",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Blog Writer",
+ "tags": [
+ "chatbots",
+ "content-generation"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json
new file mode 100644
index 0000000..d6202ce
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Custom Component Maker.json
@@ -0,0 +1,2198 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Memory",
+ "id": "Memory-v5bRq",
+ "name": "messages_text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "CHAT_HISTORY",
+ "id": "Prompt-QIyYT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Memory-v5bRq{œdataTypeœ:œMemoryœ,œidœ:œMemory-v5bRqœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-QIyYT{œfieldNameœ:œCHAT_HISTORYœ,œidœ:œPrompt-QIyYTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Memory-v5bRq",
+ "sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-v5bRqœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-QIyYT",
+ "targetHandle": "{œfieldNameœ: œCHAT_HISTORYœ, œidœ: œPrompt-QIyYTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-Z88lk",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "USER_INPUT",
+ "id": "Prompt-QIyYT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-Z88lk{œdataTypeœ:œChatInputœ,œidœ:œChatInput-Z88lkœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-QIyYT{œfieldNameœ:œUSER_INPUTœ,œidœ:œPrompt-QIyYTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-Z88lk",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-Z88lkœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-QIyYT",
+ "targetHandle": "{œfieldNameœ: œUSER_INPUTœ, œidœ: œPrompt-QIyYTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "URL",
+ "id": "URL-wNOPd",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "EXAMPLE_COMPONENTS",
+ "id": "Prompt-QIyYT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-URL-wNOPd{œdataTypeœ:œURLœ,œidœ:œURL-wNOPdœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-QIyYT{œfieldNameœ:œEXAMPLE_COMPONENTSœ,œidœ:œPrompt-QIyYTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "URL-wNOPd",
+ "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-wNOPdœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-QIyYT",
+ "targetHandle": "{œfieldNameœ: œEXAMPLE_COMPONENTSœ, œidœ: œPrompt-QIyYTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "URL",
+ "id": "URL-pFAuR",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "BASE_COMPONENT_CODE",
+ "id": "Prompt-QIyYT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-URL-pFAuR{œdataTypeœ:œURLœ,œidœ:œURL-pFAuRœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-QIyYT{œfieldNameœ:œBASE_COMPONENT_CODEœ,œidœ:œPrompt-QIyYTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "URL-pFAuR",
+ "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-pFAuRœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-QIyYT",
+ "targetHandle": "{œfieldNameœ: œBASE_COMPONENT_CODEœ, œidœ: œPrompt-QIyYTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "URL",
+ "id": "URL-KARv3",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "CUSTOM_COMPONENT_CODE",
+ "id": "Prompt-QIyYT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-URL-KARv3{œdataTypeœ:œURLœ,œidœ:œURL-KARv3œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-QIyYT{œfieldNameœ:œCUSTOM_COMPONENT_CODEœ,œidœ:œPrompt-QIyYTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "URL-KARv3",
+ "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-KARv3œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-QIyYT",
+ "targetHandle": "{œfieldNameœ: œCUSTOM_COMPONENT_CODEœ, œidœ: œPrompt-QIyYTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-QIyYT",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "AnthropicModel-RQQCm",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-QIyYT{œdataTypeœ:œPromptœ,œidœ:œPrompt-QIyYTœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-RQQCm{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-RQQCmœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-QIyYT",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-QIyYTœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "AnthropicModel-RQQCm",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-RQQCmœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AnthropicModel",
+ "id": "AnthropicModel-RQQCm",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-Jj8M6",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-AnthropicModel-RQQCm{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-RQQCmœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-Jj8M6{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-Jj8M6œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "AnthropicModel-RQQCm",
+ "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-RQQCmœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-Jj8M6",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-Jj8M6œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "id": "ChatInput-Z88lk",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Failed to get YouTube transcripts: 1 validation error for Data\ndata\n Input should be a valid dictionary [type=dict_type, input_value=Document(metadata={'sourc...adding the API key and\"), input_type=Document]\n For further information visit https://errors.pydantic.dev/2.9/v/dict_type\n\n\nAlso, adapt the \"text\" bc it returns this \"Document(metadata={'source': 'UkV79sJAvz8'}, page_content=\"assembly AI is one of the leaders in transcription services so you can convert speech into text and they have many different products available on their platform and we can use assembly within langlow and the way we can get started is to First make an account with assembly Ai and once you get started you will be provided with an API key this is something we're going to need in langlow now back in link flow there are a few different components available and in this example we are using the start transcript component within this component we can provide a file and what I did is I uploaded a 1-hour talk by Andre karpati and this is intro to the large language models and this is an MP3 file so after adding the API key and then the audio file we can select a model that we want to use for the transcription there are two different options available here best and Nano now after you select the model you can either have the language detection on or leave defaults so I left everything in default and then I started the task and once we run the flow we get a transcript ID and attaching this component with the assembly AI pole transcript component we can now get the results and if we were to look at the results available able from this component there are quite a lot of fields that we can see as a result of this component and some of the most important ones you can see is the text from the transcript as you can see it's quite a large file and all of that was converted from speech to text easily by assembly AI it just took a few seconds and then we can see Word level timestamps if needed as what was spoken at what time the starting and end time for that and also the confidence if there are multiple speakers then it also identifies the speakers for us and then we can also see the utterances at different times so there's also word there's a full text and there's some additional information available here now we can use this data for many different things one is we can parse the transcript so we can just look at the full transcript that was available from this video or in this case this MP3 file and then we can also run to get subtitles and this could be used for any Services where we want to add subtitles in different formats so there is the SRT and the VT format available and the way this looks so I ran it for SRT We have basically the time stamps as well as the sentences those were converted from those time stamps and we can see that it goes on for the full length of the audio file and then if needed we can also convert that to vtt last thing is that if you have credits available in your assembly AI account you can also perform a summary of the audio or you could perhaps do some additional task so for example in our case we could say that create a summary of the transcript we could also say that create a blog post from the transcript or perhaps an essay from the transcript so we can get creative with the available information since the transcript of the file is now available and we can utilize that text for many different purposes the flow and the components should be available in the store be sure to add your API key in all of these components wherever it says to add the API key if not it might throw some errors and there are also some additional components available you can check those out based on your use cases as well give it a try and let us know if you found it helpful\")\" \n\nwe only want the page_content "
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 231,
+ "id": "ChatInput-Z88lk",
+ "measured": {
+ "height": 231,
+ "width": 320
+ },
+ "position": {
+ "x": 1436.7228707197569,
+ "y": 1045.2749109595
+ },
+ "positionAbsolute": {
+ "x": 1436.7228707197569,
+ "y": 1045.2749109595
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Retrieves stored chat messages from Langflow tables or an external memory.",
+ "display_name": "Chat Memory",
+ "id": "Memory-v5bRq",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Retrieves stored chat messages from Langflow tables or an external memory.",
+ "display_name": "Chat Memory",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template"
+ ],
+ "frozen": false,
+ "icon": "message-square-more",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "retrieve_messages",
+ "name": "messages",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "retrieve_messages_as_text",
+ "name": "messages_text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import aget_messages\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Message History\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"Memory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Message\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n ]\n\n async def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory and not hasattr(self.memory, \"aget_messages\"):\n memory_name = type(self.memory).__name__\n err_msg = f\"External Memory object ({memory_name}) must have 'aget_messages' method.\"\n raise AttributeError(err_msg)\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = await self.memory.aget_messages()\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = await aget_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n async def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, await self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n"
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Memory"
+ },
+ "dragging": false,
+ "height": 262,
+ "id": "Memory-v5bRq",
+ "measured": {
+ "height": 262,
+ "width": 320
+ },
+ "position": {
+ "x": 1830.6888981898887,
+ "y": 946.1205963195098
+ },
+ "positionAbsolute": {
+ "x": 1830.6888981898887,
+ "y": 946.1205963195098
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-QIyYT",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "BASE_COMPONENT_CODE",
+ "CUSTOM_COMPONENT_CODE",
+ "EXAMPLE_COMPONENTS",
+ "CHAT_HISTORY",
+ "USER_INPUT"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "BASE_COMPONENT_CODE": {
+ "advanced": false,
+ "display_name": "BASE_COMPONENT_CODE",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "BASE_COMPONENT_CODE",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "CHAT_HISTORY": {
+ "advanced": false,
+ "display_name": "CHAT_HISTORY",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "CHAT_HISTORY",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "CUSTOM_COMPONENT_CODE": {
+ "advanced": false,
+ "display_name": "CUSTOM_COMPONENT_CODE",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "CUSTOM_COMPONENT_CODE",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "EXAMPLE_COMPONENTS": {
+ "advanced": false,
+ "display_name": "EXAMPLE_COMPONENTS",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "EXAMPLE_COMPONENTS",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "USER_INPUT": {
+ "advanced": false,
+ "display_name": "USER_INPUT",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "USER_INPUT",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "\nYou are an AI assistant specialized in creating Langflow components based on user requirements. Your task is to generate the code for a custom Langflow component according to the user's specifications.\n\nFirst, review the following code snippets for reference:\n\n\n{BASE_COMPONENT_CODE}\n \n\n\n{CUSTOM_COMPONENT_CODE}\n \n\n\n{EXAMPLE_COMPONENTS}\n \n\nNow, follow these steps to create a custom Langflow component:\n\n1. Analyze the user's input to determine the requirements for the component.\n2. Use an section to plan out the component structure and features based on the user's requirements.\n3. Generate the code for the custom component, using the provided code snippets as reference and inspiration.\n4. Provide a brief explanation of the component's functionality and how to use it.\n\nHere's the chat history and user input:\n\n\n{CHAT_HISTORY}\n \n\n\n{USER_INPUT}\n \n\nBased on the user's input, create a custom Langflow component that meets their requirements. Your response should include:\n\n1. \n Use this section to analyze the user's requirements and plan the component structure.\n \n\n2. \n Generate the complete code for the custom Langflow component here.\n \n\n3. \n Provide a brief explanation of the component's functionality and how to use it.\n \n\nRemember to:\n- Use the provided code snippets as a reference, but create a unique component tailored to the user's needs.\n- Include all necessary imports and class definitions.\n- Implement the required inputs, outputs, and any additional features specified by the user.\n- Use clear and descriptive variable names and comments to enhance code readability.\n- Ensure that the component follows Langflow best practices and conventions.\n\nIf the user's input is unclear or lacks specific details, make reasonable assumptions based on the context and explain these assumptions in your response.\n\n "
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 685,
+ "id": "Prompt-QIyYT",
+ "measured": {
+ "height": 685,
+ "width": 320
+ },
+ "position": {
+ "x": 2219.5265974825707,
+ "y": 521.6320563271215
+ },
+ "positionAbsolute": {
+ "x": 2219.5265974825707,
+ "y": 521.6320563271215
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-Jj8M6",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 232,
+ "id": "ChatOutput-Jj8M6",
+ "measured": {
+ "height": 232,
+ "width": 320
+ },
+ "position": {
+ "x": 2947.267779013826,
+ "y": 891.8123698756774
+ },
+ "positionAbsolute": {
+ "x": 2947.267779013826,
+ "y": 891.8123698756774
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-M2lYQ",
+ "node": {
+ "description": "# Fetch Components code \n\nUsing the URL component we are extracting from Github, the code from a few classes to provide as example to the LLM. \n\nThis ensures we are always up to date with recent information from the codebase.",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-M2lYQ",
+ "measured": {
+ "height": 324,
+ "width": 325
+ },
+ "position": {
+ "x": 1430.2014058924922,
+ "y": -19.30392196909918
+ },
+ "positionAbsolute": {
+ "x": 1430.2014058924922,
+ "y": -19.30392196909918
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-nxR1T",
+ "node": {
+ "description": "# 🛠️ Custom Component Generator 🚀\n\nHi! I'm here to help you create custom components for Langflow. Think of me as your technical partner who can help turn your ideas into working components! \n\n## 🎯 How to Work With Me\n\n### 1. 💭 Tell Me What You Want to Build\nSimply describe what you want your component to do in plain English. For example:\n- \"I need a component that sends Slack messages\"\n- \"I want to create a tool that can process CSV files\"\n- \"I need something that can translate text\"\n\n### 2. 📚 Share Any Relevant Information\nIf you're working with a specific:\n- 🔑 API or service (just share the documentation link or main endpoints)\n- 📄 File format\n- 🔄 Data structure\n- 🔧 Existing component you want to modify\n\n### 3. 🎨 Let Me Help Design It\nI'll help by:\n- 📊 Breaking down complex requirements into manageable pieces\n- 💡 Suggesting the best way to structure inputs and outputs\n- ⚙️ Creating the component code\n- 📝 Explaining how to use it\n\n### 4. 🔄 Iterative Refinement\nWe can then:\n- ✅ Test and refine the component\n- ⭐ Add features\n- 🔧 Modify behavior\n- 🛡️ Improve error handling\n- 📖 Add documentation\n\n## 🚀 What I Can Help With\n\nI can help create components that:\n- 📊 Process different file types (CSV, JSON, Excel, etc.)\n- 🔌 Integrate with external APIs\n- 🔄 Transform data\n- 🔀 Route messages\n- 🌐 Handle web requests\n- 🎯 Parse structured data\n- ✨ And much more!\n\n## 💡 Tips for Best Results\n\n1. **Be Specific** 🎯: The more details you provide about what you want to accomplish, the better I can help.\n\n2. **Share Examples** 📋: If you have example data or specific use cases, share them.\n\n3. **Ask Questions** ❓: Don't hesitate to ask for clarification or modifications.\n\nJust start by telling me what kind of component you'd like to create, and I'll guide you through the process! \n\nReady to build something awesome? 🚀 Let's get started!",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 573,
+ "id": "note-nxR1T",
+ "measured": {
+ "height": 573,
+ "width": 324
+ },
+ "position": {
+ "x": 807.6293964045135,
+ "y": 605.6504562080672
+ },
+ "positionAbsolute": {
+ "x": 807.6293964045135,
+ "y": 605.6504562080672
+ },
+ "resizing": false,
+ "selected": true,
+ "style": {
+ "height": 573,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "URL-pFAuR",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Fetch content from one or more URLs.",
+ "display_name": "URL",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "urls",
+ "format"
+ ],
+ "frozen": false,
+ "icon": "layout-template",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "fetch_content",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "fetch_content_text",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import re\n\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Load and retrive data from specified URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.\",\n options=[\"Text\", \"Raw HTML\"],\n value=\"Text\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Message\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n msg = f\"Invalid URL: {string}\"\n raise ValueError(msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.fetch_content())\n"
+ },
+ "format": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Output Format",
+ "dynamic": false,
+ "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ "name": "format",
+ "options": [
+ "Text",
+ "Raw HTML"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Text"
+ },
+ "urls": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URLs",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": true,
+ "load_from_db": false,
+ "name": "urls",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/custom/custom_component/component.py"
+ ]
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "URL"
+ },
+ "dragging": false,
+ "height": 365,
+ "id": "URL-pFAuR",
+ "measured": {
+ "height": 365,
+ "width": 320
+ },
+ "position": {
+ "x": 1436.3617127766433,
+ "y": 264.218898085405
+ },
+ "positionAbsolute": {
+ "x": 1436.3617127766433,
+ "y": 264.218898085405
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "URL-wNOPd",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Fetch content from one or more URLs.",
+ "display_name": "URL",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "urls",
+ "format"
+ ],
+ "frozen": false,
+ "icon": "layout-template",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "fetch_content",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "fetch_content_text",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import re\n\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Load and retrive data from specified URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.\",\n options=[\"Text\", \"Raw HTML\"],\n value=\"Text\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Message\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n msg = f\"Invalid URL: {string}\"\n raise ValueError(msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.fetch_content())\n"
+ },
+ "format": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Output Format",
+ "dynamic": false,
+ "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ "name": "format",
+ "options": [
+ "Text",
+ "Raw HTML"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Text"
+ },
+ "urls": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URLs",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": true,
+ "load_from_db": false,
+ "name": "urls",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/agents/agent.py",
+ "https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/helpers/structured_output.py",
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/calculator.py",
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/tools/tavily_search.py",
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/models/ollama.py",
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/logic/conditional_router.py",
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/data/file.py"
+ ]
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "URL"
+ },
+ "dragging": false,
+ "height": 661,
+ "id": "URL-wNOPd",
+ "measured": {
+ "height": 661,
+ "width": 320
+ },
+ "position": {
+ "x": 1831.5895760156684,
+ "y": 245.62940316018893
+ },
+ "positionAbsolute": {
+ "x": 1831.5895760156684,
+ "y": 245.62940316018893
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "URL-KARv3",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Fetch content from one or more URLs.",
+ "display_name": "URL",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "urls",
+ "format"
+ ],
+ "frozen": false,
+ "icon": "layout-template",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "fetch_content",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "fetch_content_text",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import re\n\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Load and retrive data from specified URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.\",\n options=[\"Text\", \"Raw HTML\"],\n value=\"Text\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Message\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n msg = f\"Invalid URL: {string}\"\n raise ValueError(msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.fetch_content())\n"
+ },
+ "format": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Output Format",
+ "dynamic": false,
+ "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ "name": "format",
+ "options": [
+ "Text",
+ "Raw HTML"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Text"
+ },
+ "urls": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URLs",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": true,
+ "load_from_db": false,
+ "name": "urls",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "https://raw.githubusercontent.com/langflow-ai/langflow/refs/heads/main/src/backend/base/langflow/components/custom_component/custom_component.py"
+ ]
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "URL"
+ },
+ "dragging": false,
+ "height": 365,
+ "id": "URL-KARv3",
+ "measured": {
+ "height": 365,
+ "width": 320
+ },
+ "position": {
+ "x": 1436.982480021523,
+ "y": 651.1409296825055
+ },
+ "positionAbsolute": {
+ "x": 1436.982480021523,
+ "y": 651.1409296825055
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "AnthropicModel-RQQCm",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.",
+ "display_name": "Anthropic",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_name",
+ "api_key",
+ "temperature",
+ "base_url",
+ "tool_model_enabled",
+ "prefill"
+ ],
+ "frozen": false,
+ "icon": "Anthropic",
+ "key": "AnthropicModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0005851173668140926,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Anthropic API Key",
+ "dynamic": false,
+ "info": "Your Anthropic API key.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ANTHROPIC_API_KEY"
+ },
+ "base_url": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Anthropic API URL",
+ "dynamic": false,
+ "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "base_url",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "https://api.anthropic.com"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\"} and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4096
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "claude-3-5-sonnet-latest",
+ "claude-3-5-haiku-latest",
+ "claude-3-opus-latest",
+ "claude-3-5-sonnet-20240620",
+ "claude-3-5-sonnet-20241022",
+ "claude-3-5-haiku-20241022",
+ "claude-3-sonnet-20240229",
+ "claude-3-haiku-20240307"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "refresh_button": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "claude-3-5-sonnet-latest"
+ },
+ "prefill": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Prefill",
+ "dynamic": false,
+ "info": "Prefill text to guide the model's response.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "prefill",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "admin@admin.com"
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "tool_model_enabled": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Enable Tool Models",
+ "dynamic": false,
+ "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "tool_model_enabled",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AnthropicModel"
+ },
+ "dragging": false,
+ "id": "AnthropicModel-RQQCm",
+ "measured": {
+ "height": 797,
+ "width": 320
+ },
+ "position": {
+ "x": 2585.5577736139826,
+ "y": 454.98013556663204
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -366.41363591649906,
+ "y": 264.64346206947675,
+ "zoom": 0.5351858410044688
+ }
+ },
+ "description": "Generates well-structured code for custom components following Langflow's specifications.",
+ "endpoint_name": null,
+ "gradient": "1",
+ "icon": "SquareCode",
+ "id": "5155918c-68b1-4013-8d7b-61f31ffa931c",
+ "is_component": false,
+ "last_tested_version": "1.1.0",
+ "name": "Custom Component Generator",
+ "tags": [
+ "coding",
+ "web-scraping"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json
new file mode 100644
index 0000000..db0a592
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Document Q&A.json
@@ -0,0 +1,1759 @@
+{
+ "data": {
+ "edges": [
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "File",
+ "id": "File-GwJQZ",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-BbvKb",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-File-GwJQZ{œdataTypeœ:œFileœ,œidœ:œFile-GwJQZœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-BbvKb{œfieldNameœ:œdataœ,œidœ:œParseData-BbvKbœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "File-GwJQZ",
+ "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-GwJQZœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-BbvKb",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-BbvKbœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-BbvKb",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "Document",
+ "id": "Prompt-yvZHT",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-BbvKb{œdataTypeœ:œParseDataœ,œidœ:œParseData-BbvKbœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-yvZHT{œfieldNameœ:œDocumentœ,œidœ:œPrompt-yvZHTœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseData-BbvKb",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-BbvKbœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-yvZHT",
+ "targetHandle": "{œfieldNameœ: œDocumentœ, œidœ: œPrompt-yvZHTœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-li477",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-atkmo",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-li477{œdataTypeœ:œChatInputœ,œidœ:œChatInput-li477œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-atkmo{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-atkmoœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-li477",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-li477œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-atkmo",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-atkmoœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-yvZHT",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-atkmo",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-yvZHT{œdataTypeœ:œPromptœ,œidœ:œPrompt-yvZHTœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-atkmo{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-atkmoœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-yvZHT",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-yvZHTœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-atkmo",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-atkmoœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-atkmo",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-8pgwS",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-atkmo{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-atkmoœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-8pgwS{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-8pgwSœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-atkmo",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-atkmoœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-8pgwS",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-8pgwSœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "id": "ChatInput-li477",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "What is this document is about?"
+ },
+ "sender": {
+ "advanced": true,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-li477",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 516.7529480335185,
+ "y": 237.04967879541528
+ },
+ "positionAbsolute": {
+ "x": 516.7529480335185,
+ "y": 237.04967879541528
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-8pgwS",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-8pgwS",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 1631.3766926569258,
+ "y": 136.66509468115308
+ },
+ "positionAbsolute": {
+ "x": 1631.3766926569258,
+ "y": 136.66509468115308
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "id": "ParseData-BbvKb",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ }
+ }
+ },
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "height": 302,
+ "id": "ParseData-BbvKb",
+ "measured": {
+ "height": 302,
+ "width": 320
+ },
+ "position": {
+ "x": 514.8054600415829,
+ "y": -117.1921617826383
+ },
+ "positionAbsolute": {
+ "x": 514.8054600415829,
+ "y": -117.1921617826383
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-e77cr",
+ "node": {
+ "description": "## Get Your OpenAI API Key\n\n**Steps**:\n\n1. **Visit** [OpenAI's API Key Page](https://platform.openai.com/api-keys).\n\n2. **Log In/Sign Up**:\n - Log in or create a new OpenAI account.\n\n3. **Generate API Key**:\n - Click \"Create New Secret Key\" to obtain your key.\n\n4. **Store Your Key Securely**:\n - Note it down as it will only display once.\n\n5. **Enter API Key**:\n - Input your key in the OpenAI API Key field within the component setup.\n\nKeep your key safe and manage it responsibly!",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "rose"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": true,
+ "height": 325,
+ "id": "note-e77cr",
+ "measured": {
+ "height": 324,
+ "width": 325
+ },
+ "position": {
+ "x": 1253.2038187140245,
+ "y": -421.5721019678553
+ },
+ "positionAbsolute": {
+ "x": 1253.2038187140245,
+ "y": -421.5721019678553
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "note-0kA8I",
+ "node": {
+ "description": "# Document Q&A\n\n**Purpose:**\nThis flow leverages a language model to answer questions based on content from a loaded document. It's ideal for obtaining quick insights from PDFs or other text files by asking direct questions.\n\n**Components**:\n1. **File Component**: Loads and processes your document in supported formats.\n2. **Parse Data**: Converts the loaded document into text using a specified template for consistent processing.\n3. **Prompt Component**: Forms a structured query by combining the parsed document content with user questions.\n4. **OpenAI Model**: Engages OpenAI's language model to generate responses to queries based on the document context.\n5. **Chat Input/Output**: Facilitates user queries and displays AI-generated answers seamlessly.\n\n**Steps to Use**:\n1. **Upload Document**: Use the \"File\" component to upload a document or text file you want to query.\n2. **Enter Question**: Through the \"Chat Input\" field, type your question related to the document content.\n3. **Run the Flow**: Activate the flow to process the input and generate an answer using the OpenAI model.\n4. **View Response**: Read the generated answer in the \"Chat Output\" field for immediate insights.\n\n**Benefits**:\n- Simplifies the process of extracting information from documents.\n- Provides a user-friendly interface for interactive document exploration using AI.\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 452,
+ "id": "note-0kA8I",
+ "measured": {
+ "height": 408,
+ "width": 325
+ },
+ "position": {
+ "x": -338.7070086205371,
+ "y": -177.11912020709357
+ },
+ "positionAbsolute": {
+ "x": -338.7070086205371,
+ "y": -177.11912020709357
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 452,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "File-GwJQZ",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Load a file to be used in your project.",
+ "display_name": "File",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "path",
+ "silent_errors",
+ "use_multithreading",
+ "concurrency_multithreading"
+ ],
+ "frozen": false,
+ "icon": "file-text",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "load_files",
+ "name": "data",
+ "required_inputs": [],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data import BaseFileComponent\nfrom langflow.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data\nfrom langflow.io import BoolInput, IntInput\nfrom langflow.schema import Data\n\n\nclass FileComponent(BaseFileComponent):\n \"\"\"Handles loading and processing of individual or zipped text files.\n\n This component supports processing multiple valid files within a zip archive,\n resolving paths, validating file types, and optionally using multithreading for processing.\n \"\"\"\n\n display_name = \"File\"\n description = \"Load a file to be used in your project.\"\n icon = \"file-text\"\n name = \"File\"\n\n VALID_EXTENSIONS = TEXT_FILE_TYPES\n\n inputs = [\n *BaseFileComponent._base_inputs,\n BoolInput(\n name=\"use_multithreading\",\n display_name=\"[Deprecated] Use Multithreading\",\n advanced=True,\n value=True,\n info=\"Set 'Processing Concurrency' greater than 1 to enable multithreading.\",\n ),\n IntInput(\n name=\"concurrency_multithreading\",\n display_name=\"Processing Concurrency\",\n advanced=True,\n info=\"When multiple files are being processed, the number of files to process concurrently.\",\n value=1,\n ),\n ]\n\n outputs = [\n *BaseFileComponent._base_outputs,\n ]\n\n def process_files(self, file_list: list[BaseFileComponent.BaseFile]) -> list[BaseFileComponent.BaseFile]:\n \"\"\"Processes files either sequentially or in parallel, depending on concurrency settings.\n\n Args:\n file_list (list[BaseFileComponent.BaseFile]): List of files to process.\n\n Returns:\n list[BaseFileComponent.BaseFile]: Updated list of files with merged data.\n \"\"\"\n\n def process_file(file_path: str, *, silent_errors: bool = False) -> Data | None:\n \"\"\"Processes a single file and returns its Data object.\"\"\"\n try:\n return parse_text_file_to_data(file_path, silent_errors=silent_errors)\n except FileNotFoundError as e:\n msg = f\"File not found: {file_path}. Error: {e}\"\n self.log(msg)\n if not silent_errors:\n raise\n return None\n except Exception as e:\n msg = f\"Unexpected error processing {file_path}: {e}\"\n self.log(msg)\n if not silent_errors:\n raise\n return None\n\n if not file_list:\n msg = \"No files to process.\"\n raise ValueError(msg)\n\n concurrency = 1 if not self.use_multithreading else max(1, self.concurrency_multithreading)\n file_count = len(file_list)\n\n parallel_processing_threshold = 2\n if concurrency < parallel_processing_threshold or file_count < parallel_processing_threshold:\n if file_count > 1:\n self.log(f\"Processing {file_count} files sequentially.\")\n processed_data = [process_file(str(file.path), silent_errors=self.silent_errors) for file in file_list]\n else:\n self.log(f\"Starting parallel processing of {file_count} files with concurrency: {concurrency}.\")\n file_paths = [str(file.path) for file in file_list]\n processed_data = parallel_load_data(\n file_paths,\n silent_errors=self.silent_errors,\n load_function=process_file,\n max_concurrency=concurrency,\n )\n\n # Use rollup_basefile_data to merge processed data with BaseFile objects\n return self.rollup_data(file_list, processed_data)\n"
+ },
+ "concurrency_multithreading": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Processing Concurrency",
+ "dynamic": false,
+ "info": "When multiple files are being processed, the number of files to process concurrently.",
+ "list": false,
+ "name": "concurrency_multithreading",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4
+ },
+ "delete_server_file_after_processing": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Delete Server File After Processing",
+ "dynamic": false,
+ "info": "If true, the Server File Path will be deleted after processing.",
+ "list": false,
+ "name": "delete_server_file_after_processing",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "file_path": {
+ "_input_type": "HandleInput",
+ "advanced": true,
+ "display_name": "Server File Path",
+ "dynamic": false,
+ "info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.",
+ "input_types": [
+ "Data",
+ "Message"
+ ],
+ "list": true,
+ "name": "file_path",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "ignore_unspecified_files": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Ignore Unspecified Files",
+ "dynamic": false,
+ "info": "If true, Data with no 'file_path' property will be ignored.",
+ "list": false,
+ "name": "ignore_unspecified_files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "ignore_unsupported_extensions": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Ignore Unsupported Extensions",
+ "dynamic": false,
+ "info": "If true, files with unsupported extensions will not be processed.",
+ "list": false,
+ "name": "ignore_unsupported_extensions",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "path": {
+ "_input_type": "FileInput",
+ "advanced": false,
+ "display_name": "Path",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "zip",
+ "tar",
+ "tgz",
+ "bz2",
+ "gz"
+ ],
+ "file_path": "",
+ "info": "Supported file extensions: txt, md, mdx, csv, json, yaml, yml, xml, html, htm, pdf, docx, py, sh, sql, js, ts, tsx; optionally bundled in file extensions: zip, tar, tgz, bz2, gz",
+ "list": false,
+ "name": "path",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "silent_errors": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Silent Errors",
+ "dynamic": false,
+ "info": "If true, errors will not raise an exception.",
+ "list": false,
+ "name": "silent_errors",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "use_multithreading": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "[Deprecated] Use Multithreading",
+ "dynamic": false,
+ "info": "Set 'Processing Concurrency' greater than 1 to enable multithreading.",
+ "list": false,
+ "name": "use_multithreading",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "File"
+ },
+ "dragging": false,
+ "height": 232,
+ "id": "File-GwJQZ",
+ "measured": {
+ "height": 232,
+ "width": 320
+ },
+ "position": {
+ "x": 150.6029945346864,
+ "y": -88.71582365936283
+ },
+ "positionAbsolute": {
+ "x": 155.39382083637838,
+ "y": -82.32805525710685
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-yvZHT",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "Document"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "error": null,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "full_path": null,
+ "icon": "prompts",
+ "is_composition": null,
+ "is_input": null,
+ "is_output": null,
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "name": "",
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "Document": {
+ "advanced": false,
+ "display_name": "Document",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "Document",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Answer user's questions based on the document below:\n\n---\n\n{Document}\n\n---\n\nQuestion:"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 347,
+ "id": "Prompt-yvZHT",
+ "measured": {
+ "height": 347,
+ "width": 320
+ },
+ "position": {
+ "x": 882.4192413332464,
+ "y": -63.08797684105531
+ },
+ "positionAbsolute": {
+ "x": 895.1947781377585,
+ "y": -59.89409263992732
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-atkmo",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-atkmo",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 1257.1422612381514,
+ "y": -99.92567023810267
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": 285.05178335748246,
+ "y": 410.84335125276027,
+ "zoom": 0.6261967792362849
+ }
+ },
+ "description": "Integrates PDF reading with a language model to answer document-specific questions. Ideal for small-scale texts, it facilitates direct queries with immediate insights.",
+ "endpoint_name": null,
+ "gradient": "3",
+ "icon": "FileQuestion",
+ "id": "febba2f9-69b3-484b-8aef-65626810ec8a",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Document Q&A",
+ "tags": [
+ "rag",
+ "q-a",
+ "openai"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json
new file mode 100644
index 0000000..136370b
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json
@@ -0,0 +1,1647 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-hx0nZ",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "StructuredOutputv2-Io4Zq",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-hx0nZ{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-hx0nZœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputv2-Io4Zq{œfieldNameœ:œllmœ,œidœ:œStructuredOutputv2-Io4Zqœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "OpenAIModel-hx0nZ",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-hx0nZœ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}",
+ "target": "StructuredOutputv2-Io4Zq",
+ "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputv2-Io4Zqœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-Gb2ag",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "StructuredOutputv2-Io4Zq",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-Gb2ag{œdataTypeœ:œChatInputœ,œidœ:œChatInput-Gb2agœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputv2-Io4Zq{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputv2-Io4Zqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-Gb2ag",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-Gb2agœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "StructuredOutputv2-Io4Zq",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputv2-Io4Zqœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseDataFrame",
+ "id": "ParseDataFrame-PwX09",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-xjU9g",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseDataFrame-PwX09{œdataTypeœ:œParseDataFrameœ,œidœ:œParseDataFrame-PwX09œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-xjU9g{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-xjU9gœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseDataFrame-PwX09",
+ "sourceHandle": "{œdataTypeœ: œParseDataFrameœ, œidœ: œParseDataFrame-PwX09œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-xjU9g",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-xjU9gœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "StructuredOutput",
+ "id": "StructuredOutputv2-Io4Zq",
+ "name": "structured_output_dataframe",
+ "output_types": [
+ "DataFrame"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "df",
+ "id": "ParseDataFrame-PwX09",
+ "inputTypes": [
+ "DataFrame"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__StructuredOutputv2-Io4Zq{œdataTypeœ:œStructuredOutputœ,œidœ:œStructuredOutputv2-Io4Zqœ,œnameœ:œstructured_output_dataframeœ,œoutput_typesœ:[œDataFrameœ]}-ParseDataFrame-PwX09{œfieldNameœ:œdfœ,œidœ:œParseDataFrame-PwX09œ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "StructuredOutputv2-Io4Zq",
+ "sourceHandle": "{œdataTypeœ: œStructuredOutputœ, œidœ: œStructuredOutputv2-Io4Zqœ, œnameœ: œstructured_output_dataframeœ, œoutput_typesœ: [œDataFrameœ]}",
+ "target": "ParseDataFrame-PwX09",
+ "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œParseDataFrame-PwX09œ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "OpenAIModel-hx0nZ",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.001,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-hx0nZ",
+ "measured": {
+ "height": 656,
+ "width": 320
+ },
+ "position": {
+ "x": 929.32546849971,
+ "y": -379.0571813289482
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-xjU9g",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.003169567463043492,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "id": "ChatOutput-xjU9g",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 2235,
+ "y": 435
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatInput-Gb2ag",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "inputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatInput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0020353564437605998,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "In 2022, the company demonstrated strong financial performance, reporting a gross profit of $1.2 billion, reflecting stable revenue generation and effective cost management. The EBITDA stood at $900 million, highlighting the company’s solid operational efficiency and profitability before interest, taxes, depreciation, and amortization. Despite a slight increase in operating expenses compared to 2021, the company maintained a healthy bottom line, achieving a net income of $500 million. This growth underscores the company’s ability to navigate economic challenges while sustaining profitability, reinforcing its financial stability and competitive position in the market."
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "id": "ChatInput-Gb2ag",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 866.761331501802,
+ "y": 581.619639019103
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-Nb5am",
+ "node": {
+ "description": "### 💡 Add your OpenAI API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "id": "note-Nb5am",
+ "measured": {
+ "height": 324,
+ "width": 324
+ },
+ "position": {
+ "x": 903.4968193095694,
+ "y": -432.3984534767629
+ },
+ "selected": false,
+ "type": "noteNode"
+ },
+ {
+ "data": {
+ "id": "note-qRCyj",
+ "node": {
+ "description": "\n# Financial Report Parser\n\nThis template extracts key financial metrics from a given financial report text using OpenAI's GPT-4o-mini model. The extracted data is structured and formatted for chat consumption.\n\n## Prerequisites\n\n- **[OpenAI API Key](https://platform.openai.com/)**\n\n## Quickstart\n\n1. Add your OpenAI API key to the OpenAI model.\n2. To run the flow, click **Playground**.\nThe **Chat Input** component in this template is pre-loaded with a sample financial report for demonstrating how structured data is extracted.\n\n* The **OpenAI** model component identifies and retrieves Gross Profit, EBITDA, Net Income, and Operating Expenses from the financial report.\n* The **Structured Output** component formats extracted data into a structured format for better readability and further processing.\n* The **Data to Message** component converts extracted data into formatted messages for chat consumption.\n\n\n\n\n\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 688,
+ "id": "note-qRCyj",
+ "measured": {
+ "height": 688,
+ "width": 620
+ },
+ "position": {
+ "x": 270.9912976390468,
+ "y": -396.43811550696176
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 619
+ },
+ {
+ "data": {
+ "id": "StructuredOutputv2-Io4Zq",
+ "node": {
+ "base_classes": [
+ "Data",
+ "DataFrame"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.",
+ "display_name": "Structured Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "llm",
+ "input_value",
+ "system_prompt",
+ "schema_name",
+ "output_schema"
+ ],
+ "frozen": false,
+ "icon": "braces",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Structured Output",
+ "method": "build_structured_output",
+ "name": "structured_output",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "structured_output_dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import TYPE_CHECKING, cast\n\nfrom pydantic import BaseModel, Field, create_model\n\nfrom langflow.base.models.chat_result import get_chat_result\nfrom langflow.custom import Component\nfrom langflow.helpers.base_model import build_model_from_schema\nfrom langflow.io import (\n BoolInput,\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n TableInput,\n)\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.table import EditMode\n\nif TYPE_CHECKING:\n from langflow.field_typing.constants import LanguageModel\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = (\n \"Transforms LLM responses into **structured data formats**. Ideal for extracting specific information \"\n \"or creating consistent outputs.\"\n )\n name = \"StructuredOutput\"\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Input Message\",\n info=\"The input message to the language model.\",\n tool_mode=True,\n required=True,\n ),\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Format Instructions\",\n info=\"The instructions to the language model for formatting the output.\",\n value=(\n \"You are an AI system designed to extract structured information from unstructured text.\"\n \"Given the input_text, return a JSON object with predefined keys based on the expected structure.\"\n \"Extract values accurately and format them according to the specified type \"\n \"(e.g., string, integer, float, date).\"\n \"If a value is missing or cannot be determined, return a default \"\n \"(e.g., null, 0, or 'N/A').\"\n \"If multiple instances of the expected structure exist within the input_text, \"\n \"stream each as a separate JSON object.\"\n ),\n required=True,\n advanced=True,\n ),\n MessageTextInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n advanced=True,\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n required=True,\n # TODO: remove deault value\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n \"edit_mode\": EditMode.INLINE,\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n \"edit_mode\": EditMode.POPOVER,\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"edit_mode\": EditMode.INLINE,\n \"description\": (\n \"Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).\"\n ),\n \"options\": [\"str\", \"int\", \"float\", \"bool\", \"list\", \"dict\"],\n \"default\": \"str\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"Multiple\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n \"edit_mode\": EditMode.INLINE,\n },\n ],\n value=[\n {\n \"name\": \"field\",\n \"description\": \"description of field\",\n \"type\": \"str\",\n \"multiple\": \"False\",\n }\n ],\n ),\n BoolInput(\n name=\"multiple\",\n advanced=True,\n display_name=\"Generate Multiple\",\n info=\"[Deplrecated] Always set to True\",\n value=True,\n ),\n ]\n\n outputs = [\n Output(\n name=\"structured_output\",\n display_name=\"Structured Output\",\n method=\"build_structured_output\",\n ),\n Output(\n name=\"structured_output_dataframe\",\n display_name=\"DataFrame\",\n method=\"as_dataframe\",\n ),\n ]\n\n def build_structured_output_base(self) -> Data:\n schema_name = self.schema_name or \"OutputModel\"\n\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n\n output_model = create_model(\n schema_name,\n objects=(list[output_model_], Field(description=f\"A list of {schema_name}.\")), # type: ignore[valid-type]\n )\n\n try:\n llm_with_structured_output = cast(\"LanguageModel\", self.llm).with_structured_output(schema=output_model) # type: ignore[valid-type, attr-defined]\n\n except NotImplementedError as exc:\n msg = f\"{self.llm.__class__.__name__} does not support structured output.\"\n raise TypeError(msg) from exc\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n result = get_chat_result(\n runnable=llm_with_structured_output,\n system_message=self.system_prompt,\n input_value=self.input_value,\n config=config_dict,\n )\n if isinstance(result, BaseModel):\n result = result.model_dump()\n if \"objects\" in result:\n return result[\"objects\"]\n return result\n\n def build_structured_output(self) -> Data:\n output = self.build_structured_output_base()\n\n return Data(results=output)\n\n def as_dataframe(self) -> DataFrame:\n output = self.build_structured_output_base()\n if isinstance(output, list):\n return DataFrame(data=output)\n return DataFrame(data=[output])\n"
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input Message",
+ "dynamic": false,
+ "info": "The input message to the language model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "llm": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Language Model",
+ "dynamic": false,
+ "info": "The language model to use to generate the structured output.",
+ "input_types": [
+ "LanguageModel"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "llm",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "multiple": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Generate Multiple",
+ "dynamic": false,
+ "info": "[Deplrecated] Always set to True",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "multiple",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "output_schema": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Output Schema",
+ "dynamic": false,
+ "info": "Define the structure and data types for the model's output.",
+ "is_list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "output_schema",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "table_icon": "Table",
+ "table_schema": {
+ "columns": [
+ {
+ "default": "field",
+ "description": "Specify the name of the output field.",
+ "disable_edit": false,
+ "display_name": "Name",
+ "edit_mode": "inline",
+ "filterable": true,
+ "formatter": "text",
+ "hidden": false,
+ "name": "name",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "description of field",
+ "description": "Describe the purpose of the output field.",
+ "disable_edit": false,
+ "display_name": "Description",
+ "edit_mode": "popover",
+ "filterable": true,
+ "formatter": "text",
+ "hidden": false,
+ "name": "description",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "text",
+ "description": "Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).",
+ "disable_edit": false,
+ "display_name": "Type",
+ "edit_mode": "inline",
+ "filterable": true,
+ "formatter": "text",
+ "hidden": false,
+ "name": "type",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "False",
+ "description": "Set to True if this output field should be a list of the specified type.",
+ "disable_edit": false,
+ "display_name": "Multiple",
+ "edit_mode": "inline",
+ "filterable": true,
+ "formatter": "text",
+ "hidden": false,
+ "name": "multiple",
+ "sortable": true,
+ "type": "boolean"
+ }
+ ]
+ },
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "trigger_icon": "Table",
+ "trigger_text": "Open table",
+ "type": "table",
+ "value": [
+ {
+ "description": "description of field",
+ "multiple": "False",
+ "name": "EBITIDA",
+ "type": "text"
+ },
+ {
+ "description": "description of field",
+ "multiple": "False",
+ "name": "NET_INCOME",
+ "type": "text"
+ },
+ {
+ "description": "description of field",
+ "multiple": "False",
+ "name": "GROSS_PROFIT",
+ "type": "text"
+ }
+ ]
+ },
+ "schema_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Schema Name",
+ "dynamic": false,
+ "info": "Provide a name for the output data schema.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "schema_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "system_prompt": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Format Instructions",
+ "dynamic": false,
+ "info": "The instructions to the language model for formatting the output.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_prompt",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "You are an AI system designed to extract structured information from unstructured text.Given the input_text, return a JSON object with predefined keys based on the expected structure.Extract values accurately and format them according to the specified type (e.g., string, integer, float, date).If a value is missing or cannot be determined, return a default (e.g., null, 0, or 'N/A').If multiple instances of the expected structure exist within the input_text, stream each as a separate JSON object."
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "StructuredOutput"
+ },
+ "dragging": false,
+ "id": "StructuredOutputv2-Io4Zq",
+ "measured": {
+ "height": 447,
+ "width": 320
+ },
+ "position": {
+ "x": 1378.7263469848428,
+ "y": 167.37722508100572
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ParseDataFrame-PwX09",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "processing",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert a DataFrame into plain text following a specified template. Each column in the DataFrame is treated as a possible template key, e.g. {col_name}.",
+ "display_name": "Parse DataFrame",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "df",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "braces",
+ "key": "ParseDataFrame",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Text",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.007568328950209746,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.io import DataFrameInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataFrameComponent(Component):\n display_name = \"Parse DataFrame\"\n description = (\n \"Convert a DataFrame into plain text following a specified template. \"\n \"Each column in the DataFrame is treated as a possible template key, e.g. {col_name}.\"\n )\n icon = \"braces\"\n name = \"ParseDataFrame\"\n\n inputs = [\n DataFrameInput(name=\"df\", display_name=\"DataFrame\", info=\"The DataFrame to convert to text rows.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=(\n \"The template for formatting each row. \"\n \"Use placeholders matching column names in the DataFrame, for example '{col1}', '{col2}'.\"\n ),\n value=\"{text}\",\n ),\n StrInput(\n name=\"sep\",\n display_name=\"Separator\",\n advanced=True,\n value=\"\\n\",\n info=\"String that joins all row texts when building the single Text output.\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Text\",\n name=\"text\",\n info=\"All rows combined into a single text, each row formatted by the template and separated by `sep`.\",\n method=\"parse_data\",\n ),\n ]\n\n def _clean_args(self):\n dataframe = self.df\n template = self.template or \"{text}\"\n sep = self.sep or \"\\n\"\n return dataframe, template, sep\n\n def parse_data(self) -> Message:\n \"\"\"Converts each row of the DataFrame into a formatted string using the template.\n\n then joins them with `sep`. Returns a single combined string as a Message.\n \"\"\"\n dataframe, template, sep = self._clean_args()\n\n lines = []\n # For each row in the DataFrame, build a dict and format\n for _, row in dataframe.iterrows():\n row_dict = row.to_dict()\n text_line = template.format(**row_dict) # e.g. template=\"{text}\", row_dict={\"text\": \"Hello\"}\n lines.append(text_line)\n\n # Join all lines with the provided separator\n result_string = sep.join(lines)\n self.status = result_string # store in self.status for UI logs\n return Message(text=result_string)\n"
+ },
+ "df": {
+ "_input_type": "DataFrameInput",
+ "advanced": false,
+ "display_name": "DataFrame",
+ "dynamic": false,
+ "info": "The DataFrame to convert to text rows.",
+ "input_types": [
+ "DataFrame"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "df",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "String that joins all row texts when building the single Text output.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template for formatting each row. Use placeholders matching column names in the DataFrame, for example '{col1}', '{col2}'.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "EBITIDA: {EBITIDA} , Net Income: {NET_INCOME} , GROSS_PROFIT: {GROSS_PROFIT}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseDataFrame"
+ },
+ "dragging": false,
+ "id": "ParseDataFrame-PwX09",
+ "measured": {
+ "height": 334,
+ "width": 320
+ },
+ "position": {
+ "x": 1783.5562660355338,
+ "y": 269.9258338477598
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -467.41512715571344,
+ "y": 404.01011532746,
+ "zoom": 0.8539331731519323
+ }
+ },
+ "description": "Extracts key financial metrics like Gross Profit, EBITDA, and Net Income from financial reports and structures them for easy analysis, using Structured Output Component",
+ "endpoint_name": "parse_financial_report",
+ "icon": "receipt",
+ "id": "00f4e809-0c6a-493e-8199-8ea22ddbfe64",
+ "is_component": false,
+ "last_tested_version": "1.1.5",
+ "name": "Financial Report Parser",
+ "tags": [
+ "chatbots",
+ "content-generation"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json
new file mode 100644
index 0000000..3dab12f
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Gmail Agent.json
@@ -0,0 +1,1824 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-fifot",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "Agent-5rqMu",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-fifot{œdataTypeœ:œChatInputœ,œidœ:œChatInput-fifotœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-5rqMu{œfieldNameœ:œinput_valueœ,œidœ:œAgent-5rqMuœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-fifot",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-fifotœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Agent-5rqMu",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-5rqMuœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Agent",
+ "id": "Agent-5rqMu",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-mXpv2",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Agent-5rqMu{œdataTypeœ:œAgentœ,œidœ:œAgent-5rqMuœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-mXpv2{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-mXpv2œ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Agent-5rqMu",
+ "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-5rqMuœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-mXpv2",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-mXpv2œ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ComposioAPI",
+ "id": "ComposioAPI-Z0Iiy",
+ "name": "tools",
+ "output_types": [
+ "Tool"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "Agent-5rqMu",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-ComposioAPI-Z0Iiy{œdataTypeœ:œComposioAPIœ,œidœ:œComposioAPI-Z0Iiyœ,œnameœ:œtoolsœ,œoutput_typesœ:[œToolœ]}-Agent-5rqMu{œfieldNameœ:œtoolsœ,œidœ:œAgent-5rqMuœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "source": "ComposioAPI-Z0Iiy",
+ "sourceHandle": "{œdataTypeœ: œComposioAPIœ, œidœ: œComposioAPI-Z0Iiyœ, œnameœ: œtoolsœ, œoutput_typesœ: [œToolœ]}",
+ "target": "Agent-5rqMu",
+ "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-5rqMuœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "Agent-5rqMu",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "agents",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "agent_llm",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout",
+ "system_prompt",
+ "tools",
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "agent_description",
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template",
+ "add_current_date_tool"
+ ],
+ "frozen": false,
+ "icon": "bot",
+ "key": "Agent",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Response",
+ "method": "message_response",
+ "name": "response",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 1.1732828199964098e-19,
+ "template": {
+ "_type": "Component",
+ "add_current_date_tool": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Current Date",
+ "dynamic": false,
+ "info": "If true, will add a tool to the agent that returns the current date.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "add_current_date_tool",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "agent_description": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Agent Description [Deprecated]",
+ "dynamic": false,
+ "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "agent_description",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "A helpful assistant with access to the following tools:"
+ },
+ "agent_llm": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Model Provider",
+ "dynamic": false,
+ "info": "The provider of the language model that the agent will use to generate responses.",
+ "input_types": [],
+ "name": "agent_llm",
+ "options": [
+ "Amazon Bedrock",
+ "Anthropic",
+ "Azure OpenAI",
+ "Google Generative AI",
+ "Groq",
+ "NVIDIA",
+ "OpenAI",
+ "SambaNova",
+ "Custom"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "OpenAI"
+ },
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Validate tools\n if not self.tools:\n msg = \"Tools are required to run the agent. Please add at least one tool.\"\n raise ValueError(msg)\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools,\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def to_toolkit(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n"
+ },
+ "handle_parsing_errors": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Handle Parse Errors",
+ "dynamic": false,
+ "info": "Should the Agent fix errors when reading user input for better processing?",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "handle_parsing_errors",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The input provided by the user for the agent to process.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_iterations": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Iterations",
+ "dynamic": false,
+ "info": "The maximum number of attempts the agent can make to complete its task before it stops.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_iterations",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 15
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": true,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "real_time_refresh": false,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o"
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "system_prompt": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Agent Instructions",
+ "dynamic": false,
+ "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_prompt",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "You are a helpful assistant that can use tools to answer questions and perform tasks."
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": true,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ },
+ "tools": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Tools",
+ "dynamic": false,
+ "info": "These are the tools that the agent can use to help with tasks.",
+ "input_types": [
+ "Tool"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "tools",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "verbose": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Verbose",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "verbose",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "Agent"
+ },
+ "dragging": false,
+ "id": "Agent-5rqMu",
+ "measured": {
+ "height": 624,
+ "width": 320
+ },
+ "position": {
+ "x": 246.2965482704007,
+ "y": 49.54798016575572
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatInput-fifot",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "inputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatInput",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0020353564437605998,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "id": "ChatInput-fifot",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": -74.59464648081578,
+ "y": 605.4102099043162
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-mXpv2",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.003169567463043492,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "id": "ChatOutput-mXpv2",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 641.2349415828351,
+ "y": 617.3336058447763
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-Oh8JB",
+ "node": {
+ "description": "# Gmail Agent\nUsing this flow you can send emails, create drafts, fetch emails and more\n\n## Instructions\n\n1. Get Composio API Key\n - Visit https://app.composio.dev\n - Enter the key in the \"Composio API Key\" field\n\n2. Authenticate Gmail Account\n - Select Gmail App from the dropdown menu in the App Names field\n - Click the refresh button next to the App Name\n - Follow the Gmail authentication link\n - After authenticating, click refresh again\n - Verify that authentication status shows as successful\n\n3. Select Actions\n - Default actions (pre-selected):\n - GMAIL_SEND_EMAIL: Send emails directly\n - GMAIL_CREATE_EMAIL_DRAFT: Create draft emails\n - Select additional actions based on your needs\n\n4. Configure OpenAI\n - Enter your OpenAI API key in the Agent OpenAI API key field\n\n5. Run Agent\n Example prompts:\n - \"Send an email to johndoe@gmail.com wishing them Happy birthday!\"\n - \"Create a draft email about project updates\"",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 842,
+ "id": "note-Oh8JB",
+ "measured": {
+ "height": 842,
+ "width": 395
+ },
+ "position": {
+ "x": -699.0352178208514,
+ "y": -87.30330362954265
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 394
+ },
+ {
+ "data": {
+ "description": "Use Composio toolset to run actions with your agent",
+ "display_name": "Composio Tools",
+ "id": "ComposioAPI-Z0Iiy",
+ "node": {
+ "base_classes": [
+ "Tool"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Use Composio toolset to run actions with your agent",
+ "display_name": "Composio Tools",
+ "documentation": "https://docs.composio.dev",
+ "edited": false,
+ "field_order": [
+ "entity_id",
+ "api_key",
+ "app_names",
+ "app_credentials",
+ "username",
+ "auth_link",
+ "auth_status",
+ "action_names"
+ ],
+ "frozen": false,
+ "icon": "Composio",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Tools",
+ "hidden": null,
+ "method": "build_tool",
+ "name": "tools",
+ "required_inputs": null,
+ "selected": "Tool",
+ "tool_mode": true,
+ "types": [
+ "Tool"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "action_names": {
+ "_input_type": "MultiselectInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Actions to use",
+ "dynamic": true,
+ "info": "The actions to pass to agent to execute",
+ "list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "action_names",
+ "options": [
+ "GMAIL_GET_PEOPLE",
+ "GMAIL_FETCH_EMAILS",
+ "GMAIL_FETCH_MESSAGE_BY_THREAD_ID",
+ "GMAIL_SEARCH_PEOPLE",
+ "GMAIL_SEND_EMAIL",
+ "GMAIL_CREATE_EMAIL_DRAFT",
+ "GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID",
+ "GMAIL_CREATE_LABEL",
+ "GMAIL_GET_ATTACHMENT",
+ "GMAIL_FIND_EMAIL_ID",
+ "GMAIL_REMOVE_LABEL",
+ "GMAIL_GET_PROFILE",
+ "GMAIL_ADD_LABEL_TO_EMAIL",
+ "GMAIL_GET_CONTACTS",
+ "GMAIL_REPLY_TO_THREAD",
+ "GMAIL_LIST_LABELS",
+ "GMAIL_FETCH_LAST_THREE_MESSAGES",
+ "GMAIL_LIST_THREADS",
+ "GMAIL_FETCH_EMAILS_WITH_LABEL",
+ "GMAIL_MODIFY_THREAD_LABELS"
+ ],
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "GMAIL_GET_PEOPLE"
+ ]
+ },
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Composio API Key",
+ "dynamic": false,
+ "info": "Refer to https://docs.composio.dev/faq/api_key/api_key",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "COMPOSIO_API_KEY"
+ },
+ "app_credentials": {
+ "_input_type": "SecretStrInput",
+ "advanced": true,
+ "display_name": "App Credentials",
+ "dynamic": true,
+ "info": "Credentials for app authentication (API Key, Password, etc)",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "app_credentials",
+ "password": true,
+ "placeholder": "",
+ "required": false,
+ "show": false,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "app_names": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "App Name",
+ "dynamic": false,
+ "info": "The app name to use. Please refresh after selecting app name",
+ "load_from_db": false,
+ "name": "app_names",
+ "options": [
+ "ACCELO",
+ "AIRTABLE",
+ "AMAZON",
+ "APALEO",
+ "ASANA",
+ "ATLASSIAN",
+ "ATTIO",
+ "AUTH0",
+ "BATTLENET",
+ "BITBUCKET",
+ "BLACKBAUD",
+ "BLACKBOARD",
+ "BOLDSIGN",
+ "BORNEO",
+ "BOX",
+ "BRAINTREE",
+ "BREX",
+ "BREX_STAGING",
+ "BRIGHTPEARL",
+ "CALENDLY",
+ "CANVA",
+ "CANVAS",
+ "CHATWORK",
+ "CLICKUP",
+ "CONFLUENCE",
+ "CONTENTFUL",
+ "D2LBRIGHTSPACE",
+ "DEEL",
+ "DISCORD",
+ "DISCORDBOT",
+ "DOCUSIGN",
+ "DROPBOX",
+ "DROPBOX_SIGN",
+ "DYNAMICS365",
+ "EPIC_GAMES",
+ "EVENTBRITE",
+ "EXIST",
+ "FACEBOOK",
+ "FIGMA",
+ "FITBIT",
+ "FRESHBOOKS",
+ "FRONT",
+ "GITHUB",
+ "GMAIL",
+ "GMAIL_BETA",
+ "GO_TO_WEBINAR",
+ "GOOGLE_ANALYTICS",
+ "GOOGLE_DRIVE_BETA",
+ "GOOGLE_MAPS",
+ "GOOGLECALENDAR",
+ "GOOGLEDOCS",
+ "GOOGLEDRIVE",
+ "GOOGLEMEET",
+ "GOOGLEPHOTOS",
+ "GOOGLESHEETS",
+ "GOOGLETASKS",
+ "GORGIAS",
+ "GUMROAD",
+ "HARVEST",
+ "HIGHLEVEL",
+ "HUBSPOT",
+ "ICIMS_TALENT_CLOUD",
+ "INTERCOM",
+ "JIRA",
+ "KEAP",
+ "KLAVIYO",
+ "LASTPASS",
+ "LEVER",
+ "LEVER_SANDBOX",
+ "LINEAR",
+ "LINKEDIN",
+ "LINKHUT",
+ "MAILCHIMP",
+ "MICROSOFT_TEAMS",
+ "MICROSOFT_TENANT",
+ "MIRO",
+ "MONDAY",
+ "MURAL",
+ "NETSUITE",
+ "NOTION",
+ "ONE_DRIVE",
+ "OUTLOOK",
+ "PAGERDUTY",
+ "PIPEDRIVE",
+ "PRODUCTBOARD",
+ "REDDIT",
+ "RING_CENTRAL",
+ "RIPPLING",
+ "SAGE",
+ "SALESFORCE",
+ "SEISMIC",
+ "SERVICEM8",
+ "SHARE_POINT",
+ "SHOPIFY",
+ "SLACK",
+ "SLACKBOT",
+ "SMARTRECRUITERS",
+ "SPOTIFY",
+ "SQUARE",
+ "STACK_EXCHANGE",
+ "SURVEY_MONKEY",
+ "TIMELY",
+ "TODOIST",
+ "TONEDEN",
+ "TRELLO",
+ "TWITCH",
+ "TWITTER",
+ "TWITTER_MEDIA",
+ "WAKATIME",
+ "WAVE_ACCOUNTING",
+ "WEBEX",
+ "WIZ",
+ "WRIKE",
+ "XERO",
+ "YANDEX",
+ "YNAB",
+ "YOUTUBE",
+ "ZENDESK",
+ "ZOHO",
+ "ZOHO_BIGIN",
+ "ZOHO_BOOKS",
+ "ZOHO_DESK",
+ "ZOHO_INVENTORY",
+ "ZOHO_INVOICE",
+ "ZOHO_MAIL",
+ "ZOOM"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "refresh_button": true,
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "GMAIL"
+ },
+ "auth_link": {
+ "_input_type": "LinkInput",
+ "advanced": true,
+ "display_name": "Authentication Link",
+ "dynamic": true,
+ "info": "Click to authenticate with OAuth2",
+ "load_from_db": false,
+ "name": "auth_link",
+ "placeholder": "Click to authenticate",
+ "required": false,
+ "show": false,
+ "title_case": false,
+ "type": "link",
+ "value": ""
+ },
+ "auth_status": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Auth Status",
+ "dynamic": true,
+ "info": "Current authentication status",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "auth_status",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "✅"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "# Standard library imports\nfrom collections.abc import Sequence\nfrom typing import Any\n\nimport requests\n\n# Third-party imports\nfrom composio.client.collections import AppAuthScheme\nfrom composio.client.exceptions import NoItemsFound\nfrom composio_langchain import Action, ComposioToolSet\nfrom langchain_core.tools import Tool\nfrom loguru import logger\n\n# Local imports\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import DropdownInput, LinkInput, MessageTextInput, MultiselectInput, SecretStrInput, StrInput\nfrom langflow.io import Output\n\n\nclass ComposioAPIComponent(LCToolComponent):\n display_name: str = \"Composio Tools\"\n description: str = \"Use Composio toolset to run actions with your agent\"\n name = \"ComposioAPI\"\n icon = \"Composio\"\n documentation: str = \"https://docs.composio.dev\"\n\n inputs = [\n # Basic configuration inputs\n MessageTextInput(name=\"entity_id\", display_name=\"Entity ID\", value=\"default\", advanced=True),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Composio API Key\",\n required=True,\n info=\"Refer to https://docs.composio.dev/faq/api_key/api_key\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"app_names\",\n display_name=\"App Name\",\n options=[],\n value=\"\",\n info=\"The app name to use. Please refresh after selecting app name\",\n refresh_button=True,\n required=True,\n ),\n # Authentication-related inputs (initially hidden)\n SecretStrInput(\n name=\"app_credentials\",\n display_name=\"App Credentials\",\n required=False,\n dynamic=True,\n show=False,\n info=\"Credentials for app authentication (API Key, Password, etc)\",\n load_from_db=False,\n ),\n MessageTextInput(\n name=\"username\",\n display_name=\"Username\",\n required=False,\n dynamic=True,\n show=False,\n info=\"Username for Basic authentication\",\n ),\n LinkInput(\n name=\"auth_link\",\n display_name=\"Authentication Link\",\n value=\"\",\n info=\"Click to authenticate with OAuth2\",\n dynamic=True,\n show=False,\n placeholder=\"Click to authenticate\",\n ),\n StrInput(\n name=\"auth_status\",\n display_name=\"Auth Status\",\n value=\"Not Connected\",\n info=\"Current authentication status\",\n dynamic=True,\n show=False,\n ),\n MultiselectInput(\n name=\"action_names\",\n display_name=\"Actions to use\",\n required=True,\n options=[],\n value=[],\n info=\"The actions to pass to agent to execute\",\n dynamic=True,\n show=False,\n ),\n ]\n\n outputs = [\n Output(name=\"tools\", display_name=\"Tools\", method=\"build_tool\"),\n ]\n\n def _check_for_authorization(self, app: str) -> str:\n \"\"\"Checks if the app is authorized.\n\n Args:\n app (str): The app name to check authorization for.\n\n Returns:\n str: The authorization status or URL.\n \"\"\"\n toolset = self._build_wrapper()\n entity = toolset.client.get_entity(id=self.entity_id)\n try:\n # Check if user is already connected\n entity.get_connection(app=app)\n except NoItemsFound:\n # Get auth scheme for the app\n auth_scheme = self._get_auth_scheme(app)\n return self._handle_auth_by_scheme(entity, app, auth_scheme)\n except Exception: # noqa: BLE001\n logger.exception(\"Authorization error\")\n return \"Error checking authorization\"\n else:\n return f\"{app} CONNECTED\"\n\n def _get_auth_scheme(self, app_name: str) -> AppAuthScheme:\n \"\"\"Get the primary auth scheme for an app.\n\n Args:\n app_name (str): The name of the app to get auth scheme for.\n\n Returns:\n AppAuthScheme: The auth scheme details.\n \"\"\"\n toolset = self._build_wrapper()\n try:\n return toolset.get_auth_scheme_for_app(app=app_name.lower())\n except Exception: # noqa: BLE001\n logger.exception(f\"Error getting auth scheme for {app_name}\")\n return None\n\n def _get_oauth_apps(self, api_key: str) -> list[str]:\n \"\"\"Fetch OAuth-enabled apps from Composio API.\n\n Args:\n api_key (str): The Composio API key.\n\n Returns:\n list[str]: A list containing OAuth-enabled app names.\n \"\"\"\n oauth_apps = []\n try:\n url = \"https://backend.composio.dev/api/v1/apps\"\n headers = {\"x-api-key\": api_key}\n params = {\n \"includeLocal\": \"true\",\n \"additionalFields\": \"auth_schemes\",\n \"sortBy\": \"alphabet\",\n }\n\n response = requests.get(url, headers=headers, params=params, timeout=20)\n data = response.json()\n\n for item in data.get(\"items\", []):\n for auth_scheme in item.get(\"auth_schemes\", []):\n if auth_scheme.get(\"mode\") in {\"OAUTH1\", \"OAUTH2\"}:\n oauth_apps.append(item[\"key\"].upper())\n break\n except requests.RequestException as e:\n logger.error(f\"Error fetching OAuth apps: {e}\")\n return []\n else:\n return oauth_apps\n\n def _handle_auth_by_scheme(self, entity: Any, app: str, auth_scheme: AppAuthScheme) -> str:\n \"\"\"Handle authentication based on the auth scheme.\n\n Args:\n entity (Any): The entity instance.\n app (str): The app name.\n auth_scheme (AppAuthScheme): The auth scheme details.\n\n Returns:\n str: The authentication status or URL.\n \"\"\"\n auth_mode = auth_scheme.auth_mode\n\n try:\n # First check if already connected\n entity.get_connection(app=app)\n except NoItemsFound:\n # If not connected, handle new connection based on auth mode\n if auth_mode == \"API_KEY\":\n if hasattr(self, \"app_credentials\") and self.app_credentials:\n try:\n entity.initiate_connection(\n app_name=app,\n auth_mode=\"API_KEY\",\n auth_config={\"api_key\": self.app_credentials},\n use_composio_auth=False,\n force_new_integration=True,\n )\n except Exception as e: # noqa: BLE001\n logger.error(f\"Error connecting with API Key: {e}\")\n return \"Invalid API Key\"\n else:\n return f\"{app} CONNECTED\"\n return \"Enter API Key\"\n\n if (\n auth_mode == \"BASIC\"\n and hasattr(self, \"username\")\n and hasattr(self, \"app_credentials\")\n and self.username\n and self.app_credentials\n ):\n try:\n entity.initiate_connection(\n app_name=app,\n auth_mode=\"BASIC\",\n auth_config={\"username\": self.username, \"password\": self.app_credentials},\n use_composio_auth=False,\n force_new_integration=True,\n )\n except Exception as e: # noqa: BLE001\n logger.error(f\"Error connecting with Basic Auth: {e}\")\n return \"Invalid credentials\"\n else:\n return f\"{app} CONNECTED\"\n elif auth_mode == \"BASIC\":\n return \"Enter Username and Password\"\n\n if auth_mode == \"OAUTH2\":\n try:\n return self._initiate_default_connection(entity, app)\n except Exception as e: # noqa: BLE001\n logger.error(f\"Error initiating OAuth2: {e}\")\n return \"OAuth2 initialization failed\"\n\n return \"Unsupported auth mode\"\n except Exception as e: # noqa: BLE001\n logger.error(f\"Error checking connection status: {e}\")\n return f\"Error: {e!s}\"\n else:\n return f\"{app} CONNECTED\"\n\n def _initiate_default_connection(self, entity: Any, app: str) -> str:\n connection = entity.initiate_connection(app_name=app, use_composio_auth=True, force_new_integration=True)\n return connection.redirectUrl\n\n def _get_connected_app_names_for_entity(self) -> list[str]:\n toolset = self._build_wrapper()\n connections = toolset.client.get_entity(id=self.entity_id).get_connections()\n return list({connection.appUniqueId for connection in connections})\n\n def _get_normalized_app_name(self) -> str:\n \"\"\"Get app name without connection status suffix.\n\n Returns:\n str: Normalized app name.\n \"\"\"\n return self.app_names.replace(\" ✅\", \"\").replace(\"_connected\", \"\")\n\n def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: # noqa: ARG002\n # Update the available apps options from the API\n if hasattr(self, \"api_key\") and self.api_key != \"\":\n toolset = self._build_wrapper()\n build_config[\"app_names\"][\"options\"] = self._get_oauth_apps(api_key=self.api_key)\n\n # First, ensure all dynamic fields are hidden by default\n dynamic_fields = [\"app_credentials\", \"username\", \"auth_link\", \"auth_status\", \"action_names\"]\n for field in dynamic_fields:\n if field in build_config:\n if build_config[field][\"value\"] is None or build_config[field][\"value\"] == \"\":\n build_config[field][\"show\"] = False\n build_config[field][\"advanced\"] = True\n build_config[field][\"load_from_db\"] = False\n else:\n build_config[field][\"show\"] = True\n build_config[field][\"advanced\"] = False\n\n if field_name == \"app_names\" and (not hasattr(self, \"app_names\") or not self.app_names):\n build_config[\"auth_status\"][\"show\"] = True\n build_config[\"auth_status\"][\"value\"] = \"Please select an app first\"\n return build_config\n\n if field_name == \"app_names\" and hasattr(self, \"api_key\") and self.api_key != \"\":\n # app_name = self._get_normalized_app_name()\n app_name = self.app_names\n try:\n toolset = self._build_wrapper()\n entity = toolset.client.get_entity(id=self.entity_id)\n\n # Always show auth_status when app is selected\n build_config[\"auth_status\"][\"show\"] = True\n build_config[\"auth_status\"][\"advanced\"] = False\n\n try:\n # Check if already connected\n entity.get_connection(app=app_name)\n build_config[\"auth_status\"][\"value\"] = \"✅\"\n build_config[\"auth_link\"][\"show\"] = False\n # Show action selection for connected apps\n build_config[\"action_names\"][\"show\"] = True\n build_config[\"action_names\"][\"advanced\"] = False\n\n except NoItemsFound:\n # Get auth scheme and show relevant fields\n auth_scheme = self._get_auth_scheme(app_name)\n auth_mode = auth_scheme.auth_mode\n logger.info(f\"Auth mode for {app_name}: {auth_mode}\")\n\n if auth_mode == \"API_KEY\":\n build_config[\"app_credentials\"][\"show\"] = True\n build_config[\"app_credentials\"][\"advanced\"] = False\n build_config[\"app_credentials\"][\"display_name\"] = \"API Key\"\n build_config[\"auth_status\"][\"value\"] = \"Enter API Key\"\n\n elif auth_mode == \"BASIC\":\n build_config[\"username\"][\"show\"] = True\n build_config[\"username\"][\"advanced\"] = False\n build_config[\"app_credentials\"][\"show\"] = True\n build_config[\"app_credentials\"][\"advanced\"] = False\n build_config[\"app_credentials\"][\"display_name\"] = \"Password\"\n build_config[\"auth_status\"][\"value\"] = \"Enter Username and Password\"\n\n elif auth_mode == \"OAUTH2\":\n build_config[\"auth_link\"][\"show\"] = True\n build_config[\"auth_link\"][\"advanced\"] = False\n auth_url = self._initiate_default_connection(entity, app_name)\n build_config[\"auth_link\"][\"value\"] = auth_url\n build_config[\"auth_status\"][\"value\"] = \"Click link to authenticate\"\n\n else:\n build_config[\"auth_status\"][\"value\"] = \"Unsupported auth mode\"\n\n # Update action names if connected\n if build_config[\"auth_status\"][\"value\"] == \"✅\":\n all_action_names = [str(action).replace(\"Action.\", \"\") for action in Action.all()]\n app_action_names = [\n action_name\n for action_name in all_action_names\n if action_name.lower().startswith(app_name.lower() + \"_\")\n ]\n if build_config[\"action_names\"][\"options\"] != app_action_names:\n build_config[\"action_names\"][\"options\"] = app_action_names\n build_config[\"action_names\"][\"value\"] = [app_action_names[0]] if app_action_names else [\"\"]\n\n except Exception as e: # noqa: BLE001\n logger.error(f\"Error checking auth status: {e}, app: {app_name}\")\n build_config[\"auth_status\"][\"value\"] = f\"Error: {e!s}\"\n\n return build_config\n\n def build_tool(self) -> Sequence[Tool]:\n \"\"\"Build Composio tools based on selected actions.\n\n Returns:\n Sequence[Tool]: List of configured Composio tools.\n \"\"\"\n composio_toolset = self._build_wrapper()\n return composio_toolset.get_tools(actions=self.action_names)\n\n def _build_wrapper(self) -> ComposioToolSet:\n \"\"\"Build the Composio toolset wrapper.\n\n Returns:\n ComposioToolSet: The initialized toolset.\n\n Raises:\n ValueError: If the API key is not found or invalid.\n \"\"\"\n try:\n if not self.api_key:\n msg = \"Composio API Key is required\"\n raise ValueError(msg)\n return ComposioToolSet(api_key=self.api_key, entity_id=self.entity_id)\n except ValueError as e:\n logger.error(f\"Error building Composio wrapper: {e}\")\n msg = \"Please provide a valid Composio API Key in the component settings\"\n raise ValueError(msg) from e\n"
+ },
+ "entity_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Entity ID",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "entity_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "default"
+ },
+ "username": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Username",
+ "dynamic": true,
+ "info": "Username for Basic authentication",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "username",
+ "placeholder": "",
+ "required": false,
+ "show": false,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ComposioAPI"
+ },
+ "dragging": false,
+ "id": "ComposioAPI-Z0Iiy",
+ "measured": {
+ "height": 497,
+ "width": 320
+ },
+ "position": {
+ "x": -137.53986902236176,
+ "y": 20.325147658297382
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": 568.8302643946312,
+ "y": 91.93195183355544,
+ "zoom": 0.7104297128050097
+ }
+ },
+ "description": "Interact with Gmail to send emails, create drafts, and fetch messages",
+ "endpoint_name": null,
+ "id": "0473161e-ca7e-413c-9113-e98a142313ed",
+ "is_component": false,
+ "name": "Gmail Agent",
+ "tags": [
+ "agents"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json
new file mode 100644
index 0000000..e021c7e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Graph Vector Store RAG.json
@@ -0,0 +1,5533 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIEmbeddings",
+ "id": "OpenAIEmbeddings-fcwMC",
+ "name": "embeddings",
+ "output_types": [
+ "Embeddings"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "embedding_model",
+ "id": "AstraDBGraph-jr8pY",
+ "inputTypes": [
+ "Embeddings"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-OpenAIEmbeddings-jyvkr{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-jyvkrœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDBGraph-jr8pY{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDBGraph-jr8pYœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "OpenAIEmbeddings-fcwMC",
+ "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-fcwMCœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}",
+ "target": "AstraDBGraph-jr8pY",
+ "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDBGraph-jr8pYœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-DA114",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "search_query",
+ "id": "AstraDBGraph-jr8pY",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-ZCSfi{œdataTypeœ:œChatInputœ,œidœ:œChatInput-ZCSfiœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDBGraph-jr8pY{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDBGraph-jr8pYœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-DA114",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-DA114œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "AstraDBGraph-jr8pY",
+ "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œAstraDBGraph-jr8pYœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AstraDBGraph",
+ "id": "AstraDBGraph-jr8pY",
+ "name": "search_results",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-alciW",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-AstraDBGraph-jr8pY{œdataTypeœ:œAstraDBGraphœ,œidœ:œAstraDBGraph-jr8pYœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-T6FGT{œfieldNameœ:œdataœ,œidœ:œParseData-T6FGTœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "AstraDBGraph-jr8pY",
+ "sourceHandle": "{œdataTypeœ: œAstraDBGraphœ, œidœ: œAstraDBGraph-jr8pYœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-alciW",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-alciWœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-alciW",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "context",
+ "id": "Prompt-2M2d5",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-alciW{œdataTypeœ:œParseDataœ,œidœ:œParseData-alciWœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-rmO8w{œfieldNameœ:œcontextœ,œidœ:œPrompt-rmO8wœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseData-alciW",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-alciWœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-rmO8w",
+ "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-2M2d5œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-DA114",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "question",
+ "id": "Prompt-2M2d5",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-DA114{œdataTypeœ:œChatInputœ,œidœ:œChatInput-DA114œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-rmO8w{œfieldNameœ:œquestionœ,œidœ:œPrompt-rmO8wœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-DA114",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-DA114œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-rmO8w",
+ "targetHandle": "{œfieldNameœ: œquestionœ, œidœ: œPrompt-2M2d5œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-rmO8w",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-a26gL",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-rmO8w{œdataTypeœ:œPromptœ,œidœ:œPrompt-rmO8wœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-LnWKb{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-LnWKbœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-rmO8w",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-rmO8wœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-LnWKb",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-a26gLœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-LnWKb",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-KIkbc",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-LnWKb{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-LnWKbœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-KIkbc{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-KIkbcœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-LnWKb",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-LnWKbœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-KIkbc",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-KIkbcœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "URL",
+ "id": "URL-qOh1r",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data_input",
+ "id": "LanguageRecursiveTextSplitter-jefpx",
+ "inputTypes": [
+ "Document",
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-URL-qOh1r{œdataTypeœ:œURLœ,œidœ:œURL-qOh1rœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LanguageRecursiveTextSplitter-KDtC3{œfieldNameœ:œdata_inputœ,œidœ:œLanguageRecursiveTextSplitter-KDtC3œ,œinputTypesœ:[œDocumentœ,œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "URL-qOh1r",
+ "sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-qOh1rœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "LanguageRecursiveTextSplitter-KDtC3",
+ "targetHandle": "{œfieldNameœ: œdata_inputœ, œidœ: œLanguageRecursiveTextSplitter-jefpxœ, œinputTypesœ: [œDocumentœ, œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "LanguageRecursiveTextSplitter",
+ "id": "LanguageRecursiveTextSplitter-KDtC3",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data_input",
+ "id": "HtmlLinkExtractor-exHgk",
+ "inputTypes": [
+ "Document",
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-LanguageRecursiveTextSplitter-KDtC3{œdataTypeœ:œLanguageRecursiveTextSplitterœ,œidœ:œLanguageRecursiveTextSplitter-KDtC3œ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-HtmlLinkExtractor-LWuvQ{œfieldNameœ:œdata_inputœ,œidœ:œHtmlLinkExtractor-LWuvQœ,œinputTypesœ:[œDocumentœ,œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "LanguageRecursiveTextSplitter-KDtC3",
+ "sourceHandle": "{œdataTypeœ: œLanguageRecursiveTextSplitterœ, œidœ: œLanguageRecursiveTextSplitter-KDtC3œ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "HtmlLinkExtractor-LWuvQ",
+ "targetHandle": "{œfieldNameœ: œdata_inputœ, œidœ: œHtmlLinkExtractor-exHgkœ, œinputTypesœ: [œDocumentœ, œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "HtmlLinkExtractor",
+ "id": "HtmlLinkExtractor-exHgk",
+ "name": "data",
+ "output_types": []
+ },
+ "targetHandle": {
+ "fieldName": "ingest_data",
+ "id": "AstraDBGraph-FX0tA",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__ChatInput-DA114{œdataTypeœ:œChatInputœ,œidœ:œChatInput-DA114œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-AstraDBGraph-xJiDN{œfieldNameœ:œsearch_queryœ,œidœ:œAstraDBGraph-xJiDNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ChatInput-DA114",
+ "sourceHandle": "{œdataTypeœ: œHtmlLinkExtractorœ, œidœ: œHtmlLinkExtractor-exHgkœ, œnameœ: œdataœ, œoutput_typesœ: []}",
+ "target": "AstraDBGraph-xJiDN",
+ "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDBGraph-FX0tAœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIEmbeddings",
+ "id": "OpenAIEmbeddings-fcwMC",
+ "name": "embeddings",
+ "output_types": [
+ "Embeddings"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "embedding_model",
+ "id": "AstraDBGraph-FX0tA",
+ "inputTypes": [
+ "Embeddings"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__OpenAIEmbeddings-fcwMC{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-fcwMCœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDBGraph-xJiDN{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDBGraph-xJiDNœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}",
+ "source": "OpenAIEmbeddings-fcwMC",
+ "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-fcwMCœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}",
+ "target": "AstraDBGraph-xJiDN",
+ "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDBGraph-FX0tAœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "AstraDBGraph",
+ "id": "AstraDBGraph-xJiDN",
+ "name": "search_results",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-alciW",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__AstraDBGraph-xJiDN{œdataTypeœ:œAstraDBGraphœ,œidœ:œAstraDBGraph-xJiDNœ,œnameœ:œsearch_resultsœ,œoutput_typesœ:[œDataœ]}-ParseData-alciW{œfieldNameœ:œdataœ,œidœ:œParseData-alciWœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "AstraDBGraph-xJiDN",
+ "sourceHandle": "{œdataTypeœ: œAstraDBGraphœ, œidœ: œAstraDBGraph-xJiDNœ, œnameœ: œsearch_resultsœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-alciW",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-alciWœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIEmbeddings",
+ "id": "OpenAIEmbeddings-XKhhV",
+ "name": "embeddings",
+ "output_types": [
+ "Embeddings"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "embedding_model",
+ "id": "AstraDBGraph-uza6S",
+ "inputTypes": [
+ "Embeddings"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__OpenAIEmbeddings-XKhhV{œdataTypeœ:œOpenAIEmbeddingsœ,œidœ:œOpenAIEmbeddings-XKhhVœ,œnameœ:œembeddingsœ,œoutput_typesœ:[œEmbeddingsœ]}-AstraDBGraph-uza6S{œfieldNameœ:œembedding_modelœ,œidœ:œAstraDBGraph-uza6Sœ,œinputTypesœ:[œEmbeddingsœ],œtypeœ:œotherœ}",
+ "source": "OpenAIEmbeddings-XKhhV",
+ "sourceHandle": "{œdataTypeœ: œOpenAIEmbeddingsœ, œidœ: œOpenAIEmbeddings-XKhhVœ, œnameœ: œembeddingsœ, œoutput_typesœ: [œEmbeddingsœ]}",
+ "target": "AstraDBGraph-uza6S",
+ "targetHandle": "{œfieldNameœ: œembedding_modelœ, œidœ: œAstraDBGraph-uza6Sœ, œinputTypesœ: [œEmbeddingsœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "HtmlLinkExtractor",
+ "id": "HtmlLinkExtractor-LWuvQ",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "ingest_data",
+ "id": "AstraDBGraph-uza6S",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__HtmlLinkExtractor-LWuvQ{œdataTypeœ:œHtmlLinkExtractorœ,œidœ:œHtmlLinkExtractor-LWuvQœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-AstraDBGraph-uza6S{œfieldNameœ:œingest_dataœ,œidœ:œAstraDBGraph-uza6Sœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "HtmlLinkExtractor-LWuvQ",
+ "sourceHandle": "{œdataTypeœ: œHtmlLinkExtractorœ, œidœ: œHtmlLinkExtractor-LWuvQœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "AstraDBGraph-uza6S",
+ "targetHandle": "{œfieldNameœ: œingest_dataœ, œidœ: œAstraDBGraph-uza6Sœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "ChatInput-DA114",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "How does Haskell handle function composition and what are some practical examples of its use?"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 233,
+ "id": "ChatInput-DA114",
+ "measured": {
+ "height": 233,
+ "width": 360
+ },
+ "position": {
+ "x": -1800.3752844686821,
+ "y": 7028.578266524353
+ },
+ "positionAbsolute": {
+ "x": -1516.4270244619845,
+ "y": 6211.967942125529
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "OpenAIEmbeddings-fcwMC",
+ "node": {
+ "base_classes": [
+ "Embeddings"
+ ],
+ "beta": false,
+ "category": "embeddings",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generate embeddings using OpenAI models.",
+ "display_name": "OpenAI Embeddings",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "default_headers",
+ "default_query",
+ "chunk_size",
+ "client",
+ "deployment",
+ "embedding_ctx_length",
+ "max_retries",
+ "model",
+ "model_kwargs",
+ "openai_api_key",
+ "openai_api_base",
+ "openai_api_type",
+ "openai_api_version",
+ "openai_organization",
+ "openai_proxy",
+ "request_timeout",
+ "show_progress_bar",
+ "skip_empty",
+ "tiktoken_model_name",
+ "tiktoken_enable",
+ "dimensions"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIEmbeddings",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Embeddings",
+ "method": "build_embeddings",
+ "name": "embeddings",
+ "required_inputs": [
+ "openai_api_key"
+ ],
+ "selected": "Embeddings",
+ "tool_mode": true,
+ "types": [
+ "Embeddings"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.000052003277518821525,
+ "template": {
+ "_type": "Component",
+ "chunk_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Chunk Size",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "chunk_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1000
+ },
+ "client": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Client",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "client",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import OpenAIEmbeddings\n\nfrom langflow.base.embeddings.model import LCEmbeddingsModel\nfrom langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES\nfrom langflow.field_typing import Embeddings\nfrom langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput\n\n\nclass OpenAIEmbeddingsComponent(LCEmbeddingsModel):\n display_name = \"OpenAI Embeddings\"\n description = \"Generate embeddings using OpenAI models.\"\n icon = \"OpenAI\"\n name = \"OpenAIEmbeddings\"\n\n inputs = [\n DictInput(\n name=\"default_headers\",\n display_name=\"Default Headers\",\n advanced=True,\n info=\"Default headers to use for the API request.\",\n ),\n DictInput(\n name=\"default_query\",\n display_name=\"Default Query\",\n advanced=True,\n info=\"Default query parameters to use for the API request.\",\n ),\n IntInput(name=\"chunk_size\", display_name=\"Chunk Size\", advanced=True, value=1000),\n MessageTextInput(name=\"client\", display_name=\"Client\", advanced=True),\n MessageTextInput(name=\"deployment\", display_name=\"Deployment\", advanced=True),\n IntInput(name=\"embedding_ctx_length\", display_name=\"Embedding Context Length\", advanced=True, value=1536),\n IntInput(name=\"max_retries\", display_name=\"Max Retries\", value=3, advanced=True),\n DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n advanced=False,\n options=OPENAI_EMBEDDING_MODEL_NAMES,\n value=\"text-embedding-3-small\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n SecretStrInput(name=\"openai_api_key\", display_name=\"OpenAI API Key\", value=\"OPENAI_API_KEY\", required=True),\n MessageTextInput(name=\"openai_api_base\", display_name=\"OpenAI API Base\", advanced=True),\n MessageTextInput(name=\"openai_api_type\", display_name=\"OpenAI API Type\", advanced=True),\n MessageTextInput(name=\"openai_api_version\", display_name=\"OpenAI API Version\", advanced=True),\n MessageTextInput(\n name=\"openai_organization\",\n display_name=\"OpenAI Organization\",\n advanced=True,\n ),\n MessageTextInput(name=\"openai_proxy\", display_name=\"OpenAI Proxy\", advanced=True),\n FloatInput(name=\"request_timeout\", display_name=\"Request Timeout\", advanced=True),\n BoolInput(name=\"show_progress_bar\", display_name=\"Show Progress Bar\", advanced=True),\n BoolInput(name=\"skip_empty\", display_name=\"Skip Empty\", advanced=True),\n MessageTextInput(\n name=\"tiktoken_model_name\",\n display_name=\"TikToken Model Name\",\n advanced=True,\n ),\n BoolInput(\n name=\"tiktoken_enable\",\n display_name=\"TikToken Enable\",\n advanced=True,\n value=True,\n info=\"If False, you must have transformers installed.\",\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n ]\n\n def build_embeddings(self) -> Embeddings:\n return OpenAIEmbeddings(\n client=self.client or None,\n model=self.model,\n dimensions=self.dimensions or None,\n deployment=self.deployment or None,\n api_version=self.openai_api_version or None,\n base_url=self.openai_api_base or None,\n openai_api_type=self.openai_api_type or None,\n openai_proxy=self.openai_proxy or None,\n embedding_ctx_length=self.embedding_ctx_length,\n api_key=self.openai_api_key or None,\n organization=self.openai_organization or None,\n allowed_special=\"all\",\n disallowed_special=\"all\",\n chunk_size=self.chunk_size,\n max_retries=self.max_retries,\n timeout=self.request_timeout or None,\n tiktoken_enabled=self.tiktoken_enable,\n tiktoken_model_name=self.tiktoken_model_name or None,\n show_progress_bar=self.show_progress_bar,\n model_kwargs=self.model_kwargs,\n skip_empty=self.skip_empty,\n default_headers=self.default_headers or None,\n default_query=self.default_query or None,\n )\n"
+ },
+ "default_headers": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Default Headers",
+ "dynamic": false,
+ "info": "Default headers to use for the API request.",
+ "list": false,
+ "name": "default_headers",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "default_query": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Default Query",
+ "dynamic": false,
+ "info": "Default query parameters to use for the API request.",
+ "list": false,
+ "name": "default_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "deployment": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Deployment",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "deployment",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "dimensions": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Dimensions",
+ "dynamic": false,
+ "info": "The number of dimensions the resulting output embeddings should have. Only supported by certain models.",
+ "list": false,
+ "name": "dimensions",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "embedding_ctx_length": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Embedding Context Length",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "embedding_ctx_length",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1536
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 3
+ },
+ "model": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Model",
+ "dynamic": false,
+ "info": "",
+ "name": "model",
+ "options": [
+ "text-embedding-3-small",
+ "text-embedding-3-large",
+ "text-embedding-ada-002"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "text-embedding-3-large"
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "openai_api_base": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "openai_api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_type": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Type",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_type",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_version": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Version",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_version",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_organization": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI Organization",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_organization",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_proxy": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI Proxy",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_proxy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "request_timeout": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Request Timeout",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "request_timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": ""
+ },
+ "show_progress_bar": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Show Progress Bar",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "show_progress_bar",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "skip_empty": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Skip Empty",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "skip_empty",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "tiktoken_enable": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "TikToken Enable",
+ "dynamic": false,
+ "info": "If False, you must have transformers installed.",
+ "list": false,
+ "name": "tiktoken_enable",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "tiktoken_model_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "TikToken Model Name",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tiktoken_model_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "OpenAIEmbeddings"
+ },
+ "dragging": false,
+ "height": 320,
+ "id": "OpenAIEmbeddings-fcwMC",
+ "measured": {
+ "height": 320,
+ "width": 360
+ },
+ "position": {
+ "x": -1794.2005649575194,
+ "y": 7363.047766731913
+ },
+ "positionAbsolute": {
+ "x": -1530.978455316274,
+ "y": 6600.325433283265
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Implementation of Graph Vector Store using Astra DB",
+ "display_name": "Astra DB Graph",
+ "id": "AstraDBGraph-jr8pY",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Implementation of Graph Vector Store using Astra DB",
+ "display_name": "Astra DB Graph",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "token",
+ "api_endpoint",
+ "collection_name",
+ "metadata_incoming_links_key",
+ "search_query",
+ "ingest_data",
+ "keyspace",
+ "embedding_model",
+ "metric",
+ "batch_size",
+ "bulk_insert_batch_concurrency",
+ "bulk_insert_overwrite_concurrency",
+ "bulk_delete_concurrency",
+ "setup_mode",
+ "pre_delete_collection",
+ "metadata_indexing_include",
+ "metadata_indexing_exclude",
+ "collection_indexing_policy",
+ "number_of_results",
+ "search_type",
+ "search_score_threshold",
+ "search_filter"
+ ],
+ "frozen": false,
+ "icon": "AstraDB",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Search Results",
+ "method": "search_documents",
+ "name": "search_results",
+ "required_inputs": [
+ "api_endpoint",
+ "collection_name",
+ "token"
+ ],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "required_inputs": [],
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_endpoint": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "API Endpoint",
+ "dynamic": false,
+ "info": "API endpoint URL for the Astra DB service.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_endpoint",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ASTRA_DB_API_ENDPOINT"
+ },
+ "batch_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Batch Size",
+ "dynamic": false,
+ "info": "Optional number of data to process in a single batch.",
+ "list": false,
+ "name": "batch_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_delete_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Delete Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk delete operations.",
+ "list": false,
+ "name": "bulk_delete_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_batch_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Batch Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations.",
+ "list": false,
+ "name": "bulk_insert_batch_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_overwrite_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Overwrite Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations that overwrite existing data.",
+ "list": false,
+ "name": "bulk_insert_overwrite_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import os\n\nimport orjson\nfrom astrapy.admin import parse_api_endpoint\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\n\n\nclass AstraDBGraphVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB Graph\"\n description: str = \"Implementation of Graph Vector Store using Astra DB\"\n name = \"AstraDBGraph\"\n icon: str = \"AstraDB\"\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n ),\n SecretStrInput(\n name=\"api_endpoint\",\n display_name=\"Database\" if os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\" else \"API Endpoint\",\n info=\"API endpoint URL for the Astra DB service.\",\n value=\"ASTRA_DB_API_ENDPOINT\",\n required=True,\n ),\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n ),\n StrInput(\n name=\"metadata_incoming_links_key\",\n display_name=\"Metadata incoming links key\",\n info=\"Metadata key used for incoming links.\",\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"Metric\",\n info=\"Optional distance metric for vector comparisons in the vector store.\",\n options=[\"cosine\", \"dot_product\", \"euclidean\"],\n value=\"cosine\",\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_batch_concurrency\",\n display_name=\"Bulk Insert Batch Concurrency\",\n info=\"Optional concurrency level for bulk insert operations.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_overwrite_concurrency\",\n display_name=\"Bulk Insert Overwrite Concurrency\",\n info=\"Optional concurrency level for bulk insert operations that overwrite existing data.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_delete_concurrency\",\n display_name=\"Bulk Delete Concurrency\",\n info=\"Optional concurrency level for bulk delete operations.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.\",\n options=[\"Sync\", \"Off\"],\n advanced=True,\n value=\"Sync\",\n ),\n BoolInput(\n name=\"pre_delete_collection\",\n display_name=\"Pre Delete Collection\",\n info=\"Boolean flag to determine whether to delete the collection before creating a new one.\",\n advanced=True,\n value=False,\n ),\n StrInput(\n name=\"metadata_indexing_include\",\n display_name=\"Metadata Indexing Include\",\n info=\"Optional list of metadata fields to include in the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"metadata_indexing_exclude\",\n display_name=\"Metadata Indexing Exclude\",\n info=\"Optional list of metadata fields to exclude from the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"collection_indexing_policy\",\n display_name=\"Collection Indexing Policy\",\n info='Optional JSON string for the \"indexing\" field of the collection. '\n \"See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n \"Graph Traversal\",\n \"MMR (Max Marginal Relevance) Graph Traversal\",\n ],\n value=\"MMR (Max Marginal Relevance) Graph Traversal\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBGraphVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n try:\n self.log(f\"Initializing Graph Vector Store {self.collection_name}\")\n\n vector_store = AstraDBGraphVectorStore(\n embedding=self.embedding_model,\n collection_name=self.collection_name,\n metadata_incoming_links_key=self.metadata_incoming_links_key or \"incoming_links\",\n token=self.token,\n api_endpoint=self.api_endpoint,\n namespace=self.keyspace or None,\n environment=parse_api_endpoint(self.api_endpoint).environment if self.api_endpoint else None,\n metric=self.metric or None,\n batch_size=self.batch_size or None,\n bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,\n bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,\n bulk_delete_concurrency=self.bulk_delete_concurrency or None,\n setup_mode=setup_mode_value,\n pre_delete_collection=self.pre_delete_collection,\n metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,\n metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,\n collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode(\"utf-8\"))\n if self.collection_indexing_policy\n else None,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Vector Store initialized: {vector_store.astra_env.collection_name}\")\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n match self.search_type:\n case \"Similarity\":\n return \"similarity\"\n case \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n case \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n case \"Graph Traversal\":\n return \"traversal\"\n case \"MMR (Max Marginal Relevance) Graph Traversal\":\n return \"mmr_traversal\"\n case _:\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n if not vector_store:\n vector_store = self.build_vector_store()\n\n self.log(\"Searching for documents in AstraDBGraphVectorStore.\")\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n\n # Drop links from the metadata. At this point the links don't add any value for building the\n # context and haven't been restored to json which causes the conversion to fail.\n self.log(\"Removing links from metadata.\")\n for doc in docs:\n if \"links\" in doc.metadata:\n doc.metadata.pop(\"links\")\n\n except Exception as e:\n msg = f\"Error performing search in AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n\n self.log(f\"Converted documents to data: {len(data)}\")\n\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"
+ },
+ "collection_indexing_policy": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Collection Indexing Policy",
+ "dynamic": false,
+ "info": "Optional JSON string for the \"indexing\" field of the collection. See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option",
+ "list": false,
+ "load_from_db": false,
+ "name": "collection_indexing_policy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "collection_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Collection Name",
+ "dynamic": false,
+ "info": "The name of the collection within Astra DB where the vectors will be stored.",
+ "list": false,
+ "load_from_db": false,
+ "name": "collection_name",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "haskell_chunked"
+ },
+ "embedding_model": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Embedding Model",
+ "dynamic": false,
+ "info": "Allows an embedding model configuration.",
+ "input_types": [
+ "Embeddings"
+ ],
+ "list": false,
+ "name": "embedding_model",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "ingest_data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Ingest Data",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "name": "ingest_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "keyspace": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Keyspace",
+ "dynamic": false,
+ "info": "Optional keyspace within Astra DB to use for the collection.",
+ "list": false,
+ "load_from_db": false,
+ "name": "keyspace",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_incoming_links_key": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata incoming links key",
+ "dynamic": false,
+ "info": "Metadata key used for incoming links.",
+ "list": false,
+ "load_from_db": false,
+ "name": "metadata_incoming_links_key",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_exclude": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Exclude",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to exclude from the indexing.",
+ "list": true,
+ "load_from_db": false,
+ "name": "metadata_indexing_exclude",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ ""
+ ]
+ },
+ "metadata_indexing_include": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Include",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to include in the indexing.",
+ "list": true,
+ "load_from_db": false,
+ "name": "metadata_indexing_include",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metric": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Metric",
+ "dynamic": false,
+ "info": "Optional distance metric for vector comparisons in the vector store.",
+ "name": "metric",
+ "options": [
+ "cosine",
+ "dot_product",
+ "euclidean"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "cosine"
+ },
+ "number_of_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Results",
+ "dynamic": false,
+ "info": "Number of results to return.",
+ "list": false,
+ "load_from_db": false,
+ "name": "number_of_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 10
+ },
+ "pre_delete_collection": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Pre Delete Collection",
+ "dynamic": false,
+ "info": "Boolean flag to determine whether to delete the collection before creating a new one.",
+ "list": false,
+ "name": "pre_delete_collection",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "search_filter": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Search Metadata Filter",
+ "dynamic": false,
+ "info": "Optional dictionary of filters to apply to the search query.",
+ "list": true,
+ "name": "search_filter",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "search_query": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "search_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_score_threshold": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Search Score Threshold",
+ "dynamic": false,
+ "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')",
+ "list": false,
+ "load_from_db": false,
+ "name": "search_score_threshold",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": -2
+ },
+ "search_type": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Search Type",
+ "dynamic": false,
+ "info": "Search type to use",
+ "name": "search_type",
+ "options": [
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ "Graph Traversal",
+ "MMR (Max Marginal Relevance) Graph Traversal"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "MMR (Max Marginal Relevance) Graph Traversal"
+ },
+ "setup_mode": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Setup Mode",
+ "dynamic": false,
+ "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.",
+ "name": "setup_mode",
+ "options": [
+ "Sync",
+ "Off"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Sync"
+ },
+ "should_cache_vector_store": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Cache Vector Store",
+ "dynamic": false,
+ "info": "If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_cache_vector_store",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "token": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Astra DB Application Token",
+ "dynamic": false,
+ "info": "Authentication token for accessing Astra DB.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "token",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ASTRA_DB_APPLICATION_TOKEN"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "AstraDBGraph"
+ },
+ "dragging": false,
+ "id": "AstraDBGraph-jr8pY",
+ "measured": {
+ "height": 631,
+ "width": 320
+ },
+ "position": {
+ "x": -1277.8700822899038,
+ "y": 7080.542929356194
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "id": "ParseData-alciW",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{data}"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "id": "ParseData-alciW",
+ "measured": {
+ "height": 383,
+ "width": 360
+ },
+ "position": {
+ "x": -711.6072978271402,
+ "y": 7008.616446963303
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "Prompt-rmO8w",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "context",
+ "question"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template",
+ "tool_placeholder"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "context": {
+ "advanced": false,
+ "display_name": "context",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "context",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "question": {
+ "advanced": false,
+ "display_name": "question",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "question",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "{context}\n\n---\n\nGiven the context above, answer the question as best as possible. If there isn't information on the context about the question, respond by saying so in a funny way\n\nQuestion: {question}\n\nAnswer: "
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "id": "Prompt-rmO8w",
+ "measured": {
+ "height": 448,
+ "width": 360
+ },
+ "position": {
+ "x": -241.95389238895368,
+ "y": 7132.715577428476
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-LnWKb",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4-turbo"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-LnWKb",
+ "measured": {
+ "height": 734,
+ "width": 360
+ },
+ "position": {
+ "x": 317.8221390146513,
+ "y": 7078.366528905622
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-KIkbc",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "id": "ChatOutput-KIkbc",
+ "measured": {
+ "height": 257,
+ "width": 360
+ },
+ "position": {
+ "x": 804.9181649370599,
+ "y": 7243.316205548675
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "URL-qOh1r",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "category": "data",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Fetch content from one or more URLs.",
+ "display_name": "URL",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "urls",
+ "format"
+ ],
+ "frozen": false,
+ "icon": "layout-template",
+ "key": "URL",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "fetch_content",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "fetch_content_text",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 2.220446049250313e-16,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import re\n\nfrom langchain_community.document_loaders import AsyncHtmlLoader, WebBaseLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Load and retrive data from specified URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n is_list=True,\n tool_mode=True,\n placeholder=\"Enter a URL...\",\n list_add_label=\"Add URL\",\n ),\n DropdownInput(\n name=\"format\",\n display_name=\"Output Format\",\n info=\"Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.\",\n options=[\"Text\", \"Raw HTML\"],\n value=\"Text\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Message\", name=\"text\", method=\"fetch_content_text\"),\n Output(display_name=\"DataFrame\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n msg = f\"Invalid URL: {string}\"\n raise ValueError(msg)\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n if self.format == \"Raw HTML\":\n loader = AsyncHtmlLoader(web_path=urls, encoding=\"utf-8\")\n else:\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.fetch_content())\n"
+ },
+ "format": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Output Format",
+ "dynamic": false,
+ "info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
+ "name": "format",
+ "options": [
+ "Text",
+ "Raw HTML"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Raw HTML"
+ },
+ "urls": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URLs",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": true,
+ "load_from_db": false,
+ "name": "urls",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": [
+ "https://learnyouahaskell.com/introduction",
+ "https://learnyouahaskell.com/starting-out",
+ "https://learnyouahaskell.com/types-and-typeclasses",
+ "https://learnyouahaskell.com/syntax-in-functions",
+ "https://learnyouahaskell.com/recursion",
+ "https://learnyouahaskell.com/higher-order-functions",
+ "https://learnyouahaskell.com/modules",
+ "https://learnyouahaskell.com/making-our-own-types-and-typeclasses",
+ "https://learnyouahaskell.com/input-and-output",
+ "https://learnyouahaskell.com/functionally-solving-problems",
+ "https://learnyouahaskell.com/functors-applicative-functors-and-monoids",
+ "https://learnyouahaskell.com/a-fistful-of-monads",
+ "https://learnyouahaskell.com/for-a-few-monads-more",
+ "https://learnyouahaskell.com/zippers"
+ ]
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "URL"
+ },
+ "dragging": false,
+ "id": "URL-qOh1r",
+ "measured": {
+ "height": 1194,
+ "width": 360
+ },
+ "position": {
+ "x": -1742.8952423980936,
+ "y": 5868.349789218463
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "LanguageRecursiveTextSplitter-KDtC3",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "category": "vectorstores",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Implementation of Graph Vector Store using Astra DB",
+ "display_name": "Astra DB Graph",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "token",
+ "api_endpoint",
+ "collection_name",
+ "metadata_incoming_links_key",
+ "search_query",
+ "ingest_data",
+ "keyspace",
+ "embedding_model",
+ "metric",
+ "batch_size",
+ "bulk_insert_batch_concurrency",
+ "bulk_insert_overwrite_concurrency",
+ "bulk_delete_concurrency",
+ "setup_mode",
+ "pre_delete_collection",
+ "metadata_indexing_include",
+ "metadata_indexing_exclude",
+ "collection_indexing_policy",
+ "number_of_results",
+ "search_type",
+ "search_score_threshold",
+ "search_filter"
+ ],
+ "frozen": false,
+ "icon": "AstraDB",
+ "key": "AstraDBGraph",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Search Results",
+ "method": "search_documents",
+ "name": "search_results",
+ "required_inputs": [
+ "api_endpoint",
+ "collection_name",
+ "token"
+ ],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "required_inputs": [],
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.13582020910317566,
+ "template": {
+ "_type": "Component",
+ "api_endpoint": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "API Endpoint",
+ "dynamic": false,
+ "info": "API endpoint URL for the Astra DB service.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_endpoint",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ASTRA_DB_API_ENDPOINT"
+ },
+ "batch_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Batch Size",
+ "dynamic": false,
+ "info": "Optional number of data to process in a single batch.",
+ "list": false,
+ "name": "batch_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_delete_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Delete Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk delete operations.",
+ "list": false,
+ "name": "bulk_delete_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_batch_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Batch Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations.",
+ "list": false,
+ "name": "bulk_insert_batch_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_overwrite_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Overwrite Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations that overwrite existing data.",
+ "list": false,
+ "name": "bulk_insert_overwrite_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import os\n\nimport orjson\nfrom astrapy.admin import parse_api_endpoint\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\n\n\nclass AstraDBGraphVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB Graph\"\n description: str = \"Implementation of Graph Vector Store using Astra DB\"\n name = \"AstraDBGraph\"\n icon: str = \"AstraDB\"\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n ),\n SecretStrInput(\n name=\"api_endpoint\",\n display_name=\"Database\" if os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\" else \"API Endpoint\",\n info=\"API endpoint URL for the Astra DB service.\",\n value=\"ASTRA_DB_API_ENDPOINT\",\n required=True,\n ),\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n ),\n StrInput(\n name=\"metadata_incoming_links_key\",\n display_name=\"Metadata incoming links key\",\n info=\"Metadata key used for incoming links.\",\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"Metric\",\n info=\"Optional distance metric for vector comparisons in the vector store.\",\n options=[\"cosine\", \"dot_product\", \"euclidean\"],\n value=\"cosine\",\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_batch_concurrency\",\n display_name=\"Bulk Insert Batch Concurrency\",\n info=\"Optional concurrency level for bulk insert operations.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_overwrite_concurrency\",\n display_name=\"Bulk Insert Overwrite Concurrency\",\n info=\"Optional concurrency level for bulk insert operations that overwrite existing data.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_delete_concurrency\",\n display_name=\"Bulk Delete Concurrency\",\n info=\"Optional concurrency level for bulk delete operations.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.\",\n options=[\"Sync\", \"Off\"],\n advanced=True,\n value=\"Sync\",\n ),\n BoolInput(\n name=\"pre_delete_collection\",\n display_name=\"Pre Delete Collection\",\n info=\"Boolean flag to determine whether to delete the collection before creating a new one.\",\n advanced=True,\n value=False,\n ),\n StrInput(\n name=\"metadata_indexing_include\",\n display_name=\"Metadata Indexing Include\",\n info=\"Optional list of metadata fields to include in the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"metadata_indexing_exclude\",\n display_name=\"Metadata Indexing Exclude\",\n info=\"Optional list of metadata fields to exclude from the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"collection_indexing_policy\",\n display_name=\"Collection Indexing Policy\",\n info='Optional JSON string for the \"indexing\" field of the collection. '\n \"See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n \"Graph Traversal\",\n \"MMR (Max Marginal Relevance) Graph Traversal\",\n ],\n value=\"MMR (Max Marginal Relevance) Graph Traversal\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBGraphVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n try:\n self.log(f\"Initializing Graph Vector Store {self.collection_name}\")\n\n vector_store = AstraDBGraphVectorStore(\n embedding=self.embedding_model,\n collection_name=self.collection_name,\n metadata_incoming_links_key=self.metadata_incoming_links_key or \"incoming_links\",\n token=self.token,\n api_endpoint=self.api_endpoint,\n namespace=self.keyspace or None,\n environment=parse_api_endpoint(self.api_endpoint).environment if self.api_endpoint else None,\n metric=self.metric or None,\n batch_size=self.batch_size or None,\n bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,\n bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,\n bulk_delete_concurrency=self.bulk_delete_concurrency or None,\n setup_mode=setup_mode_value,\n pre_delete_collection=self.pre_delete_collection,\n metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,\n metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,\n collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode(\"utf-8\"))\n if self.collection_indexing_policy\n else None,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Vector Store initialized: {vector_store.astra_env.collection_name}\")\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n match self.search_type:\n case \"Similarity\":\n return \"similarity\"\n case \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n case \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n case \"Graph Traversal\":\n return \"traversal\"\n case \"MMR (Max Marginal Relevance) Graph Traversal\":\n return \"mmr_traversal\"\n case _:\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n if not vector_store:\n vector_store = self.build_vector_store()\n\n self.log(\"Searching for documents in AstraDBGraphVectorStore.\")\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n\n # Drop links from the metadata. At this point the links don't add any value for building the\n # context and haven't been restored to json which causes the conversion to fail.\n self.log(\"Removing links from metadata.\")\n for doc in docs:\n if \"links\" in doc.metadata:\n doc.metadata.pop(\"links\")\n\n except Exception as e:\n msg = f\"Error performing search in AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n\n self.log(f\"Converted documents to data: {len(data)}\")\n\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"
+ },
+ "collection_indexing_policy": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Collection Indexing Policy",
+ "dynamic": false,
+ "info": "Optional JSON string for the \"indexing\" field of the collection. See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option",
+ "list": false,
+ "load_from_db": false,
+ "name": "collection_indexing_policy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "collection_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Collection Name",
+ "dynamic": false,
+ "info": "The name of the collection within Astra DB where the vectors will be stored.",
+ "list": false,
+ "load_from_db": false,
+ "name": "collection_name",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "haskell_chunked"
+ },
+ "embedding_model": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Embedding Model",
+ "dynamic": false,
+ "info": "Allows an embedding model configuration.",
+ "input_types": [
+ "Embeddings"
+ ],
+ "list": false,
+ "name": "embedding_model",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "ingest_data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Ingest Data",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "name": "ingest_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "keyspace": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Keyspace",
+ "dynamic": false,
+ "info": "Optional keyspace within Astra DB to use for the collection.",
+ "list": false,
+ "load_from_db": false,
+ "name": "keyspace",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_incoming_links_key": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata incoming links key",
+ "dynamic": false,
+ "info": "Metadata key used for incoming links.",
+ "list": false,
+ "load_from_db": false,
+ "name": "metadata_incoming_links_key",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_exclude": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Exclude",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to exclude from the indexing.",
+ "list": true,
+ "load_from_db": false,
+ "name": "metadata_indexing_exclude",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_include": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Include",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to include in the indexing.",
+ "list": true,
+ "load_from_db": false,
+ "name": "metadata_indexing_include",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metric": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Metric",
+ "dynamic": false,
+ "info": "Optional distance metric for vector comparisons in the vector store.",
+ "name": "metric",
+ "options": [
+ "cosine",
+ "dot_product",
+ "euclidean"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "cosine"
+ },
+ "number_of_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Results",
+ "dynamic": false,
+ "info": "Number of results to return.",
+ "list": false,
+ "name": "number_of_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4
+ },
+ "pre_delete_collection": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Pre Delete Collection",
+ "dynamic": false,
+ "info": "Boolean flag to determine whether to delete the collection before creating a new one.",
+ "list": false,
+ "name": "pre_delete_collection",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "search_filter": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Search Metadata Filter",
+ "dynamic": false,
+ "info": "Optional dictionary of filters to apply to the search query.",
+ "list": true,
+ "name": "search_filter",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "search_query": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "search_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_score_threshold": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Search Score Threshold",
+ "dynamic": false,
+ "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')",
+ "list": false,
+ "name": "search_score_threshold",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 0
+ },
+ "search_type": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Search Type",
+ "dynamic": false,
+ "info": "Search type to use",
+ "name": "search_type",
+ "options": [
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ "Graph Traversal",
+ "MMR (Max Marginal Relevance) Graph Traversal"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "MMR (Max Marginal Relevance) Graph Traversal"
+ },
+ "setup_mode": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Setup Mode",
+ "dynamic": false,
+ "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.",
+ "name": "setup_mode",
+ "options": [
+ "Sync",
+ "Off"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Sync"
+ },
+ "should_cache_vector_store": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Cache Vector Store",
+ "dynamic": false,
+ "info": "If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_cache_vector_store",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "token": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Astra DB Application Token",
+ "dynamic": false,
+ "info": "Authentication token for accessing Astra DB.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "token",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ASTRA_DB_APPLICATION_TOKEN"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AstraDBGraph"
+ },
+ "dragging": false,
+ "id": "AstraDBGraph-FX0tA",
+ "measured": {
+ "height": 631,
+ "width": 320
+ },
+ "position": {
+ "x": 16.3856689286402,
+ "y": 5909.200823867262
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "LanguageRecursiveTextSplitter-jefpx",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "category": "langchain_utilities",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Split text into chunks of a specified length based on language.",
+ "display_name": "Language Recursive Text Splitter",
+ "documentation": "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter",
+ "edited": false,
+ "field_order": [
+ "chunk_size",
+ "chunk_overlap",
+ "data_input",
+ "code_language"
+ ],
+ "frozen": false,
+ "icon": "LangChain",
+ "key": "LanguageRecursiveTextSplitter",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "transform_data",
+ "name": "data",
+ "required_inputs": [],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.12416523075924112,
+ "template": {
+ "_type": "Component",
+ "chunk_overlap": {
+ "_input_type": "IntInput",
+ "advanced": false,
+ "display_name": "Chunk Overlap",
+ "dynamic": false,
+ "info": "The amount of overlap between chunks.",
+ "list": false,
+ "name": "chunk_overlap",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 200
+ },
+ "chunk_size": {
+ "_input_type": "IntInput",
+ "advanced": false,
+ "display_name": "Chunk Size",
+ "dynamic": false,
+ "info": "The maximum length of each chunk.",
+ "list": false,
+ "name": "chunk_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1000
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import Any\n\nfrom langchain_text_splitters import Language, RecursiveCharacterTextSplitter, TextSplitter\n\nfrom langflow.base.textsplitters.model import LCTextSplitterComponent\nfrom langflow.inputs import DataInput, DropdownInput, IntInput\n\n\nclass LanguageRecursiveTextSplitterComponent(LCTextSplitterComponent):\n display_name: str = \"Language Recursive Text Splitter\"\n description: str = \"Split text into chunks of a specified length based on language.\"\n documentation: str = \"https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter\"\n name = \"LanguageRecursiveTextSplitter\"\n icon = \"LangChain\"\n\n inputs = [\n IntInput(\n name=\"chunk_size\",\n display_name=\"Chunk Size\",\n info=\"The maximum length of each chunk.\",\n value=1000,\n ),\n IntInput(\n name=\"chunk_overlap\",\n display_name=\"Chunk Overlap\",\n info=\"The amount of overlap between chunks.\",\n value=200,\n ),\n DataInput(\n name=\"data_input\",\n display_name=\"Input\",\n info=\"The texts to split.\",\n input_types=[\"Document\", \"Data\"],\n required=True,\n ),\n DropdownInput(\n name=\"code_language\", display_name=\"Code Language\", options=[x.value for x in Language], value=\"python\"\n ),\n ]\n\n def get_data_input(self) -> Any:\n return self.data_input\n\n def build_text_splitter(self) -> TextSplitter:\n return RecursiveCharacterTextSplitter.from_language(\n language=Language(self.code_language),\n chunk_size=self.chunk_size,\n chunk_overlap=self.chunk_overlap,\n )\n"
+ },
+ "code_language": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Code Language",
+ "dynamic": false,
+ "info": "",
+ "name": "code_language",
+ "options": [
+ "cpp",
+ "go",
+ "java",
+ "kotlin",
+ "js",
+ "ts",
+ "php",
+ "proto",
+ "python",
+ "rst",
+ "ruby",
+ "rust",
+ "scala",
+ "swift",
+ "markdown",
+ "latex",
+ "html",
+ "sol",
+ "csharp",
+ "cobol",
+ "c",
+ "lua",
+ "perl",
+ "haskell",
+ "elixir",
+ "powershell"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "html"
+ },
+ "data_input": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The texts to split.",
+ "input_types": [
+ "Document",
+ "Data"
+ ],
+ "list": false,
+ "name": "data_input",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "LanguageRecursiveTextSplitter"
+ },
+ "dragging": false,
+ "id": "LanguageRecursiveTextSplitter-KDtC3",
+ "measured": {
+ "height": 513,
+ "width": 360
+ },
+ "position": {
+ "x": -1312.6794416503267,
+ "y": 5995.335519138236
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "HtmlLinkExtractor-LWuvQ",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "category": "langchain_utilities",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Extract hyperlinks from HTML content.",
+ "display_name": "HTML Link Extractor",
+ "documentation": "https://python.langchain.com/v0.2/api_reference/community/graph_vectorstores/langchain_community.graph_vectorstores.extractors.html_link_extractor.HtmlLinkExtractor.html",
+ "edited": false,
+ "field_order": [
+ "kind",
+ "drop_fragments",
+ "data_input"
+ ],
+ "frozen": false,
+ "icon": "LangChain",
+ "key": "HtmlLinkExtractor",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "transform_data",
+ "name": "data",
+ "required_inputs": [],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.01857804455091699,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import Any\n\nfrom langchain_community.graph_vectorstores.extractors import HtmlLinkExtractor, LinkExtractorTransformer\nfrom langchain_core.documents import BaseDocumentTransformer\n\nfrom langflow.base.document_transformers.model import LCDocumentTransformerComponent\nfrom langflow.inputs import BoolInput, DataInput, StrInput\n\n\nclass HtmlLinkExtractorComponent(LCDocumentTransformerComponent):\n display_name = \"HTML Link Extractor\"\n description = \"Extract hyperlinks from HTML content.\"\n documentation = \"https://python.langchain.com/v0.2/api_reference/community/graph_vectorstores/langchain_community.graph_vectorstores.extractors.html_link_extractor.HtmlLinkExtractor.html\"\n name = \"HtmlLinkExtractor\"\n icon = \"LangChain\"\n\n inputs = [\n StrInput(name=\"kind\", display_name=\"Kind of edge\", value=\"hyperlink\", required=False),\n BoolInput(name=\"drop_fragments\", display_name=\"Drop URL fragments\", value=True, required=False),\n DataInput(\n name=\"data_input\",\n display_name=\"Input\",\n info=\"The texts from which to extract links.\",\n input_types=[\"Document\", \"Data\"],\n required=True,\n ),\n ]\n\n def get_data_input(self) -> Any:\n return self.data_input\n\n def build_document_transformer(self) -> BaseDocumentTransformer:\n return LinkExtractorTransformer(\n [HtmlLinkExtractor(kind=self.kind, drop_fragments=self.drop_fragments).as_document_extractor()]\n )\n"
+ },
+ "data_input": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The texts from which to extract links.",
+ "input_types": [
+ "Document",
+ "Data"
+ ],
+ "list": false,
+ "name": "data_input",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "drop_fragments": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Drop URL fragments",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "drop_fragments",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "kind": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Kind of edge",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "kind",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "hyperlink"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "HtmlLinkExtractor"
+ },
+ "dragging": false,
+ "id": "HtmlLinkExtractor-LWuvQ",
+ "measured": {
+ "height": 354,
+ "width": 360
+ },
+ "position": {
+ "x": -819.2825457919793,
+ "y": 6116.365935466731
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIEmbeddings-XKhhV",
+ "node": {
+ "base_classes": [
+ "Embeddings"
+ ],
+ "beta": false,
+ "category": "embeddings",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generate embeddings using OpenAI models.",
+ "display_name": "OpenAI Embeddings",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "default_headers",
+ "default_query",
+ "chunk_size",
+ "client",
+ "deployment",
+ "embedding_ctx_length",
+ "max_retries",
+ "model",
+ "model_kwargs",
+ "openai_api_key",
+ "openai_api_base",
+ "openai_api_type",
+ "openai_api_version",
+ "openai_organization",
+ "openai_proxy",
+ "request_timeout",
+ "show_progress_bar",
+ "skip_empty",
+ "tiktoken_model_name",
+ "tiktoken_enable",
+ "dimensions"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIEmbeddings",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Embeddings",
+ "method": "build_embeddings",
+ "name": "embeddings",
+ "required_inputs": [
+ "openai_api_key"
+ ],
+ "selected": "Embeddings",
+ "tool_mode": true,
+ "types": [
+ "Embeddings"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.000052003277518821525,
+ "template": {
+ "_type": "Component",
+ "chunk_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Chunk Size",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "chunk_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1000
+ },
+ "client": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Client",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "client",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import OpenAIEmbeddings\n\nfrom langflow.base.embeddings.model import LCEmbeddingsModel\nfrom langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES\nfrom langflow.field_typing import Embeddings\nfrom langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput\n\n\nclass OpenAIEmbeddingsComponent(LCEmbeddingsModel):\n display_name = \"OpenAI Embeddings\"\n description = \"Generate embeddings using OpenAI models.\"\n icon = \"OpenAI\"\n name = \"OpenAIEmbeddings\"\n\n inputs = [\n DictInput(\n name=\"default_headers\",\n display_name=\"Default Headers\",\n advanced=True,\n info=\"Default headers to use for the API request.\",\n ),\n DictInput(\n name=\"default_query\",\n display_name=\"Default Query\",\n advanced=True,\n info=\"Default query parameters to use for the API request.\",\n ),\n IntInput(name=\"chunk_size\", display_name=\"Chunk Size\", advanced=True, value=1000),\n MessageTextInput(name=\"client\", display_name=\"Client\", advanced=True),\n MessageTextInput(name=\"deployment\", display_name=\"Deployment\", advanced=True),\n IntInput(name=\"embedding_ctx_length\", display_name=\"Embedding Context Length\", advanced=True, value=1536),\n IntInput(name=\"max_retries\", display_name=\"Max Retries\", value=3, advanced=True),\n DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n advanced=False,\n options=OPENAI_EMBEDDING_MODEL_NAMES,\n value=\"text-embedding-3-small\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n SecretStrInput(name=\"openai_api_key\", display_name=\"OpenAI API Key\", value=\"OPENAI_API_KEY\", required=True),\n MessageTextInput(name=\"openai_api_base\", display_name=\"OpenAI API Base\", advanced=True),\n MessageTextInput(name=\"openai_api_type\", display_name=\"OpenAI API Type\", advanced=True),\n MessageTextInput(name=\"openai_api_version\", display_name=\"OpenAI API Version\", advanced=True),\n MessageTextInput(\n name=\"openai_organization\",\n display_name=\"OpenAI Organization\",\n advanced=True,\n ),\n MessageTextInput(name=\"openai_proxy\", display_name=\"OpenAI Proxy\", advanced=True),\n FloatInput(name=\"request_timeout\", display_name=\"Request Timeout\", advanced=True),\n BoolInput(name=\"show_progress_bar\", display_name=\"Show Progress Bar\", advanced=True),\n BoolInput(name=\"skip_empty\", display_name=\"Skip Empty\", advanced=True),\n MessageTextInput(\n name=\"tiktoken_model_name\",\n display_name=\"TikToken Model Name\",\n advanced=True,\n ),\n BoolInput(\n name=\"tiktoken_enable\",\n display_name=\"TikToken Enable\",\n advanced=True,\n value=True,\n info=\"If False, you must have transformers installed.\",\n ),\n IntInput(\n name=\"dimensions\",\n display_name=\"Dimensions\",\n info=\"The number of dimensions the resulting output embeddings should have. \"\n \"Only supported by certain models.\",\n advanced=True,\n ),\n ]\n\n def build_embeddings(self) -> Embeddings:\n return OpenAIEmbeddings(\n client=self.client or None,\n model=self.model,\n dimensions=self.dimensions or None,\n deployment=self.deployment or None,\n api_version=self.openai_api_version or None,\n base_url=self.openai_api_base or None,\n openai_api_type=self.openai_api_type or None,\n openai_proxy=self.openai_proxy or None,\n embedding_ctx_length=self.embedding_ctx_length,\n api_key=self.openai_api_key or None,\n organization=self.openai_organization or None,\n allowed_special=\"all\",\n disallowed_special=\"all\",\n chunk_size=self.chunk_size,\n max_retries=self.max_retries,\n timeout=self.request_timeout or None,\n tiktoken_enabled=self.tiktoken_enable,\n tiktoken_model_name=self.tiktoken_model_name or None,\n show_progress_bar=self.show_progress_bar,\n model_kwargs=self.model_kwargs,\n skip_empty=self.skip_empty,\n default_headers=self.default_headers or None,\n default_query=self.default_query or None,\n )\n"
+ },
+ "default_headers": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Default Headers",
+ "dynamic": false,
+ "info": "Default headers to use for the API request.",
+ "list": false,
+ "name": "default_headers",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "default_query": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Default Query",
+ "dynamic": false,
+ "info": "Default query parameters to use for the API request.",
+ "list": false,
+ "name": "default_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "deployment": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Deployment",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "deployment",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "dimensions": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Dimensions",
+ "dynamic": false,
+ "info": "The number of dimensions the resulting output embeddings should have. Only supported by certain models.",
+ "list": false,
+ "name": "dimensions",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "embedding_ctx_length": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Embedding Context Length",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "embedding_ctx_length",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1536
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 3
+ },
+ "model": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Model",
+ "dynamic": false,
+ "info": "",
+ "name": "model",
+ "options": [
+ "text-embedding-3-small",
+ "text-embedding-3-large",
+ "text-embedding-ada-002"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "text-embedding-3-large"
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "openai_api_base": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "openai_api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_type": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Type",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_type",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_api_version": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI API Version",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_version",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_organization": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI Organization",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_organization",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "openai_proxy": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "OpenAI Proxy",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_proxy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "request_timeout": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Request Timeout",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "request_timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": ""
+ },
+ "show_progress_bar": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Show Progress Bar",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "show_progress_bar",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "skip_empty": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Skip Empty",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "skip_empty",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "tiktoken_enable": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "TikToken Enable",
+ "dynamic": false,
+ "info": "If False, you must have transformers installed.",
+ "list": false,
+ "name": "tiktoken_enable",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "tiktoken_model_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "TikToken Model Name",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tiktoken_model_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIEmbeddings"
+ },
+ "dragging": false,
+ "id": "OpenAIEmbeddings-XKhhV",
+ "measured": {
+ "height": 349,
+ "width": 360
+ },
+ "position": {
+ "x": -826.5880014143661,
+ "y": 6515.86676043142
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-Nosro",
+ "node": {
+ "description": "## 📖 README\n\nLoad your data into a vector database with the 📚 **Load Data** flow, and then use your data as chat context with the 🐕 **Retriever** flow.\n\n**🚨 Add your OpenAI API key as a global variable to easily add it to all of the OpenAI components in this flow.** \n\n**Quick start**\n1. Run the 📚 **Load Data** flow.\n2. Run the 🐕 **Retriever** flow.\n\n**Next steps** \n\n- Experiment by changing the prompt and the loaded data to see how the bot's responses change. ",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 695,
+ "id": "note-Nosro",
+ "measured": {
+ "height": 695,
+ "width": 328
+ },
+ "position": {
+ "x": -2651.9749287591367,
+ "y": 6278.741596879104
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-Cps5A",
+ "node": {
+ "description": "## 📚 1. Load Data Flow\n\nRun this first! Load data multiple urls, and embed it into the vector database in a graph based format.\n\nClick ▶️ **Run component** on the **Astra DB Graph** component to load your data.\n\n* If you're using OSS Langflow, add your Astra DB Application Token to the Astra DB component.\n\n#### Next steps:\n Experiment by changing the prompt and the contextual data to see how the retrieval flow's responses change.",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 499,
+ "id": "note-Cps5A",
+ "measured": {
+ "height": 499,
+ "width": 329
+ },
+ "position": {
+ "x": -2156.877891666127,
+ "y": 5887.194729165318
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "note-VWvJC",
+ "node": {
+ "description": "## 🐕 2. Retriever Flow\n\nThis flow answers your questions with contextual data retrieved from your vector database. using graph RAG.\n\nOpen the **Playground** and ask, \n\n```\nhow to create a function in Haskell ?\n```\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "id": "note-VWvJC",
+ "measured": {
+ "height": 324,
+ "width": 328
+ },
+ "position": {
+ "x": -2255.4854518934735,
+ "y": 7190.4854518934735
+ },
+ "selected": false,
+ "type": "noteNode"
+ },
+ {
+ "data": {
+ "id": "AstraDBGraph-xJiDN",
+ "node": {
+ "base_classes": [
+ "Data",
+ "DataFrame"
+ ],
+ "beta": false,
+ "category": "vectorstores",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Implementation of Graph Vector Store using Astra DB",
+ "display_name": "Astra DB Graph",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "token",
+ "api_endpoint",
+ "collection_name",
+ "metadata_incoming_links_key",
+ "ingest_data",
+ "search_query",
+ "should_cache_vector_store",
+ "keyspace",
+ "embedding_model",
+ "metric",
+ "batch_size",
+ "bulk_insert_batch_concurrency",
+ "bulk_insert_overwrite_concurrency",
+ "bulk_delete_concurrency",
+ "setup_mode",
+ "pre_delete_collection",
+ "metadata_indexing_include",
+ "metadata_indexing_exclude",
+ "collection_indexing_policy",
+ "number_of_results",
+ "search_type",
+ "search_score_threshold",
+ "search_filter"
+ ],
+ "frozen": false,
+ "icon": "AstraDB",
+ "key": "AstraDBGraph",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Search Results",
+ "method": "search_documents",
+ "name": "search_results",
+ "required_inputs": [
+ "api_endpoint",
+ "collection_name",
+ "token"
+ ],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "required_inputs": [],
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.01857804455091699,
+ "template": {
+ "_type": "Component",
+ "api_endpoint": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "API Endpoint",
+ "dynamic": false,
+ "info": "API endpoint URL for the Astra DB service.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_endpoint",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "batch_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Batch Size",
+ "dynamic": false,
+ "info": "Optional number of data to process in a single batch.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "batch_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_delete_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Delete Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk delete operations.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_delete_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_batch_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Batch Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_insert_batch_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_overwrite_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Overwrite Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations that overwrite existing data.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_insert_overwrite_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import os\n\nimport orjson\nfrom astrapy.admin import parse_api_endpoint\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\n\n\nclass AstraDBGraphVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB Graph\"\n description: str = \"Implementation of Graph Vector Store using Astra DB\"\n name = \"AstraDBGraph\"\n icon: str = \"AstraDB\"\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n ),\n SecretStrInput(\n name=\"api_endpoint\",\n display_name=\"Database\" if os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\" else \"API Endpoint\",\n info=\"API endpoint URL for the Astra DB service.\",\n value=\"ASTRA_DB_API_ENDPOINT\",\n required=True,\n ),\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n ),\n StrInput(\n name=\"metadata_incoming_links_key\",\n display_name=\"Metadata incoming links key\",\n info=\"Metadata key used for incoming links.\",\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"Metric\",\n info=\"Optional distance metric for vector comparisons in the vector store.\",\n options=[\"cosine\", \"dot_product\", \"euclidean\"],\n value=\"cosine\",\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_batch_concurrency\",\n display_name=\"Bulk Insert Batch Concurrency\",\n info=\"Optional concurrency level for bulk insert operations.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_overwrite_concurrency\",\n display_name=\"Bulk Insert Overwrite Concurrency\",\n info=\"Optional concurrency level for bulk insert operations that overwrite existing data.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_delete_concurrency\",\n display_name=\"Bulk Delete Concurrency\",\n info=\"Optional concurrency level for bulk delete operations.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.\",\n options=[\"Sync\", \"Off\"],\n advanced=True,\n value=\"Sync\",\n ),\n BoolInput(\n name=\"pre_delete_collection\",\n display_name=\"Pre Delete Collection\",\n info=\"Boolean flag to determine whether to delete the collection before creating a new one.\",\n advanced=True,\n value=False,\n ),\n StrInput(\n name=\"metadata_indexing_include\",\n display_name=\"Metadata Indexing Include\",\n info=\"Optional list of metadata fields to include in the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"metadata_indexing_exclude\",\n display_name=\"Metadata Indexing Exclude\",\n info=\"Optional list of metadata fields to exclude from the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"collection_indexing_policy\",\n display_name=\"Collection Indexing Policy\",\n info='Optional JSON string for the \"indexing\" field of the collection. '\n \"See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n \"Graph Traversal\",\n \"MMR (Max Marginal Relevance) Graph Traversal\",\n ],\n value=\"MMR (Max Marginal Relevance) Graph Traversal\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBGraphVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n try:\n self.log(f\"Initializing Graph Vector Store {self.collection_name}\")\n\n vector_store = AstraDBGraphVectorStore(\n embedding=self.embedding_model,\n collection_name=self.collection_name,\n metadata_incoming_links_key=self.metadata_incoming_links_key or \"incoming_links\",\n token=self.token,\n api_endpoint=self.api_endpoint,\n namespace=self.keyspace or None,\n environment=parse_api_endpoint(self.api_endpoint).environment if self.api_endpoint else None,\n metric=self.metric or None,\n batch_size=self.batch_size or None,\n bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,\n bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,\n bulk_delete_concurrency=self.bulk_delete_concurrency or None,\n setup_mode=setup_mode_value,\n pre_delete_collection=self.pre_delete_collection,\n metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,\n metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,\n collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode(\"utf-8\"))\n if self.collection_indexing_policy\n else None,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Vector Store initialized: {vector_store.astra_env.collection_name}\")\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n match self.search_type:\n case \"Similarity\":\n return \"similarity\"\n case \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n case \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n case \"Graph Traversal\":\n return \"traversal\"\n case \"MMR (Max Marginal Relevance) Graph Traversal\":\n return \"mmr_traversal\"\n case _:\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n if not vector_store:\n vector_store = self.build_vector_store()\n\n self.log(\"Searching for documents in AstraDBGraphVectorStore.\")\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n\n # Drop links from the metadata. At this point the links don't add any value for building the\n # context and haven't been restored to json which causes the conversion to fail.\n self.log(\"Removing links from metadata.\")\n for doc in docs:\n if \"links\" in doc.metadata:\n doc.metadata.pop(\"links\")\n\n except Exception as e:\n msg = f\"Error performing search in AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n\n self.log(f\"Converted documents to data: {len(data)}\")\n\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"
+ },
+ "collection_indexing_policy": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Collection Indexing Policy",
+ "dynamic": false,
+ "info": "Optional JSON string for the \"indexing\" field of the collection. See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "collection_indexing_policy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "collection_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Collection Name",
+ "dynamic": false,
+ "info": "The name of the collection within Astra DB where the vectors will be stored.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "collection_name",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "embedding_model": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Embedding Model",
+ "dynamic": false,
+ "info": "Allows an embedding model configuration.",
+ "input_types": [
+ "Embeddings"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "embedding_model",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "ingest_data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Ingest Data",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "ingest_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "keyspace": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Keyspace",
+ "dynamic": false,
+ "info": "Optional keyspace within Astra DB to use for the collection.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "keyspace",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_incoming_links_key": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata incoming links key",
+ "dynamic": false,
+ "info": "Metadata key used for incoming links.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_incoming_links_key",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_exclude": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Exclude",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to exclude from the indexing.",
+ "list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_indexing_exclude",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_include": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Include",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to include in the indexing.",
+ "list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_indexing_include",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metric": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Metric",
+ "dynamic": false,
+ "info": "Optional distance metric for vector comparisons in the vector store.",
+ "name": "metric",
+ "options": [
+ "cosine",
+ "dot_product",
+ "euclidean"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "cosine"
+ },
+ "number_of_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Results",
+ "dynamic": false,
+ "info": "Number of results to return.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "number_of_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4
+ },
+ "pre_delete_collection": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Pre Delete Collection",
+ "dynamic": false,
+ "info": "Boolean flag to determine whether to delete the collection before creating a new one.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "pre_delete_collection",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "search_filter": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Search Metadata Filter",
+ "dynamic": false,
+ "info": "Optional dictionary of filters to apply to the search query.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "search_filter",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "search_query": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "search_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_score_threshold": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Search Score Threshold",
+ "dynamic": false,
+ "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "search_score_threshold",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 0
+ },
+ "search_type": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Type",
+ "dynamic": false,
+ "info": "Search type to use",
+ "name": "search_type",
+ "options": [
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ "Graph Traversal",
+ "MMR (Max Marginal Relevance) Graph Traversal"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "MMR (Max Marginal Relevance) Graph Traversal"
+ },
+ "setup_mode": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Setup Mode",
+ "dynamic": false,
+ "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.",
+ "name": "setup_mode",
+ "options": [
+ "Sync",
+ "Off"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Sync"
+ },
+ "should_cache_vector_store": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Cache Vector Store",
+ "dynamic": false,
+ "info": "If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_cache_vector_store",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "token": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Astra DB Application Token",
+ "dynamic": false,
+ "info": "Authentication token for accessing Astra DB.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "token",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AstraDBGraph"
+ },
+ "dragging": false,
+ "id": "AstraDBGraph-xJiDN",
+ "measured": {
+ "height": 709,
+ "width": 360
+ },
+ "position": {
+ "x": -1289.3629248195448,
+ "y": 7017.257953990425
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "AstraDBGraph-uza6S",
+ "node": {
+ "base_classes": [
+ "Data",
+ "DataFrame"
+ ],
+ "beta": false,
+ "category": "vectorstores",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Implementation of Graph Vector Store using Astra DB",
+ "display_name": "Astra DB Graph",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "token",
+ "api_endpoint",
+ "collection_name",
+ "metadata_incoming_links_key",
+ "ingest_data",
+ "search_query",
+ "should_cache_vector_store",
+ "keyspace",
+ "embedding_model",
+ "metric",
+ "batch_size",
+ "bulk_insert_batch_concurrency",
+ "bulk_insert_overwrite_concurrency",
+ "bulk_delete_concurrency",
+ "setup_mode",
+ "pre_delete_collection",
+ "metadata_indexing_include",
+ "metadata_indexing_exclude",
+ "collection_indexing_policy",
+ "number_of_results",
+ "search_type",
+ "search_score_threshold",
+ "search_filter"
+ ],
+ "frozen": false,
+ "icon": "AstraDB",
+ "key": "AstraDBGraph",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Search Results",
+ "method": "search_documents",
+ "name": "search_results",
+ "required_inputs": [
+ "api_endpoint",
+ "collection_name",
+ "token"
+ ],
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "DataFrame",
+ "method": "as_dataframe",
+ "name": "dataframe",
+ "required_inputs": [],
+ "selected": "DataFrame",
+ "tool_mode": true,
+ "types": [
+ "DataFrame"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.01857804455091699,
+ "template": {
+ "_type": "Component",
+ "api_endpoint": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "API Endpoint",
+ "dynamic": false,
+ "info": "API endpoint URL for the Astra DB service.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_endpoint",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "batch_size": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Batch Size",
+ "dynamic": false,
+ "info": "Optional number of data to process in a single batch.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "batch_size",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_delete_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Delete Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk delete operations.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_delete_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_batch_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Batch Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_insert_batch_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "bulk_insert_overwrite_concurrency": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Bulk Insert Overwrite Concurrency",
+ "dynamic": false,
+ "info": "Optional concurrency level for bulk insert operations that overwrite existing data.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "bulk_insert_overwrite_concurrency",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import os\n\nimport orjson\nfrom astrapy.admin import parse_api_endpoint\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\n\n\nclass AstraDBGraphVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB Graph\"\n description: str = \"Implementation of Graph Vector Store using Astra DB\"\n name = \"AstraDBGraph\"\n icon: str = \"AstraDB\"\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n ),\n SecretStrInput(\n name=\"api_endpoint\",\n display_name=\"Database\" if os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\" else \"API Endpoint\",\n info=\"API endpoint URL for the Astra DB service.\",\n value=\"ASTRA_DB_API_ENDPOINT\",\n required=True,\n ),\n StrInput(\n name=\"collection_name\",\n display_name=\"Collection Name\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n ),\n StrInput(\n name=\"metadata_incoming_links_key\",\n display_name=\"Metadata incoming links key\",\n info=\"Metadata key used for incoming links.\",\n advanced=True,\n ),\n *LCVectorStoreComponent.inputs,\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n DropdownInput(\n name=\"metric\",\n display_name=\"Metric\",\n info=\"Optional distance metric for vector comparisons in the vector store.\",\n options=[\"cosine\", \"dot_product\", \"euclidean\"],\n value=\"cosine\",\n advanced=True,\n ),\n IntInput(\n name=\"batch_size\",\n display_name=\"Batch Size\",\n info=\"Optional number of data to process in a single batch.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_batch_concurrency\",\n display_name=\"Bulk Insert Batch Concurrency\",\n info=\"Optional concurrency level for bulk insert operations.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_insert_overwrite_concurrency\",\n display_name=\"Bulk Insert Overwrite Concurrency\",\n info=\"Optional concurrency level for bulk insert operations that overwrite existing data.\",\n advanced=True,\n ),\n IntInput(\n name=\"bulk_delete_concurrency\",\n display_name=\"Bulk Delete Concurrency\",\n info=\"Optional concurrency level for bulk delete operations.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"setup_mode\",\n display_name=\"Setup Mode\",\n info=\"Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.\",\n options=[\"Sync\", \"Off\"],\n advanced=True,\n value=\"Sync\",\n ),\n BoolInput(\n name=\"pre_delete_collection\",\n display_name=\"Pre Delete Collection\",\n info=\"Boolean flag to determine whether to delete the collection before creating a new one.\",\n advanced=True,\n value=False,\n ),\n StrInput(\n name=\"metadata_indexing_include\",\n display_name=\"Metadata Indexing Include\",\n info=\"Optional list of metadata fields to include in the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"metadata_indexing_exclude\",\n display_name=\"Metadata Indexing Exclude\",\n info=\"Optional list of metadata fields to exclude from the indexing.\",\n advanced=True,\n list=True,\n ),\n StrInput(\n name=\"collection_indexing_policy\",\n display_name=\"Collection Indexing Policy\",\n info='Optional JSON string for the \"indexing\" field of the collection. '\n \"See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option\",\n advanced=True,\n ),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\n \"Similarity\",\n \"Similarity with score threshold\",\n \"MMR (Max Marginal Relevance)\",\n \"Graph Traversal\",\n \"MMR (Max Marginal Relevance) Graph Traversal\",\n ],\n value=\"MMR (Max Marginal Relevance) Graph Traversal\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n DictInput(\n name=\"search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n is_list=True,\n ),\n ]\n\n @check_cached_vector_store\n def build_vector_store(self):\n try:\n from langchain_astradb import AstraDBGraphVectorStore\n from langchain_astradb.utils.astradb import SetupMode\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n try:\n if not self.setup_mode:\n self.setup_mode = self._inputs[\"setup_mode\"].options[0]\n\n setup_mode_value = SetupMode[self.setup_mode.upper()]\n except KeyError as e:\n msg = f\"Invalid setup mode: {self.setup_mode}\"\n raise ValueError(msg) from e\n\n try:\n self.log(f\"Initializing Graph Vector Store {self.collection_name}\")\n\n vector_store = AstraDBGraphVectorStore(\n embedding=self.embedding_model,\n collection_name=self.collection_name,\n metadata_incoming_links_key=self.metadata_incoming_links_key or \"incoming_links\",\n token=self.token,\n api_endpoint=self.api_endpoint,\n namespace=self.keyspace or None,\n environment=parse_api_endpoint(self.api_endpoint).environment if self.api_endpoint else None,\n metric=self.metric or None,\n batch_size=self.batch_size or None,\n bulk_insert_batch_concurrency=self.bulk_insert_batch_concurrency or None,\n bulk_insert_overwrite_concurrency=self.bulk_insert_overwrite_concurrency or None,\n bulk_delete_concurrency=self.bulk_delete_concurrency or None,\n setup_mode=setup_mode_value,\n pre_delete_collection=self.pre_delete_collection,\n metadata_indexing_include=[s for s in self.metadata_indexing_include if s] or None,\n metadata_indexing_exclude=[s for s in self.metadata_indexing_exclude if s] or None,\n collection_indexing_policy=orjson.loads(self.collection_indexing_policy.encode(\"utf-8\"))\n if self.collection_indexing_policy\n else None,\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Vector Store initialized: {vector_store.astra_env.collection_name}\")\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n match self.search_type:\n case \"Similarity\":\n return \"similarity\"\n case \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n case \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n case \"Graph Traversal\":\n return \"traversal\"\n case \"MMR (Max Marginal Relevance) Graph Traversal\":\n return \"mmr_traversal\"\n case _:\n return \"similarity\"\n\n def _build_search_args(self):\n args = {\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n\n if self.search_filter:\n clean_filter = {k: v for k, v in self.search_filter.items() if k and v}\n if len(clean_filter) > 0:\n args[\"filter\"] = clean_filter\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n if not vector_store:\n vector_store = self.build_vector_store()\n\n self.log(\"Searching for documents in AstraDBGraphVectorStore.\")\n self.log(f\"Search query: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n try:\n search_type = self._map_search_type()\n search_args = self._build_search_args()\n\n docs = vector_store.search(query=self.search_query, search_type=search_type, **search_args)\n\n # Drop links from the metadata. At this point the links don't add any value for building the\n # context and haven't been restored to json which causes the conversion to fail.\n self.log(\"Removing links from metadata.\")\n for doc in docs:\n if \"links\" in doc.metadata:\n doc.metadata.pop(\"links\")\n\n except Exception as e:\n msg = f\"Error performing search in AstraDBGraphVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n\n self.log(f\"Converted documents to data: {len(data)}\")\n\n self.status = data\n return data\n self.log(\"No search input provided. Skipping search.\")\n return []\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n"
+ },
+ "collection_indexing_policy": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Collection Indexing Policy",
+ "dynamic": false,
+ "info": "Optional JSON string for the \"indexing\" field of the collection. See https://docs.datastax.com/en/astra-db-serverless/api-reference/collections.html#the-indexing-option",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "collection_indexing_policy",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "collection_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Collection Name",
+ "dynamic": false,
+ "info": "The name of the collection within Astra DB where the vectors will be stored.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "collection_name",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "embedding_model": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Embedding Model",
+ "dynamic": false,
+ "info": "Allows an embedding model configuration.",
+ "input_types": [
+ "Embeddings"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "embedding_model",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "ingest_data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Ingest Data",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "ingest_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "keyspace": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Keyspace",
+ "dynamic": false,
+ "info": "Optional keyspace within Astra DB to use for the collection.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "keyspace",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_incoming_links_key": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata incoming links key",
+ "dynamic": false,
+ "info": "Metadata key used for incoming links.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_incoming_links_key",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_exclude": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Exclude",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to exclude from the indexing.",
+ "list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_indexing_exclude",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metadata_indexing_include": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Metadata Indexing Include",
+ "dynamic": false,
+ "info": "Optional list of metadata fields to include in the indexing.",
+ "list": true,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "metadata_indexing_include",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "metric": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Metric",
+ "dynamic": false,
+ "info": "Optional distance metric for vector comparisons in the vector store.",
+ "name": "metric",
+ "options": [
+ "cosine",
+ "dot_product",
+ "euclidean"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "cosine"
+ },
+ "number_of_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Results",
+ "dynamic": false,
+ "info": "Number of results to return.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "number_of_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4
+ },
+ "pre_delete_collection": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Pre Delete Collection",
+ "dynamic": false,
+ "info": "Boolean flag to determine whether to delete the collection before creating a new one.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "pre_delete_collection",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "search_filter": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Search Metadata Filter",
+ "dynamic": false,
+ "info": "Optional dictionary of filters to apply to the search query.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "search_filter",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "search_query": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "search_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_score_threshold": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Search Score Threshold",
+ "dynamic": false,
+ "info": "Minimum similarity score threshold for search results. (when using 'Similarity with score threshold')",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "search_score_threshold",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 0
+ },
+ "search_type": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Type",
+ "dynamic": false,
+ "info": "Search type to use",
+ "name": "search_type",
+ "options": [
+ "Similarity",
+ "Similarity with score threshold",
+ "MMR (Max Marginal Relevance)",
+ "Graph Traversal",
+ "MMR (Max Marginal Relevance) Graph Traversal"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "MMR (Max Marginal Relevance) Graph Traversal"
+ },
+ "setup_mode": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Setup Mode",
+ "dynamic": false,
+ "info": "Configuration mode for setting up the vector store, with options like 'Sync', or 'Off'.",
+ "name": "setup_mode",
+ "options": [
+ "Sync",
+ "Off"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Sync"
+ },
+ "should_cache_vector_store": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Cache Vector Store",
+ "dynamic": false,
+ "info": "If True, the vector store will be cached for the current build of the component. This is useful for components that have multiple output methods and want to share the same vector store.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_cache_vector_store",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "token": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Astra DB Application Token",
+ "dynamic": false,
+ "info": "Authentication token for accessing Astra DB.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "token",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AstraDBGraph"
+ },
+ "dragging": false,
+ "id": "AstraDBGraph-uza6S",
+ "measured": {
+ "height": 709,
+ "width": 360
+ },
+ "position": {
+ "x": -351.01793894175796,
+ "y": 6101.568873207264
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": 1127.2300847911954,
+ "y": -2570.5545905062804,
+ "zoom": 0.4514602881701939
+ }
+ },
+ "description": "Extracts links from web pages and processes the content using Graph RAG Chain with Maximal Marginal Relevance (MMR) traversal.",
+ "endpoint_name": null,
+ "icon": "chartNetwork",
+ "id": "73ceae85-141a-42e3-9f41-a0749434ca0e",
+ "is_component": false,
+ "last_tested_version": "1.1.1",
+ "name": "Graph RAG",
+ "tags": [
+ "rag",
+ "q-a"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json
new file mode 100644
index 0000000..7dc961d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Image Sentiment Analysis.json
@@ -0,0 +1,1734 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "StructuredOutputComponent",
+ "id": "StructuredOutputComponent-XYoUc",
+ "name": "structured_output",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-HzweJ",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-StructuredOutputComponent-XYoUc{œdataTypeœ:œStructuredOutputComponentœ,œidœ:œStructuredOutputComponent-XYoUcœ,œnameœ:œstructured_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-HzweJ{œfieldNameœ:œdataœ,œidœ:œParseData-HzweJœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "StructuredOutputComponent-XYoUc",
+ "sourceHandle": "{œdataTypeœ: œStructuredOutputComponentœ, œidœ: œStructuredOutputComponent-XYoUcœ, œnameœ: œstructured_outputœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-HzweJ",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-HzweJœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-HzweJ",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-xQxLm",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-HzweJ{œdataTypeœ:œParseDataœ,œidœ:œParseData-HzweJœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-xQxLm{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-xQxLmœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseData-HzweJ",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-HzweJœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-xQxLm",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-xQxLmœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-rAWlE",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-cqeNw",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-rAWlE{œdataTypeœ:œChatInputœ,œidœ:œChatInput-rAWlEœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-cqeNw{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-cqeNwœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-rAWlE",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-rAWlEœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-cqeNw",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-cqeNwœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-AzK6t",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-cqeNw",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-AzK6t{œdataTypeœ:œPromptœ,œidœ:œPrompt-AzK6tœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-cqeNw{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-cqeNwœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-AzK6t",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-AzK6tœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-cqeNw",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-cqeNwœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-cqeNw",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "StructuredOutputComponent-XYoUc",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-cqeNw{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-cqeNwœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputComponent-XYoUc{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputComponent-XYoUcœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-cqeNw",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-cqeNwœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "StructuredOutputComponent-XYoUc",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputComponent-XYoUcœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-cqeNw",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "StructuredOutputComponent-XYoUc",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-cqeNw{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-cqeNwœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputComponent-XYoUc{œfieldNameœ:œllmœ,œidœ:œStructuredOutputComponent-XYoUcœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "OpenAIModel-cqeNw",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-cqeNwœ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}",
+ "target": "StructuredOutputComponent-XYoUc",
+ "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputComponent-XYoUcœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "id": "ChatInput-rAWlE",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-rAWlE",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 1258.8272095125978,
+ "y": 367.0048451335054
+ },
+ "positionAbsolute": {
+ "x": 1258.8272095125978,
+ "y": 367.0048451335054
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-xQxLm",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-xQxLm",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 2742.72534045604,
+ "y": 681.9098282545469
+ },
+ "positionAbsolute": {
+ "x": 2742.72534045604,
+ "y": 681.9098282545469
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-K9w8w",
+ "node": {
+ "description": "# Image Sentiment Analysis\nWelcome to the Image Sentiment Classifier - an AI tool for quick image sentiment analysis!\n\n## Instructions\n\n1. **Prepare Your Image**\n - Image should be clear and visible\n\n2. **Upload Options**\n - Open the Playground\n - Click file attachment icon\n - Or drag and drop into playground\n\n3. **Wait for Analysis**\n - System will process the image\n - Uses zero-shot learning\n - Classification happens automatically\n\n4. **Review Results**\n - Get classification: Positive/Negative/Neutral\n - Review confidence level\n - Check reasoning if provided\n\n5. **Expected Classifications**\n - Positive: Happy scenes, smiles, celebrations\n - Negative: Sad scenes, problems, conflicts\n - Neutral: Objects, landscapes, neutral scenes\n\nRemember: The clearer the image, the more accurate the classification! 📸✨",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 583,
+ "id": "note-K9w8w",
+ "measured": {
+ "height": 570,
+ "width": 325
+ },
+ "position": {
+ "x": 791.7294511578832,
+ "y": 340.1333942936967
+ },
+ "positionAbsolute": {
+ "x": 791.7294511578832,
+ "y": 340.1333942936967
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 583,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.",
+ "display_name": "Structured Output",
+ "id": "StructuredOutputComponent-XYoUc",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.",
+ "display_name": "Structured Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "llm",
+ "input_value",
+ "schema_name",
+ "output_schema",
+ "multiple"
+ ],
+ "frozen": false,
+ "icon": "braces",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "cache": true,
+ "display_name": "Structured Output",
+ "method": "build_structured_output",
+ "name": "structured_output",
+ "selected": "Data",
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import TYPE_CHECKING, cast\n\nfrom pydantic import BaseModel, Field, create_model\n\nfrom langflow.base.models.chat_result import get_chat_result\nfrom langflow.custom import Component\nfrom langflow.helpers.base_model import build_model_from_schema\nfrom langflow.io import BoolInput, HandleInput, MessageTextInput, Output, StrInput, TableInput\nfrom langflow.schema.data import Data\n\nif TYPE_CHECKING:\n from langflow.field_typing.constants import LanguageModel\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = (\n \"Transforms LLM responses into **structured data formats**. Ideal for extracting specific information \"\n \"or creating consistent outputs.\"\n )\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n ),\n MessageTextInput(name=\"input_value\", display_name=\"Input message\"),\n StrInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"description\": (\n \"Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).\"\n ),\n \"default\": \"text\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"Multiple\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n },\n ],\n value=[{\"name\": \"field\", \"description\": \"description of field\", \"type\": \"text\", \"multiple\": \"False\"}],\n ),\n BoolInput(\n name=\"multiple\",\n display_name=\"Generate Multiple\",\n info=\"Set to True if the model should generate a list of outputs instead of a single output.\",\n ),\n ]\n\n outputs = [\n Output(name=\"structured_output\", display_name=\"Structured Output\", method=\"build_structured_output\"),\n ]\n\n def build_structured_output(self) -> Data:\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n if self.multiple:\n output_model = create_model(\n self.schema_name,\n objects=(list[output_model_], Field(description=f\"A list of {self.schema_name}.\")), # type: ignore[valid-type]\n )\n else:\n output_model = output_model_\n try:\n llm_with_structured_output = cast(\"LanguageModel\", self.llm).with_structured_output(schema=output_model) # type: ignore[valid-type, attr-defined]\n\n except NotImplementedError as exc:\n msg = f\"{self.llm.__class__.__name__} does not support structured output.\"\n raise TypeError(msg) from exc\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n output = get_chat_result(runnable=llm_with_structured_output, input_value=self.input_value, config=config_dict)\n if isinstance(output, BaseModel):\n output_dict = output.model_dump()\n else:\n msg = f\"Output should be a Pydantic BaseModel, got {type(output)} ({output})\"\n raise TypeError(msg)\n return Data(data=output_dict)\n"
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input message",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "llm": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Language Model",
+ "dynamic": false,
+ "info": "The language model to use to generate the structured output.",
+ "input_types": [
+ "LanguageModel"
+ ],
+ "list": false,
+ "name": "llm",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "multiple": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Generate Multiple",
+ "dynamic": false,
+ "info": "Set to True if the model should generate a list of outputs instead of a single output.",
+ "list": false,
+ "name": "multiple",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "output_schema": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Output Schema",
+ "dynamic": false,
+ "info": "Define the structure and data types for the model's output.",
+ "is_list": true,
+ "load_from_db": false,
+ "name": "output_schema",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "table_schema": {
+ "columns": [
+ {
+ "description": "Specify the name of the output field.",
+ "display_name": "Name",
+ "filterable": true,
+ "formatter": "text",
+ "name": "name",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "description": "Describe the purpose of the output field.",
+ "display_name": "Description",
+ "filterable": true,
+ "formatter": "text",
+ "name": "description",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "text",
+ "description": "Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).",
+ "display_name": "Type",
+ "filterable": true,
+ "formatter": "text",
+ "name": "type",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "False",
+ "description": "Set to True if this output field should be a list of the specified type.",
+ "display_name": "Multiple",
+ "filterable": true,
+ "formatter": "text",
+ "name": "multiple",
+ "sortable": true,
+ "type": "boolean"
+ }
+ ]
+ },
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "table",
+ "value": [
+ {
+ "description": "A Positive|Negative value that represents the image.",
+ "multiple": "False",
+ "name": "sentiment",
+ "type": "text"
+ },
+ {
+ "description": "Brief Description of the image",
+ "multiple": "False",
+ "name": "description",
+ "type": "text"
+ }
+ ]
+ },
+ "schema_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Schema Name",
+ "dynamic": false,
+ "info": "Provide a name for the output data schema.",
+ "list": false,
+ "load_from_db": false,
+ "name": "schema_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "image_classification"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "StructuredOutputComponent"
+ },
+ "dragging": false,
+ "height": 541,
+ "id": "StructuredOutputComponent-XYoUc",
+ "measured": {
+ "height": 541,
+ "width": 320
+ },
+ "position": {
+ "x": 2029.441019694193,
+ "y": 414.7974622616549
+ },
+ "positionAbsolute": {
+ "x": 2029.441019694193,
+ "y": 414.7974622616549
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "ParseData-HzweJ",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Sentiment: {sentiment} \n\nDescription: {description} "
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "height": 302,
+ "id": "ParseData-HzweJ",
+ "measured": {
+ "height": 302,
+ "width": 320
+ },
+ "position": {
+ "x": 2389.490977317181,
+ "y": 646.9530981549555
+ },
+ "positionAbsolute": {
+ "x": 2389.490977317181,
+ "y": 646.9530981549555
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-AzK6t",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": []
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Classify the image into neutral, negative or positive. "
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 260,
+ "id": "Prompt-AzK6t",
+ "measured": {
+ "height": 260,
+ "width": 320
+ },
+ "position": {
+ "x": 1262.0179832573751,
+ "y": 632.1360181124842
+ },
+ "positionAbsolute": {
+ "x": 1262.0179832573751,
+ "y": 632.1360181124842
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-cqeNw",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-cqeNw",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 1638.1662423437713,
+ "y": 374.7199736704084
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -426.91879919031885,
+ "y": 21.85679755101154,
+ "zoom": 0.6313688898572775
+ }
+ },
+ "description": "Analyzes images and categorizes them as positive, negative, or neutral using zero-shot learning.",
+ "endpoint_name": null,
+ "gradient": "2",
+ "icon": "Image",
+ "id": "0caf0da8-c233-4fc5-9df3-41bb58403885",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Image Sentiment Analysis",
+ "tags": [
+ "classification"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json
new file mode 100644
index 0000000..2ae477e
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json
@@ -0,0 +1,3396 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "TextInput",
+ "id": "TextInput-jcEYO",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "guidelines",
+ "id": "Prompt-5uTbs",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-TextInput-jcEYO{œdataTypeœ:œTextInputœ,œidœ:œTextInput-jcEYOœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-5uTbs{œfieldNameœ:œguidelinesœ,œidœ:œPrompt-5uTbsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "TextInput-jcEYO",
+ "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-jcEYOœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-5uTbs",
+ "targetHandle": "{œfieldNameœ: œguidelinesœ, œidœ: œPrompt-5uTbsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-RiIAB",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "Agent-nxMr2",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-RiIAB{œdataTypeœ:œChatInputœ,œidœ:œChatInput-RiIABœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-nxMr2{œfieldNameœ:œinput_valueœ,œidœ:œAgent-nxMr2œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ChatInput-RiIAB",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-RiIABœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Agent-nxMr2",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-nxMr2œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Agent",
+ "id": "Agent-nxMr2",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "context",
+ "id": "Prompt-5uTbs",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Agent-nxMr2{œdataTypeœ:œAgentœ,œidœ:œAgent-nxMr2œ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-Prompt-5uTbs{œfieldNameœ:œcontextœ,œidœ:œPrompt-5uTbsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "Agent-nxMr2",
+ "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-nxMr2œ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-5uTbs",
+ "targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-5uTbsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "TavilySearchComponent",
+ "id": "TavilySearchComponent-XbcIl",
+ "name": "component_as_tool",
+ "output_types": [
+ "Tool"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "Agent-nxMr2",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-TavilySearchComponent-XbcIl{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-XbcIlœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-nxMr2{œfieldNameœ:œtoolsœ,œidœ:œAgent-nxMr2œ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "source": "TavilySearchComponent-XbcIl",
+ "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-XbcIlœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}",
+ "target": "Agent-nxMr2",
+ "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-nxMr2œ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-5uTbs",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-BUxp5",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-5uTbs{œdataTypeœ:œPromptœ,œidœ:œPrompt-5uTbsœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-BUxp5{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-BUxp5œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-5uTbs",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-5uTbsœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-BUxp5",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-BUxp5œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-BUxp5",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "post",
+ "id": "Prompt-vNJrn",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-BUxp5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BUxp5œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-vNJrn{œfieldNameœ:œpostœ,œidœ:œPrompt-vNJrnœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "OpenAIModel-BUxp5",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-BUxp5œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-vNJrn",
+ "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-vNJrnœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-BUxp5",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "post",
+ "id": "Prompt-kfn20",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-BUxp5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-BUxp5œ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-kfn20{œfieldNameœ:œpostœ,œidœ:œPrompt-kfn20œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "OpenAIModel-BUxp5",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-BUxp5œ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-kfn20",
+ "targetHandle": "{œfieldNameœ: œpostœ, œidœ: œPrompt-kfn20œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-vNJrn",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-EuPBl",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-vNJrn{œdataTypeœ:œPromptœ,œidœ:œPrompt-vNJrnœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-EuPBl{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-EuPBlœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-vNJrn",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-vNJrnœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-EuPBl",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-EuPBlœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-EuPBl",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "image_description",
+ "id": "Prompt-kfn20",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-EuPBl{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-EuPBlœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-Prompt-kfn20{œfieldNameœ:œimage_descriptionœ,œidœ:œPrompt-kfn20œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "source": "OpenAIModel-EuPBl",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-EuPBlœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-kfn20",
+ "targetHandle": "{œfieldNameœ: œimage_descriptionœ, œidœ: œPrompt-kfn20œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-kfn20",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-AZo3Y",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__Prompt-kfn20{œdataTypeœ:œPromptœ,œidœ:œPrompt-kfn20œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-AZo3Y{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-AZo3Yœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}",
+ "source": "Prompt-kfn20",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-kfn20œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-AZo3Y",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-AZo3Yœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "ChatInput-RiIAB",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Create a Langflow post"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-RiIAB",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 5183.264962599111,
+ "y": 3024.7129453201533
+ },
+ "positionAbsolute": {
+ "x": 5183.264962599111,
+ "y": 3024.7129453201533
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-5uTbs",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "context",
+ "guidelines"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "context": {
+ "advanced": false,
+ "display_name": "context",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "context",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "guidelines": {
+ "advanced": false,
+ "display_name": "guidelines",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "guidelines",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Based on the following context: \n\n{context} \n\n\nFollow these guidelines: \n\n{guidelines}"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 433,
+ "id": "Prompt-5uTbs",
+ "measured": {
+ "height": 433,
+ "width": 360
+ },
+ "position": {
+ "x": 6044.447585613556,
+ "y": 2937.851014457363
+ },
+ "positionAbsolute": {
+ "x": 6013.179772864059,
+ "y": 2937.851014457363
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "TextInput-jcEYO",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get text inputs from the Playground.",
+ "display_name": "Text Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value"
+ ],
+ "frozen": false,
+ "icon": "type",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Playground.\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n return Message(\n text=self.input_value,\n )\n"
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Text to be passed as input.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Build a post for Instagram:\n\n1. **Opening Line**: Start with a powerful, intriguing question or statement to grab attention. Example: \"Ever wondered what it feels like to fly?\"\n\n2. **Main Content**: \n - Briefly share a personal story or insight related to the opening line. Keep it engaging and relatable.\n - Include valuable information or a lesson learned that your audience can benefit from.\n\n3. **Emojis**: Integrate emojis naturally within your text to emphasize key points and add a playful tone.\n\n4. **Call to Action (CTA)**: End with a clear CTA. Encourage your audience to share their thoughts, experiences, or to take a specific action. Example: \"Share your dream adventure in the comments! 🌍✈️\"\n\n5. **Hashtags**: Conclude with a selection of relevant hashtags. Place them at the end of your post to maintain focus on your message."
+ }
+ }
+ },
+ "type": "TextInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "TextInput-jcEYO",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 5672.768365094557,
+ "y": 3457.8151629350077
+ },
+ "positionAbsolute": {
+ "x": 5671.190001393486,
+ "y": 3422.371192525402
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-vNJrn",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "post"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "post": {
+ "advanced": false,
+ "display_name": "post",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "post",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "Based on the following post: \n\n{post} \n\nCraft a compelling prompt for image generator involving a blend of specificity, creativity, and clarity. Begin with a clear, concise description of the subject or scene you envision, incorporating specific details such as the setting, mood, and any key elements that are crucial to your vision. It's important to use descriptive language that conveys not just the visual aspects but also the emotional tone or atmosphere you wish to capture. Modifiers that specify the style, technique, or artistic influences can greatly enhance the prompt, guiding the AI to produce results that align closely with your expectations. Additionally, consider including any particular textures, lighting styles, or perspectives that will help refine the image to your liking. The goal is to provide Leonardo AI with a well-rounded, detailed description that leaves little room for ambiguity, enabling it to generate an image that closely matches your request.\n\nA good prompt should read like a brief to an artist, containing all the necessary information but leaving enough creative freedom for the AI to work effectively. It's a delicate balance between being overly prescriptive and too vague. The inclusion of what to avoid, using negative prompts, can also be helpful in steering the AI away from undesired outcomes. Remember, the effectiveness of a prompt often improves with experimentation and iteration, refining your approach based on the results you receive.\n\nExample 1: \"Create a digital painting of a serene lakeside at dusk, reflecting the vibrant hues of the sunset. The scene should be framed by weeping willows, with a lone wooden rowboat gently bobbing on the water's surface. Aim for a realistic style with a touch of impressionism, focusing on the interplay of light and shadow.\"\n\nExample 2: \"Illustrate a bustling medieval marketplace scene, vibrant and full of life, set within a walled city. Include diverse merchants, from a blacksmith to a spice trader, and townsfolk in period attire. The artwork should capture the dynamic energy of the market, with attention to historical accuracy and rich, earthy colors. Opt for a detailed, digital illustration style that brings out the textures of fabrics, metals, and natural elements.\""
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 347,
+ "id": "Prompt-vNJrn",
+ "measured": {
+ "height": 347,
+ "width": 360
+ },
+ "position": {
+ "x": 6818.9410289594325,
+ "y": 3033.5959958342423
+ },
+ "positionAbsolute": {
+ "x": 6786.650693383261,
+ "y": 3042.4668667721307
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-AZo3Y",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-AZo3Y",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 7980.617825443558,
+ "y": 3377.2219674389726
+ },
+ "positionAbsolute": {
+ "x": 7980.617825443558,
+ "y": 3377.2219674389726
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "id": "Agent-nxMr2",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "agent_llm",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "output_parser",
+ "system_prompt",
+ "tools",
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "agent_description",
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template",
+ "add_current_date_tool"
+ ],
+ "frozen": false,
+ "icon": "bot",
+ "legacy": false,
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Response",
+ "method": "message_response",
+ "name": "response",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "add_current_date_tool": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Current Date",
+ "dynamic": false,
+ "info": "If true, will add a tool to the agent that returns the current date.",
+ "list": false,
+ "name": "add_current_date_tool",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "agent_description": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Agent Description [Deprecated]",
+ "dynamic": false,
+ "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "agent_description",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "A helpful assistant with access to the following tools:"
+ },
+ "agent_llm": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Model Provider",
+ "dynamic": false,
+ "info": "The provider of the language model that the agent will use to generate responses.",
+ "input_types": [],
+ "name": "agent_llm",
+ "options": [
+ "Amazon Bedrock",
+ "Anthropic",
+ "Azure OpenAI",
+ "Google Generative AI",
+ "Groq",
+ "NVIDIA",
+ "OpenAI",
+ "SambaNova",
+ "Custom"
+ ],
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "OpenAI"
+ },
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Validate tools\n if not self.tools:\n msg = \"Tools are required to run the agent. Please add at least one tool.\"\n raise ValueError(msg)\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools,\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def to_toolkit(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n"
+ },
+ "handle_parsing_errors": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Handle Parse Errors",
+ "dynamic": false,
+ "info": "Should the Agent fix errors when reading user input for better processing?",
+ "list": false,
+ "name": "handle_parsing_errors",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The input provided by the user for the agent to process.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_iterations": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Iterations",
+ "dynamic": false,
+ "info": "The maximum number of attempts the agent can make to complete its task before it stops.",
+ "list": false,
+ "name": "max_iterations",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 15
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": true,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "system_prompt": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Agent Instructions",
+ "dynamic": false,
+ "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_prompt",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "You are a helpful AI assistant. Use the following information from a web search to answer the user's question. If the search results don't contain relevant information, say so and offer to help with something else.\n\n{input}"
+ },
+ "temperature": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "temperature",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 0.1
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ },
+ "tools": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Tools",
+ "dynamic": false,
+ "info": "These are the tools that the agent can use to help with tasks.",
+ "input_types": [
+ "Tool"
+ ],
+ "list": true,
+ "name": "tools",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "verbose": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Verbose",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "verbose",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Agent"
+ },
+ "dragging": false,
+ "height": 650,
+ "id": "Agent-nxMr2",
+ "measured": {
+ "height": 650,
+ "width": 360
+ },
+ "position": {
+ "x": 5665.465212822881,
+ "y": 2760.0819124193113
+ },
+ "positionAbsolute": {
+ "x": 5665.465212822881,
+ "y": 2760.0819124193113
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "id": "Prompt-kfn20",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "post",
+ "image_description"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "icon": "prompts",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "image_description": {
+ "advanced": false,
+ "display_name": "image_description",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "image_description",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "post": {
+ "advanced": false,
+ "display_name": "post",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "post",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "{post}\n \n\n{image_description} "
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 433,
+ "id": "Prompt-kfn20",
+ "measured": {
+ "height": 433,
+ "width": 360
+ },
+ "position": {
+ "x": 7613.837241084599,
+ "y": 3139.8282595890087
+ },
+ "positionAbsolute": {
+ "x": 7613.837241084599,
+ "y": 3139.8282595890087
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-MxQ6f",
+ "node": {
+ "description": "# Instagram Copywriter \n\nWelcome to the Instagram Copywriter! This flow helps you create compelling Instagram posts with AI-generated content and image prompts.\n\n## Instructions\n1. Enter Your Topic\n - In the Chat Input, enter a brief description of the topic you want to post about.\n - Example: \"Create a post about meditation and its benefits\"\n\n2. Review the Generated Content\n - The flow will use AI to research your topic and generate a formatted Instagram post.\n - The post will include an opening line, main content, emojis, a call-to-action, and hashtags.\n\n3. Check the Image Prompt\n - The flow will also generate a detailed image prompt based on your post content.\n - This prompt can be used with image generation tools to create a matching visual.\n\n4. Copy the Final Output\n - The Chat Output will display the complete Instagram post text followed by the image generation prompt.\n - Copy this output to use in your Instagram content creation process.\n\n5. Refine if Needed\n - If you're not satisfied with the result, you can adjust the input or modify the OpenAI model settings for different outputs.\n\nRemember: Keep your initial topic input clear and concise for best results! 🎨✨",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "amber"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 648,
+ "id": "note-MxQ6f",
+ "measured": {
+ "height": 648,
+ "width": 558
+ },
+ "position": {
+ "x": 4492.051129290571,
+ "y": 2746.336592524463
+ },
+ "positionAbsolute": {
+ "x": 4560.051129290571,
+ "y": 2746.336592524463
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 648,
+ "width": 554
+ },
+ "type": "noteNode",
+ "width": 554
+ },
+ {
+ "data": {
+ "id": "note-sZVfP",
+ "node": {
+ "description": "**Text Input (Guidelines Prompt)**\n - NOTE: \"Contains Instagram post formatting rules. Don't modify this component as it maintains format consistency.\"\n - Maintains fixed guidelines for:\n * Opening structure\n * Main content\n * Emoji usage\n * Call to Action (CTA)\n * Hashtags\n\n4. **First Prompt + OpenAI Sequence**\n - NOTE: \"Generates initial post content following Instagram guidelines\"\n - Settings:\n * Temperature: 0.7 (good balance between creativity and consistency)\n * Input: Receives research context\n * Output: Generates formatted post text\n\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 325,
+ "id": "note-sZVfP",
+ "measured": {
+ "height": 325,
+ "width": 329
+ },
+ "position": {
+ "x": 5666.120349284508,
+ "y": 3705.9211122250185
+ },
+ "positionAbsolute": {
+ "x": 5667.476249937603,
+ "y": 3644.9055828357396
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "note-psLpP",
+ "node": {
+ "description": "**Second Prompt + OpenAI Sequence**\n - NOTE: \"Transforms the generated post into a prompt for image generation\"\n - Settings:\n * Temperature: 0.7\n * Input: Receives generated post\n * Output: Creates detailed description for image generation\n\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 325,
+ "id": "note-psLpP",
+ "measured": {
+ "height": 325,
+ "width": 329
+ },
+ "position": {
+ "x": 6786.375917286389,
+ "y": 3393.8522072000146
+ },
+ "positionAbsolute": {
+ "x": 6786.375917286389,
+ "y": 3393.8522072000146
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "note-4jPBZ",
+ "node": {
+ "description": "**Final Prompt**\n - NOTE: \"Combines Instagram post with image prompt in a final format\"\n - Structure:\n * First part: Complete Instagram post\n * Second part: Image generation prompt\n * Separator: Uses \"**Prompt:**\" to divide sections\n\n7. **Chat Output (Final Output)**\n - NOTE: \"Presents the combined final result that can be copied and used directly\"\n\nGENERAL USAGE TIPS:\n- Keep initial inputs clear and specific\n- Don't modify pre-defined Instagram guidelines\n- If style adjustments are needed, only modify the OpenAI models' temperature\n- Verify all connections are correct before running\n- Final result will always have two parts: post + image prompt\n\nFLOW CONSIDERATIONS:\n- All tools connect only to the Tool Calling Agent\n- The flow is unidirectional (no loops)\n- Each prompt template maintains specific formatting\n- Temperatures are set for optimal creativity/consistency balance\n\nTROUBLESHOOTING NOTES:\n- If output is too creative: Lower temperature",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 325,
+ "id": "note-4jPBZ",
+ "measured": {
+ "height": 325,
+ "width": 329
+ },
+ "position": {
+ "x": 7606.419013912975,
+ "y": 3612.8149429707646
+ },
+ "positionAbsolute": {
+ "x": 7606.419013912975,
+ "y": 3612.8149429707646
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "note-WwfNL",
+ "node": {
+ "description": "# 🔑 Tavily AI Search Needs API Key\n\nYou can get 1000 searches/month free [here](https://tavily.com/) ",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "lime"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 325,
+ "id": "note-WwfNL",
+ "measured": {
+ "height": 325,
+ "width": 329
+ },
+ "position": {
+ "x": 5174.678177457385,
+ "y": 3339.6628854203204
+ },
+ "positionAbsolute": {
+ "x": 5174.678177457385,
+ "y": 3339.6628854203204
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "TavilySearchComponent-XbcIl",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "display_name": "Tavily AI Search",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "api_key",
+ "query",
+ "search_depth",
+ "topic",
+ "time_range",
+ "max_results",
+ "include_images",
+ "include_answer"
+ ],
+ "frozen": false,
+ "icon": "TavilyIcon",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Toolset",
+ "hidden": null,
+ "method": "to_toolkit",
+ "name": "component_as_tool",
+ "required_inputs": null,
+ "selected": "Tool",
+ "tool_mode": true,
+ "types": [
+ "Tool"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Tavily API Key",
+ "dynamic": false,
+ "info": "Your Tavily API Key.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "TAVILY_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n ]\n\n def fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n"
+ },
+ "include_answer": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Include Answer",
+ "dynamic": false,
+ "info": "Include a short answer to original query.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "include_answer",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "include_images": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Include Images",
+ "dynamic": false,
+ "info": "Include a list of query-related images in the response.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "include_images",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "max_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Results",
+ "dynamic": false,
+ "info": "The maximum number of search results to return.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "query": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "The search query you want to execute with Tavily.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_depth": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Depth",
+ "dynamic": false,
+ "info": "The depth of the search.",
+ "name": "search_depth",
+ "options": [
+ "basic",
+ "advanced"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "advanced"
+ },
+ "time_range": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Time Range",
+ "dynamic": false,
+ "info": "The time range back from the current date to include in the search results.",
+ "name": "time_range",
+ "options": [
+ "day",
+ "week",
+ "month",
+ "year"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str"
+ },
+ "tools_metadata": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Edit tools",
+ "dynamic": false,
+ "info": "",
+ "is_list": true,
+ "list_add_label": "Add More",
+ "name": "tools_metadata",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "table_icon": "Hammer",
+ "table_options": {
+ "block_add": true,
+ "block_delete": true,
+ "block_edit": true,
+ "block_filter": true,
+ "block_hide": true,
+ "block_select": true,
+ "block_sort": true,
+ "description": "Modify tool names and descriptions to help agents understand when to use each tool.",
+ "field_parsers": {
+ "commands": "commands",
+ "name": [
+ "snake_case",
+ "no_blank"
+ ]
+ },
+ "hide_options": true
+ },
+ "table_schema": {
+ "columns": [
+ {
+ "description": "Specify the name of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Name",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "name",
+ "sortable": false,
+ "type": "text"
+ },
+ {
+ "description": "Describe the purpose of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Description",
+ "edit_mode": "popover",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "description",
+ "sortable": false,
+ "type": "text"
+ },
+ {
+ "description": "The default identifiers for the tools and cannot be changed.",
+ "disable_edit": true,
+ "display_name": "Tool Identifiers",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": true,
+ "name": "tags",
+ "sortable": false,
+ "type": "text"
+ }
+ ]
+ },
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "trigger_icon": "Hammer",
+ "trigger_text": "",
+ "type": "table",
+ "value": [
+ {
+ "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "name": "TavilySearchComponent-fetch_content",
+ "tags": [
+ "TavilySearchComponent-fetch_content"
+ ]
+ },
+ {
+ "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "name": "TavilySearchComponent-fetch_content_text",
+ "tags": [
+ "TavilySearchComponent-fetch_content_text"
+ ]
+ }
+ ]
+ },
+ "topic": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Topic",
+ "dynamic": false,
+ "info": "The category of the search.",
+ "name": "topic",
+ "options": [
+ "general",
+ "news"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "general"
+ }
+ },
+ "tool_mode": true
+ },
+ "showNode": true,
+ "type": "TavilySearchComponent"
+ },
+ "dragging": false,
+ "id": "TavilySearchComponent-XbcIl",
+ "measured": {
+ "height": 489,
+ "width": 360
+ },
+ "position": {
+ "x": 5176.638828210268,
+ "y": 3500.9392260830805
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-BUxp5",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-BUxp5",
+ "measured": {
+ "height": 734,
+ "width": 360
+ },
+ "position": {
+ "x": 6411.984293767987,
+ "y": 2939.9259924956145
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-EuPBl",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-EuPBl",
+ "measured": {
+ "height": 734,
+ "width": 360
+ },
+ "position": {
+ "x": 7206.924894456788,
+ "y": 2971.632992278429
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -6905.9718468947285,
+ "y": -3158.1897396901054,
+ "zoom": 1.0286473888530476
+ }
+ },
+ "description": " Create engaging Instagram posts with AI-generated content and image prompts, streamlining social media content creation.",
+ "endpoint_name": null,
+ "gradient": "0",
+ "icon": "InstagramIcon",
+ "id": "4bb309e6-42b4-4565-b960-8bd0f7e431f2",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Instagram Copywriter",
+ "tags": [
+ "content-generation",
+ "chatbots",
+ "agents"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json
new file mode 100644
index 0000000..14aabb6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json
@@ -0,0 +1,1952 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ArXivComponent",
+ "id": "ArXivComponent-LChQN",
+ "name": "papers",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "LoopComponent-3vpc1",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__ArXivComponent-LChQN{œdataTypeœ:œArXivComponentœ,œidœ:œArXivComponent-LChQNœ,œnameœ:œpapersœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œfieldNameœ:œdataœ,œidœ:œLoopComponent-3vpc1œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "ArXivComponent-LChQN",
+ "sourceHandle": "{œdataTypeœ: œArXivComponentœ, œidœ: œArXivComponent-LChQNœ, œnameœ: œpapersœ, œoutput_typesœ: [œDataœ]}",
+ "target": "LoopComponent-3vpc1",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œLoopComponent-3vpc1œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "LoopComponent",
+ "id": "LoopComponent-3vpc1",
+ "name": "item",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-Pf12J",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-Pf12J{œfieldNameœ:œdataœ,œidœ:œParseData-Pf12Jœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "LoopComponent-3vpc1",
+ "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-Pf12J",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-Pf12Jœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-Pf12J",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "AnthropicModel-beO6B",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__ParseData-Pf12J{œdataTypeœ:œParseDataœ,œidœ:œParseData-Pf12Jœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-beO6B{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-beO6Bœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ParseData-Pf12J",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-Pf12Jœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "AnthropicModel-beO6B",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-beO6Bœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AnthropicModel",
+ "id": "AnthropicModel-beO6B",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "message",
+ "id": "MessagetoData-QRSBb",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__AnthropicModel-beO6B{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-beO6Bœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-QRSBb{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-QRSBbœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "AnthropicModel-beO6B",
+ "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-beO6Bœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "MessagetoData-QRSBb",
+ "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œMessagetoData-QRSBbœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "MessagetoData",
+ "id": "MessagetoData-QRSBb",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "dataType": "LoopComponent",
+ "id": "LoopComponent-3vpc1",
+ "name": "item",
+ "output_types": [
+ "Data"
+ ]
+ }
+ },
+ "id": "xy-edge__MessagetoData-QRSBb{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-QRSBbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}",
+ "source": "MessagetoData-QRSBb",
+ "sourceHandle": "{œdataTypeœ: œMessagetoDataœ, œidœ: œMessagetoData-QRSBbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "LoopComponent-3vpc1",
+ "targetHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "LoopComponent",
+ "id": "LoopComponent-3vpc1",
+ "name": "done",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-igEkj",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-igEkj{œfieldNameœ:œdataœ,œidœ:œParseData-igEkjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "LoopComponent-3vpc1",
+ "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œdoneœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-igEkj",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-igEkjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-igEkj",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-UZgon",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__ParseData-igEkj{œdataTypeœ:œParseDataœ,œidœ:œParseData-igEkjœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-UZgon{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-UZgonœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ParseData-igEkj",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-igEkjœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-UZgon",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-UZgonœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-m10vc",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "search_query",
+ "id": "ArXivComponent-LChQN",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__ChatInput-m10vc{œdataTypeœ:œChatInputœ,œidœ:œChatInput-m10vcœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ArXivComponent-LChQN{œfieldNameœ:œsearch_queryœ,œidœ:œArXivComponent-LChQNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ChatInput-m10vc",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-m10vcœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ArXivComponent-LChQN",
+ "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œArXivComponent-LChQNœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "ArXivComponent-LChQN",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Search and retrieve papers from arXiv.org",
+ "display_name": "arXiv",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "search_query",
+ "search_type",
+ "max_results"
+ ],
+ "frozen": false,
+ "icon": "arXiv",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Papers",
+ "method": "search_papers",
+ "name": "papers",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Papers\", name=\"papers\", method=\"search_papers\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query\n search_query = f\"{self.search_type}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in {\"http\", \"https\"}:\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n"
+ },
+ "max_results": {
+ "_input_type": "IntInput",
+ "advanced": false,
+ "display_name": "Max Results",
+ "dynamic": false,
+ "info": "Maximum number of results to return",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 3
+ },
+ "search_query": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "The search query for arXiv papers (e.g., 'quantum computing')",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "search_query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_type": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Field",
+ "dynamic": false,
+ "info": "The field to search in",
+ "name": "search_type",
+ "options": [
+ "all",
+ "title",
+ "abstract",
+ "author",
+ "cat"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "all"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ArXivComponent"
+ },
+ "dragging": false,
+ "id": "ArXivComponent-LChQN",
+ "measured": {
+ "height": 395,
+ "width": 320
+ },
+ "position": {
+ "x": 81.59312530546094,
+ "y": 3.9397854556273906
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "LoopComponent-3vpc1",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "category": "logic",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.",
+ "display_name": "Loop",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data"
+ ],
+ "frozen": false,
+ "icon": "infinity",
+ "key": "LoopComponent",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": true,
+ "cache": true,
+ "display_name": "Item",
+ "method": "item_output",
+ "name": "item",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Done",
+ "method": "done_output",
+ "name": "done",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 2.220446049250313e-16,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.io import DataInput, Output\nfrom langflow.schema import Data\n\n\nclass LoopComponent(Component):\n display_name = \"Loop\"\n description = (\n \"Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.\"\n )\n icon = \"infinity\"\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The initial list of Data objects to iterate over.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Item\", name=\"item\", method=\"item_output\", allows_loop=True),\n Output(display_name=\"Done\", name=\"done\", method=\"done_output\"),\n ]\n\n def initialize_data(self) -> None:\n \"\"\"Initialize the data list, context index, and aggregated list.\"\"\"\n if self.ctx.get(f\"{self._id}_initialized\", False):\n return\n\n # Ensure data is a list of Data objects\n data_list = self._validate_data(self.data)\n\n # Store the initial data and context variables\n self.update_ctx(\n {\n f\"{self._id}_data\": data_list,\n f\"{self._id}_index\": 0,\n f\"{self._id}_aggregated\": [],\n f\"{self._id}_initialized\": True,\n }\n )\n\n def _validate_data(self, data):\n \"\"\"Validate and return a list of Data objects.\"\"\"\n if isinstance(data, Data):\n return [data]\n if isinstance(data, list) and all(isinstance(item, Data) for item in data):\n return data\n msg = \"The 'data' input must be a list of Data objects or a single Data object.\"\n raise TypeError(msg)\n\n def evaluate_stop_loop(self) -> bool:\n \"\"\"Evaluate whether to stop item or done output.\"\"\"\n current_index = self.ctx.get(f\"{self._id}_index\", 0)\n data_length = len(self.ctx.get(f\"{self._id}_data\", []))\n return current_index > data_length\n\n def item_output(self) -> Data:\n \"\"\"Output the next item in the list or stop if done.\"\"\"\n self.initialize_data()\n current_item = Data(text=\"\")\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n return Data(text=\"\")\n\n # Get data list and current index\n data_list, current_index = self.loop_variables()\n if current_index < len(data_list):\n # Output current item and increment index\n try:\n current_item = data_list[current_index]\n except IndexError:\n current_item = Data(text=\"\")\n self.aggregated_output()\n self.update_ctx({f\"{self._id}_index\": current_index + 1})\n return current_item\n\n def done_output(self) -> Data:\n \"\"\"Trigger the done output when iteration is complete.\"\"\"\n self.initialize_data()\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n self.start(\"done\")\n\n return self.ctx.get(f\"{self._id}_aggregated\", [])\n self.stop(\"done\")\n return Data(text=\"\")\n\n def loop_variables(self):\n \"\"\"Retrieve loop variables from context.\"\"\"\n return (\n self.ctx.get(f\"{self._id}_data\", []),\n self.ctx.get(f\"{self._id}_index\", 0),\n )\n\n def aggregated_output(self) -> Data:\n \"\"\"Return the aggregated list once all items are processed.\"\"\"\n self.initialize_data()\n\n # Get data list and aggregated list\n data_list = self.ctx.get(f\"{self._id}_data\", [])\n aggregated = self.ctx.get(f\"{self._id}_aggregated\", [])\n\n # Check if loop input is provided and append to aggregated list\n if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):\n aggregated.append(self.item)\n self.update_ctx({f\"{self._id}_aggregated\": aggregated})\n return aggregated\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The initial list of Data objects to iterate over.",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "LoopComponent"
+ },
+ "dragging": false,
+ "id": "LoopComponent-3vpc1",
+ "measured": {
+ "height": 280,
+ "width": 320
+ },
+ "position": {
+ "x": 517.2087344858119,
+ "y": 55.497845490859035
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ParseData-Pf12J",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "category": "processing",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data objects into Messages using any {field_name} from input data.",
+ "display_name": "Data to Message",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "key": "ParseData",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.01857804455091699,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{data}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "id": "ParseData-Pf12J",
+ "measured": {
+ "height": 342,
+ "width": 320
+ },
+ "position": {
+ "x": 996.5733307455723,
+ "y": 35.284413822605146
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "AnthropicModel-beO6B",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.",
+ "display_name": "Anthropic",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_name",
+ "api_key",
+ "temperature",
+ "base_url",
+ "tool_model_enabled",
+ "prefill"
+ ],
+ "frozen": false,
+ "icon": "Anthropic",
+ "key": "AnthropicModel",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0005851173668140926,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Anthropic API Key",
+ "dynamic": false,
+ "info": "Your Anthropic API key.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ANTHROPIC_API_KEY"
+ },
+ "base_url": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Anthropic API URL",
+ "dynamic": false,
+ "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "base_url",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "https://api.anthropic.com"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\"} and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 4096
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "claude-3-5-sonnet-latest",
+ "claude-3-5-haiku-latest",
+ "claude-3-opus-latest",
+ "claude-3-5-sonnet-20240620",
+ "claude-3-5-sonnet-20241022",
+ "claude-3-5-haiku-20241022",
+ "claude-3-sonnet-20240229",
+ "claude-3-haiku-20240307"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "refresh_button": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "claude-3-5-sonnet-20241022"
+ },
+ "prefill": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Prefill",
+ "dynamic": false,
+ "info": "Prefill text to guide the model's response.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "prefill",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Translate to Portuguese and output in structured format\nReturn only the JSON and no additional text."
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "tool_model_enabled": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Enable Tool Models",
+ "dynamic": false,
+ "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "tool_model_enabled",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AnthropicModel"
+ },
+ "dragging": false,
+ "id": "AnthropicModel-beO6B",
+ "measured": {
+ "height": 801,
+ "width": 320
+ },
+ "position": {
+ "x": 1493.0259340837038,
+ "y": -225.9942853677759
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "MessagetoData-QRSBb",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": true,
+ "category": "processing",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert a Message object to a Data object",
+ "display_name": "Message to Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "message"
+ ],
+ "frozen": false,
+ "icon": "message-square-share",
+ "key": "MessagetoData",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "convert_message_to_data",
+ "name": "data",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.008222426499470714,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass MessageToDataComponent(Component):\n display_name = \"Message to Data\"\n description = \"Convert a Message object to a Data object\"\n icon = \"message-square-share\"\n beta = True\n name = \"MessagetoData\"\n\n inputs = [\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The Message object to convert to a Data object\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"convert_message_to_data\"),\n ]\n\n def convert_message_to_data(self) -> Data:\n if isinstance(self.message, Message):\n # Convert Message to Data\n return Data(data=self.message.data)\n\n msg = \"Error converting Message to Data: Input must be a Message object\"\n logger.opt(exception=True).debug(msg)\n self.status = msg\n return Data(data={\"error\": msg})\n"
+ },
+ "message": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Message",
+ "dynamic": false,
+ "info": "The Message object to convert to a Data object",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "MessagetoData"
+ },
+ "dragging": false,
+ "id": "MessagetoData-QRSBb",
+ "measured": {
+ "height": 230,
+ "width": 320
+ },
+ "position": {
+ "x": 1863.8805295746977,
+ "y": 440.56780240629814
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ParseData-igEkj",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "category": "processing",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data objects into Messages using any {field_name} from input data.",
+ "display_name": "Data to Message",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "key": "ParseData",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.008664293911514902,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "id": "ParseData-igEkj",
+ "measured": {
+ "height": 342,
+ "width": 320
+ },
+ "position": {
+ "x": 982.6945056277784,
+ "y": 823.4439549777719
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-UZgon",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "id": "ChatOutput-UZgon",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 1498.3739454769902,
+ "y": 1061.1606281984073
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatInput-m10vc",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "id": "ChatInput-m10vc",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": -235.49853728839307,
+ "y": 107.75353484470551
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-g4YbJ",
+ "node": {
+ "description": "### 💡 Add your Anthropic API key here 👇",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-g4YbJ",
+ "measured": {
+ "height": 324,
+ "width": 358
+ },
+ "position": {
+ "x": 1479.4278434913201,
+ "y": -274.26903478612456
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 359
+ },
+ {
+ "data": {
+ "id": "note-e6tLL",
+ "node": {
+ "description": "# **Langflow Loop Component Template - ArXiv search result Translator** \nThis template translates research paper summaries on ArXiv into Portuguese and summarizes them. \n Using **Langflow’s looping mechanism**, the template iterates through multiple research papers, translates them with the **Anthropic** model component, and outputs an aggregated version of all translated papers. \n\n## Quickstart \n 1. Add your Anthropic API key to the **Anthropic** component. \n2. In the **Playground**, enter a query related to a research topic (for example, “Quantum Computing Advancements”). \n\n The flow fetches a list of research papers from ArXiv matching the query. Each paper in the retrieved list is processed one-by-one using the Langflow **Loop component**. \n\n The abstract of each paper is translated into Portuguese by the **Anthropic** model component. \n\n Once all papers are translated, the system aggregates them into a **single structured output**.",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 647,
+ "id": "note-e6tLL",
+ "measured": {
+ "height": 647,
+ "width": 577
+ },
+ "position": {
+ "x": -890.9006297459302,
+ "y": -233.44894493951168
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 576
+ }
+ ],
+ "viewport": {
+ "x": 440.1573282533791,
+ "y": 190.151319158377,
+ "zoom": 0.21962836650867892
+ }
+ },
+ "description": "This template iterates over search results using LoopComponent and translates each result into Portuguese automatically. 🚀",
+ "endpoint_name": "search_loop",
+ "gradient": "3",
+ "icon": "Infinity",
+ "id": "a004bc07-f6ca-4a02-b8f8-7fb8ebaed7b0",
+ "is_component": false,
+ "last_tested_version": "1.1.5",
+ "name": "Research Translation Loop",
+ "tags": [
+ "chatbots",
+ "content-generation"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json
new file mode 100644
index 0000000..cd3ad61
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Market Research.json
@@ -0,0 +1,2721 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "StructuredOutputComponent",
+ "id": "StructuredOutputComponent-Kqbq4",
+ "name": "structured_output",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-7XOFR",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-StructuredOutputComponent-Kqbq4{œdataTypeœ:œStructuredOutputComponentœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œnameœ:œstructured_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-7XOFR{œfieldNameœ:œdataœ,œidœ:œParseData-7XOFRœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "StructuredOutputComponent-Kqbq4",
+ "sourceHandle": "{œdataTypeœ: œStructuredOutputComponentœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œnameœ: œstructured_outputœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-7XOFR",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-7XOFRœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-tMLRq",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "Agent-dcKuR",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-tMLRq{œdataTypeœ:œChatInputœ,œidœ:œChatInput-tMLRqœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-dcKuR{œfieldNameœ:œinput_valueœ,œidœ:œAgent-dcKuRœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-tMLRq",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-tMLRqœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Agent-dcKuR",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-dcKuRœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Agent",
+ "id": "Agent-dcKuR",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "StructuredOutputComponent-Kqbq4",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Agent-dcKuR{œdataTypeœ:œAgentœ,œidœ:œAgent-dcKuRœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputComponent-Kqbq4{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Agent-dcKuR",
+ "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-dcKuRœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "StructuredOutputComponent-Kqbq4",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "TavilySearchComponent",
+ "id": "TavilySearchComponent-cGK9T",
+ "name": "component_as_tool",
+ "output_types": [
+ "Tool"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "Agent-dcKuR",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-TavilySearchComponent-cGK9T{œdataTypeœ:œTavilySearchComponentœ,œidœ:œTavilySearchComponent-cGK9Tœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-dcKuR{œfieldNameœ:œtoolsœ,œidœ:œAgent-dcKuRœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "source": "TavilySearchComponent-cGK9T",
+ "sourceHandle": "{œdataTypeœ: œTavilySearchComponentœ, œidœ: œTavilySearchComponent-cGK9Tœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}",
+ "target": "Agent-dcKuR",
+ "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-dcKuRœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-prL67",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "StructuredOutputComponent-Kqbq4",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-prL67{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-prL67œ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputComponent-Kqbq4{œfieldNameœ:œllmœ,œidœ:œStructuredOutputComponent-Kqbq4œ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "source": "OpenAIModel-prL67",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-prL67œ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}",
+ "target": "StructuredOutputComponent-Kqbq4",
+ "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputComponent-Kqbq4œ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-7XOFR",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-JrLxU",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "xy-edge__ParseData-7XOFR{œdataTypeœ:œParseDataœ,œidœ:œParseData-7XOFRœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-JrLxU{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JrLxUœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}",
+ "source": "ParseData-7XOFR",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-7XOFRœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-JrLxU",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-JrLxUœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "id": "ChatInput-tMLRq",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Amazon"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ }
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-tMLRq",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 472.38251755471583,
+ "y": 889.8398446936101
+ },
+ "positionAbsolute": {
+ "x": 472.38251755471583,
+ "y": 889.8398446936101
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-JrLxU",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatOutput-JrLxU",
+ "measured": {
+ "height": 234,
+ "width": 360
+ },
+ "position": {
+ "x": 2518.282039019285,
+ "y": 855.3686932779933
+ },
+ "positionAbsolute": {
+ "x": 2518.282039019285,
+ "y": 855.3686932779933
+ },
+ "selected": true,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-IHTZn",
+ "node": {
+ "description": "The StructuredOutputComponent, when utilized with our company information schema, performs the following functions:\n\n1. Accepts an input query regarding a company.\n2. Employs a Language Model (LLM) to analyze the query.\n3. Instructs the LLM to generate a structured response adhering to the predefined schema:\n - Domain\n - LinkedIn URL\n - Cheapest Plan\n - Has Free Trial\n - Has Enterprise Plan\n - Has API\n - Market\n - Pricing Tiers\n - Key Features\n - Target Industries\n\n4. Validates the LLM output against this schema.\n5. Returns a Data object containing the company information structured according to the schema.\n\nIn essence, this component transforms a free-text query about a company into a structured, consistent dataset, facilitating subsequent analysis and application of the information.",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-IHTZn",
+ "measured": {
+ "height": 324,
+ "width": 328
+ },
+ "position": {
+ "x": 2089.5869930853464,
+ "y": 311.41660832449514
+ },
+ "positionAbsolute": {
+ "x": 2089.5869930853464,
+ "y": 311.41660832449514
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-oro5s",
+ "node": {
+ "description": "PURPOSE:\nConverts unstructured company research into standardized JSON format\n\nKEY FUNCTIONS:\n- Extracts specific business data points\n- Validates and formats information\n- Ensures data consistency\n\nINPUT:\n- Raw company research data\n\nOUTPUT:\nStructured JSON with:\n- Domain information\n- Social links\n- Pricing details\n- Feature availability\n- Market classification\n- Product features\n- Industry focus\n\nRULES:\n1. Uses strict boolean values\n2. Standardizes pricing formats\n3. Validates market categories\n4. Handles missing data consistently",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "blue"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-oro5s",
+ "measured": {
+ "height": 324,
+ "width": 328
+ },
+ "position": {
+ "x": 1237.6627823432912,
+ "y": 111.53860932079613
+ },
+ "positionAbsolute": {
+ "x": 1237.6627823432912,
+ "y": 169.53860932079613
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-b5iUI",
+ "node": {
+ "description": "# Market Research\nThis flow helps you gather comprehensive information about companies for sales and business intelligence purposes.\n\n## Instructions\n\n1. Enter Company Name\n - In the Chat Input node, type the name of the company you want to research\n - Example inputs: \"Salesforce.com\", \"Shopify\", \"Zoom Video Communications\"\n\n2. Initiate Research\n - The Agent will use the Tavily AI Search tool to gather information\n - It will focus on key areas like pricing, features, and market positioning\n\n3. Review Structured Output\n - The flow will generate a structured JSON output with standardized fields\n - This includes domain, LinkedIn URL, pricing details, and key features\n\n4. Examine Formatted Results\n - The Parse Data component will convert the JSON into a readable format\n - You'll see a comprehensive company profile with organized sections\n\n5. Analyze and Use Data\n - Use the generated information for sales prospecting, competitive analysis, or market research\n - The structured format allows for easy comparison between different companies\n\nRemember: Always verify critical information from official sources before making business decisions! 🔍💼",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "emerald"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-b5iUI",
+ "measured": {
+ "height": 324,
+ "width": 328
+ },
+ "position": {
+ "x": 244.92297036777086,
+ "y": 340.99805740871204
+ },
+ "positionAbsolute": {
+ "x": 244.92297036777086,
+ "y": 340.99805740871204
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 324,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.",
+ "display_name": "Structured Output",
+ "id": "StructuredOutputComponent-Kqbq4",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Transforms LLM responses into **structured data formats**. Ideal for extracting specific information or creating consistent outputs.",
+ "display_name": "Structured Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "llm",
+ "input_value",
+ "schema_name",
+ "output_schema",
+ "multiple"
+ ],
+ "frozen": false,
+ "icon": "braces",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "cache": true,
+ "display_name": "Structured Output",
+ "method": "build_structured_output",
+ "name": "structured_output",
+ "selected": "Data",
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from typing import TYPE_CHECKING, cast\n\nfrom pydantic import BaseModel, Field, create_model\n\nfrom langflow.base.models.chat_result import get_chat_result\nfrom langflow.custom import Component\nfrom langflow.helpers.base_model import build_model_from_schema\nfrom langflow.io import BoolInput, HandleInput, MessageTextInput, Output, StrInput, TableInput\nfrom langflow.schema.data import Data\n\nif TYPE_CHECKING:\n from langflow.field_typing.constants import LanguageModel\n\n\nclass StructuredOutputComponent(Component):\n display_name = \"Structured Output\"\n description = (\n \"Transforms LLM responses into **structured data formats**. Ideal for extracting specific information \"\n \"or creating consistent outputs.\"\n )\n icon = \"braces\"\n\n inputs = [\n HandleInput(\n name=\"llm\",\n display_name=\"Language Model\",\n info=\"The language model to use to generate the structured output.\",\n input_types=[\"LanguageModel\"],\n ),\n MessageTextInput(name=\"input_value\", display_name=\"Input message\"),\n StrInput(\n name=\"schema_name\",\n display_name=\"Schema Name\",\n info=\"Provide a name for the output data schema.\",\n ),\n TableInput(\n name=\"output_schema\",\n display_name=\"Output Schema\",\n info=\"Define the structure and data types for the model's output.\",\n table_schema=[\n {\n \"name\": \"name\",\n \"display_name\": \"Name\",\n \"type\": \"str\",\n \"description\": \"Specify the name of the output field.\",\n \"default\": \"field\",\n },\n {\n \"name\": \"description\",\n \"display_name\": \"Description\",\n \"type\": \"str\",\n \"description\": \"Describe the purpose of the output field.\",\n \"default\": \"description of field\",\n },\n {\n \"name\": \"type\",\n \"display_name\": \"Type\",\n \"type\": \"str\",\n \"description\": (\n \"Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).\"\n ),\n \"default\": \"text\",\n },\n {\n \"name\": \"multiple\",\n \"display_name\": \"Multiple\",\n \"type\": \"boolean\",\n \"description\": \"Set to True if this output field should be a list of the specified type.\",\n \"default\": \"False\",\n },\n ],\n value=[{\"name\": \"field\", \"description\": \"description of field\", \"type\": \"text\", \"multiple\": \"False\"}],\n ),\n BoolInput(\n name=\"multiple\",\n display_name=\"Generate Multiple\",\n info=\"Set to True if the model should generate a list of outputs instead of a single output.\",\n ),\n ]\n\n outputs = [\n Output(name=\"structured_output\", display_name=\"Structured Output\", method=\"build_structured_output\"),\n ]\n\n def build_structured_output(self) -> Data:\n if not hasattr(self.llm, \"with_structured_output\"):\n msg = \"Language model does not support structured output.\"\n raise TypeError(msg)\n if not self.output_schema:\n msg = \"Output schema cannot be empty\"\n raise ValueError(msg)\n\n output_model_ = build_model_from_schema(self.output_schema)\n if self.multiple:\n output_model = create_model(\n self.schema_name,\n objects=(list[output_model_], Field(description=f\"A list of {self.schema_name}.\")), # type: ignore[valid-type]\n )\n else:\n output_model = output_model_\n try:\n llm_with_structured_output = cast(\"LanguageModel\", self.llm).with_structured_output(schema=output_model) # type: ignore[valid-type, attr-defined]\n\n except NotImplementedError as exc:\n msg = f\"{self.llm.__class__.__name__} does not support structured output.\"\n raise TypeError(msg) from exc\n config_dict = {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n output = get_chat_result(runnable=llm_with_structured_output, input_value=self.input_value, config=config_dict)\n if isinstance(output, BaseModel):\n output_dict = output.model_dump()\n else:\n msg = f\"Output should be a Pydantic BaseModel, got {type(output)} ({output})\"\n raise TypeError(msg)\n return Data(data=output_dict)\n"
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input message",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "llm": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Language Model",
+ "dynamic": false,
+ "info": "The language model to use to generate the structured output.",
+ "input_types": [
+ "LanguageModel"
+ ],
+ "list": false,
+ "name": "llm",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "multiple": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Generate Multiple",
+ "dynamic": false,
+ "info": "Set to True if the model should generate a list of outputs instead of a single output.",
+ "list": false,
+ "name": "multiple",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "output_schema": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Output Schema",
+ "dynamic": false,
+ "info": "Define the structure and data types for the model's output.",
+ "is_list": true,
+ "load_from_db": false,
+ "name": "output_schema",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "table_schema": {
+ "columns": [
+ {
+ "description": "Specify the name of the output field.",
+ "display_name": "Name",
+ "filterable": true,
+ "formatter": "text",
+ "name": "name",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "description": "Describe the purpose of the output field.",
+ "display_name": "Description",
+ "filterable": true,
+ "formatter": "text",
+ "name": "description",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "text",
+ "description": "Indicate the data type of the output field (e.g., str, int, float, bool, list, dict).",
+ "display_name": "Type",
+ "filterable": true,
+ "formatter": "text",
+ "name": "type",
+ "sortable": true,
+ "type": "text"
+ },
+ {
+ "default": "False",
+ "description": "Set to True if this output field should be a list of the specified type.",
+ "display_name": "Multiple",
+ "filterable": true,
+ "formatter": "text",
+ "name": "multiple",
+ "sortable": true,
+ "type": "boolean"
+ }
+ ]
+ },
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "table",
+ "value": [
+ {
+ "description": "Primary company domain name",
+ "multiple": "False",
+ "name": "domain",
+ "type": "text"
+ },
+ {
+ "description": "Company's LinkedIn URL",
+ "multiple": "False",
+ "name": "linkedinUrl",
+ "type": "text"
+ },
+ {
+ "description": "Lowest priced plan in USD (number only)",
+ "multiple": "False",
+ "name": "cheapestPlan",
+ "type": "text"
+ },
+ {
+ "description": "Boolean indicating if they offer a free trial",
+ "multiple": "False",
+ "name": "hasFreeTrial",
+ "type": "bool"
+ },
+ {
+ "description": "Boolean indicating if they have enterprise options",
+ "multiple": "False",
+ "name": "hasEnterprisePlan",
+ "type": "bool"
+ },
+ {
+ "description": "Boolean indicating if they offer API access",
+ "multiple": "False",
+ "name": "hasAPI",
+ "type": "bool"
+ },
+ {
+ "description": "Either 'B2B' or 'B2C' or 'Both",
+ "multiple": "False",
+ "name": "market",
+ "type": "text"
+ },
+ {
+ "description": "List of available pricing tiers",
+ "multiple": "True",
+ "name": "pricingTiers",
+ "type": "text"
+ },
+ {
+ "description": "List of main features",
+ "multiple": "True",
+ "name": "KeyFeatures",
+ "type": "text"
+ },
+ {
+ "description": "List of target industries",
+ "multiple": "True",
+ "name": "targetIndustries",
+ "type": "text"
+ }
+ ]
+ },
+ "schema_name": {
+ "_input_type": "StrInput",
+ "advanced": false,
+ "display_name": "Schema Name",
+ "dynamic": false,
+ "info": "Provide a name for the output data schema.",
+ "list": false,
+ "load_from_db": false,
+ "name": "schema_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "output_schema"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "StructuredOutputComponent"
+ },
+ "dragging": false,
+ "height": 541,
+ "id": "StructuredOutputComponent-Kqbq4",
+ "measured": {
+ "height": 541,
+ "width": 360
+ },
+ "position": {
+ "x": 1716.7237308033855,
+ "y": 459.2476214962564
+ },
+ "positionAbsolute": {
+ "x": 1770.7096106546323,
+ "y": 518.8182475390113
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "ParseData-7XOFR",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "helpers",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data into plain text following a specified template.",
+ "display_name": "Parse Data",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "key": "ParseData",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "# Company Profile\n\n## Basic Information\n- **Domain:** {domain}\n- **LinkedIn URL:** {linkedinUrl}\n\n## Pricing and Plans\n- **Cheapest Plan:** {cheapestPlan}\n- **Has Free Trial:** {hasFreeTrial}\n- **Has Enterprise Plan:** {hasEnterprisePlan}\n\n## Technical Capabilities\n- **Has API:** {hasAPI}\n\n## Market and Target Audience\n- **Market:** {market}\n- **Target Industries:** {targetIndustries}\n\n## Pricing Structure\n{pricingTiers}\n\n## Key Features\n{KeyFeatures}\n"
+ }
+ }
+ },
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "height": 302,
+ "id": "ParseData-7XOFR",
+ "measured": {
+ "height": 302,
+ "width": 360
+ },
+ "position": {
+ "x": 2139.05558520377,
+ "y": 780.6849187394922
+ },
+ "positionAbsolute": {
+ "x": 2139.05558520377,
+ "y": 780.6849187394922
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "id": "Agent-dcKuR",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "agent_llm",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "output_schema",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "output_parser",
+ "system_prompt",
+ "tools",
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "agent_description",
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template",
+ "add_current_date_tool"
+ ],
+ "frozen": false,
+ "icon": "bot",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Response",
+ "method": "message_response",
+ "name": "response",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "add_current_date_tool": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Current Date",
+ "dynamic": false,
+ "info": "If true, will add a tool to the agent that returns the current date.",
+ "list": false,
+ "name": "add_current_date_tool",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "agent_description": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Agent Description [Deprecated]",
+ "dynamic": false,
+ "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "agent_description",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "A helpful assistant with access to the following tools:"
+ },
+ "agent_llm": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "display_name": "Model Provider",
+ "dynamic": false,
+ "info": "The provider of the language model that the agent will use to generate responses.",
+ "input_types": [],
+ "name": "agent_llm",
+ "options": [
+ "Amazon Bedrock",
+ "Anthropic",
+ "Azure OpenAI",
+ "Google Generative AI",
+ "Groq",
+ "NVIDIA",
+ "OpenAI",
+ "SambaNova",
+ "Custom"
+ ],
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "OpenAI"
+ },
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Validate tools\n if not self.tools:\n msg = \"Tools are required to run the agent. Please add at least one tool.\"\n raise ValueError(msg)\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools,\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def to_toolkit(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n"
+ },
+ "handle_parsing_errors": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Handle Parse Errors",
+ "dynamic": false,
+ "info": "Should the Agent fix errors when reading user input for better processing?",
+ "list": false,
+ "name": "handle_parsing_errors",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The input provided by the user for the agent to process.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_iterations": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Iterations",
+ "dynamic": false,
+ "info": "The maximum number of attempts the agent can make to complete its task before it stops.",
+ "list": false,
+ "name": "max_iterations",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 15
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": true,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "system_prompt": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Agent Instructions",
+ "dynamic": false,
+ "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_prompt",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "You are an expert business research agent. Your task is to gather comprehensive information about companies. When researching a company, focus on the following key areas: 1. Basic company information (website, domain, social presence) 2. Product and pricing information 3. Technical capabilities and integrations 4. Market positioning and target audience 5. Key features and offerings For the company/domain provided, search thoroughly and provide detailed information about: - Their main website and domain - Their pricing structure - Product features and capabilities - Market presence and focus - Technical offerings like APIs - Social media presence, especially LinkedIn Search comprehensively and provide detailed, factual information that will help determine: - Pricing tiers and structure - Whether they offer free trials - If they have enterprise solutions - Their technical capabilities - Their primary market (B2B/B2C) INPUT: {input} Respond with detailed, factual information about these aspects, avoiding speculation. Include direct quotes or specific information you find."
+ },
+ "temperature": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "temperature",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 0.1
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ },
+ "tools": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Tools",
+ "dynamic": false,
+ "info": "These are the tools that the agent can use to help with tasks.",
+ "input_types": [
+ "Tool"
+ ],
+ "list": true,
+ "name": "tools",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "verbose": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Verbose",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "verbose",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Agent"
+ },
+ "dragging": false,
+ "height": 650,
+ "id": "Agent-dcKuR",
+ "measured": {
+ "height": 650,
+ "width": 360
+ },
+ "position": {
+ "x": 1287.5681517817056,
+ "y": 519.8701526087884
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-7JQqd",
+ "node": {
+ "description": "# 🔑 Tavily AI Search Needs API Key\n\nYou can get 1000 searches/month free [here](https://tavily.com/) ",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "lime"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-7JQqd",
+ "measured": {
+ "height": 324,
+ "width": 328
+ },
+ "position": {
+ "x": 878.7898510090017,
+ "y": 640.2524241641511
+ },
+ "positionAbsolute": {
+ "x": 921.6062384772317,
+ "y": 642.1140062279873
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "display_name": "Tavily AI Search",
+ "id": "TavilySearchComponent-cGK9T",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "**Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "display_name": "Tavily AI Search",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "api_key",
+ "query",
+ "search_depth",
+ "topic",
+ "time_range",
+ "max_results",
+ "include_images",
+ "include_answer"
+ ],
+ "frozen": false,
+ "icon": "TavilyIcon",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Toolset",
+ "hidden": null,
+ "method": "to_toolkit",
+ "name": "component_as_tool",
+ "required_inputs": null,
+ "selected": "Tool",
+ "tool_mode": true,
+ "types": [
+ "Tool"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Tavily API Key",
+ "dynamic": false,
+ "info": "Your Tavily API Key.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "TAVILY_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass TavilySearchComponent(Component):\n display_name = \"Tavily AI Search\"\n description = \"\"\"**Tavily AI** is a search engine optimized for LLMs and RAG, \\\n aimed at efficient, quick, and persistent search results.\"\"\"\n icon = \"TavilyIcon\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Tavily API Key\",\n required=True,\n info=\"Your Tavily API Key.\",\n ),\n MessageTextInput(\n name=\"query\",\n display_name=\"Search Query\",\n info=\"The search query you want to execute with Tavily.\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_depth\",\n display_name=\"Search Depth\",\n info=\"The depth of the search.\",\n options=[\"basic\", \"advanced\"],\n value=\"advanced\",\n advanced=True,\n ),\n DropdownInput(\n name=\"topic\",\n display_name=\"Search Topic\",\n info=\"The category of the search.\",\n options=[\"general\", \"news\"],\n value=\"general\",\n advanced=True,\n ),\n DropdownInput(\n name=\"time_range\",\n display_name=\"Time Range\",\n info=\"The time range back from the current date to include in the search results.\",\n options=[\"day\", \"week\", \"month\", \"year\"],\n value=None,\n advanced=True,\n combobox=True,\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"The maximum number of search results to return.\",\n value=5,\n advanced=True,\n ),\n BoolInput(\n name=\"include_images\",\n display_name=\"Include Images\",\n info=\"Include a list of query-related images in the response.\",\n value=True,\n advanced=True,\n ),\n BoolInput(\n name=\"include_answer\",\n display_name=\"Include Answer\",\n info=\"Include a short answer to original query.\",\n value=True,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n ]\n\n def fetch_content(self) -> list[Data]:\n try:\n url = \"https://api.tavily.com/search\"\n headers = {\n \"content-type\": \"application/json\",\n \"accept\": \"application/json\",\n }\n payload = {\n \"api_key\": self.api_key,\n \"query\": self.query,\n \"search_depth\": self.search_depth,\n \"topic\": self.topic,\n \"max_results\": self.max_results,\n \"include_images\": self.include_images,\n \"include_answer\": self.include_answer,\n \"time_range\": self.time_range,\n }\n\n with httpx.Client() as client:\n response = client.post(url, json=payload, headers=headers)\n\n response.raise_for_status()\n search_results = response.json()\n\n data_results = []\n\n if self.include_answer and search_results.get(\"answer\"):\n data_results.append(Data(text=search_results[\"answer\"]))\n\n for result in search_results.get(\"results\", []):\n content = result.get(\"content\", \"\")\n data_results.append(\n Data(\n text=content,\n data={\n \"title\": result.get(\"title\"),\n \"url\": result.get(\"url\"),\n \"content\": content,\n \"score\": result.get(\"score\"),\n },\n )\n )\n\n if self.include_images and search_results.get(\"images\"):\n data_results.append(Data(text=\"Images found\", data={\"images\": search_results[\"images\"]}))\n except httpx.HTTPStatusError as exc:\n error_message = f\"HTTP error occurred: {exc.response.status_code} - {exc.response.text}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except httpx.RequestError as exc:\n error_message = f\"Request error occurred: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n except ValueError as exc:\n error_message = f\"Invalid response format: {exc}\"\n logger.error(error_message)\n return [Data(text=error_message, data={\"error\": error_message})]\n else:\n self.status = data_results\n return data_results\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n"
+ },
+ "include_answer": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Include Answer",
+ "dynamic": false,
+ "info": "Include a short answer to original query.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "include_answer",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "include_images": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Include Images",
+ "dynamic": false,
+ "info": "Include a list of query-related images in the response.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "include_images",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "max_results": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Results",
+ "dynamic": false,
+ "info": "The maximum number of search results to return.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_results",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "query": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Search Query",
+ "dynamic": false,
+ "info": "The search query you want to execute with Tavily.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "query",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "search_depth": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Depth",
+ "dynamic": false,
+ "info": "The depth of the search.",
+ "name": "search_depth",
+ "options": [
+ "basic",
+ "advanced"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "advanced"
+ },
+ "time_range": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Time Range",
+ "dynamic": false,
+ "info": "The time range back from the current date to include in the search results.",
+ "name": "time_range",
+ "options": [
+ "day",
+ "week",
+ "month",
+ "year"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str"
+ },
+ "tools_metadata": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Edit tools",
+ "dynamic": false,
+ "info": "",
+ "is_list": true,
+ "list_add_label": "Add More",
+ "name": "tools_metadata",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "table_icon": "Hammer",
+ "table_options": {
+ "block_add": true,
+ "block_delete": true,
+ "block_edit": true,
+ "block_filter": true,
+ "block_hide": true,
+ "block_select": true,
+ "block_sort": true,
+ "description": "Modify tool names and descriptions to help agents understand when to use each tool.",
+ "field_parsers": {
+ "commands": "commands",
+ "name": [
+ "snake_case",
+ "no_blank"
+ ]
+ },
+ "hide_options": true
+ },
+ "table_schema": {
+ "columns": [
+ {
+ "description": "Specify the name of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Name",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "name",
+ "sortable": false,
+ "type": "text"
+ },
+ {
+ "description": "Describe the purpose of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Description",
+ "edit_mode": "popover",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "description",
+ "sortable": false,
+ "type": "text"
+ },
+ {
+ "description": "The default identifiers for the tools and cannot be changed.",
+ "disable_edit": true,
+ "display_name": "Tool Identifiers",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": true,
+ "name": "tags",
+ "sortable": false,
+ "type": "text"
+ }
+ ]
+ },
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "trigger_icon": "Hammer",
+ "trigger_text": "",
+ "type": "table",
+ "value": [
+ {
+ "description": "fetch_content(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "name": "TavilySearchComponent-fetch_content",
+ "tags": [
+ "TavilySearchComponent-fetch_content"
+ ]
+ },
+ {
+ "description": "fetch_content_text(api_key: Message) - **Tavily AI** is a search engine optimized for LLMs and RAG, aimed at efficient, quick, and persistent search results.",
+ "name": "TavilySearchComponent-fetch_content_text",
+ "tags": [
+ "TavilySearchComponent-fetch_content_text"
+ ]
+ }
+ ]
+ },
+ "topic": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Search Topic",
+ "dynamic": false,
+ "info": "The category of the search.",
+ "name": "topic",
+ "options": [
+ "general",
+ "news"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "general"
+ }
+ },
+ "tool_mode": true
+ },
+ "showNode": true,
+ "type": "TavilySearchComponent"
+ },
+ "dragging": false,
+ "id": "TavilySearchComponent-cGK9T",
+ "measured": {
+ "height": 489,
+ "width": 360
+ },
+ "position": {
+ "x": 875.7686789989679,
+ "y": 798.478848045035
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-prL67",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-prL67",
+ "measured": {
+ "height": 734,
+ "width": 360
+ },
+ "position": {
+ "x": 1718.9581068990763,
+ "y": 1081.137733422722
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -49.933183848022736,
+ "y": 46.49268690110034,
+ "zoom": 0.4973930841732965
+ }
+ },
+ "description": "Researches companies, extracts key business data, and presents structured information for efficient analysis. ",
+ "endpoint_name": null,
+ "gradient": "1",
+ "icon": "PieChart",
+ "id": "153a05e5-86bd-4de8-b159-2cb4f9f94de5",
+ "is_component": false,
+ "last_tested_version": "1.1.1",
+ "name": "Market Research",
+ "tags": [
+ "assistants",
+ "agents"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json
new file mode 100644
index 0000000..5c29f8c
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json
@@ -0,0 +1,3629 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobPoller",
+ "id": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "name": "transcription_result",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-LUfjb",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-AssemblyAITranscriptionJobPoller-bxKgt{œdataTypeœ:œAssemblyAITranscriptionJobPollerœ,œidœ:œAssemblyAITranscriptionJobPoller-bxKgtœ,œnameœ:œtranscription_resultœ,œoutput_typesœ:[œDataœ]}-ParseData-LUfjb{œfieldNameœ:œdataœ,œidœ:œParseData-LUfjbœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "selected": false,
+ "source": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "sourceHandle": "{œdataTypeœ: œAssemblyAITranscriptionJobPollerœ, œidœ: œAssemblyAITranscriptionJobPoller-bxKgtœ, œnameœ: œtranscription_resultœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-LUfjb",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-LUfjbœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-LUfjb",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "transcript",
+ "id": "Prompt-vYcSa",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-LUfjb{œdataTypeœ:œParseDataœ,œidœ:œParseData-LUfjbœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-vYcSa{œfieldNameœ:œtranscriptœ,œidœ:œPrompt-vYcSaœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseData-LUfjb",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-LUfjbœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-vYcSa",
+ "targetHandle": "{œfieldNameœ: œtranscriptœ, œidœ: œPrompt-vYcSaœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-vYcSa",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-iudDZ",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-vYcSa{œdataTypeœ:œPromptœ,œidœ:œPrompt-vYcSaœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-iudDZ{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-iudDZœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-vYcSa",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-vYcSaœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-iudDZ",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-iudDZœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-iudDZ",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-l7B6O",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-iudDZ{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-iudDZœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-l7B6O{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-l7B6Oœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-iudDZ",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-iudDZœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-l7B6O",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-l7B6Oœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-LUfjb",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-BMxpl",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-LUfjb{œdataTypeœ:œParseDataœ,œidœ:œParseData-LUfjbœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-BMxpl{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-BMxplœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ParseData-LUfjb",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-LUfjbœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-BMxpl",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BMxplœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-8fyum",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-04Red",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-8fyum{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-8fyumœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-04Red{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-04Redœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-8fyum",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-8fyumœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-04Red",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-04Redœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Memory",
+ "id": "Memory-0odic",
+ "name": "messages_text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "history",
+ "id": "Prompt-f4vcK",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Memory-0odic{œdataTypeœ:œMemoryœ,œidœ:œMemory-0odicœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-f4vcK{œfieldNameœ:œhistoryœ,œidœ:œPrompt-f4vcKœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Memory-0odic",
+ "sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-0odicœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-f4vcK",
+ "targetHandle": "{œfieldNameœ: œhistoryœ, œidœ: œPrompt-f4vcKœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-d3z9H",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input",
+ "id": "Prompt-f4vcK",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-d3z9H{œdataTypeœ:œChatInputœ,œidœ:œChatInput-d3z9Hœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-f4vcK{œfieldNameœ:œinputœ,œidœ:œPrompt-f4vcKœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-d3z9H",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-d3z9Hœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-f4vcK",
+ "targetHandle": "{œfieldNameœ: œinputœ, œidœ: œPrompt-f4vcKœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-f4vcK",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-8fyum",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-f4vcK{œdataTypeœ:œPromptœ,œidœ:œPrompt-f4vcKœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-8fyum{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-8fyumœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-f4vcK",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-f4vcKœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-8fyum",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-8fyumœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "AssemblyAITranscriptionJobCreator",
+ "id": "AssemblyAITranscriptionJobCreator-ylQES",
+ "name": "transcript_id",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "transcript_id",
+ "id": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__AssemblyAITranscriptionJobCreator-ylQES{œdataTypeœ:œAssemblyAITranscriptionJobCreatorœ,œidœ:œAssemblyAITranscriptionJobCreator-ylQESœ,œnameœ:œtranscript_idœ,œoutput_typesœ:[œDataœ]}-AssemblyAITranscriptionJobPoller-bxKgt{œfieldNameœ:œtranscript_idœ,œidœ:œAssemblyAITranscriptionJobPoller-bxKgtœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "AssemblyAITranscriptionJobCreator-ylQES",
+ "sourceHandle": "{œdataTypeœ: œAssemblyAITranscriptionJobCreatorœ, œidœ: œAssemblyAITranscriptionJobCreator-ylQESœ, œnameœ: œtranscript_idœ, œoutput_typesœ: [œDataœ]}",
+ "target": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "targetHandle": "{œfieldNameœ: œtranscript_idœ, œidœ: œAssemblyAITranscriptionJobPoller-bxKgtœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Poll for the status of a transcription job using AssemblyAI",
+ "display_name": "AssemblyAI Poll Transcript",
+ "documentation": "https://www.assemblyai.com/docs",
+ "edited": false,
+ "field_order": [
+ "api_key",
+ "transcript_id",
+ "polling_interval"
+ ],
+ "frozen": false,
+ "icon": "AssemblyAI",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Transcription Result",
+ "method": "poll_transcription_job",
+ "name": "transcription_result",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Assembly API Key",
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "ASSEMBLYAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import assemblyai as aai\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import DataInput, FloatInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAITranscriptionJobPoller(Component):\n display_name = \"AssemblyAI Poll Transcript\"\n description = \"Poll for the status of a transcription job using AssemblyAI\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n DataInput(\n name=\"transcript_id\",\n display_name=\"Transcript ID\",\n info=\"The ID of the transcription job to poll\",\n required=True,\n ),\n FloatInput(\n name=\"polling_interval\",\n display_name=\"Polling Interval\",\n value=3.0,\n info=\"The polling interval in seconds\",\n advanced=True,\n range_spec=RangeSpec(min=3, max=30),\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcription Result\", name=\"transcription_result\", method=\"poll_transcription_job\"),\n ]\n\n def poll_transcription_job(self) -> Data:\n \"\"\"Polls the transcription status until completion and returns the Data.\"\"\"\n aai.settings.api_key = self.api_key\n aai.settings.polling_interval = self.polling_interval\n\n # check if it's an error message from the previous step\n if self.transcript_id.data.get(\"error\"):\n self.status = self.transcript_id.data[\"error\"]\n return self.transcript_id\n\n try:\n transcript = aai.Transcript.get_by_id(self.transcript_id.data[\"transcript_id\"])\n except Exception as e: # noqa: BLE001\n error = f\"Getting transcription failed: {e}\"\n logger.opt(exception=True).debug(error)\n self.status = error\n return Data(data={\"error\": error})\n\n if transcript.status == aai.TranscriptStatus.completed:\n json_response = transcript.json_response\n text = json_response.pop(\"text\", None)\n utterances = json_response.pop(\"utterances\", None)\n transcript_id = json_response.pop(\"id\", None)\n sorted_data = {\"text\": text, \"utterances\": utterances, \"id\": transcript_id}\n sorted_data.update(json_response)\n data = Data(data=sorted_data)\n self.status = data\n return data\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n"
+ },
+ "polling_interval": {
+ "_input_type": "FloatInput",
+ "advanced": true,
+ "display_name": "Polling Interval",
+ "dynamic": false,
+ "info": "The polling interval in seconds",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "polling_interval",
+ "placeholder": "",
+ "range_spec": {
+ "max": 30,
+ "min": 3,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "float",
+ "value": 3
+ },
+ "transcript_id": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Transcript ID",
+ "dynamic": false,
+ "info": "The ID of the transcription job to poll",
+ "input_types": [
+ "Data"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "transcript_id",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AssemblyAITranscriptionJobPoller"
+ },
+ "id": "AssemblyAITranscriptionJobPoller-bxKgt",
+ "measured": {
+ "height": 294,
+ "width": 320
+ },
+ "position": {
+ "x": 943.468098795128,
+ "y": 282.3188316337007
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ParseData-LUfjb",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data objects into Messages using any {field_name} from input data.",
+ "display_name": "Data to Message",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseData"
+ },
+ "id": "ParseData-LUfjb",
+ "measured": {
+ "height": 342,
+ "width": 320
+ },
+ "position": {
+ "x": 1330.927281184057,
+ "y": 382.3516758942169
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-iudDZ",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "id": "OpenAIModel-iudDZ",
+ "measured": {
+ "height": 656,
+ "width": 320
+ },
+ "position": {
+ "x": 2159.856153607566,
+ "y": 546.0283268474204
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "Prompt-vYcSa",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "transcript"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "error": null,
+ "field_order": [
+ "template",
+ "tool_placeholder"
+ ],
+ "frozen": false,
+ "full_path": null,
+ "icon": "prompts",
+ "is_composition": null,
+ "is_input": null,
+ "is_output": null,
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "name": "",
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "{transcript}\n\n---\n\nSummarize the action items and main ideas based on the conversation above. Be objective and avoid redundancy. \n\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "transcript": {
+ "advanced": false,
+ "display_name": "transcript",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "transcript",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "Prompt"
+ },
+ "id": "Prompt-vYcSa",
+ "measured": {
+ "height": 339,
+ "width": 320
+ },
+ "position": {
+ "x": 1752.6947356866303,
+ "y": 491.51833853334665
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-l7B6O",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.00012027401062119145,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ChatOutput"
+ },
+ "id": "ChatOutput-l7B6O",
+ "measured": {
+ "height": 230,
+ "width": 320
+ },
+ "position": {
+ "x": 2586.3668922112406,
+ "y": 713.1816341478374
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-BMxpl",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.1.1",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.00012027401062119145,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Original"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "id": "ChatOutput-BMxpl",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 1808.9025759312458,
+ "y": 928.6030474712679
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-8fyum",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "id": "OpenAIModel-8fyum",
+ "measured": {
+ "height": 656,
+ "width": 320
+ },
+ "position": {
+ "x": 1668.2863585030223,
+ "y": 1394.531585772563
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-04Red",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.00012027401062119145,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "id": "ChatOutput-04Red",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 2166.5776099158024,
+ "y": 1717.8823325046656
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "Prompt-f4vcK",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "history",
+ "input"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "error": null,
+ "field_order": [
+ "template",
+ "tool_placeholder"
+ ],
+ "frozen": false,
+ "full_path": null,
+ "icon": "prompts",
+ "is_composition": null,
+ "is_input": null,
+ "is_output": null,
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "name": "",
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "history": {
+ "advanced": false,
+ "display_name": "history",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "history",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "input": {
+ "advanced": false,
+ "display_name": "input",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "{history}\n\n{input}\n\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "Prompt"
+ },
+ "id": "Prompt-f4vcK",
+ "measured": {
+ "height": 421,
+ "width": 320
+ },
+ "position": {
+ "x": 1201.39455884454,
+ "y": 1434.0090202145623
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "Memory-0odic",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Retrieves stored chat messages from Langflow tables or an external memory.",
+ "display_name": "Message History",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template"
+ ],
+ "frozen": false,
+ "icon": "message-square-more",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "retrieve_messages",
+ "name": "messages",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "retrieve_messages_as_text",
+ "name": "messages_text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import aget_messages\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Message History\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"Memory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Message\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n ]\n\n async def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory and not hasattr(self.memory, \"aget_messages\"):\n memory_name = type(self.memory).__name__\n err_msg = f\"External Memory object ({memory_name}) must have 'aget_messages' method.\"\n raise AttributeError(err_msg)\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = await self.memory.aget_messages()\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = await aget_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n async def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, await self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n"
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "Memory"
+ },
+ "id": "Memory-0odic",
+ "measured": {
+ "height": 260,
+ "width": 320
+ },
+ "position": {
+ "x": 685.5892616330983,
+ "y": 1365.244705292662
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatInput-d3z9H",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatInput"
+ },
+ "id": "ChatInput-d3z9H",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 701.8270533776066,
+ "y": 1829.9409906958817
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-2icq2",
+ "node": {
+ "description": "### 💡 Add your Assembly AI API key and audio file here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-2icq2",
+ "measured": {
+ "height": 324,
+ "width": 455
+ },
+ "position": {
+ "x": 452.7834981529654,
+ "y": 186.89794978262478
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 456
+ },
+ {
+ "data": {
+ "id": "note-OejoR",
+ "node": {
+ "description": "### 💡 Add your Assembly AI API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-OejoR",
+ "measured": {
+ "height": 324,
+ "width": 364
+ },
+ "position": {
+ "x": 920.5426847894952,
+ "y": 231.76073203017918
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 365
+ },
+ {
+ "data": {
+ "id": "note-9B1rT",
+ "node": {
+ "description": "### 💡 Add your OpenAI API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 324,
+ "id": "note-9B1rT",
+ "measured": {
+ "height": 324,
+ "width": 334
+ },
+ "position": {
+ "x": 2151.1746324575247,
+ "y": 500.2170739157981
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 335
+ },
+ {
+ "data": {
+ "id": "note-tO2On",
+ "node": {
+ "description": "### 💡 Add your OpenAI API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "id": "note-tO2On",
+ "measured": {
+ "height": 324,
+ "width": 324
+ },
+ "position": {
+ "x": 1665.156818365488,
+ "y": 1348.5600122190888
+ },
+ "selected": false,
+ "type": "noteNode"
+ },
+ {
+ "data": {
+ "id": "AssemblyAITranscriptionJobCreator-ylQES",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "category": "assemblyai",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Create a transcription job for an audio file using AssemblyAI with advanced options",
+ "display_name": "AssemblyAI Start Transcript",
+ "documentation": "https://www.assemblyai.com/docs",
+ "edited": false,
+ "field_order": [
+ "api_key",
+ "audio_file",
+ "audio_file_url",
+ "speech_model",
+ "language_detection",
+ "language_code",
+ "speaker_labels",
+ "speakers_expected",
+ "punctuate",
+ "format_text"
+ ],
+ "frozen": false,
+ "icon": "AssemblyAI",
+ "key": "AssemblyAITranscriptionJobCreator",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Transcript ID",
+ "method": "create_transcription_job",
+ "name": "transcript_id",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.000018578044550916993,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "Assembly API Key",
+ "dynamic": false,
+ "info": "Your AssemblyAI API key. You can get one from https://www.assemblyai.com/",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "audio_file": {
+ "_input_type": "FileInput",
+ "advanced": false,
+ "display_name": "Audio File",
+ "dynamic": false,
+ "fileTypes": [
+ "3ga",
+ "8svx",
+ "aac",
+ "ac3",
+ "aif",
+ "aiff",
+ "alac",
+ "amr",
+ "ape",
+ "au",
+ "dss",
+ "flac",
+ "flv",
+ "m4a",
+ "m4b",
+ "m4p",
+ "m4r",
+ "mp3",
+ "mpga",
+ "ogg",
+ "oga",
+ "mogg",
+ "opus",
+ "qcp",
+ "tta",
+ "voc",
+ "wav",
+ "wma",
+ "wv",
+ "webm",
+ "mts",
+ "m2ts",
+ "ts",
+ "mov",
+ "mp2",
+ "mp4",
+ "m4p",
+ "m4v",
+ "mxf"
+ ],
+ "file_path": "",
+ "info": "The audio file to transcribe",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "audio_file",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "audio_file_url": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Audio File URL",
+ "dynamic": false,
+ "info": "The URL of the audio file to transcribe (Can be used instead of a File)",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "audio_file_url",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from pathlib import Path\n\nimport assemblyai as aai\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput\nfrom langflow.schema import Data\n\n\nclass AssemblyAITranscriptionJobCreator(Component):\n display_name = \"AssemblyAI Start Transcript\"\n description = \"Create a transcription job for an audio file using AssemblyAI with advanced options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n FileInput(\n name=\"audio_file\",\n display_name=\"Audio File\",\n file_types=[\n \"3ga\",\n \"8svx\",\n \"aac\",\n \"ac3\",\n \"aif\",\n \"aiff\",\n \"alac\",\n \"amr\",\n \"ape\",\n \"au\",\n \"dss\",\n \"flac\",\n \"flv\",\n \"m4a\",\n \"m4b\",\n \"m4p\",\n \"m4r\",\n \"mp3\",\n \"mpga\",\n \"ogg\",\n \"oga\",\n \"mogg\",\n \"opus\",\n \"qcp\",\n \"tta\",\n \"voc\",\n \"wav\",\n \"wma\",\n \"wv\",\n \"webm\",\n \"mts\",\n \"m2ts\",\n \"ts\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"m4p\",\n \"m4v\",\n \"mxf\",\n ],\n info=\"The audio file to transcribe\",\n required=True,\n ),\n MessageTextInput(\n name=\"audio_file_url\",\n display_name=\"Audio File URL\",\n info=\"The URL of the audio file to transcribe (Can be used instead of a File)\",\n advanced=True,\n ),\n DropdownInput(\n name=\"speech_model\",\n display_name=\"Speech Model\",\n options=[\n \"best\",\n \"nano\",\n ],\n value=\"best\",\n info=\"The speech model to use for the transcription\",\n advanced=True,\n ),\n BoolInput(\n name=\"language_detection\",\n display_name=\"Automatic Language Detection\",\n info=\"Enable automatic language detection\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"language_code\",\n display_name=\"Language\",\n info=(\n \"\"\"\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages \"\"\"\n \"for a list of supported language codes.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"speaker_labels\",\n display_name=\"Enable Speaker Labels\",\n info=\"Enable speaker diarization\",\n ),\n MessageTextInput(\n name=\"speakers_expected\",\n display_name=\"Expected Number of Speakers\",\n info=\"Set the expected number of speakers (optional, enter a number)\",\n advanced=True,\n ),\n BoolInput(\n name=\"punctuate\",\n display_name=\"Punctuate\",\n info=\"Enable automatic punctuation\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"format_text\",\n display_name=\"Format Text\",\n info=\"Enable text formatting\",\n advanced=True,\n value=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript ID\", name=\"transcript_id\", method=\"create_transcription_job\"),\n ]\n\n def create_transcription_job(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # Convert speakers_expected to int if it's not empty\n speakers_expected = None\n if self.speakers_expected and self.speakers_expected.strip():\n try:\n speakers_expected = int(self.speakers_expected)\n except ValueError:\n self.status = \"Error: Expected Number of Speakers must be a valid integer\"\n return Data(data={\"error\": \"Error: Expected Number of Speakers must be a valid integer\"})\n\n language_code = self.language_code or None\n\n config = aai.TranscriptionConfig(\n speech_model=self.speech_model,\n language_detection=self.language_detection,\n language_code=language_code,\n speaker_labels=self.speaker_labels,\n speakers_expected=speakers_expected,\n punctuate=self.punctuate,\n format_text=self.format_text,\n )\n\n audio = None\n if self.audio_file:\n if self.audio_file_url:\n logger.warning(\"Both an audio file an audio URL were specified. The audio URL was ignored.\")\n\n # Check if the file exists\n if not Path(self.audio_file).exists():\n self.status = \"Error: Audio file not found\"\n return Data(data={\"error\": \"Error: Audio file not found\"})\n audio = self.audio_file\n elif self.audio_file_url:\n audio = self.audio_file_url\n else:\n self.status = \"Error: Either an audio file or an audio URL must be specified\"\n return Data(data={\"error\": \"Error: Either an audio file or an audio URL must be specified\"})\n\n try:\n transcript = aai.Transcriber().submit(audio, config=config)\n except Exception as e: # noqa: BLE001\n logger.opt(exception=True).debug(\"Error submitting transcription job\")\n self.status = f\"An error occurred: {e}\"\n return Data(data={\"error\": f\"An error occurred: {e}\"})\n\n if transcript.error:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n result = Data(data={\"transcript_id\": transcript.id})\n self.status = result\n return result\n"
+ },
+ "format_text": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Format Text",
+ "dynamic": false,
+ "info": "Enable text formatting",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "format_text",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "language_code": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Language",
+ "dynamic": false,
+ "info": "\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages for a list of supported language codes.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "language_code",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "language_detection": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Automatic Language Detection",
+ "dynamic": false,
+ "info": "Enable automatic language detection",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "language_detection",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "punctuate": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Punctuate",
+ "dynamic": false,
+ "info": "Enable automatic punctuation",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "punctuate",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "speaker_labels": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Enable Speaker Labels",
+ "dynamic": false,
+ "info": "Enable speaker diarization",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "speaker_labels",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "speakers_expected": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Expected Number of Speakers",
+ "dynamic": false,
+ "info": "Set the expected number of speakers (optional, enter a number)",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "speakers_expected",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "speech_model": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Speech Model",
+ "dynamic": false,
+ "info": "The speech model to use for the transcription",
+ "name": "speech_model",
+ "options": [
+ "best",
+ "nano"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "best"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "AssemblyAITranscriptionJobCreator"
+ },
+ "dragging": false,
+ "id": "AssemblyAITranscriptionJobCreator-ylQES",
+ "measured": {
+ "height": 373,
+ "width": 320
+ },
+ "position": {
+ "x": 515.589850902064,
+ "y": 232.58183434411956
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-fB5Sk",
+ "node": {
+ "description": "# Meeting Summary Generator\n\nThis flow automatically transcribes and summarizes meetings by converting audio recordings into concise summaries using **AssemblyAI** and **OpenAI GPT-4**. \n\n## Prerequisites\n\n- **[AssemblyAI API Key](https://www.assemblyai.com/)**\n- **[OpenAI API Key](https://platform.openai.com/)**\n\n## Quickstart\n\n1. Upload an audio file. Most common audio file formats are [supported](https://github.com/langflow-ai/langflow/blob/main/src/backend/base/langflow/components/assemblyai/assemblyai_start_transcript.py#L27).\n2. To run the summary generator flow, click **Playground**.\n\nThe flow transcribes the audio using **AssemblyAI**.\nThe transcript is formatted for AI processing.\nThe **GPT-4** model extracts key points and insights.\nThe summarized meeting details are displayed in a chat-friendly format.\n\n\n\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 612,
+ "id": "note-fB5Sk",
+ "measured": {
+ "height": 612,
+ "width": 549
+ },
+ "position": {
+ "x": -128.87171443390673,
+ "y": 227.16742082405324
+ },
+ "resizing": false,
+ "selected": false,
+ "type": "noteNode",
+ "width": 548
+ }
+ ],
+ "viewport": {
+ "x": 199.66634321516733,
+ "y": -38.61016993098076,
+ "zoom": 0.49475443116609724
+ }
+ },
+ "description": "An AI-powered meeting summary generator that transcribes and summarizes meetings using AssemblyAI and OpenAI for quick insights.",
+ "endpoint_name": "meeting_summary",
+ "icon": "headset",
+ "id": "5b99326e-70dc-4c7a-b791-67665ee1dac3",
+ "is_component": false,
+ "last_tested_version": "1.1.5",
+ "name": "Meeting Summary",
+ "tags": [
+ "chatbots",
+ "content-generation"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json
new file mode 100644
index 0000000..860246d
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Memory Chatbot.json
@@ -0,0 +1,1593 @@
+{
+ "data": {
+ "edges": [
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Memory",
+ "id": "Memory-gWJrq",
+ "name": "messages_text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "memory",
+ "id": "Prompt-yhdMP",
+ "inputTypes": [
+ "Message",
+ "Text"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Memory-gWJrq{œdataTypeœ:œMemoryœ,œidœ:œMemory-gWJrqœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-yhdMP{œfieldNameœ:œmemoryœ,œidœ:œPrompt-yhdMPœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Memory-gWJrq",
+ "sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-gWJrqœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Prompt-yhdMP",
+ "targetHandle": "{œfieldNameœ: œmemoryœ, œidœ: œPrompt-yhdMPœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-PEO9d",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "OpenAIModel-63o3Q",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-PEO9d{œdataTypeœ:œChatInputœ,œidœ:œChatInput-PEO9dœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-63o3Q{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-63o3Qœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "ChatInput-PEO9d",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-PEO9dœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-63o3Q",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-63o3Qœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Prompt",
+ "id": "Prompt-yhdMP",
+ "name": "prompt",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "OpenAIModel-63o3Q",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Prompt-yhdMP{œdataTypeœ:œPromptœ,œidœ:œPrompt-yhdMPœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-63o3Q{œfieldNameœ:œsystem_messageœ,œidœ:œOpenAIModel-63o3Qœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "Prompt-yhdMP",
+ "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-yhdMPœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "OpenAIModel-63o3Q",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œOpenAIModel-63o3Qœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "OpenAIModel",
+ "id": "OpenAIModel-63o3Q",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-BIXzI",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-OpenAIModel-63o3Q{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-63o3Qœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-BIXzI{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-BIXzIœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "selected": false,
+ "source": "OpenAIModel-63o3Q",
+ "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-63o3Qœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-BIXzI",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BIXzIœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "ChatInput-PEO9d",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "what is my name"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "height": 234,
+ "id": "ChatInput-PEO9d",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 2321.5543981677606,
+ "y": 374.0457826421628
+ },
+ "positionAbsolute": {
+ "x": 2321.5543981677606,
+ "y": 374.0457826421628
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "id": "ChatOutput-BIXzI",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "ChatOutput"
+ },
+ "dragging": true,
+ "height": 234,
+ "id": "ChatOutput-BIXzI",
+ "measured": {
+ "height": 234,
+ "width": 320
+ },
+ "position": {
+ "x": 3113.9880204333917,
+ "y": 769.7618411016429
+ },
+ "positionAbsolute": {
+ "x": 3101.965731391458,
+ "y": 776.4408905693839
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "note-jlIQj",
+ "node": {
+ "description": "# Memory Chatbot\n\nA flexible chatbot implementation featuring advanced conversation memory capabilities. This serves as a foundational tool for building chat experiences with persistent context.\n\n## Core Components\n\n1. **Chat Input**\n - Accepts user messages\n - Configures conversation storage\n - Tracks session identity\n\n2. **Chat Memory**\n - Stores and retrieves up to 100 previous messages\n - Maintains conversation context\n - Tracks separate chat sessions\n - Preserves sender information and message order\n\n3. **Prompt**\n - Creates dynamic prompt templates\n - Integrates memory into conversation flow\n\n4. **OpenAI**\n - Processes user input with context\n - Accesses conversation history\n - Includes options for model configuration and API key setup\n\n5. **Chat Output**\n - Displays formatted responses\n - Maintains conversation flow\n - Syncs with memory storage\n\n## Memory Features\n\n- Stores message history\n- Plans conversation trajectory\n- Differentiates between chat sessions\n- Preserves sender and message metadata\n\n## Quick Start\n\n1. **Initialize** with a clear session ID\n2. **Enter** message in Chat Input\n3. **AI Processes** with context from memory\n4. **Response** appears in Chat Output\n5. Context remains available for follow-ups\n\nThis robust system demonstrates thorough memory integration with minimal complexity. 🧠💬\n",
+ "display_name": "",
+ "documentation": "",
+ "template": {}
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 736,
+ "id": "note-jlIQj",
+ "measured": {
+ "height": 733,
+ "width": 325
+ },
+ "position": {
+ "x": 1512.8976594415833,
+ "y": 312.9558305744385
+ },
+ "positionAbsolute": {
+ "x": 1512.8976594415833,
+ "y": 312.9558305744385
+ },
+ "resizing": false,
+ "selected": false,
+ "style": {
+ "height": 736,
+ "width": 324
+ },
+ "type": "noteNode",
+ "width": 324
+ },
+ {
+ "data": {
+ "id": "note-oU1IW",
+ "node": {
+ "description": "## Get Your OpenAI API Key\n\n**Steps**:\n\n1. **Visit** [OpenAI's API Key Page](https://platform.openai.com/api-keys).\n\n2. **Log In/Sign Up**:\n - Log in or create a new OpenAI account.\n\n3. **Generate API Key**:\n - Click \"Create New Secret Key\" to obtain your key.\n\n4. **Store Your Key Securely**:\n - Note it down as it will only display once.\n\n5. **Enter API Key**:\n - Input your key in the OpenAI API Key field within the component setup.\n\nKeep your key safe and manage it responsibly!",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "rose"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 325,
+ "id": "note-oU1IW",
+ "measured": {
+ "height": 324,
+ "width": 325
+ },
+ "position": {
+ "x": 2727.7060397092964,
+ "y": 115.42518754847691
+ },
+ "positionAbsolute": {
+ "x": 2727.7060397092964,
+ "y": 115.42518754847691
+ },
+ "selected": false,
+ "type": "noteNode",
+ "width": 325
+ },
+ {
+ "data": {
+ "id": "Memory-gWJrq",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Retrieves stored chat messages from Langflow tables or an external memory.",
+ "display_name": "Chat Memory",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template"
+ ],
+ "frozen": false,
+ "icon": "message-square-more",
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data",
+ "method": "retrieve_messages",
+ "name": "messages",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "retrieve_messages_as_text",
+ "name": "messages_text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.inputs import HandleInput\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, MultilineInput, Output\nfrom langflow.memory import aget_messages\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER\n\n\nclass MemoryComponent(Component):\n display_name = \"Message History\"\n description = \"Retrieves stored chat messages from Langflow tables or an external memory.\"\n icon = \"message-square-more\"\n name = \"Memory\"\n\n inputs = [\n HandleInput(\n name=\"memory\",\n display_name=\"External Memory\",\n input_types=[\"Memory\"],\n info=\"Retrieve messages from an external memory. If empty, it will use the Langflow tables.\",\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER, \"Machine and User\"],\n value=\"Machine and User\",\n info=\"Filter by sender type.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Filter by sender name.\",\n advanced=True,\n ),\n IntInput(\n name=\"n_messages\",\n display_name=\"Number of Messages\",\n value=100,\n info=\"Number of messages to retrieve.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"order\",\n display_name=\"Order\",\n options=[\"Ascending\", \"Descending\"],\n value=\"Ascending\",\n info=\"Order of the messages.\",\n advanced=True,\n tool_mode=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {sender} or any other key in the message data.\",\n value=\"{sender_name}: {text}\",\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"messages\", method=\"retrieve_messages\"),\n Output(display_name=\"Message\", name=\"messages_text\", method=\"retrieve_messages_as_text\"),\n ]\n\n async def retrieve_messages(self) -> Data:\n sender = self.sender\n sender_name = self.sender_name\n session_id = self.session_id\n n_messages = self.n_messages\n order = \"DESC\" if self.order == \"Descending\" else \"ASC\"\n\n if sender == \"Machine and User\":\n sender = None\n\n if self.memory and not hasattr(self.memory, \"aget_messages\"):\n memory_name = type(self.memory).__name__\n err_msg = f\"External Memory object ({memory_name}) must have 'aget_messages' method.\"\n raise AttributeError(err_msg)\n\n if self.memory:\n # override session_id\n self.memory.session_id = session_id\n\n stored = await self.memory.aget_messages()\n # langchain memories are supposed to return messages in ascending order\n if order == \"DESC\":\n stored = stored[::-1]\n if n_messages:\n stored = stored[:n_messages]\n stored = [Message.from_lc_message(m) for m in stored]\n if sender:\n expected_type = MESSAGE_SENDER_AI if sender == MESSAGE_SENDER_AI else MESSAGE_SENDER_USER\n stored = [m for m in stored if m.type == expected_type]\n else:\n stored = await aget_messages(\n sender=sender,\n sender_name=sender_name,\n session_id=session_id,\n limit=n_messages,\n order=order,\n )\n self.status = stored\n return stored\n\n async def retrieve_messages_as_text(self) -> Message:\n stored_text = data_to_text(self.template, await self.retrieve_messages())\n self.status = stored_text\n return Message(text=stored_text)\n"
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Memory"
+ },
+ "dragging": false,
+ "height": 264,
+ "id": "Memory-gWJrq",
+ "measured": {
+ "height": 264,
+ "width": 320
+ },
+ "position": {
+ "x": 1947.7805399474369,
+ "y": 766.1115984799474
+ },
+ "positionAbsolute": {
+ "x": 1947.7805399474369,
+ "y": 766.1115984799474
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "Prompt-yhdMP",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {
+ "template": [
+ "memory"
+ ]
+ },
+ "description": "Create a prompt template with dynamic variables.",
+ "display_name": "Prompt",
+ "documentation": "",
+ "edited": false,
+ "error": null,
+ "field_order": [
+ "template"
+ ],
+ "frozen": false,
+ "full_path": null,
+ "icon": "prompts",
+ "is_composition": null,
+ "is_input": null,
+ "is_output": null,
+ "legacy": false,
+ "lf_version": "1.0.19.post2",
+ "metadata": {},
+ "name": "",
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Prompt Message",
+ "method": "build_prompt",
+ "name": "prompt",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import MessageTextInput, Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n MessageTextInput(\n name=\"tool_placeholder\",\n display_name=\"Tool Placeholder\",\n tool_mode=True,\n advanced=True,\n info=\"A placeholder input for tool mode.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(self) -> Message:\n prompt = Message.from_template(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n async def update_frontend_node(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"This function is called after the code validation is done.\"\"\"\n frontend_node = await super().update_frontend_node(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n"
+ },
+ "memory": {
+ "advanced": false,
+ "display_name": "memory",
+ "dynamic": false,
+ "field_type": "str",
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "input_types": [
+ "Message",
+ "Text"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "template": {
+ "_input_type": "PromptInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "prompt",
+ "value": "You are a helpful assistant that answer questions.\n\nUse markdown to format your answer, properly embedding images and urls.\n\nHistory: \n\n{memory}\n"
+ },
+ "tool_placeholder": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Tool Placeholder",
+ "dynamic": false,
+ "info": "A placeholder input for tool mode.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "load_from_db": false,
+ "name": "tool_placeholder",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "type": "Prompt"
+ },
+ "dragging": false,
+ "height": 347,
+ "id": "Prompt-yhdMP",
+ "measured": {
+ "height": 347,
+ "width": 320
+ },
+ "position": {
+ "x": 2327.422938009026,
+ "y": 675.992123914672
+ },
+ "positionAbsolute": {
+ "x": 2327.422938009026,
+ "y": 675.992123914672
+ },
+ "selected": false,
+ "type": "genericNode",
+ "width": 320
+ },
+ {
+ "data": {
+ "id": "OpenAIModel-63o3Q",
+ "node": {
+ "base_classes": [
+ "LanguageModel",
+ "Message"
+ ],
+ "beta": false,
+ "category": "models",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Generates text using OpenAI LLMs.",
+ "display_name": "OpenAI",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "system_message",
+ "stream",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed"
+ ],
+ "frozen": false,
+ "icon": "OpenAI",
+ "key": "OpenAIModel",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text_output",
+ "required_inputs": [],
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Language Model",
+ "method": "build_model",
+ "name": "model_output",
+ "required_inputs": [
+ "api_key"
+ ],
+ "selected": "LanguageModel",
+ "tool_mode": true,
+ "types": [
+ "LanguageModel"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.14285714285714285,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": true,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": "OPENAI_API_KEY"
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "stream": {
+ "_input_type": "BoolInput",
+ "advanced": false,
+ "display_name": "Stream",
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "stream",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "system_message": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "System Message",
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": false,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 1,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "OpenAIModel"
+ },
+ "dragging": false,
+ "id": "OpenAIModel-63o3Q",
+ "measured": {
+ "height": 653,
+ "width": 320
+ },
+ "position": {
+ "x": 2726.7429097065215,
+ "y": 460.96432586131795
+ },
+ "selected": false,
+ "type": "genericNode"
+ }
+ ],
+ "viewport": {
+ "x": -1173.6150415418488,
+ "y": -168.93756272342318,
+ "zoom": 0.7486095175892006
+ }
+ },
+ "description": "Create a chatbot that saves and references previous messages, enabling the model to maintain context throughout the conversation.",
+ "endpoint_name": null,
+ "gradient": "4",
+ "icon": "MessagesSquare",
+ "id": "7d334df6-6cf5-4d09-b6bf-169247b20446",
+ "is_component": false,
+ "last_tested_version": "1.0.19.post2",
+ "name": "Memory Chatbot",
+ "tags": [
+ "chatbots",
+ "openai",
+ "assistants"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json
new file mode 100644
index 0000000..e95acc6
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/News Aggregator.json
@@ -0,0 +1,1759 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AgentQL",
+ "id": "AgentQL-XIw0m",
+ "name": "component_as_tool",
+ "output_types": [
+ "Tool"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "tools",
+ "id": "Agent-O1xzr",
+ "inputTypes": [
+ "Tool"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-AgentQL-XIw0m{œdataTypeœ:œAgentQLœ,œidœ:œAgentQL-XIw0mœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-O1xzr{œfieldNameœ:œtoolsœ,œidœ:œAgent-O1xzrœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}",
+ "source": "AgentQL-XIw0m",
+ "sourceHandle": "{œdataTypeœ: œAgentQLœ, œidœ: œAgentQL-XIw0mœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}",
+ "target": "Agent-O1xzr",
+ "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-O1xzrœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "Agent",
+ "id": "Agent-O1xzr",
+ "name": "response",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-QiEpu",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-Agent-O1xzr{œdataTypeœ:œAgentœ,œidœ:œAgent-O1xzrœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-QiEpu{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-QiEpuœ,œinputTypesœ:[œDataœ,œDataFrameœ,œMessageœ],œtypeœ:œstrœ}",
+ "source": "Agent-O1xzr",
+ "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-O1xzrœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-QiEpu",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-QiEpuœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ChatInput",
+ "id": "ChatInput-KdiYi",
+ "name": "message",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "Agent-O1xzr",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ChatInput-KdiYi{œdataTypeœ:œChatInputœ,œidœ:œChatInput-KdiYiœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Agent-O1xzr{œfieldNameœ:œinput_valueœ,œidœ:œAgent-O1xzrœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ChatInput-KdiYi",
+ "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-KdiYiœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "Agent-O1xzr",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-O1xzrœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "note-vGF5M",
+ "node": {
+ "description": "### 💡 Add your OpenAI API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "id": "note-vGF5M",
+ "measured": {
+ "height": 324,
+ "width": 324
+ },
+ "position": {
+ "x": 1170.377736042162,
+ "y": 143.70815416701694
+ },
+ "selected": false,
+ "type": "noteNode"
+ },
+ {
+ "data": {
+ "id": "note-VvhWf",
+ "node": {
+ "description": "### 💡 Add your AgentQL API key here",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "transparent"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "height": 346,
+ "id": "note-VvhWf",
+ "measured": {
+ "height": 346,
+ "width": 324
+ },
+ "position": {
+ "x": 741.8464477206785,
+ "y": 270.1565987952192
+ },
+ "selected": false,
+ "type": "noteNode"
+ },
+ {
+ "data": {
+ "description": "Uses AgentQL API to extract structured data from a given URL.",
+ "display_name": "AgentQL Query Data",
+ "id": "AgentQL-XIw0m",
+ "node": {
+ "base_classes": [
+ "Data"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Uses AgentQL API to extract structured data from a given URL.",
+ "display_name": "AgentQL Query Data",
+ "documentation": "https://docs.agentql.com/rest-api/api-reference",
+ "edited": false,
+ "field_order": [
+ "api_key",
+ "url",
+ "query",
+ "timeout",
+ "params"
+ ],
+ "frozen": false,
+ "icon": "AgentQL",
+ "legacy": false,
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Toolset",
+ "hidden": null,
+ "method": "to_toolkit",
+ "name": "component_as_tool",
+ "required_inputs": null,
+ "selected": "Tool",
+ "tool_mode": true,
+ "types": [
+ "Tool"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "AgentQL API Key",
+ "dynamic": false,
+ "info": "Your AgentQL API key. Get one at https://dev.agentql.com.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "import httpx\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import (\n DictInput,\n IntInput,\n MessageTextInput,\n MultilineInput,\n Output,\n SecretStrInput,\n)\nfrom langflow.schema import Data\n\n\nclass AgentQL(Component):\n display_name = \"AgentQL Query Data\"\n description = \"Uses AgentQL API to extract structured data from a given URL.\"\n documentation: str = \"https://docs.agentql.com/rest-api/api-reference\"\n icon = \"AgentQL\"\n name = \"AgentQL\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"AgentQL API Key\",\n required=True,\n password=True,\n info=\"Your AgentQL API key. Get one at https://dev.agentql.com.\",\n ),\n MessageTextInput(\n name=\"url\",\n display_name=\"URL\",\n required=True,\n info=\"The public URL of the webpage to extract data from.\",\n tool_mode=True,\n ),\n MultilineInput(\n name=\"query\",\n display_name=\"AgentQL Query\",\n required=True,\n info=\"The AgentQL query to execute. Read more at https://docs.agentql.com/agentql-query.\",\n tool_mode=True,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"Timeout in seconds for the request. Increase if data extraction takes too long.\",\n value=900,\n advanced=True,\n ),\n DictInput(\n name=\"params\",\n display_name=\"Additional Params\",\n info=\"The additional params to send with the request. For details refer to https://docs.agentql.com/rest-api/api-reference#request-body.\",\n is_list=True,\n value={\n \"mode\": \"fast\",\n \"wait_for\": 0,\n \"is_scroll_to_bottom_enabled\": False,\n \"is_screenshot_enabled\": False,\n },\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"build_output\"),\n ]\n\n def build_output(self) -> Data:\n endpoint = \"https://api.agentql.com/v1/query-data\"\n headers = {\n \"X-API-Key\": self.api_key,\n \"Content-Type\": \"application/json\",\n \"X-TF-Request-Origin\": \"langflow\",\n }\n\n payload = {\n \"url\": self.url,\n \"query\": self.query,\n \"params\": self.params,\n }\n\n try:\n response = httpx.post(endpoint, headers=headers, json=payload, timeout=self.timeout)\n response.raise_for_status()\n\n json = response.json()\n data = Data(result=json[\"data\"], metadata=json[\"metadata\"])\n\n except httpx.HTTPStatusError as e:\n response = e.response\n if response.status_code in {401, 403}:\n self.status = \"Please, provide a valid API Key. You can create one at https://dev.agentql.com.\"\n else:\n try:\n error_json = response.json()\n logger.error(\n f\"Failure response: '{response.status_code} {response.reason_phrase}' with body: {error_json}\"\n )\n msg = error_json[\"error_info\"] if \"error_info\" in error_json else error_json[\"detail\"]\n except (ValueError, TypeError):\n msg = f\"HTTP {e}.\"\n self.status = msg\n raise ValueError(self.status) from e\n\n else:\n self.status = data\n return data\n"
+ },
+ "params": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Additional Params",
+ "dynamic": false,
+ "info": "The additional params to send with the request. For details refer to https://docs.agentql.com/rest-api/api-reference#request-body.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "params",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {
+ "is_screenshot_enabled": false,
+ "is_scroll_to_bottom_enabled": false,
+ "mode": "fast",
+ "wait_for": 0
+ }
+ },
+ "query": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "AgentQL Query",
+ "dynamic": false,
+ "info": "The AgentQL query to execute. Read more at https://docs.agentql.com/agentql-query.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "query",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "Timeout in seconds for the request. Increase if data extraction takes too long.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 900
+ },
+ "tools_metadata": {
+ "_input_type": "TableInput",
+ "advanced": false,
+ "display_name": "Edit tools",
+ "dynamic": false,
+ "info": "",
+ "is_list": true,
+ "list_add_label": "Add More",
+ "name": "tools_metadata",
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "table_icon": "Hammer",
+ "table_options": {
+ "block_add": true,
+ "block_delete": true,
+ "block_edit": true,
+ "block_filter": true,
+ "block_hide": true,
+ "block_select": true,
+ "block_sort": true,
+ "description": "Modify tool names and descriptions to help agents understand when to use each tool.",
+ "field_parsers": {
+ "commands": "commands",
+ "name": [
+ "snake_case",
+ "no_blank"
+ ]
+ },
+ "hide_options": true
+ },
+ "table_schema": {
+ "columns": [
+ {
+ "default": "None",
+ "description": "Specify the name of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Name",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "name",
+ "sortable": false,
+ "type": "str"
+ },
+ {
+ "default": "None",
+ "description": "Describe the purpose of the tool.",
+ "disable_edit": false,
+ "display_name": "Tool Description",
+ "edit_mode": "popover",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": false,
+ "name": "description",
+ "sortable": false,
+ "type": "str"
+ },
+ {
+ "default": "None",
+ "description": "The default identifiers for the tools and cannot be changed.",
+ "disable_edit": true,
+ "display_name": "Tool Identifiers",
+ "edit_mode": "inline",
+ "filterable": false,
+ "formatter": "text",
+ "hidden": true,
+ "name": "tags",
+ "sortable": false,
+ "type": "str"
+ }
+ ]
+ },
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "trigger_icon": "Hammer",
+ "trigger_text": "",
+ "type": "table",
+ "value": [
+ {
+ "description": "build_output(api_key: Message, query: Message, url: Message) - Uses AgentQL API to extract structured data from a given URL.",
+ "name": "AgentQL-build_output",
+ "tags": [
+ "AgentQL-build_output"
+ ]
+ }
+ ]
+ },
+ "url": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "URL",
+ "dynamic": false,
+ "info": "The public URL of the webpage to extract data from.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "url",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": true
+ },
+ "showNode": true,
+ "type": "AgentQL"
+ },
+ "dragging": false,
+ "id": "AgentQL-XIw0m",
+ "measured": {
+ "height": 499,
+ "width": 320
+ },
+ "position": {
+ "x": 746.6171255053692,
+ "y": 323.12336325015775
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatInput-KdiYi",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "inputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get chat inputs from the Playground.",
+ "display_name": "Chat Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "files",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatInput",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0020353564437605998,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n"
+ },
+ "files": {
+ "_input_type": "FileInput",
+ "advanced": true,
+ "display_name": "Files",
+ "dynamic": false,
+ "fileTypes": [
+ "txt",
+ "md",
+ "mdx",
+ "csv",
+ "json",
+ "yaml",
+ "yml",
+ "xml",
+ "html",
+ "htm",
+ "pdf",
+ "docx",
+ "py",
+ "sh",
+ "sql",
+ "js",
+ "ts",
+ "tsx",
+ "jpg",
+ "jpeg",
+ "png",
+ "bmp",
+ "image"
+ ],
+ "file_path": "",
+ "info": "Files to be sent with the message.",
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "files",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "file",
+ "value": ""
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as input.",
+ "input_types": [],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "User"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatInput"
+ },
+ "dragging": false,
+ "id": "ChatInput-KdiYi",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 414.26981499855697,
+ "y": 618.0969310476024
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ChatOutput-QiEpu",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "outputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Display a chat message in the Playground.",
+ "display_name": "Chat Output",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value",
+ "should_store_message",
+ "sender",
+ "sender_name",
+ "session_id",
+ "data_template",
+ "background_color",
+ "chat_icon",
+ "text_color"
+ ],
+ "frozen": false,
+ "icon": "MessagesSquare",
+ "key": "ChatOutput",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": true,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "message_response",
+ "name": "message",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.007568328950209746,
+ "template": {
+ "_type": "Component",
+ "background_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Background Color",
+ "dynamic": false,
+ "info": "The background color of the icon.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "background_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "chat_icon": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Icon",
+ "dynamic": false,
+ "info": "The icon of the message.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "chat_icon",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "clean_data": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Basic Clean Data",
+ "dynamic": false,
+ "info": "Whether to clean the data",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "clean_data",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from collections.abc import Generator\nfrom typing import Any\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.inputs.inputs import HandleInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.schema.data import Data\nfrom langflow.schema.dataframe import DataFrame\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n HandleInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n input_types=[\"Data\", \"DataFrame\", \"Message\"],\n required=True,\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n BoolInput(\n name=\"clean_data\",\n display_name=\"Basic Clean Data\",\n value=True,\n info=\"Whether to clean the data\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n # Handle case where source is a ChatOpenAI object\n if hasattr(source, \"model_name\"):\n source_dict[\"source\"] = source.model_name\n elif hasattr(source, \"model\"):\n source_dict[\"source\"] = str(source.model)\n else:\n source_dict[\"source\"] = str(source)\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n # First convert the input to string if needed\n text = self.convert_to_string()\n # Get source properties\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n\n # Create or use existing Message object\n if isinstance(self.input_value, Message):\n message = self.input_value\n # Update message properties\n message.text = text\n else:\n message = Message(text=text)\n\n # Set message properties\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n\n # Store message if needed\n if self.session_id and self.should_store_message:\n stored_message = await self.send_message(message)\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n\n def _validate_input(self) -> None:\n \"\"\"Validate the input data and raise ValueError if invalid.\"\"\"\n if self.input_value is None:\n msg = \"Input data cannot be None\"\n raise ValueError(msg)\n if isinstance(self.input_value, list) and not all(\n isinstance(item, Message | Data | DataFrame | str) for item in self.input_value\n ):\n invalid_types = [\n type(item).__name__\n for item in self.input_value\n if not isinstance(item, Message | Data | DataFrame | str)\n ]\n msg = f\"Expected Data or DataFrame or Message or str, got {invalid_types}\"\n raise TypeError(msg)\n if not isinstance(\n self.input_value,\n Message | Data | DataFrame | str | list | Generator | type(None),\n ):\n type_name = type(self.input_value).__name__\n msg = f\"Expected Data or DataFrame or Message or str, Generator or None, got {type_name}\"\n raise TypeError(msg)\n\n def _safe_convert(self, data: Any) -> str:\n \"\"\"Safely convert input data to string.\"\"\"\n try:\n if isinstance(data, str):\n return data\n if isinstance(data, Message):\n return data.get_text()\n if isinstance(data, Data):\n if data.get_text() is None:\n msg = \"Empty Data object\"\n raise ValueError(msg)\n return data.get_text()\n if isinstance(data, DataFrame):\n if self.clean_data:\n # Remove empty rows\n data = data.dropna(how=\"all\")\n # Remove empty lines in each cell\n data = data.replace(r\"^\\s*$\", \"\", regex=True)\n # Replace multiple newlines with a single newline\n data = data.replace(r\"\\n+\", \"\\n\", regex=True)\n\n # Replace pipe characters to avoid markdown table issues\n processed_data = data.replace(r\"\\|\", r\"\\\\|\", regex=True)\n\n processed_data = processed_data.map(\n lambda x: str(x).replace(\"\\n\", \" \") if isinstance(x, str) else x\n )\n\n return processed_data.to_markdown(index=False)\n return str(data)\n except (ValueError, TypeError, AttributeError) as e:\n msg = f\"Error converting data: {e!s}\"\n raise ValueError(msg) from e\n\n def convert_to_string(self) -> str | Generator[Any, None, None]:\n \"\"\"Convert input data to string with proper error handling.\"\"\"\n self._validate_input()\n if isinstance(self.input_value, list):\n return \"\\n\".join([self._safe_convert(item) for item in self.input_value])\n if isinstance(self.input_value, Generator):\n return self.input_value\n return self._safe_convert(self.input_value)\n"
+ },
+ "data_template": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Data Template",
+ "dynamic": false,
+ "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "data_template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ },
+ "input_value": {
+ "_input_type": "MessageInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Message to be passed as output.",
+ "input_types": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Type of sender.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Name of the sender.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "AI"
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "should_store_message": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Store Messages",
+ "dynamic": false,
+ "info": "Store the message in the history.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "should_store_message",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "text_color": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Text Color",
+ "dynamic": false,
+ "info": "The text color of the name",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "text_color",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": false,
+ "type": "ChatOutput"
+ },
+ "dragging": false,
+ "id": "ChatOutput-QiEpu",
+ "measured": {
+ "height": 66,
+ "width": 192
+ },
+ "position": {
+ "x": 1564.8269684087277,
+ "y": 540
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "Agent-O1xzr",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "agents",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Define the agent's instructions, then enter a task to complete using tools.",
+ "display_name": "Agent",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "agent_llm",
+ "max_tokens",
+ "model_kwargs",
+ "json_mode",
+ "model_name",
+ "openai_api_base",
+ "api_key",
+ "temperature",
+ "seed",
+ "max_retries",
+ "timeout",
+ "system_prompt",
+ "tools",
+ "input_value",
+ "handle_parsing_errors",
+ "verbose",
+ "max_iterations",
+ "agent_description",
+ "memory",
+ "sender",
+ "sender_name",
+ "n_messages",
+ "session_id",
+ "order",
+ "template",
+ "add_current_date_tool"
+ ],
+ "frozen": false,
+ "icon": "bot",
+ "key": "Agent",
+ "legacy": false,
+ "lf_version": "1.2.0",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Response",
+ "method": "message_response",
+ "name": "response",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 1.1732828199964098e-19,
+ "template": {
+ "_type": "Component",
+ "add_current_date_tool": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Current Date",
+ "dynamic": false,
+ "info": "If true, will add a tool to the agent that returns the current date.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "add_current_date_tool",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "agent_description": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Agent Description [Deprecated]",
+ "dynamic": false,
+ "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "agent_description",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "A helpful assistant with access to the following tools:"
+ },
+ "agent_llm": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Model Provider",
+ "dynamic": false,
+ "info": "The provider of the language model that the agent will use to generate responses.",
+ "input_types": [],
+ "name": "agent_llm",
+ "options": [
+ "Amazon Bedrock",
+ "Anthropic",
+ "Azure OpenAI",
+ "Google Generative AI",
+ "Groq",
+ "NVIDIA",
+ "OpenAI",
+ "SambaNova",
+ "Custom"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "real_time_refresh": true,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "OpenAI"
+ },
+ "api_key": {
+ "_input_type": "SecretStrInput",
+ "advanced": false,
+ "display_name": "OpenAI API Key",
+ "dynamic": false,
+ "info": "The OpenAI API Key to use for the OpenAI model.",
+ "input_types": [
+ "Message"
+ ],
+ "load_from_db": false,
+ "name": "api_key",
+ "password": true,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "str",
+ "value": ""
+ },
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langchain_core.tools import StructuredTool\n\nfrom langflow.base.agents.agent import LCToolsAgentComponent\nfrom langflow.base.agents.events import ExceptionWithMessageError\nfrom langflow.base.models.model_input_constants import (\n ALL_PROVIDER_FIELDS,\n MODEL_DYNAMIC_UPDATE_FIELDS,\n MODEL_PROVIDERS_DICT,\n MODELS_METADATA,\n)\nfrom langflow.base.models.model_utils import get_model_name\nfrom langflow.components.helpers import CurrentDateComponent\nfrom langflow.components.helpers.memory import MemoryComponent\nfrom langflow.components.langchain_utilities.tool_calling import ToolCallingAgentComponent\nfrom langflow.custom.custom_component.component import _get_component_toolkit\nfrom langflow.custom.utils import update_component_build_config\nfrom langflow.field_typing import Tool\nfrom langflow.io import BoolInput, DropdownInput, MultilineInput, Output\nfrom langflow.logging import logger\nfrom langflow.schema.dotdict import dotdict\nfrom langflow.schema.message import Message\n\n\ndef set_advanced_true(component_input):\n component_input.advanced = True\n return component_input\n\n\nclass AgentComponent(ToolCallingAgentComponent):\n display_name: str = \"Agent\"\n description: str = \"Define the agent's instructions, then enter a task to complete using tools.\"\n icon = \"bot\"\n beta = False\n name = \"Agent\"\n\n memory_inputs = [set_advanced_true(component_input) for component_input in MemoryComponent().inputs]\n\n inputs = [\n DropdownInput(\n name=\"agent_llm\",\n display_name=\"Model Provider\",\n info=\"The provider of the language model that the agent will use to generate responses.\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"OpenAI\",\n real_time_refresh=True,\n input_types=[],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())] + [{\"icon\": \"brain\"}],\n ),\n *MODEL_PROVIDERS_DICT[\"OpenAI\"][\"inputs\"],\n MultilineInput(\n name=\"system_prompt\",\n display_name=\"Agent Instructions\",\n info=\"System Prompt: Initial instructions and context provided to guide the agent's behavior.\",\n value=\"You are a helpful assistant that can use tools to answer questions and perform tasks.\",\n advanced=False,\n ),\n *LCToolsAgentComponent._base_inputs,\n *memory_inputs,\n BoolInput(\n name=\"add_current_date_tool\",\n display_name=\"Current Date\",\n advanced=True,\n info=\"If true, will add a tool to the agent that returns the current date.\",\n value=True,\n ),\n ]\n outputs = [Output(name=\"response\", display_name=\"Response\", method=\"message_response\")]\n\n async def message_response(self) -> Message:\n try:\n # Get LLM model and validate\n llm_model, display_name = self.get_llm()\n if llm_model is None:\n msg = \"No language model selected. Please choose a model to proceed.\"\n raise ValueError(msg)\n self.model_name = get_model_name(llm_model, display_name=display_name)\n\n # Get memory data\n self.chat_history = await self.get_memory_data()\n\n # Add current date tool if enabled\n if self.add_current_date_tool:\n if not isinstance(self.tools, list): # type: ignore[has-type]\n self.tools = []\n current_date_tool = (await CurrentDateComponent(**self.get_base_args()).to_toolkit()).pop(0)\n if not isinstance(current_date_tool, StructuredTool):\n msg = \"CurrentDateComponent must be converted to a StructuredTool\"\n raise TypeError(msg)\n self.tools.append(current_date_tool)\n\n # Validate tools\n if not self.tools:\n msg = \"Tools are required to run the agent. Please add at least one tool.\"\n raise ValueError(msg)\n\n # Set up and run agent\n self.set(\n llm=llm_model,\n tools=self.tools,\n chat_history=self.chat_history,\n input_value=self.input_value,\n system_prompt=self.system_prompt,\n )\n agent = self.create_agent_runnable()\n return await self.run_agent(agent)\n\n except (ValueError, TypeError, KeyError) as e:\n logger.error(f\"{type(e).__name__}: {e!s}\")\n raise\n except ExceptionWithMessageError as e:\n logger.error(f\"ExceptionWithMessageError occurred: {e}\")\n raise\n except Exception as e:\n logger.error(f\"Unexpected error: {e!s}\")\n raise\n\n async def get_memory_data(self):\n memory_kwargs = {\n component_input.name: getattr(self, f\"{component_input.name}\") for component_input in self.memory_inputs\n }\n # filter out empty values\n memory_kwargs = {k: v for k, v in memory_kwargs.items() if v}\n\n return await MemoryComponent(**self.get_base_args()).set(**memory_kwargs).retrieve_messages()\n\n def get_llm(self):\n if not isinstance(self.agent_llm, str):\n return self.agent_llm, None\n\n try:\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if not provider_info:\n msg = f\"Invalid model provider: {self.agent_llm}\"\n raise ValueError(msg)\n\n component_class = provider_info.get(\"component_class\")\n display_name = component_class.display_name\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\", \"\")\n\n return self._build_llm_model(component_class, inputs, prefix), display_name\n\n except Exception as e:\n logger.error(f\"Error building {self.agent_llm} language model: {e!s}\")\n msg = f\"Failed to initialize language model: {e!s}\"\n raise ValueError(msg) from e\n\n def _build_llm_model(self, component, inputs, prefix=\"\"):\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n return component.set(**model_kwargs).build_model()\n\n def set_component_params(self, component):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n inputs = provider_info.get(\"inputs\")\n prefix = provider_info.get(\"prefix\")\n model_kwargs = {input_.name: getattr(self, f\"{prefix}{input_.name}\") for input_ in inputs}\n\n return component.set(**model_kwargs)\n return component\n\n def delete_fields(self, build_config: dotdict, fields: dict | list[str]) -> None:\n \"\"\"Delete specified fields from build_config.\"\"\"\n for field in fields:\n build_config.pop(field, None)\n\n def update_input_types(self, build_config: dotdict) -> dotdict:\n \"\"\"Update input types for all fields in build_config.\"\"\"\n for key, value in build_config.items():\n if isinstance(value, dict):\n if value.get(\"input_types\") is None:\n build_config[key][\"input_types\"] = []\n elif hasattr(value, \"input_types\") and value.input_types is None:\n value.input_types = []\n return build_config\n\n async def update_build_config(\n self, build_config: dotdict, field_value: str, field_name: str | None = None\n ) -> dotdict:\n # Iterate over all providers in the MODEL_PROVIDERS_DICT\n # Existing logic for updating build_config\n if field_name in (\"agent_llm\",):\n build_config[\"agent_llm\"][\"value\"] = field_value\n provider_info = MODEL_PROVIDERS_DICT.get(field_value)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call the component class's update_build_config method\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n\n provider_configs: dict[str, tuple[dict, list[dict]]] = {\n provider: (\n MODEL_PROVIDERS_DICT[provider][\"fields\"],\n [\n MODEL_PROVIDERS_DICT[other_provider][\"fields\"]\n for other_provider in MODEL_PROVIDERS_DICT\n if other_provider != provider\n ],\n )\n for provider in MODEL_PROVIDERS_DICT\n }\n if field_value in provider_configs:\n fields_to_add, fields_to_delete = provider_configs[field_value]\n\n # Delete fields from other providers\n for fields in fields_to_delete:\n self.delete_fields(build_config, fields)\n\n # Add provider-specific fields\n if field_value == \"OpenAI\" and not any(field in build_config for field in fields_to_add):\n build_config.update(fields_to_add)\n else:\n build_config.update(fields_to_add)\n # Reset input types for agent_llm\n build_config[\"agent_llm\"][\"input_types\"] = []\n elif field_value == \"Custom\":\n # Delete all provider fields\n self.delete_fields(build_config, ALL_PROVIDER_FIELDS)\n # Update with custom component\n custom_component = DropdownInput(\n name=\"agent_llm\",\n display_name=\"Language Model\",\n options=[*sorted(MODEL_PROVIDERS_DICT.keys()), \"Custom\"],\n value=\"Custom\",\n real_time_refresh=True,\n input_types=[\"LanguageModel\"],\n options_metadata=[MODELS_METADATA[key] for key in sorted(MODELS_METADATA.keys())]\n + [{\"icon\": \"brain\"}],\n )\n build_config.update({\"agent_llm\": custom_component.to_dict()})\n # Update input types for all fields\n build_config = self.update_input_types(build_config)\n\n # Validate required keys\n default_keys = [\n \"code\",\n \"_type\",\n \"agent_llm\",\n \"tools\",\n \"input_value\",\n \"add_current_date_tool\",\n \"system_prompt\",\n \"agent_description\",\n \"max_iterations\",\n \"handle_parsing_errors\",\n \"verbose\",\n ]\n missing_keys = [key for key in default_keys if key not in build_config]\n if missing_keys:\n msg = f\"Missing required keys in build_config: {missing_keys}\"\n raise ValueError(msg)\n if (\n isinstance(self.agent_llm, str)\n and self.agent_llm in MODEL_PROVIDERS_DICT\n and field_name in MODEL_DYNAMIC_UPDATE_FIELDS\n ):\n provider_info = MODEL_PROVIDERS_DICT.get(self.agent_llm)\n if provider_info:\n component_class = provider_info.get(\"component_class\")\n component_class = self.set_component_params(component_class)\n prefix = provider_info.get(\"prefix\")\n if component_class and hasattr(component_class, \"update_build_config\"):\n # Call each component class's update_build_config method\n # remove the prefix from the field_name\n if isinstance(field_name, str) and isinstance(prefix, str):\n field_name = field_name.replace(prefix, \"\")\n build_config = await update_component_build_config(\n component_class, build_config, field_value, \"model_name\"\n )\n return dotdict({k: v.to_dict() if hasattr(v, \"to_dict\") else v for k, v in build_config.items()})\n\n async def to_toolkit(self) -> list[Tool]:\n component_toolkit = _get_component_toolkit()\n tools_names = self._build_tools_names()\n agent_description = self.get_tool_description()\n # TODO: Agent Description Depreciated Feature to be removed\n description = f\"{agent_description}{tools_names}\"\n tools = component_toolkit(component=self).get_tools(\n tool_name=self.get_tool_name(), tool_description=description, callbacks=self.get_langchain_callbacks()\n )\n if hasattr(self, \"tools_metadata\"):\n tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)\n return tools\n"
+ },
+ "handle_parsing_errors": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Handle Parse Errors",
+ "dynamic": false,
+ "info": "Should the Agent fix errors when reading user input for better processing?",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "handle_parsing_errors",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ },
+ "input_value": {
+ "_input_type": "MessageTextInput",
+ "advanced": false,
+ "display_name": "Input",
+ "dynamic": false,
+ "info": "The input provided by the user for the agent to process.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "json_mode": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "JSON Mode",
+ "dynamic": false,
+ "info": "If True, it will output JSON regardless of passing a schema.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "json_mode",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": false
+ },
+ "max_iterations": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Iterations",
+ "dynamic": false,
+ "info": "The maximum number of attempts the agent can make to complete its task before it stops.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_iterations",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 15
+ },
+ "max_retries": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Retries",
+ "dynamic": false,
+ "info": "The maximum number of retries to make when generating.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_retries",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 5
+ },
+ "max_tokens": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Max Tokens",
+ "dynamic": false,
+ "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "max_tokens",
+ "placeholder": "",
+ "range_spec": {
+ "max": 128000,
+ "min": 0,
+ "step": 0.1,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": ""
+ },
+ "memory": {
+ "_input_type": "HandleInput",
+ "advanced": true,
+ "display_name": "External Memory",
+ "dynamic": false,
+ "info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
+ "input_types": [
+ "Memory"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "memory",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "model_kwargs": {
+ "_input_type": "DictInput",
+ "advanced": true,
+ "display_name": "Model Kwargs",
+ "dynamic": false,
+ "info": "Additional keyword arguments to pass to the model.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "model_kwargs",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "type": "dict",
+ "value": {}
+ },
+ "model_name": {
+ "_input_type": "DropdownInput",
+ "advanced": false,
+ "combobox": true,
+ "dialog_inputs": {},
+ "display_name": "Model Name",
+ "dynamic": false,
+ "info": "To see the model names, first choose a provider. Then, enter your API key and click the refresh button next to the model name.",
+ "name": "model_name",
+ "options": [
+ "gpt-4o-mini",
+ "gpt-4o",
+ "gpt-4.5-preview",
+ "gpt-4-turbo",
+ "gpt-4-turbo-preview",
+ "gpt-4",
+ "gpt-3.5-turbo"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "real_time_refresh": false,
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "gpt-4o-mini"
+ },
+ "n_messages": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Number of Messages",
+ "dynamic": false,
+ "info": "Number of messages to retrieve.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "n_messages",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 100
+ },
+ "openai_api_base": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "OpenAI API Base",
+ "dynamic": false,
+ "info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "openai_api_base",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "order": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Order",
+ "dynamic": false,
+ "info": "Order of the messages.",
+ "name": "order",
+ "options": [
+ "Ascending",
+ "Descending"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Ascending"
+ },
+ "seed": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Seed",
+ "dynamic": false,
+ "info": "The seed controls the reproducibility of the job.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "seed",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 1
+ },
+ "sender": {
+ "_input_type": "DropdownInput",
+ "advanced": true,
+ "combobox": false,
+ "dialog_inputs": {},
+ "display_name": "Sender Type",
+ "dynamic": false,
+ "info": "Filter by sender type.",
+ "name": "sender",
+ "options": [
+ "Machine",
+ "User",
+ "Machine and User"
+ ],
+ "options_metadata": [],
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Machine and User"
+ },
+ "sender_name": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Sender Name",
+ "dynamic": false,
+ "info": "Filter by sender name.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sender_name",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "session_id": {
+ "_input_type": "MessageTextInput",
+ "advanced": true,
+ "display_name": "Session ID",
+ "dynamic": false,
+ "info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "session_id",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": ""
+ },
+ "system_prompt": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Agent Instructions",
+ "dynamic": false,
+ "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "system_prompt",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "You are a helpful assistant that can use tools to answer questions and perform tasks."
+ },
+ "temperature": {
+ "_input_type": "SliderInput",
+ "advanced": true,
+ "display_name": "Temperature",
+ "dynamic": false,
+ "info": "",
+ "max_label": "",
+ "max_label_icon": "",
+ "min_label": "",
+ "min_label_icon": "",
+ "name": "temperature",
+ "placeholder": "",
+ "range_spec": {
+ "max": 2,
+ "min": 0,
+ "step": 0.01,
+ "step_type": "float"
+ },
+ "required": false,
+ "show": true,
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "title_case": false,
+ "tool_mode": false,
+ "type": "slider",
+ "value": 0.1
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": true,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{sender_name}: {text}"
+ },
+ "timeout": {
+ "_input_type": "IntInput",
+ "advanced": true,
+ "display_name": "Timeout",
+ "dynamic": false,
+ "info": "The timeout for requests to OpenAI completion API.",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "timeout",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "int",
+ "value": 700
+ },
+ "tools": {
+ "_input_type": "HandleInput",
+ "advanced": false,
+ "display_name": "Tools",
+ "dynamic": false,
+ "info": "These are the tools that the agent can use to help with tasks.",
+ "input_types": [
+ "Tool"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "tools",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "verbose": {
+ "_input_type": "BoolInput",
+ "advanced": true,
+ "display_name": "Verbose",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "name": "verbose",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "bool",
+ "value": true
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "Agent"
+ },
+ "dragging": false,
+ "id": "Agent-O1xzr",
+ "measured": {
+ "height": 624,
+ "width": 320
+ },
+ "position": {
+ "x": 1176.7234802624862,
+ "y": 190.59802023099996
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "note-OAlDJ",
+ "node": {
+ "description": "# News Aggregator\n\nThis flow extracts structured data from a URL.\n## Prerequisites\n\n* **[AgentQL API Key](https://dev.agentql.com/api-keys)**\n* **[OpenAI API Key](https://platform.openai.com/)**\n\n## Quick Start\n\n1. Add your [AgentQL API Key](https://dev.agentql.com/api-keys) to the **AgentQL** component.\n2. Add your [OpenAI API Key](https://platform.openai.com/) to the **Agent** component.\n3. Click **Playground** and enter a question.\nThe **Agent** component populates the **Agent QL** component's **URL** and **Query** fields, and returns a structured response to your question.",
+ "display_name": "",
+ "documentation": "",
+ "template": {
+ "backgroundColor": "amber"
+ }
+ },
+ "type": "note"
+ },
+ "dragging": false,
+ "id": "note-OAlDJ",
+ "measured": {
+ "height": 604,
+ "width": 325
+ },
+ "position": {
+ "x": 215.10951666579462,
+ "y": -25.20466668876412
+ },
+ "selected": false,
+ "type": "noteNode"
+ }
+ ],
+ "viewport": {
+ "x": -185.56277678480728,
+ "y": 98.64473921569584,
+ "zoom": 1.3444274567846564
+ }
+ },
+ "description": "Extracts data and information from webpages.",
+ "endpoint_name": null,
+ "icon": "Newspaper",
+ "id": "a5383bea-acb4-44ba-adeb-80c0fcb168b9",
+ "is_component": false,
+ "last_tested_version": "1.2.0",
+ "name": "News Aggregator",
+ "tags": [
+ "web-scraping",
+ "agents"
+ ]
+}
\ No newline at end of file
diff --git a/langflow/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json
new file mode 100644
index 0000000..ab8f074
--- /dev/null
+++ b/langflow/src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json
@@ -0,0 +1,2386 @@
+{
+ "data": {
+ "edges": [
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-qN4UL",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "AnthropicModel-3QKQF",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-qN4UL{œdataTypeœ:œParseDataœ,œidœ:œParseData-qN4ULœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-3QKQF{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-3QKQFœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ParseData-qN4UL",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-qN4ULœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "AnthropicModel-3QKQF",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-3QKQFœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "TextInput",
+ "id": "TextInput-2kYku",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "system_message",
+ "id": "AnthropicModel-3QKQF",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-TextInput-2kYku{œdataTypeœ:œTextInputœ,œidœ:œTextInput-2kYkuœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-3QKQF{œfieldNameœ:œsystem_messageœ,œidœ:œAnthropicModel-3QKQFœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "TextInput-2kYku",
+ "sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-2kYkuœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "AnthropicModel-3QKQF",
+ "targetHandle": "{œfieldNameœ: œsystem_messageœ, œidœ: œAnthropicModel-3QKQFœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AnthropicModel",
+ "id": "AnthropicModel-3QKQF",
+ "name": "text_output",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "ChatOutput-Anz6s",
+ "inputTypes": [
+ "Data",
+ "DataFrame",
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-AnthropicModel-3QKQF{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-3QKQFœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-Anz6s{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-Anz6sœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "AnthropicModel-3QKQF",
+ "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-3QKQFœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "ChatOutput-Anz6s",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-Anz6sœ, œinputTypesœ: [œDataœ, œDataFrameœ, œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "AnthropicModel",
+ "id": "AnthropicModel-hy2sm",
+ "name": "model_output",
+ "output_types": [
+ "LanguageModel"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "llm",
+ "id": "StructuredOutputv2-ZQldd",
+ "inputTypes": [
+ "LanguageModel"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-AnthropicModel-hy2sm{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-hy2smœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-StructuredOutputv2-ZQldd{œfieldNameœ:œllmœ,œidœ:œStructuredOutputv2-ZQlddœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}",
+ "source": "AnthropicModel-hy2sm",
+ "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-hy2smœ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}",
+ "target": "StructuredOutputv2-ZQldd",
+ "targetHandle": "{œfieldNameœ: œllmœ, œidœ: œStructuredOutputv2-ZQlddœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}"
+ },
+ {
+ "animated": false,
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "ParseData",
+ "id": "ParseData-idUvo",
+ "name": "text",
+ "output_types": [
+ "Message"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "input_value",
+ "id": "StructuredOutputv2-ZQldd",
+ "inputTypes": [
+ "Message"
+ ],
+ "type": "str"
+ }
+ },
+ "id": "reactflow__edge-ParseData-idUvo{œdataTypeœ:œParseDataœ,œidœ:œParseData-idUvoœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-StructuredOutputv2-ZQldd{œfieldNameœ:œinput_valueœ,œidœ:œStructuredOutputv2-ZQlddœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
+ "source": "ParseData-idUvo",
+ "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-idUvoœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
+ "target": "StructuredOutputv2-ZQldd",
+ "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œStructuredOutputv2-ZQlddœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
+ },
+ {
+ "className": "",
+ "data": {
+ "sourceHandle": {
+ "dataType": "File",
+ "id": "File-GICjp",
+ "name": "data",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-idUvo",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "reactflow__edge-File-GICjp{œdataTypeœ:œFileœ,œidœ:œFile-GICjpœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-idUvo{œfieldNameœ:œdataœ,œidœ:œParseData-idUvoœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "File-GICjp",
+ "sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-GICjpœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-idUvo",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-idUvoœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ },
+ {
+ "data": {
+ "sourceHandle": {
+ "dataType": "StructuredOutput",
+ "id": "StructuredOutputv2-ZQldd",
+ "name": "structured_output",
+ "output_types": [
+ "Data"
+ ]
+ },
+ "targetHandle": {
+ "fieldName": "data",
+ "id": "ParseData-qN4UL",
+ "inputTypes": [
+ "Data"
+ ],
+ "type": "other"
+ }
+ },
+ "id": "xy-edge__StructuredOutputv2-ZQldd{œdataTypeœ:œStructuredOutputœ,œidœ:œStructuredOutputv2-ZQlddœ,œnameœ:œstructured_outputœ,œoutput_typesœ:[œDataœ]}-ParseData-qN4UL{œfieldNameœ:œdataœ,œidœ:œParseData-qN4ULœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
+ "source": "StructuredOutputv2-ZQldd",
+ "sourceHandle": "{œdataTypeœ: œStructuredOutputœ, œidœ: œStructuredOutputv2-ZQlddœ, œnameœ: œstructured_outputœ, œoutput_typesœ: [œDataœ]}",
+ "target": "ParseData-qN4UL",
+ "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-qN4ULœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
+ }
+ ],
+ "nodes": [
+ {
+ "data": {
+ "id": "ParseData-idUvo",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data objects into Messages using any {field_name} from input data.",
+ "display_name": "Data to Message",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{text}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "id": "ParseData-idUvo",
+ "measured": {
+ "height": 342,
+ "width": 320
+ },
+ "position": {
+ "x": 305.18655230615144,
+ "y": 482.32049719078225
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "ParseData-qN4UL",
+ "node": {
+ "base_classes": [
+ "Data",
+ "Message"
+ ],
+ "beta": false,
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Convert Data objects into Messages using any {field_name} from input data.",
+ "display_name": "Data to Message",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "data",
+ "template",
+ "sep"
+ ],
+ "frozen": false,
+ "icon": "message-square",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {
+ "legacy_name": "Parse Data"
+ },
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "parse_data",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ },
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Data List",
+ "method": "parse_data_as_list",
+ "name": "data_list",
+ "selected": "Data",
+ "tool_mode": true,
+ "types": [
+ "Data"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
+ },
+ "data": {
+ "_input_type": "DataInput",
+ "advanced": false,
+ "display_name": "Data",
+ "dynamic": false,
+ "info": "The data to convert to text.",
+ "input_types": [
+ "Data"
+ ],
+ "list": true,
+ "list_add_label": "Add More",
+ "name": "data",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "other",
+ "value": ""
+ },
+ "sep": {
+ "_input_type": "StrInput",
+ "advanced": true,
+ "display_name": "Separator",
+ "dynamic": false,
+ "info": "",
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "name": "sep",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "\n"
+ },
+ "template": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Template",
+ "dynamic": false,
+ "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "template",
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "{results}"
+ }
+ },
+ "tool_mode": false
+ },
+ "showNode": true,
+ "type": "ParseData"
+ },
+ "dragging": false,
+ "id": "ParseData-qN4UL",
+ "measured": {
+ "height": 342,
+ "width": 320
+ },
+ "position": {
+ "x": 1743.003626296405,
+ "y": 585.3047133010242
+ },
+ "selected": false,
+ "type": "genericNode"
+ },
+ {
+ "data": {
+ "id": "TextInput-2kYku",
+ "node": {
+ "base_classes": [
+ "Message"
+ ],
+ "beta": false,
+ "category": "inputs",
+ "conditional_paths": [],
+ "custom_fields": {},
+ "description": "Get text inputs from the Playground.",
+ "display_name": "Text Input",
+ "documentation": "",
+ "edited": false,
+ "field_order": [
+ "input_value"
+ ],
+ "frozen": false,
+ "icon": "type",
+ "key": "TextInput",
+ "legacy": false,
+ "lf_version": "1.1.5",
+ "metadata": {},
+ "minimized": false,
+ "output_types": [],
+ "outputs": [
+ {
+ "allows_loop": false,
+ "cache": true,
+ "display_name": "Message",
+ "method": "text_response",
+ "name": "text",
+ "selected": "Message",
+ "tool_mode": true,
+ "types": [
+ "Message"
+ ],
+ "value": "__UNDEFINED__"
+ }
+ ],
+ "pinned": false,
+ "score": 0.0020353564437605998,
+ "template": {
+ "_type": "Component",
+ "code": {
+ "advanced": true,
+ "dynamic": true,
+ "fileTypes": [],
+ "file_path": "",
+ "info": "",
+ "list": false,
+ "load_from_db": false,
+ "multiline": true,
+ "name": "code",
+ "password": false,
+ "placeholder": "",
+ "required": true,
+ "show": true,
+ "title_case": false,
+ "type": "code",
+ "value": "from langflow.base.io.text import TextComponent\nfrom langflow.io import MultilineInput, Output\nfrom langflow.schema.message import Message\n\n\nclass TextInputComponent(TextComponent):\n display_name = \"Text Input\"\n description = \"Get text inputs from the Playground.\"\n icon = \"type\"\n name = \"TextInput\"\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Text to be passed as input.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"text\", method=\"text_response\"),\n ]\n\n def text_response(self) -> Message:\n return Message(\n text=self.input_value,\n )\n"
+ },
+ "input_value": {
+ "_input_type": "MultilineInput",
+ "advanced": false,
+ "display_name": "Text",
+ "dynamic": false,
+ "info": "Text to be passed as input.",
+ "input_types": [
+ "Message"
+ ],
+ "list": false,
+ "list_add_label": "Add More",
+ "load_from_db": false,
+ "multiline": true,
+ "name": "input_value",
+ "placeholder": "",
+ "required": false,
+ "show": true,
+ "title_case": false,
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "type": "str",
+ "value": "Generate a single-page portfolio website using HTML and CSS that takes a resume in JSON format as input and dynamically renders the following sections with a well-structured and aesthetic layout:\n\n📌 Sections & Content Requirements:\n\t1.\tHeader:\n\t•\tDisplay the person’s name, job title, and a professional tagline prominently.\n\t•\tEnsure the name is bold and eye-catching, with subtle emphasis on the job title.\n\t2.\tAbout Me:\n\t•\tExtract and enhance the personal summary from the resume, making it engaging, concise, and readable.\n\t•\tUse short, well-structured sentences to improve clarity.\n\t3.\tExperience:\n\t•\tList past job roles with company names, durations, and a refined description of responsibilities.\n\t•\tEnsure descriptions are professionally formatted, with key contributions highlighted.\n\t4.\tProjects:\n\t•\tDisplay projects as a neatly styled list.\n\t•\tEach project includes a title, refined description, technologies used, and rounded buttons linking to GitHub or live demo (if available).\n\t5.\tSkills:\n\t•\tDisplay skills as aesthetic pill-style badges below the Skills section title. Display all skills mentioned\n\t•\tArrange in a well-balanced, ensuring readability and consistent spacing.\n\t6.\tEducation:\n\t•\tList degrees, institutions, and years attended in a clean and professional format.\n\t•\tMaintain uniformity in typography and spacing.\n\t7.\tAwards & Publications (if any):\n\t•\tIf the resume contains awards or publications, display them in a separate section.\n\t•\tEach entry includes the title, organization, and year, ensuring clean alignment.\n\t8.\tContact:\n\t•\tDisplay email, social media links, and an optional contact button.\n\t•\tEnsure social media links are clearly visible, with modern and accessible icon buttons.\n\n🎨 Styling & Aesthetic Requirements:\n\n✅ Minimalist & Elegant:\n\t•\tClean layout with ample whitespace for breathing room.\n\t•\tConsistent spacing across all sections.\n\n✅ Fast & Lightweight:\n\t•\tUse only HTML & CSS (no JavaScript required).\n\t•\tEnsure a smooth, fast-loading experience.\n\n✅ Beautiful Typography:\n\t•\tUse a modern, professional Google Font that complements the design.\n\t•\tEnsure text readability with proper size, weight, and contrast.\n\n✅ Visually Harmonious Colors & Themes:\n\t•\tFollow a cohesive color palette that ensures a modern, professional feel.\n\t•\tEnsure background colors, text colors, and section dividers are consistent and complementary.\n\t•\tAvoid hard-to-read combinations (e.g., light text on a light background).\n\n✅ Responsive & Readable Design:\n\t•\tMobile-first approach, adapting to desktop, tablet, and mobile views.\n\t•\tMaintain consistency in padding, margins, and alignment.\n\n✅ Dark Mode Support:\n\t•\tAuto-detect system settings and adjust the theme accordingly.\n\t•\tEnsure clear contrasts and readability in both light and dark modes.\n\n✅ Embedded CSS:\n\t•\tEnsure CSS is written directly in the HTML file within
+
+
+
+
+);
+export default SvgAWS;
diff --git a/langflow/src/frontend/src/icons/AWS/AWS.svg b/langflow/src/frontend/src/icons/AWS/AWS.svg
new file mode 100644
index 0000000..4715937
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AWS/AWS.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/AWS/index.tsx b/langflow/src/frontend/src/icons/AWS/index.tsx
new file mode 100644
index 0000000..39063ea
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AWS/index.tsx
@@ -0,0 +1,10 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgAWS from "./AWS";
+
+export const AWSIcon = forwardRef>(
+ (props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/AgentQL/AgentQL.jsx b/langflow/src/frontend/src/icons/AgentQL/AgentQL.jsx
new file mode 100644
index 0000000..0a3abc2
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AgentQL/AgentQL.jsx
@@ -0,0 +1,48 @@
+const SvgAgentQL = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgAgentQL;
diff --git a/langflow/src/frontend/src/icons/AgentQL/AgentQL.svg b/langflow/src/frontend/src/icons/AgentQL/AgentQL.svg
new file mode 100644
index 0000000..8ea199f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AgentQL/AgentQL.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/AgentQL/index.tsx b/langflow/src/frontend/src/icons/AgentQL/index.tsx
new file mode 100644
index 0000000..d845479
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AgentQL/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgAgentQL from "./AgentQL";
+
+export const AgentQLIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Airbyte/Airbyte.jsx b/langflow/src/frontend/src/icons/Airbyte/Airbyte.jsx
new file mode 100644
index 0000000..a4d6604
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Airbyte/Airbyte.jsx
@@ -0,0 +1,23 @@
+const SvgAirbyte = (props) => (
+
+
+
+);
+export default SvgAirbyte;
diff --git a/langflow/src/frontend/src/icons/Airbyte/airbyte.svg b/langflow/src/frontend/src/icons/Airbyte/airbyte.svg
new file mode 100644
index 0000000..a727517
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Airbyte/airbyte.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Airbyte/index.tsx b/langflow/src/frontend/src/icons/Airbyte/index.tsx
new file mode 100644
index 0000000..30f68bf
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Airbyte/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgAirbyte from "./Airbyte";
+
+export const AirbyteIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Anthropic/Anthropic.jsx b/langflow/src/frontend/src/icons/Anthropic/Anthropic.jsx
new file mode 100644
index 0000000..c5615c0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Anthropic/Anthropic.jsx
@@ -0,0 +1,17 @@
+const SvgAnthropic = (props) => (
+
+
+
+);
+export default SvgAnthropic;
diff --git a/langflow/src/frontend/src/icons/Anthropic/AnthropicBox.jsx b/langflow/src/frontend/src/icons/Anthropic/AnthropicBox.jsx
new file mode 100644
index 0000000..f31af1f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Anthropic/AnthropicBox.jsx
@@ -0,0 +1,22 @@
+const SvgAnthropicBox = (props) => (
+
+
+
+
+
+
+);
+export default SvgAnthropicBox;
diff --git a/langflow/src/frontend/src/icons/Anthropic/anthropic.svg b/langflow/src/frontend/src/icons/Anthropic/anthropic.svg
new file mode 100644
index 0000000..67ae02e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Anthropic/anthropic.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Anthropic/anthropic_box.svg b/langflow/src/frontend/src/icons/Anthropic/anthropic_box.svg
new file mode 100644
index 0000000..fa9923e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Anthropic/anthropic_box.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Anthropic/index.tsx b/langflow/src/frontend/src/icons/Anthropic/index.tsx
new file mode 100644
index 0000000..651223a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Anthropic/index.tsx
@@ -0,0 +1,14 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgAnthropicBox from "./AnthropicBox";
+
+export const AnthropicIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isDark = useDarkStore((state) => state.dark);
+
+ return (
+
+ );
+});
diff --git a/langflow/src/frontend/src/icons/Apify/Apify.jsx b/langflow/src/frontend/src/icons/Apify/Apify.jsx
new file mode 100644
index 0000000..4a60350
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Apify/Apify.jsx
@@ -0,0 +1,27 @@
+const SvgApifyLogo = (props) => (
+
+
+
+
+
+);
+export default SvgApifyLogo;
diff --git a/langflow/src/frontend/src/icons/Apify/apify.svg b/langflow/src/frontend/src/icons/Apify/apify.svg
new file mode 100644
index 0000000..65dce4c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Apify/apify.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Apify/index.tsx b/langflow/src/frontend/src/icons/Apify/index.tsx
new file mode 100644
index 0000000..bd83ec4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Apify/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgApifyLogo from "./Apify";
+
+export const ApifyIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/ArXiv/ArXivIcon.jsx b/langflow/src/frontend/src/icons/ArXiv/ArXivIcon.jsx
new file mode 100644
index 0000000..a5e051f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ArXiv/ArXivIcon.jsx
@@ -0,0 +1,120 @@
+const ArXivIcon = (props) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ArXivIcon;
diff --git a/langflow/src/frontend/src/icons/ArXiv/arxiv.svg b/langflow/src/frontend/src/icons/ArXiv/arxiv.svg
new file mode 100644
index 0000000..e1c0336
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ArXiv/arxiv.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/ArXiv/index.tsx b/langflow/src/frontend/src/icons/ArXiv/index.tsx
new file mode 100644
index 0000000..2d00436
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ArXiv/index.tsx
@@ -0,0 +1,10 @@
+import React, { forwardRef } from "react";
+import SvgArXivIcon from "./ArXivIcon";
+
+export const ArXivIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
+
+export default ArXivIcon;
diff --git a/langflow/src/frontend/src/icons/Arize/Arize.jsx b/langflow/src/frontend/src/icons/Arize/Arize.jsx
new file mode 100644
index 0000000..04b9e62
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Arize/Arize.jsx
@@ -0,0 +1,20 @@
+const SvgArize = (props) => (
+
+
+
+
+);
+
+export default SvgArize;
diff --git a/langflow/src/frontend/src/icons/Arize/arize.svg b/langflow/src/frontend/src/icons/Arize/arize.svg
new file mode 100644
index 0000000..57b6ca9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Arize/arize.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Arize/index.tsx b/langflow/src/frontend/src/icons/Arize/index.tsx
new file mode 100644
index 0000000..c473574
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Arize/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgArize from "./Arize";
+
+export const ArizeIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.jsx b/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.jsx
new file mode 100644
index 0000000..d7f83ed
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.jsx
@@ -0,0 +1,20 @@
+const AssemblyAISVG = (props) => (
+
+
+
+
+);
+export default AssemblyAISVG;
diff --git a/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.svg b/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.svg
new file mode 100644
index 0000000..85a7c19
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AssemblyAI/AssemblyAI.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/AssemblyAI/index.tsx b/langflow/src/frontend/src/icons/AssemblyAI/index.tsx
new file mode 100644
index 0000000..99b0f8f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AssemblyAI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import AssemblyAISVG from "./AssemblyAI";
+
+export const AssemblyAIIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/AstraDB/AstraDB.jsx b/langflow/src/frontend/src/icons/AstraDB/AstraDB.jsx
new file mode 100644
index 0000000..8ba383f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AstraDB/AstraDB.jsx
@@ -0,0 +1,22 @@
+import { stringToBool } from "@/utils/utils";
+
+const AstraSVG = (props) => (
+
+
+
+
+);
+export default AstraSVG;
diff --git a/langflow/src/frontend/src/icons/AstraDB/Favicon.svg b/langflow/src/frontend/src/icons/AstraDB/Favicon.svg
new file mode 100644
index 0000000..4d60c77
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AstraDB/Favicon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/AstraDB/index.tsx b/langflow/src/frontend/src/icons/AstraDB/index.tsx
new file mode 100644
index 0000000..a35786c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AstraDB/index.tsx
@@ -0,0 +1,11 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import AstraSVG from "./AstraDB";
+
+export const AstraDBIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/AstraDB/old-astra-icon.svg b/langflow/src/frontend/src/icons/AstraDB/old-astra-icon.svg
new file mode 100644
index 0000000..b62fa8f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AstraDB/old-astra-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/AzLogo/AzLogo.jsx b/langflow/src/frontend/src/icons/AzLogo/AzLogo.jsx
new file mode 100644
index 0000000..0aaa350
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AzLogo/AzLogo.jsx
@@ -0,0 +1,17 @@
+const SvgAzLogo = (props) => (
+
+
+
+);
+export default SvgAzLogo;
diff --git a/langflow/src/frontend/src/icons/AzLogo/az_logo.svg b/langflow/src/frontend/src/icons/AzLogo/az_logo.svg
new file mode 100644
index 0000000..4f2e757
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AzLogo/az_logo.svg
@@ -0,0 +1,49 @@
+
+
diff --git a/langflow/src/frontend/src/icons/AzLogo/index.tsx b/langflow/src/frontend/src/icons/AzLogo/index.tsx
new file mode 100644
index 0000000..fa8f560
--- /dev/null
+++ b/langflow/src/frontend/src/icons/AzLogo/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgAzLogo from "./AzLogo";
+
+export const AzIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Azure/Azure.jsx b/langflow/src/frontend/src/icons/Azure/Azure.jsx
new file mode 100644
index 0000000..038fe26
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Azure/Azure.jsx
@@ -0,0 +1,62 @@
+export const SvgAzure = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgAzure;
diff --git a/langflow/src/frontend/src/icons/Azure/index.tsx b/langflow/src/frontend/src/icons/Azure/index.tsx
new file mode 100644
index 0000000..cde247d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Azure/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgAzure from "./Azure";
+
+export const AzureIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Bing/Bing.jsx b/langflow/src/frontend/src/icons/Bing/Bing.jsx
new file mode 100644
index 0000000..bf40f0d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Bing/Bing.jsx
@@ -0,0 +1,94 @@
+const SvgBing = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgBing;
diff --git a/langflow/src/frontend/src/icons/Bing/bing.svg b/langflow/src/frontend/src/icons/Bing/bing.svg
new file mode 100644
index 0000000..0d93e37
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Bing/bing.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Bing/index.tsx b/langflow/src/frontend/src/icons/Bing/index.tsx
new file mode 100644
index 0000000..60e70d6
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Bing/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgBing from "./Bing";
+
+export const BingIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/BotMessageSquare/BotMessageSquare.jsx b/langflow/src/frontend/src/icons/BotMessageSquare/BotMessageSquare.jsx
new file mode 100644
index 0000000..d0f2de7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/BotMessageSquare/BotMessageSquare.jsx
@@ -0,0 +1,23 @@
+const SvgBotMessageSquare = (props) => (
+
+
+
+
+
+
+
+
+);
+export default SvgBotMessageSquare;
diff --git a/langflow/src/frontend/src/icons/BotMessageSquare/index.tsx b/langflow/src/frontend/src/icons/BotMessageSquare/index.tsx
new file mode 100644
index 0000000..c3b4b04
--- /dev/null
+++ b/langflow/src/frontend/src/icons/BotMessageSquare/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgChroma from "./BotMessageSquare";
+
+export const BotMessageSquareIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Cassandra/Cassandra.jsx b/langflow/src/frontend/src/icons/Cassandra/Cassandra.jsx
new file mode 100644
index 0000000..97a065a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cassandra/Cassandra.jsx
@@ -0,0 +1,73 @@
+const CassandraSVG = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default CassandraSVG;
diff --git a/langflow/src/frontend/src/icons/Cassandra/cassandra.svg b/langflow/src/frontend/src/icons/Cassandra/cassandra.svg
new file mode 100644
index 0000000..6d6cdf0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cassandra/cassandra.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Cassandra/index.tsx b/langflow/src/frontend/src/icons/Cassandra/index.tsx
new file mode 100644
index 0000000..7fe917b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cassandra/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import CassandraSVG from "./Cassandra";
+
+export const CassandraIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ChromaIcon/Chroma.jsx b/langflow/src/frontend/src/icons/ChromaIcon/Chroma.jsx
new file mode 100644
index 0000000..2aa0660
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ChromaIcon/Chroma.jsx
@@ -0,0 +1,22 @@
+const SvgChroma = (props) => (
+
+
+
+
+
+
+);
+export default SvgChroma;
diff --git a/langflow/src/frontend/src/icons/ChromaIcon/chroma.svg b/langflow/src/frontend/src/icons/ChromaIcon/chroma.svg
new file mode 100644
index 0000000..6409068
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ChromaIcon/chroma.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/ChromaIcon/index.tsx b/langflow/src/frontend/src/icons/ChromaIcon/index.tsx
new file mode 100644
index 0000000..657d479
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ChromaIcon/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgChroma from "./Chroma";
+
+export const ChromaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Clickhouse/Clickhouse.jsx b/langflow/src/frontend/src/icons/Clickhouse/Clickhouse.jsx
new file mode 100644
index 0000000..61880df
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Clickhouse/Clickhouse.jsx
@@ -0,0 +1,17 @@
+const SvgClickhouseIcon = (props) => (
+
+
+
+
+);
+
+export default SvgClickhouseIcon;
diff --git a/langflow/src/frontend/src/icons/Clickhouse/clickhouse.svg b/langflow/src/frontend/src/icons/Clickhouse/clickhouse.svg
new file mode 100644
index 0000000..f2144b5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Clickhouse/clickhouse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Clickhouse/index.tsx b/langflow/src/frontend/src/icons/Clickhouse/index.tsx
new file mode 100644
index 0000000..7038b83
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Clickhouse/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgClickhouseIcon from "./Clickhouse";
+
+export const ClickhouseIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Cloudflare/Cloudflare.jsx b/langflow/src/frontend/src/icons/Cloudflare/Cloudflare.jsx
new file mode 100644
index 0000000..5ce65fb
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cloudflare/Cloudflare.jsx
@@ -0,0 +1,14 @@
+const SvgCloudflareIcon = (props) => (
+
+
+
+
+);
+
+export default SvgCloudflareIcon;
diff --git a/langflow/src/frontend/src/icons/Cloudflare/cloudflare.svg b/langflow/src/frontend/src/icons/Cloudflare/cloudflare.svg
new file mode 100644
index 0000000..76559f1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cloudflare/cloudflare.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Cloudflare/index.tsx b/langflow/src/frontend/src/icons/Cloudflare/index.tsx
new file mode 100644
index 0000000..529badd
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cloudflare/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgCloudflareIcon from "./Cloudflare";
+
+export const CloudflareIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Cohere/Cohere.jsx b/langflow/src/frontend/src/icons/Cohere/Cohere.jsx
new file mode 100644
index 0000000..6b78b2b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cohere/Cohere.jsx
@@ -0,0 +1,52 @@
+const SvgCohere = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgCohere;
diff --git a/langflow/src/frontend/src/icons/Cohere/cohere.svg b/langflow/src/frontend/src/icons/Cohere/cohere.svg
new file mode 100644
index 0000000..54ead3c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cohere/cohere.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Cohere/index.tsx b/langflow/src/frontend/src/icons/Cohere/index.tsx
new file mode 100644
index 0000000..58b53b0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Cohere/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgCohere from "./Cohere";
+
+export const CohereIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Composio/Composio.svg b/langflow/src/frontend/src/icons/Composio/Composio.svg
new file mode 100644
index 0000000..7ac76eb
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Composio/Composio.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Composio/composio.jsx b/langflow/src/frontend/src/icons/Composio/composio.jsx
new file mode 100644
index 0000000..2d683ac
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Composio/composio.jsx
@@ -0,0 +1,74 @@
+const Icon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default Icon;
diff --git a/langflow/src/frontend/src/icons/Composio/index.tsx b/langflow/src/frontend/src/icons/Composio/index.tsx
new file mode 100644
index 0000000..91424b1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Composio/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import ComposioIconSVG from "./composio";
+
+export const ComposioIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Confluence/Confluence.jsx b/langflow/src/frontend/src/icons/Confluence/Confluence.jsx
new file mode 100644
index 0000000..a11e95a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Confluence/Confluence.jsx
@@ -0,0 +1,25 @@
+const ConfluenceIcon = (props) => (
+
+
+
+
+);
+
+export default ConfluenceIcon;
diff --git a/langflow/src/frontend/src/icons/Confluence/Confluence.svg b/langflow/src/frontend/src/icons/Confluence/Confluence.svg
new file mode 100644
index 0000000..f78072a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Confluence/Confluence.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Confluence/index.tsx b/langflow/src/frontend/src/icons/Confluence/index.tsx
new file mode 100644
index 0000000..fc695ad
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Confluence/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgConfluence from "./Confluence";
+
+export const ConfluenceIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Couchbase/Couchbase.jsx b/langflow/src/frontend/src/icons/Couchbase/Couchbase.jsx
new file mode 100644
index 0000000..9259aae
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Couchbase/Couchbase.jsx
@@ -0,0 +1,17 @@
+const SvgCouchbaseIcon = (props) => (
+
+
+
+);
+
+export default SvgCouchbaseIcon;
diff --git a/langflow/src/frontend/src/icons/Couchbase/couchbase.svg b/langflow/src/frontend/src/icons/Couchbase/couchbase.svg
new file mode 100644
index 0000000..6c86a8a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Couchbase/couchbase.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Couchbase/index.tsx b/langflow/src/frontend/src/icons/Couchbase/index.tsx
new file mode 100644
index 0000000..85149c2
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Couchbase/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgCouchbaseIcon from "./Couchbase";
+
+export const CouchbaseIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx b/langflow/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx
new file mode 100644
index 0000000..336c29d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/CrewAI/CrewAiIcon.jsx
@@ -0,0 +1,19 @@
+const SvgCrewAiIcon = (props) => (
+
+
+
+
+);
+export default SvgCrewAiIcon;
diff --git a/langflow/src/frontend/src/icons/CrewAI/crewai.svg b/langflow/src/frontend/src/icons/CrewAI/crewai.svg
new file mode 100644
index 0000000..a30e7c0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/CrewAI/crewai.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/CrewAI/index.tsx b/langflow/src/frontend/src/icons/CrewAI/index.tsx
new file mode 100644
index 0000000..b3afaf3
--- /dev/null
+++ b/langflow/src/frontend/src/icons/CrewAI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgCrewAiIcon from "./CrewAiIcon";
+
+export const CrewAiIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/DeepSeek/DeepSeekIcon.jsx b/langflow/src/frontend/src/icons/DeepSeek/DeepSeekIcon.jsx
new file mode 100644
index 0000000..b0747f2
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DeepSeek/DeepSeekIcon.jsx
@@ -0,0 +1,44 @@
+import { stringToBool } from "@/utils/utils";
+
+const DeepSeekSVG = (props) => (
+
+
+
+
+
+);
+
+export default DeepSeekSVG;
diff --git a/langflow/src/frontend/src/icons/DeepSeek/deepseek.svg b/langflow/src/frontend/src/icons/DeepSeek/deepseek.svg
new file mode 100644
index 0000000..e89b2ad
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DeepSeek/deepseek.svg
@@ -0,0 +1,33 @@
+
+
+
+
+Created by potrace 1.10, written by Peter Selinger 2001-2011
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/DeepSeek/index.tsx b/langflow/src/frontend/src/icons/DeepSeek/index.tsx
new file mode 100644
index 0000000..48c612f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DeepSeek/index.tsx
@@ -0,0 +1,11 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import DeepSeekSVG from "./DeepSeekIcon";
+
+export const DeepSeekIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/DuckDuckGo/DuckDuckGo.jsx b/langflow/src/frontend/src/icons/DuckDuckGo/DuckDuckGo.jsx
new file mode 100644
index 0000000..a2dc61b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DuckDuckGo/DuckDuckGo.jsx
@@ -0,0 +1,59 @@
+const SvgDuckDuckGo = ({ ...props }) => (
+
+
+
+
+ {"duckduckgo"}
+
+
+
+
+
+
+
+
+
+);
+export default SvgDuckDuckGo;
diff --git a/langflow/src/frontend/src/icons/DuckDuckGo/duckduckgo-icon.svg b/langflow/src/frontend/src/icons/DuckDuckGo/duckduckgo-icon.svg
new file mode 100644
index 0000000..8215a91
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DuckDuckGo/duckduckgo-icon.svg
@@ -0,0 +1 @@
+duckduckgo
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/DuckDuckGo/index.tsx b/langflow/src/frontend/src/icons/DuckDuckGo/index.tsx
new file mode 100644
index 0000000..9bca085
--- /dev/null
+++ b/langflow/src/frontend/src/icons/DuckDuckGo/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgDuckDuckGo from "./DuckDuckGo";
+
+export const DuckDuckGoIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{ color?: string }>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.jsx b/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.jsx
new file mode 100644
index 0000000..bbf7c04
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.jsx
@@ -0,0 +1,19 @@
+const SvgElasticsearchLogo = (props) => (
+
+
+
+
+
+);
+export default SvgElasticsearchLogo;
diff --git a/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.svg b/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.svg
new file mode 100644
index 0000000..39b7bf5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ElasticsearchStore/ElasticsearchLogo.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/ElasticsearchStore/index.tsx b/langflow/src/frontend/src/icons/ElasticsearchStore/index.tsx
new file mode 100644
index 0000000..12f2b95
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ElasticsearchStore/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgElasticsearchLogo from "./ElasticsearchLogo";
+
+export const ElasticsearchIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Evernote/EvernoteIcon.jsx b/langflow/src/frontend/src/icons/Evernote/EvernoteIcon.jsx
new file mode 100644
index 0000000..e3feab9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Evernote/EvernoteIcon.jsx
@@ -0,0 +1,13 @@
+const SvgEvernoteIcon = (props) => (
+
+
+
+);
+export default SvgEvernoteIcon;
diff --git a/langflow/src/frontend/src/icons/Evernote/evernote-icon.svg b/langflow/src/frontend/src/icons/Evernote/evernote-icon.svg
new file mode 100644
index 0000000..2a34959
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Evernote/evernote-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Evernote/index.tsx b/langflow/src/frontend/src/icons/Evernote/index.tsx
new file mode 100644
index 0000000..b2460d7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Evernote/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgEvernoteIcon from "./EvernoteIcon";
+
+export const EvernoteIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Exa/Exa.jsx b/langflow/src/frontend/src/icons/Exa/Exa.jsx
new file mode 100644
index 0000000..2e499ca
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Exa/Exa.jsx
@@ -0,0 +1,227 @@
+export const SvgExa = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgExa;
diff --git a/langflow/src/frontend/src/icons/Exa/index.tsx b/langflow/src/frontend/src/icons/Exa/index.tsx
new file mode 100644
index 0000000..31f3c08
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Exa/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgExa from "./Exa";
+
+export const ExaIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/FacebookMessenger/FacebookMessengerLogo2020.jsx b/langflow/src/frontend/src/icons/FacebookMessenger/FacebookMessengerLogo2020.jsx
new file mode 100644
index 0000000..131f11c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/FacebookMessenger/FacebookMessengerLogo2020.jsx
@@ -0,0 +1,52 @@
+const SvgFacebookMessengerLogo2020 = (props) => (
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgFacebookMessengerLogo2020;
diff --git a/langflow/src/frontend/src/icons/FacebookMessenger/Facebook_Messenger_logo_2020.svg b/langflow/src/frontend/src/icons/FacebookMessenger/Facebook_Messenger_logo_2020.svg
new file mode 100644
index 0000000..bd980c3
--- /dev/null
+++ b/langflow/src/frontend/src/icons/FacebookMessenger/Facebook_Messenger_logo_2020.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/langflow/src/frontend/src/icons/FacebookMessenger/index.tsx b/langflow/src/frontend/src/icons/FacebookMessenger/index.tsx
new file mode 100644
index 0000000..5264b37
--- /dev/null
+++ b/langflow/src/frontend/src/icons/FacebookMessenger/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgFacebookMessengerLogo2020 from "./FacebookMessengerLogo2020";
+
+export const FBIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Firecrawl/FirecrawlLogo.jsx b/langflow/src/frontend/src/icons/Firecrawl/FirecrawlLogo.jsx
new file mode 100644
index 0000000..f79173d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Firecrawl/FirecrawlLogo.jsx
@@ -0,0 +1,61 @@
+const SvgFirecrawlLogo = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgFirecrawlLogo;
diff --git a/langflow/src/frontend/src/icons/Firecrawl/firecraw-logo.svg b/langflow/src/frontend/src/icons/Firecrawl/firecraw-logo.svg
new file mode 100644
index 0000000..ea8d2c7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Firecrawl/firecraw-logo.svg
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Firecrawl/index.tsx b/langflow/src/frontend/src/icons/Firecrawl/index.tsx
new file mode 100644
index 0000000..060d053
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Firecrawl/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgFirecrawlLogo from "./FirecrawlLogo";
+
+export const FirecrawlIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/GitBook/GitbookSvgrepoCom.jsx b/langflow/src/frontend/src/icons/GitBook/GitbookSvgrepoCom.jsx
new file mode 100644
index 0000000..151f1b5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitBook/GitbookSvgrepoCom.jsx
@@ -0,0 +1,12 @@
+const SvgGitbookSvgrepoCom = (props) => (
+
+
+
+);
+export default SvgGitbookSvgrepoCom;
diff --git a/langflow/src/frontend/src/icons/GitBook/gitbook-svgrepo-com.svg b/langflow/src/frontend/src/icons/GitBook/gitbook-svgrepo-com.svg
new file mode 100644
index 0000000..75eaa6d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitBook/gitbook-svgrepo-com.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/GitBook/index.tsx b/langflow/src/frontend/src/icons/GitBook/index.tsx
new file mode 100644
index 0000000..a9ba12c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitBook/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgGitbookSvgrepoCom from "./GitbookSvgrepoCom";
+
+export const GitBookIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/GitLoader/Git.svg b/langflow/src/frontend/src/icons/GitLoader/Git.svg
new file mode 100644
index 0000000..5bf444b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitLoader/Git.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/GitLoader/GitLoader.jsx b/langflow/src/frontend/src/icons/GitLoader/GitLoader.jsx
new file mode 100644
index 0000000..f1b2916
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitLoader/GitLoader.jsx
@@ -0,0 +1,22 @@
+const GitLoaderIcon = (props) => (
+
+
+
+);
+
+export default GitLoaderIcon;
diff --git a/langflow/src/frontend/src/icons/GitLoader/index.tsx b/langflow/src/frontend/src/icons/GitLoader/index.tsx
new file mode 100644
index 0000000..51e82f4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GitLoader/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgGitLoader from "./GitLoader";
+
+export const GitLoaderIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Glean/Glean.jsx b/langflow/src/frontend/src/icons/Glean/Glean.jsx
new file mode 100644
index 0000000..acb2531
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Glean/Glean.jsx
@@ -0,0 +1,4517 @@
+const SvgGlean = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgGlean;
diff --git a/langflow/src/frontend/src/icons/Glean/glean.svg b/langflow/src/frontend/src/icons/Glean/glean.svg
new file mode 100644
index 0000000..7ff7a24
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Glean/glean.svg
@@ -0,0 +1,2266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Glean/index.tsx b/langflow/src/frontend/src/icons/Glean/index.tsx
new file mode 100644
index 0000000..c74b9b0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Glean/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgGlean from "./Glean";
+
+export const GleanIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Google/Google.jsx b/langflow/src/frontend/src/icons/Google/Google.jsx
new file mode 100644
index 0000000..6887966
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Google/Google.jsx
@@ -0,0 +1,28 @@
+const SvgGoogle = (props) => (
+
+
+
+
+
+
+);
+export default SvgGoogle;
diff --git a/langflow/src/frontend/src/icons/Google/google.svg b/langflow/src/frontend/src/icons/Google/google.svg
new file mode 100644
index 0000000..c599462
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Google/google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Google/index.tsx b/langflow/src/frontend/src/icons/Google/index.tsx
new file mode 100644
index 0000000..89bd546
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Google/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgGoogle from "./Google";
+
+export const GoogleIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/GoogleGenerativeAI/Google Gemini icon.svg b/langflow/src/frontend/src/icons/GoogleGenerativeAI/Google Gemini icon.svg
new file mode 100644
index 0000000..787c837
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GoogleGenerativeAI/Google Gemini icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/GoogleGenerativeAI/GoogleGemini.jsx b/langflow/src/frontend/src/icons/GoogleGenerativeAI/GoogleGemini.jsx
new file mode 100644
index 0000000..47eeea6
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GoogleGenerativeAI/GoogleGemini.jsx
@@ -0,0 +1,28 @@
+const SvgGoogleGenerativeAI = (props) => (
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgGoogleGenerativeAI;
diff --git a/langflow/src/frontend/src/icons/GoogleGenerativeAI/index.tsx b/langflow/src/frontend/src/icons/GoogleGenerativeAI/index.tsx
new file mode 100644
index 0000000..2123f12
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GoogleGenerativeAI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgGoogleGenerativeAI from "./GoogleGemini";
+
+export const GoogleGenerativeAIIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/GradientSparkles/index.tsx b/langflow/src/frontend/src/icons/GradientSparkles/index.tsx
new file mode 100644
index 0000000..0cb6b3f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/GradientSparkles/index.tsx
@@ -0,0 +1,74 @@
+import { Code } from "lucide-react";
+import { forwardRef } from "react";
+import ForwardedIconComponent from "../../components/common/genericIconComponent";
+
+export const GradientInfinity = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+});
+
+export const GradientSave = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return (
+ <>
+
+ >
+ );
+});
+
+export const GradientGroup = (props) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const GradientUngroup = (props) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/langflow/src/frontend/src/icons/Groq/GroqLogo.jsx b/langflow/src/frontend/src/icons/Groq/GroqLogo.jsx
new file mode 100644
index 0000000..a25f40a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Groq/GroqLogo.jsx
@@ -0,0 +1,23 @@
+const SvgGroqLogo = ({ ...props }) => (
+
+
+
+
+);
+export default SvgGroqLogo;
diff --git a/langflow/src/frontend/src/icons/Groq/GroqLogo.svg b/langflow/src/frontend/src/icons/Groq/GroqLogo.svg
new file mode 100644
index 0000000..6757dd8
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Groq/GroqLogo.svg
@@ -0,0 +1,38 @@
+
+
+ groq_logo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Groq/index.tsx b/langflow/src/frontend/src/icons/Groq/index.tsx
new file mode 100644
index 0000000..6c4283d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Groq/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgGroqLogo from "./GroqLogo";
+
+export const GroqIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/HCD/Favicon.svg b/langflow/src/frontend/src/icons/HCD/Favicon.svg
new file mode 100644
index 0000000..7fe145b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HCD/Favicon.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/HCD/HCD.jsx b/langflow/src/frontend/src/icons/HCD/HCD.jsx
new file mode 100644
index 0000000..b6c3876
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HCD/HCD.jsx
@@ -0,0 +1,30 @@
+import { stringToBool } from "@/utils/utils";
+
+const HCDSVG = (props) => (
+
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+);
+export default HCDSVG;
diff --git a/langflow/src/frontend/src/icons/HCD/index.tsx b/langflow/src/frontend/src/icons/HCD/index.tsx
new file mode 100644
index 0000000..312f12c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HCD/index.tsx
@@ -0,0 +1,11 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import HCDSVG from "./HCD";
+
+export const HCDIcon = forwardRef>(
+ (props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/HuggingFace/HfLogo.jsx b/langflow/src/frontend/src/icons/HuggingFace/HfLogo.jsx
new file mode 100644
index 0000000..eab4c7e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HuggingFace/HfLogo.jsx
@@ -0,0 +1,42 @@
+const SvgHfLogo = (props) => (
+
+
+
+
+
+
+
+
+);
+export default SvgHfLogo;
diff --git a/langflow/src/frontend/src/icons/HuggingFace/hf-logo.svg b/langflow/src/frontend/src/icons/HuggingFace/hf-logo.svg
new file mode 100644
index 0000000..ab959d1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HuggingFace/hf-logo.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/HuggingFace/index.tsx b/langflow/src/frontend/src/icons/HuggingFace/index.tsx
new file mode 100644
index 0000000..3f7ccde
--- /dev/null
+++ b/langflow/src/frontend/src/icons/HuggingFace/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgHfLogo from "./HfLogo";
+
+export const HuggingFaceIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/IFixIt/IfixitSeeklogoCom.jsx b/langflow/src/frontend/src/icons/IFixIt/IfixitSeeklogoCom.jsx
new file mode 100644
index 0000000..f20ac60
--- /dev/null
+++ b/langflow/src/frontend/src/icons/IFixIt/IfixitSeeklogoCom.jsx
@@ -0,0 +1,15 @@
+const SvgIfixitSeeklogocom = (props) => (
+
+
+
+);
+export default SvgIfixitSeeklogocom;
diff --git a/langflow/src/frontend/src/icons/IFixIt/ifixit-seeklogo.com.svg b/langflow/src/frontend/src/icons/IFixIt/ifixit-seeklogo.com.svg
new file mode 100644
index 0000000..cbb67cf
--- /dev/null
+++ b/langflow/src/frontend/src/icons/IFixIt/ifixit-seeklogo.com.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/IFixIt/index.tsx b/langflow/src/frontend/src/icons/IFixIt/index.tsx
new file mode 100644
index 0000000..318ae72
--- /dev/null
+++ b/langflow/src/frontend/src/icons/IFixIt/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgIfixitSeeklogocom from "./IfixitSeeklogoCom";
+
+export const IFixIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Icosa/Icosa.jsx b/langflow/src/frontend/src/icons/Icosa/Icosa.jsx
new file mode 100644
index 0000000..b985fc1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Icosa/Icosa.jsx
@@ -0,0 +1,41 @@
+const SvgIcosa = ({ ...props }) => (
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgIcosa;
diff --git a/langflow/src/frontend/src/icons/Icosa/Icosa.svg b/langflow/src/frontend/src/icons/Icosa/Icosa.svg
new file mode 100644
index 0000000..3d16a13
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Icosa/Icosa.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Icosa/index.tsx b/langflow/src/frontend/src/icons/Icosa/index.tsx
new file mode 100644
index 0000000..4d29d00
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Icosa/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgIcosa from "./Icosa";
+
+export const IcosaIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/LMStudio/LMStudioIcon.jsx b/langflow/src/frontend/src/icons/LMStudio/LMStudioIcon.jsx
new file mode 100644
index 0000000..54b9dbf
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LMStudio/LMStudioIcon.jsx
@@ -0,0 +1,137 @@
+const SvgLMStudio = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgLMStudio;
diff --git a/langflow/src/frontend/src/icons/LMStudio/index.tsx b/langflow/src/frontend/src/icons/LMStudio/index.tsx
new file mode 100644
index 0000000..07475bf
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LMStudio/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgLMStudio from "./LMStudioIcon";
+
+export const LMStudioIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/LMStudio/lmstudio-icon.svg b/langflow/src/frontend/src/icons/LMStudio/lmstudio-icon.svg
new file mode 100644
index 0000000..d7de9f3
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LMStudio/lmstudio-icon.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/LangChain/LangChainIcon.jsx b/langflow/src/frontend/src/icons/LangChain/LangChainIcon.jsx
new file mode 100644
index 0000000..aa5e98b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LangChain/LangChainIcon.jsx
@@ -0,0 +1,24 @@
+const SvgLangChainIcon = (props) => (
+
+
+
+
+
+);
+export default SvgLangChainIcon;
diff --git a/langflow/src/frontend/src/icons/LangChain/index.tsx b/langflow/src/frontend/src/icons/LangChain/index.tsx
new file mode 100644
index 0000000..48b8566
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LangChain/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgLangChainIcon from "./LangChainIcon";
+
+export const LangChainIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/LangChain/langchain-icon.svg b/langflow/src/frontend/src/icons/LangChain/langchain-icon.svg
new file mode 100644
index 0000000..e80068b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/LangChain/langchain-icon.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Langwatch/index.tsx b/langflow/src/frontend/src/icons/Langwatch/index.tsx
new file mode 100644
index 0000000..211d22d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Langwatch/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgLangwatch from "./langwatch";
+
+export const LangwatchIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Langwatch/langwatch-icon.svg b/langflow/src/frontend/src/icons/Langwatch/langwatch-icon.svg
new file mode 100644
index 0000000..d28cf42
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Langwatch/langwatch-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Langwatch/langwatch.jsx b/langflow/src/frontend/src/icons/Langwatch/langwatch.jsx
new file mode 100644
index 0000000..7e10894
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Langwatch/langwatch.jsx
@@ -0,0 +1,20 @@
+const SvgLangwatch = (props) => (
+
+
+
+
+);
+export default SvgLangwatch;
diff --git a/langflow/src/frontend/src/icons/Maritalk/MaritalkIcon.jsx b/langflow/src/frontend/src/icons/Maritalk/MaritalkIcon.jsx
new file mode 100644
index 0000000..17c9aaf
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Maritalk/MaritalkIcon.jsx
@@ -0,0 +1,336 @@
+const SvgMaritalkIcon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgMaritalkIcon;
diff --git a/langflow/src/frontend/src/icons/Maritalk/index.tsx b/langflow/src/frontend/src/icons/Maritalk/index.tsx
new file mode 100644
index 0000000..8137e4a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Maritalk/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgMaritalkIcon from "./MaritalkIcon";
+
+export const MaritalkIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Maritalk/maritalk-icon.svg b/langflow/src/frontend/src/icons/Maritalk/maritalk-icon.svg
new file mode 100644
index 0000000..b3121e4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Maritalk/maritalk-icon.svg
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Mem0/SvgMem.jsx b/langflow/src/frontend/src/icons/Mem0/SvgMem.jsx
new file mode 100644
index 0000000..4fcb838
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Mem0/SvgMem.jsx
@@ -0,0 +1,38 @@
+import { stringToBool } from "@/utils/utils";
+
+export default function SvgMem0(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/icons/Mem0/index.tsx b/langflow/src/frontend/src/icons/Mem0/index.tsx
new file mode 100644
index 0000000..8581232
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Mem0/index.tsx
@@ -0,0 +1,10 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgMem from "./SvgMem";
+
+export const Mem0 = forwardRef>(
+ (props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Meta/MetaIcon.jsx b/langflow/src/frontend/src/icons/Meta/MetaIcon.jsx
new file mode 100644
index 0000000..95f4908
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Meta/MetaIcon.jsx
@@ -0,0 +1,58 @@
+const SvgMetaIcon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgMetaIcon;
diff --git a/langflow/src/frontend/src/icons/Meta/index.tsx b/langflow/src/frontend/src/icons/Meta/index.tsx
new file mode 100644
index 0000000..9fb123e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Meta/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgMetaIcon from "./MetaIcon";
+
+export const MetaIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Meta/meta-icon.svg b/langflow/src/frontend/src/icons/Meta/meta-icon.svg
new file mode 100644
index 0000000..f5e8b58
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Meta/meta-icon.svg
@@ -0,0 +1 @@
+facebook-meta
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Midjorney/MidjourneyEmblem.jsx b/langflow/src/frontend/src/icons/Midjorney/MidjourneyEmblem.jsx
new file mode 100644
index 0000000..f42caae
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Midjorney/MidjourneyEmblem.jsx
@@ -0,0 +1,13 @@
+const SvgMidjourneyEmblem = (props) => (
+
+
+
+
+);
+export default SvgMidjourneyEmblem;
diff --git a/langflow/src/frontend/src/icons/Midjorney/Midjourney_Emblem.svg b/langflow/src/frontend/src/icons/Midjorney/Midjourney_Emblem.svg
new file mode 100644
index 0000000..9c90d37
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Midjorney/Midjourney_Emblem.svg
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Midjorney/index.tsx b/langflow/src/frontend/src/icons/Midjorney/index.tsx
new file mode 100644
index 0000000..f691abe
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Midjorney/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgMidjourneyEmblem from "./MidjourneyEmblem";
+
+export const MidjourneyIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Milvus/Milvus.jsx b/langflow/src/frontend/src/icons/Milvus/Milvus.jsx
new file mode 100644
index 0000000..d93b1d9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Milvus/Milvus.jsx
@@ -0,0 +1,317 @@
+export const SvgMilvus = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgMilvus;
diff --git a/langflow/src/frontend/src/icons/Milvus/index.tsx b/langflow/src/frontend/src/icons/Milvus/index.tsx
new file mode 100644
index 0000000..5e12857
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Milvus/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgMilvus from "./Milvus";
+
+export const MilvusIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/MongoDB/MongodbIcon.jsx b/langflow/src/frontend/src/icons/MongoDB/MongodbIcon.jsx
new file mode 100644
index 0000000..99cdf32
--- /dev/null
+++ b/langflow/src/frontend/src/icons/MongoDB/MongodbIcon.jsx
@@ -0,0 +1,23 @@
+const SvgMongodbIcon = (props) => (
+
+
+
+
+
+);
+export default SvgMongodbIcon;
diff --git a/langflow/src/frontend/src/icons/MongoDB/index.tsx b/langflow/src/frontend/src/icons/MongoDB/index.tsx
new file mode 100644
index 0000000..d39169c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/MongoDB/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgMongodbIcon from "./MongodbIcon";
+
+export const MongoDBIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/MongoDB/mongodb-icon.svg b/langflow/src/frontend/src/icons/MongoDB/mongodb-icon.svg
new file mode 100644
index 0000000..54403d5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/MongoDB/mongodb-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Needle/NeedleIcon.jsx b/langflow/src/frontend/src/icons/Needle/NeedleIcon.jsx
new file mode 100644
index 0000000..aa9b119
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Needle/NeedleIcon.jsx
@@ -0,0 +1,18 @@
+const SvgNeedleIcon = (props) => (
+
+
+
+
+
+);
+
+export default SvgNeedleIcon;
diff --git a/langflow/src/frontend/src/icons/Needle/index.tsx b/langflow/src/frontend/src/icons/Needle/index.tsx
new file mode 100644
index 0000000..0ed5b6a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Needle/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgNeedleIcon from "./NeedleIcon";
+
+export const NeedleIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Needle/needle-icon.svg b/langflow/src/frontend/src/icons/Needle/needle-icon.svg
new file mode 100644
index 0000000..c9c79e7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Needle/needle-icon.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/NotDiamond/NotDiamondIcon.jsx b/langflow/src/frontend/src/icons/NotDiamond/NotDiamondIcon.jsx
new file mode 100644
index 0000000..466405c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/NotDiamond/NotDiamondIcon.jsx
@@ -0,0 +1,31 @@
+const SvgNotDiamondIcon = (props) => (
+
+ {" "}
+
+ {" "}
+
+
+
+
+
+
+
+
+
+);
+export default SvgNotDiamondIcon;
diff --git a/langflow/src/frontend/src/icons/NotDiamond/index.tsx b/langflow/src/frontend/src/icons/NotDiamond/index.tsx
new file mode 100644
index 0000000..2556b54
--- /dev/null
+++ b/langflow/src/frontend/src/icons/NotDiamond/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgNotDiamondIcon from "./NotDiamondIcon";
+
+export const NotDiamondIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/NotDiamond/notdiamond-icon.svg b/langflow/src/frontend/src/icons/NotDiamond/notdiamond-icon.svg
new file mode 100644
index 0000000..d964ed0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/NotDiamond/notdiamond-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Notion/Notion-logo.svg b/langflow/src/frontend/src/icons/Notion/Notion-logo.svg
new file mode 100644
index 0000000..bf6442f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Notion/Notion-logo.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Notion/NotionLogo.jsx b/langflow/src/frontend/src/icons/Notion/NotionLogo.jsx
new file mode 100644
index 0000000..944c446
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Notion/NotionLogo.jsx
@@ -0,0 +1,22 @@
+const SvgNotionLogo = (props) => (
+
+
+
+
+);
+export default SvgNotionLogo;
diff --git a/langflow/src/frontend/src/icons/Notion/index.tsx b/langflow/src/frontend/src/icons/Notion/index.tsx
new file mode 100644
index 0000000..cf3e3f4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Notion/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgNotionLogo from "./NotionLogo";
+
+export const NotionIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Novita/index.tsx b/langflow/src/frontend/src/icons/Novita/index.tsx
new file mode 100644
index 0000000..958b063
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Novita/index.tsx
@@ -0,0 +1,12 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgNovita from "./novita";
+
+export const NovitaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Novita/novita.jsx b/langflow/src/frontend/src/icons/Novita/novita.jsx
new file mode 100644
index 0000000..479cd5e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Novita/novita.jsx
@@ -0,0 +1,17 @@
+const SvgNovita = (props) => (
+
+
+
+);
+export default SvgNovita;
diff --git a/langflow/src/frontend/src/icons/Novita/novita.svg b/langflow/src/frontend/src/icons/Novita/novita.svg
new file mode 100644
index 0000000..353f007
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Novita/novita.svg
@@ -0,0 +1 @@
+Novita AI
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Nvidia/index.tsx b/langflow/src/frontend/src/icons/Nvidia/index.tsx
new file mode 100644
index 0000000..1fb6b37
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Nvidia/index.tsx
@@ -0,0 +1,11 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import NvidiaSVG from "./nvidia";
+
+export const NvidiaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Nvidia/nvidia.jsx b/langflow/src/frontend/src/icons/Nvidia/nvidia.jsx
new file mode 100644
index 0000000..2f5af5c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Nvidia/nvidia.jsx
@@ -0,0 +1,56 @@
+import { stringToBool } from "@/utils/utils";
+
+const NvidiaSVG = (props) => (
+
+
+ generated by pstoedit version:3.44 from NVBadge_2D.eps
+
+
+
+
+
+);
+export default NvidiaSVG;
diff --git a/langflow/src/frontend/src/icons/Nvidia/nvidia.svg b/langflow/src/frontend/src/icons/Nvidia/nvidia.svg
new file mode 100644
index 0000000..9393002
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Nvidia/nvidia.svg
@@ -0,0 +1,32 @@
+
+
+
+
+generated by pstoedit version:3.44 from NVBadge_2D.eps
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Olivya/index.tsx b/langflow/src/frontend/src/icons/Olivya/index.tsx
new file mode 100644
index 0000000..705f3f4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Olivya/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import OlivyaSVG from "./olivya";
+
+export const OlivyaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Olivya/olivya.jsx b/langflow/src/frontend/src/icons/Olivya/olivya.jsx
new file mode 100644
index 0000000..bc82d19
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Olivya/olivya.jsx
@@ -0,0 +1,478 @@
+const OlivyaSVG = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default OlivyaSVG;
diff --git a/langflow/src/frontend/src/icons/Ollama/Ollama.jsx b/langflow/src/frontend/src/icons/Ollama/Ollama.jsx
new file mode 100644
index 0000000..408ff20
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Ollama/Ollama.jsx
@@ -0,0 +1,32 @@
+export const SvgOllama = (props) => (
+
+
+
+
+
+
+
+);
+export default SvgOllama;
diff --git a/langflow/src/frontend/src/icons/Ollama/Ollama.svg b/langflow/src/frontend/src/icons/Ollama/Ollama.svg
new file mode 100644
index 0000000..501606a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Ollama/Ollama.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Ollama/index.tsx b/langflow/src/frontend/src/icons/Ollama/index.tsx
new file mode 100644
index 0000000..a889d0f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Ollama/index.tsx
@@ -0,0 +1,12 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgOllama from "./Ollama";
+
+export const OllamaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isDark = useDarkStore((state) => state.dark);
+
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/OpenAi/OpenAi.jsx b/langflow/src/frontend/src/icons/OpenAi/OpenAi.jsx
new file mode 100644
index 0000000..8a69b7d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenAi/OpenAi.jsx
@@ -0,0 +1,22 @@
+const SvgOpenAi = (props) => (
+
+
+
+
+);
+export default SvgOpenAi;
diff --git a/langflow/src/frontend/src/icons/OpenAi/index.tsx b/langflow/src/frontend/src/icons/OpenAi/index.tsx
new file mode 100644
index 0000000..a1b7545
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenAi/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgOpenAi from "./OpenAi";
+
+export const OpenAiIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/OpenAi/openAI.svg b/langflow/src/frontend/src/icons/OpenAi/openAI.svg
new file mode 100644
index 0000000..e81233f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenAi/openAI.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx b/langflow/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx
new file mode 100644
index 0000000..e163391
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenRouter/OpenRouterIcon.jsx
@@ -0,0 +1,23 @@
+const SvgOpenRouter = (props) => (
+
+
+
+
+);
+export default SvgOpenRouter;
diff --git a/langflow/src/frontend/src/icons/OpenRouter/index.tsx b/langflow/src/frontend/src/icons/OpenRouter/index.tsx
new file mode 100644
index 0000000..fc79d74
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenRouter/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgOpenRouter from "./OpenRouterIcon";
+
+export const OpenRouterIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/OpenRouter/openrouter.svg b/langflow/src/frontend/src/icons/OpenRouter/openrouter.svg
new file mode 100644
index 0000000..1f4b1da
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenRouter/openrouter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/OpenSearch/OpenSearch.jsx b/langflow/src/frontend/src/icons/OpenSearch/OpenSearch.jsx
new file mode 100644
index 0000000..c68d338
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenSearch/OpenSearch.jsx
@@ -0,0 +1,149 @@
+const OpenSearchSVG = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default OpenSearchSVG;
diff --git a/langflow/src/frontend/src/icons/OpenSearch/index.tsx b/langflow/src/frontend/src/icons/OpenSearch/index.tsx
new file mode 100644
index 0000000..0cdb3a9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenSearch/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import OpenSearchSVG from "./OpenSearch";
+
+export const OpenSearch = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/OpenSearch/opensearch.svg b/langflow/src/frontend/src/icons/OpenSearch/opensearch.svg
new file mode 100644
index 0000000..8caf211
--- /dev/null
+++ b/langflow/src/frontend/src/icons/OpenSearch/opensearch.svg
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Perplexity/Perplexity.jsx b/langflow/src/frontend/src/icons/Perplexity/Perplexity.jsx
new file mode 100644
index 0000000..5258f03
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Perplexity/Perplexity.jsx
@@ -0,0 +1,21 @@
+const SvgPerplexity = (props) => (
+
+
+
+
+);
+
+export default SvgPerplexity;
diff --git a/langflow/src/frontend/src/icons/Perplexity/index.tsx b/langflow/src/frontend/src/icons/Perplexity/index.tsx
new file mode 100644
index 0000000..d610343
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Perplexity/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import PerplexitySVG from "./perplexity";
+
+export const PerplexityIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Perplexity/perplexity.svg b/langflow/src/frontend/src/icons/Perplexity/perplexity.svg
new file mode 100644
index 0000000..307f257
Binary files /dev/null and b/langflow/src/frontend/src/icons/Perplexity/perplexity.svg differ
diff --git a/langflow/src/frontend/src/icons/Pinecone/PineconeLogo.jsx b/langflow/src/frontend/src/icons/Pinecone/PineconeLogo.jsx
new file mode 100644
index 0000000..c69d44e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Pinecone/PineconeLogo.jsx
@@ -0,0 +1,133 @@
+const SvgPineconeLogo = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgPineconeLogo;
diff --git a/langflow/src/frontend/src/icons/Pinecone/index.tsx b/langflow/src/frontend/src/icons/Pinecone/index.tsx
new file mode 100644
index 0000000..bc7cebd
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Pinecone/index.tsx
@@ -0,0 +1,14 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import SvgPineconeLogo from "./PineconeLogo";
+
+export const PineconeIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ const isDark = useDarkStore((state) => state.dark);
+
+ return (
+
+ );
+});
diff --git a/langflow/src/frontend/src/icons/Pinecone/pinecone_logo.svg b/langflow/src/frontend/src/icons/Pinecone/pinecone_logo.svg
new file mode 100644
index 0000000..e9884a4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Pinecone/pinecone_logo.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Postgres/Postgres.jsx b/langflow/src/frontend/src/icons/Postgres/Postgres.jsx
new file mode 100644
index 0000000..95059e7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Postgres/Postgres.jsx
@@ -0,0 +1,67 @@
+export const SvgPostgres = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgPostgres;
diff --git a/langflow/src/frontend/src/icons/Postgres/Postgres.svg b/langflow/src/frontend/src/icons/Postgres/Postgres.svg
new file mode 100644
index 0000000..d8c30ac
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Postgres/Postgres.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Postgres/index.tsx b/langflow/src/frontend/src/icons/Postgres/index.tsx
new file mode 100644
index 0000000..0bd7cda
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Postgres/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgPostgres from "./Postgres";
+
+export const PostgresIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.jsx b/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.jsx
new file mode 100644
index 0000000..21c0cc5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.jsx
@@ -0,0 +1,76 @@
+const SvgPowerPoint = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgPowerPoint;
diff --git a/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.svg b/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.svg
new file mode 100644
index 0000000..1b9ee43
--- /dev/null
+++ b/langflow/src/frontend/src/icons/PowerPoint/PowerPoint.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/PowerPoint/index.tsx b/langflow/src/frontend/src/icons/PowerPoint/index.tsx
new file mode 100644
index 0000000..19dae1e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/PowerPoint/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgPowerPoint from "./PowerPoint";
+
+export const PowerPointIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Python/Python.jsx b/langflow/src/frontend/src/icons/Python/Python.jsx
new file mode 100644
index 0000000..8126837
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Python/Python.jsx
@@ -0,0 +1,158 @@
+export const SvgPython = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgPython;
diff --git a/langflow/src/frontend/src/icons/Python/Python.svg b/langflow/src/frontend/src/icons/Python/Python.svg
new file mode 100644
index 0000000..5d8e79a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Python/Python.svg
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Python/index.tsx b/langflow/src/frontend/src/icons/Python/index.tsx
new file mode 100644
index 0000000..2a6fc1f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Python/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgPython from "./Python";
+
+export const PythonIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/QDrant/QDrant.jsx b/langflow/src/frontend/src/icons/QDrant/QDrant.jsx
new file mode 100644
index 0000000..0400bca
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QDrant/QDrant.jsx
@@ -0,0 +1,63 @@
+const SvgQDrant = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgQDrant;
diff --git a/langflow/src/frontend/src/icons/QDrant/QDrant.svg b/langflow/src/frontend/src/icons/QDrant/QDrant.svg
new file mode 100644
index 0000000..a53c659
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QDrant/QDrant.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/QDrant/index.tsx b/langflow/src/frontend/src/icons/QDrant/index.tsx
new file mode 100644
index 0000000..48fde5a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QDrant/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgQDrant from "./QDrant";
+
+export const QDrantIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.jsx b/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.jsx
new file mode 100644
index 0000000..6724a2b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.jsx
@@ -0,0 +1,23 @@
+export const SvgQianFanChat = (props) => (
+
+
+
+
+
+);
+export default SvgQianFanChat;
diff --git a/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.svg b/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.svg
new file mode 100644
index 0000000..a25c4f6
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QianFanChat/QianFanChat.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/QianFanChat/index.tsx b/langflow/src/frontend/src/icons/QianFanChat/index.tsx
new file mode 100644
index 0000000..3b45e0c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/QianFanChat/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgQianFanChat from "./QianFanChat";
+
+export const QianFanChatIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ReadTheDocs/ReadthedocsioIcon.jsx b/langflow/src/frontend/src/icons/ReadTheDocs/ReadthedocsioIcon.jsx
new file mode 100644
index 0000000..5c55789
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ReadTheDocs/ReadthedocsioIcon.jsx
@@ -0,0 +1,9 @@
+const SvgReadthedocsioIcon = (props) => (
+
+
+
+);
+export default SvgReadthedocsioIcon;
diff --git a/langflow/src/frontend/src/icons/ReadTheDocs/index.tsx b/langflow/src/frontend/src/icons/ReadTheDocs/index.tsx
new file mode 100644
index 0000000..bb98609
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ReadTheDocs/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgReadthedocsioIcon from "./ReadthedocsioIcon";
+
+export const ReadTheDocsIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ReadTheDocs/readthedocsio-icon.svg b/langflow/src/frontend/src/icons/ReadTheDocs/readthedocsio-icon.svg
new file mode 100644
index 0000000..22f38b4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ReadTheDocs/readthedocsio-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Redis/Redis.jsx b/langflow/src/frontend/src/icons/Redis/Redis.jsx
new file mode 100644
index 0000000..6a3228c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Redis/Redis.jsx
@@ -0,0 +1,68 @@
+export const SvgRedis = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgRedis;
diff --git a/langflow/src/frontend/src/icons/Redis/Redis.svg b/langflow/src/frontend/src/icons/Redis/Redis.svg
new file mode 100644
index 0000000..6075a2b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Redis/Redis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Redis/index.tsx b/langflow/src/frontend/src/icons/Redis/index.tsx
new file mode 100644
index 0000000..80f94bd
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Redis/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import { SvgRedis } from "./Redis";
+
+export const RedisIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.jsx b/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.jsx
new file mode 100644
index 0000000..6a448c0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.jsx
@@ -0,0 +1,27 @@
+const SvgSambaNovaLogo = ({ ...props }) => (
+
+
+
+
+
+);
+export default SvgSambaNovaLogo;
diff --git a/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.svg b/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.svg
new file mode 100644
index 0000000..9b5d024
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SambaNova/SambaNovaLogo.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/SambaNova/index.tsx b/langflow/src/frontend/src/icons/SambaNova/index.tsx
new file mode 100644
index 0000000..59fe8cd
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SambaNova/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSambaNovaLogo from "./SambaNovaLogo";
+
+export const SambaNovaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.jsx b/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.jsx
new file mode 100644
index 0000000..a5013cc
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.jsx
@@ -0,0 +1,54 @@
+const ScrapeGraphAI = (props) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ScrapeGraphAI;
diff --git a/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.svg b/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.svg
new file mode 100644
index 0000000..cf34f16
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ScrapeGraphAI/ScrapeGraphAI.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/ScrapeGraphAI/index.tsx b/langflow/src/frontend/src/icons/ScrapeGraphAI/index.tsx
new file mode 100644
index 0000000..3961827
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ScrapeGraphAI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import ScrapeGraphAI from "./ScrapeGraphAI";
+
+export const ScrapeGraph = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.jsx b/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.jsx
new file mode 100644
index 0000000..5f936cb
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.jsx
@@ -0,0 +1,28 @@
+const SvgSearchApi = (props) => (
+
+
+
+
+
+
+);
+export default SvgSearchApi;
diff --git a/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.svg b/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.svg
new file mode 100644
index 0000000..10b78e8
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SearchAPI/SearchAPI.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/SearchAPI/index.tsx b/langflow/src/frontend/src/icons/SearchAPI/index.tsx
new file mode 100644
index 0000000..8122c72
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SearchAPI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSearchApi from "./SearchAPI";
+
+export const SearchAPIIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Searx/SearxLogo.jsx b/langflow/src/frontend/src/icons/Searx/SearxLogo.jsx
new file mode 100644
index 0000000..c3f8890
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Searx/SearxLogo.jsx
@@ -0,0 +1,151 @@
+const SvgSearxLogo = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgSearxLogo;
diff --git a/langflow/src/frontend/src/icons/Searx/Searx_logo.svg b/langflow/src/frontend/src/icons/Searx/Searx_logo.svg
new file mode 100644
index 0000000..45eecb1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Searx/Searx_logo.svg
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Searx/index.tsx b/langflow/src/frontend/src/icons/Searx/index.tsx
new file mode 100644
index 0000000..d1e07c3
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Searx/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgSearxLogo from "./SearxLogo";
+
+export const SearxIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.jsx b/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.jsx
new file mode 100644
index 0000000..95223bb
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.jsx
@@ -0,0 +1,536 @@
+const SvgSerpSearchAPI = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgSerpSearchAPI;
diff --git a/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.svg b/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.svg
new file mode 100644
index 0000000..4770e67
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SerpSearch/SerpSearch.svg
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/SerpSearch/index.tsx b/langflow/src/frontend/src/icons/SerpSearch/index.tsx
new file mode 100644
index 0000000..35b3c27
--- /dev/null
+++ b/langflow/src/frontend/src/icons/SerpSearch/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSerpSearchAPI from "./SerpSearch";
+
+export const SerpSearchIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Serper/Serper.jsx b/langflow/src/frontend/src/icons/Serper/Serper.jsx
new file mode 100644
index 0000000..2f6f9f9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Serper/Serper.jsx
@@ -0,0 +1,26 @@
+const SvgSerper = (props) => (
+
+
+
+
+);
+
+export default SvgSerper;
diff --git a/langflow/src/frontend/src/icons/Serper/index.tsx b/langflow/src/frontend/src/icons/Serper/index.tsx
new file mode 100644
index 0000000..35abc4b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Serper/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSerper from "./Serper";
+
+export const SerperIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Serper/serper.svg b/langflow/src/frontend/src/icons/Serper/serper.svg
new file mode 100644
index 0000000..0ada189
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Serper/serper.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Share/Share.jsx b/langflow/src/frontend/src/icons/Share/Share.jsx
new file mode 100644
index 0000000..612d67a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share/Share.jsx
@@ -0,0 +1,9 @@
+const SvgShare = (props) => (
+
+
+
+);
+export default SvgShare;
diff --git a/langflow/src/frontend/src/icons/Share/index.tsx b/langflow/src/frontend/src/icons/Share/index.tsx
new file mode 100644
index 0000000..2a68b6c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgShare from "./Share";
+
+export const ShareIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Share/share.svg b/langflow/src/frontend/src/icons/Share/share.svg
new file mode 100644
index 0000000..d99a7c5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share/share.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Share2/Share2.jsx b/langflow/src/frontend/src/icons/Share2/Share2.jsx
new file mode 100644
index 0000000..b803de1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share2/Share2.jsx
@@ -0,0 +1,17 @@
+const SvgShare2 = (props) => (
+
+
+
+
+
+);
+export default SvgShare2;
diff --git a/langflow/src/frontend/src/icons/Share2/index.tsx b/langflow/src/frontend/src/icons/Share2/index.tsx
new file mode 100644
index 0000000..c0384e2
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share2/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgShare2 from "./Share2";
+
+export const Share2Icon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Share2/share2.svg b/langflow/src/frontend/src/icons/Share2/share2.svg
new file mode 100644
index 0000000..d99a7c5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Share2/share2.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Slack/SlackIcon.jsx b/langflow/src/frontend/src/icons/Slack/SlackIcon.jsx
new file mode 100644
index 0000000..11f7bd9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Slack/SlackIcon.jsx
@@ -0,0 +1,29 @@
+const SvgSlackIcon = (props) => (
+
+
+
+
+
+
+
+
+);
+export default SvgSlackIcon;
diff --git a/langflow/src/frontend/src/icons/Slack/index.tsx b/langflow/src/frontend/src/icons/Slack/index.tsx
new file mode 100644
index 0000000..d37a8ce
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Slack/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgSlackIcon from "./SlackIcon";
+
+export const SlackIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Slack/slack-icon.svg b/langflow/src/frontend/src/icons/Slack/slack-icon.svg
new file mode 100644
index 0000000..cde79af
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Slack/slack-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Spider/SpiderIcon.jsx b/langflow/src/frontend/src/icons/Spider/SpiderIcon.jsx
new file mode 100644
index 0000000..1d10d9f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Spider/SpiderIcon.jsx
@@ -0,0 +1,18 @@
+const SvgSpiderIcon = (props) => (
+
+ Spider v0 Logo
+
+
+);
+export default SvgSpiderIcon;
diff --git a/langflow/src/frontend/src/icons/Spider/index.tsx b/langflow/src/frontend/src/icons/Spider/index.tsx
new file mode 100644
index 0000000..65e41c3
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Spider/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSpiderIcon from "./SpiderIcon";
+
+export const SpiderIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Spider/spider_logo.svg b/langflow/src/frontend/src/icons/Spider/spider_logo.svg
new file mode 100644
index 0000000..604a09d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Spider/spider_logo.svg
@@ -0,0 +1 @@
+Spider v1 Logo
diff --git a/langflow/src/frontend/src/icons/Streamlit/SvgStreamlit.jsx b/langflow/src/frontend/src/icons/Streamlit/SvgStreamlit.jsx
new file mode 100644
index 0000000..0db3372
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Streamlit/SvgStreamlit.jsx
@@ -0,0 +1,25 @@
+export default function SvgStreamlit(props) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/icons/Streamlit/index.tsx b/langflow/src/frontend/src/icons/Streamlit/index.tsx
new file mode 100644
index 0000000..1a4c551
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Streamlit/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgStreamlit from "./SvgStreamlit";
+
+export const Streamlit = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Tavily/Tavily.jsx b/langflow/src/frontend/src/icons/Tavily/Tavily.jsx
new file mode 100644
index 0000000..2741a1e
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Tavily/Tavily.jsx
@@ -0,0 +1,69 @@
+const Tavily = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default Tavily;
diff --git a/langflow/src/frontend/src/icons/Tavily/index.tsx b/langflow/src/frontend/src/icons/Tavily/index.tsx
new file mode 100644
index 0000000..a945ef4
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Tavily/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import Tavily from "./Tavily";
+
+export const TavilyIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Tavily/tavily.svg b/langflow/src/frontend/src/icons/Tavily/tavily.svg
new file mode 100644
index 0000000..ce15510
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Tavily/tavily.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Unstructured/Unstructured.jsx b/langflow/src/frontend/src/icons/Unstructured/Unstructured.jsx
new file mode 100644
index 0000000..b2ca5c8
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Unstructured/Unstructured.jsx
@@ -0,0 +1,66 @@
+const SvgGoogleGenerativeAI = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgGoogleGenerativeAI;
diff --git a/langflow/src/frontend/src/icons/Unstructured/Unstructured.svg b/langflow/src/frontend/src/icons/Unstructured/Unstructured.svg
new file mode 100644
index 0000000..bd55efb
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Unstructured/Unstructured.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Unstructured/index.tsx b/langflow/src/frontend/src/icons/Unstructured/index.tsx
new file mode 100644
index 0000000..f9e434a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Unstructured/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgUnstructured from "./Unstructured";
+
+export const UnstructuredIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Upstash/UpstashIcon.jsx b/langflow/src/frontend/src/icons/Upstash/UpstashIcon.jsx
new file mode 100644
index 0000000..7924833
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Upstash/UpstashIcon.jsx
@@ -0,0 +1,43 @@
+const UpstashIcon = (props) => (
+
+ upstash
+
+
+
+
+
+
+
+
+
+);
+
+export default UpstashIcon;
diff --git a/langflow/src/frontend/src/icons/Upstash/index.tsx b/langflow/src/frontend/src/icons/Upstash/index.tsx
new file mode 100644
index 0000000..b761101
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Upstash/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import UpstashIcon from "./UpstashIcon";
+
+export const UpstashSvgIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Upstash/upstash-icon-seeklogo.svg b/langflow/src/frontend/src/icons/Upstash/upstash-icon-seeklogo.svg
new file mode 100644
index 0000000..a0fb96a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Upstash/upstash-icon-seeklogo.svg
@@ -0,0 +1,12 @@
+
+
+ upstash
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/VectaraIcon/Vectara.jsx b/langflow/src/frontend/src/icons/VectaraIcon/Vectara.jsx
new file mode 100644
index 0000000..378dc69
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VectaraIcon/Vectara.jsx
@@ -0,0 +1,83 @@
+export const SvgVectara = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgVectara;
diff --git a/langflow/src/frontend/src/icons/VectaraIcon/index.tsx b/langflow/src/frontend/src/icons/VectaraIcon/index.tsx
new file mode 100644
index 0000000..5adc2d7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VectaraIcon/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgVectara from "./Vectara";
+
+export const VectaraIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/VectaraIcon/vectara.svg b/langflow/src/frontend/src/icons/VectaraIcon/vectara.svg
new file mode 100644
index 0000000..c111a3a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VectaraIcon/vectara.svg
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/VertexAI/VertexAi.jsx b/langflow/src/frontend/src/icons/VertexAI/VertexAi.jsx
new file mode 100644
index 0000000..11fc059
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VertexAI/VertexAi.jsx
@@ -0,0 +1,52 @@
+const SvgVertexAi = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgVertexAi;
diff --git a/langflow/src/frontend/src/icons/VertexAI/index.tsx b/langflow/src/frontend/src/icons/VertexAI/index.tsx
new file mode 100644
index 0000000..48f1931
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VertexAI/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgVertexAi from "./VertexAi";
+
+export const VertexAIIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/VertexAI/vertex_ai.svg b/langflow/src/frontend/src/icons/VertexAI/vertex_ai.svg
new file mode 100644
index 0000000..9da1d8d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/VertexAI/vertex_ai.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Weaviate/Weaviate.jsx b/langflow/src/frontend/src/icons/Weaviate/Weaviate.jsx
new file mode 100644
index 0000000..4a4ad1c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Weaviate/Weaviate.jsx
@@ -0,0 +1,357 @@
+const SvgWeaviate = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgWeaviate;
diff --git a/langflow/src/frontend/src/icons/Weaviate/index.tsx b/langflow/src/frontend/src/icons/Weaviate/index.tsx
new file mode 100644
index 0000000..0b242f8
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Weaviate/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgWeaviate from "./Weaviate";
+
+export const WeaviateIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Weaviate/weaviate.svg b/langflow/src/frontend/src/icons/Weaviate/weaviate.svg
new file mode 100644
index 0000000..69b08be
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Weaviate/weaviate.svg
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.jsx b/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.jsx
new file mode 100644
index 0000000..1d8c5e2
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.jsx
@@ -0,0 +1,904 @@
+const SvgWikipedia = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgWikipedia;
diff --git a/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.svg b/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.svg
new file mode 100644
index 0000000..4fd216d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wikipedia/Wikipedia.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Wikipedia/index.tsx b/langflow/src/frontend/src/icons/Wikipedia/index.tsx
new file mode 100644
index 0000000..20525b7
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wikipedia/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgWikipedia from "./Wikipedia";
+
+export const WikipediaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Wolfram/Wolfram.jsx b/langflow/src/frontend/src/icons/Wolfram/Wolfram.jsx
new file mode 100644
index 0000000..e7651e6
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wolfram/Wolfram.jsx
@@ -0,0 +1,23 @@
+const SvgWolfram = (props) => (
+
+
+
+
+
+);
+export default SvgWolfram;
diff --git a/langflow/src/frontend/src/icons/Wolfram/index.tsx b/langflow/src/frontend/src/icons/Wolfram/index.tsx
new file mode 100644
index 0000000..1d49b96
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wolfram/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgWolfram from "./Wolfram";
+
+export const WolframIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Wolfram/wolfram.svg b/langflow/src/frontend/src/icons/Wolfram/wolfram.svg
new file mode 100644
index 0000000..beee25d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Wolfram/wolfram.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/Word/Word.jsx b/langflow/src/frontend/src/icons/Word/Word.jsx
new file mode 100644
index 0000000..2f86db9
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Word/Word.jsx
@@ -0,0 +1,77 @@
+const SvgWord = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgWord;
diff --git a/langflow/src/frontend/src/icons/Word/index.tsx b/langflow/src/frontend/src/icons/Word/index.tsx
new file mode 100644
index 0000000..9df0d60
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Word/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgWord from "./Word";
+
+export const WordIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/Word/word.svg b/langflow/src/frontend/src/icons/Word/word.svg
new file mode 100644
index 0000000..2060f52
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Word/word.svg
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/Youtube/index.tsx b/langflow/src/frontend/src/icons/Youtube/index.tsx
new file mode 100644
index 0000000..6e49c4d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Youtube/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import YouTubeIcon from "./youtube";
+
+export const YouTubeSvgIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/Youtube/youtube.jsx b/langflow/src/frontend/src/icons/Youtube/youtube.jsx
new file mode 100644
index 0000000..1b79ece
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Youtube/youtube.jsx
@@ -0,0 +1,5950 @@
+const YouTubeIcon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default YouTubeIcon;
diff --git a/langflow/src/frontend/src/icons/Youtube/youtube.svg b/langflow/src/frontend/src/icons/Youtube/youtube.svg
new file mode 100644
index 0000000..6e3dbd1
--- /dev/null
+++ b/langflow/src/frontend/src/icons/Youtube/youtube.svg
@@ -0,0 +1,3732 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/ZepMemory/ZepMemory.jsx b/langflow/src/frontend/src/icons/ZepMemory/ZepMemory.jsx
new file mode 100644
index 0000000..eaa360c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ZepMemory/ZepMemory.jsx
@@ -0,0 +1,407 @@
+const SvgZepMemory = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgZepMemory;
diff --git a/langflow/src/frontend/src/icons/ZepMemory/index.tsx b/langflow/src/frontend/src/icons/ZepMemory/index.tsx
new file mode 100644
index 0000000..15c990a
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ZepMemory/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgZepMemory from "./ZepMemory";
+
+export const ZepMemoryIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/ZepMemory/zep-memory.svg b/langflow/src/frontend/src/icons/ZepMemory/zep-memory.svg
new file mode 100644
index 0000000..701071d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/ZepMemory/zep-memory.svg
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/icons/athena/athena.jsx b/langflow/src/frontend/src/icons/athena/athena.jsx
new file mode 100644
index 0000000..d3bc71f
--- /dev/null
+++ b/langflow/src/frontend/src/icons/athena/athena.jsx
@@ -0,0 +1,144 @@
+import { cn } from "@/utils/utils";
+export const AthenaComponent = ({ className, ...props }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/langflow/src/frontend/src/icons/athena/index.tsx b/langflow/src/frontend/src/icons/athena/index.tsx
new file mode 100644
index 0000000..2044525
--- /dev/null
+++ b/langflow/src/frontend/src/icons/athena/index.tsx
@@ -0,0 +1,10 @@
+import React, { forwardRef } from "react";
+//@ts-ignore
+import { AthenaComponent } from "./athena";
+
+export const AthenaIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/freezeAll/freezeAll.jsx b/langflow/src/frontend/src/icons/freezeAll/freezeAll.jsx
new file mode 100644
index 0000000..de98a92
--- /dev/null
+++ b/langflow/src/frontend/src/icons/freezeAll/freezeAll.jsx
@@ -0,0 +1,49 @@
+import { cn } from "../../utils/utils";
+
+const FreezeAllSvg = ({ className, ...props }) => {
+ return (
+
+ snowflake-svg
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FreezeAllSvg;
diff --git a/langflow/src/frontend/src/icons/freezeAll/index.tsx b/langflow/src/frontend/src/icons/freezeAll/index.tsx
new file mode 100644
index 0000000..62f5b53
--- /dev/null
+++ b/langflow/src/frontend/src/icons/freezeAll/index.tsx
@@ -0,0 +1,10 @@
+import React, { forwardRef } from "react";
+import SvgFreezeAll from "./freezeAll";
+("./freezeAll.jsx");
+
+export const freezeAllIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/globe-ok/globe-ok.jsx b/langflow/src/frontend/src/icons/globe-ok/globe-ok.jsx
new file mode 100644
index 0000000..1827236
--- /dev/null
+++ b/langflow/src/frontend/src/icons/globe-ok/globe-ok.jsx
@@ -0,0 +1,27 @@
+const SvgGlobeOkIcon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgGlobeOkIcon;
diff --git a/langflow/src/frontend/src/icons/globe-ok/index.tsx b/langflow/src/frontend/src/icons/globe-ok/index.tsx
new file mode 100644
index 0000000..9984bdc
--- /dev/null
+++ b/langflow/src/frontend/src/icons/globe-ok/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgGlobeOkIcon from "./globe-ok";
+
+export const GlobeOkIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/hackerNews/YCombinatorLogo.jsx b/langflow/src/frontend/src/icons/hackerNews/YCombinatorLogo.jsx
new file mode 100644
index 0000000..507ed03
--- /dev/null
+++ b/langflow/src/frontend/src/icons/hackerNews/YCombinatorLogo.jsx
@@ -0,0 +1,17 @@
+const SvgYCombinatorLogo = (props) => (
+
+
+
+
+);
+export default SvgYCombinatorLogo;
diff --git a/langflow/src/frontend/src/icons/hackerNews/Y_Combinator_logo.svg b/langflow/src/frontend/src/icons/hackerNews/Y_Combinator_logo.svg
new file mode 100644
index 0000000..b502950
--- /dev/null
+++ b/langflow/src/frontend/src/icons/hackerNews/Y_Combinator_logo.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/hackerNews/index.tsx b/langflow/src/frontend/src/icons/hackerNews/index.tsx
new file mode 100644
index 0000000..14e916c
--- /dev/null
+++ b/langflow/src/frontend/src/icons/hackerNews/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgYCombinatorLogo from "./YCombinatorLogo";
+
+export const HackerNewsIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/mistral/index.tsx b/langflow/src/frontend/src/icons/mistral/index.tsx
new file mode 100644
index 0000000..6125953
--- /dev/null
+++ b/langflow/src/frontend/src/icons/mistral/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgMistralIcon from "./mistralIcon";
+
+export const MistralIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/mistral/mistralIcon.jsx b/langflow/src/frontend/src/icons/mistral/mistralIcon.jsx
new file mode 100644
index 0000000..593e1e0
--- /dev/null
+++ b/langflow/src/frontend/src/icons/mistral/mistralIcon.jsx
@@ -0,0 +1,198 @@
+const SvgMistralIcon = (props) => (
+
+ Mistral AI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgMistralIcon;
diff --git a/langflow/src/frontend/src/icons/supabase/SupabaseIcon.jsx b/langflow/src/frontend/src/icons/supabase/SupabaseIcon.jsx
new file mode 100644
index 0000000..b7f1f08
--- /dev/null
+++ b/langflow/src/frontend/src/icons/supabase/SupabaseIcon.jsx
@@ -0,0 +1,62 @@
+const SvgSupabaseIcon = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+export default SvgSupabaseIcon;
diff --git a/langflow/src/frontend/src/icons/supabase/index.tsx b/langflow/src/frontend/src/icons/supabase/index.tsx
new file mode 100644
index 0000000..644f22b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/supabase/index.tsx
@@ -0,0 +1,9 @@
+import React, { forwardRef } from "react";
+import SvgSupabaseIcon from "./SupabaseIcon";
+
+export const SupabaseIcon = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/supabase/supabase-icon.svg b/langflow/src/frontend/src/icons/supabase/supabase-icon.svg
new file mode 100644
index 0000000..ac43e17
--- /dev/null
+++ b/langflow/src/frontend/src/icons/supabase/supabase-icon.svg
@@ -0,0 +1,99 @@
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/langflow/src/frontend/src/icons/thumbs/index.tsx b/langflow/src/frontend/src/icons/thumbs/index.tsx
new file mode 100644
index 0000000..6e99822
--- /dev/null
+++ b/langflow/src/frontend/src/icons/thumbs/index.tsx
@@ -0,0 +1,17 @@
+import React, { forwardRef } from "react";
+import ThumbDownFilled from "./thumbDown";
+import ThumbUpFilled from "./thumbUp";
+
+export const ThumbUpIconCustom = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
+
+export const ThumbDownIconCustom = forwardRef<
+ SVGSVGElement,
+ React.PropsWithChildren<{}>
+>((props, ref) => {
+ return ;
+});
diff --git a/langflow/src/frontend/src/icons/thumbs/thumbDown.jsx b/langflow/src/frontend/src/icons/thumbs/thumbDown.jsx
new file mode 100644
index 0000000..dd95eb5
--- /dev/null
+++ b/langflow/src/frontend/src/icons/thumbs/thumbDown.jsx
@@ -0,0 +1,25 @@
+const ThumbDownFilled = (props) => (
+
+
+
+
+);
+export default ThumbDownFilled;
diff --git a/langflow/src/frontend/src/icons/thumbs/thumbUp.jsx b/langflow/src/frontend/src/icons/thumbs/thumbUp.jsx
new file mode 100644
index 0000000..9e01f04
--- /dev/null
+++ b/langflow/src/frontend/src/icons/thumbs/thumbUp.jsx
@@ -0,0 +1,25 @@
+const ThumbUpFilled = (props) => (
+
+
+
+
+);
+export default ThumbUpFilled;
diff --git a/langflow/src/frontend/src/icons/xAI/index.tsx b/langflow/src/frontend/src/icons/xAI/index.tsx
new file mode 100644
index 0000000..219e33b
--- /dev/null
+++ b/langflow/src/frontend/src/icons/xAI/index.tsx
@@ -0,0 +1,10 @@
+import { useDarkStore } from "@/stores/darkStore";
+import React, { forwardRef } from "react";
+import XAISVG from "./xAIIcon.jsx";
+
+export const XAIIcon = forwardRef>(
+ (props, ref) => {
+ const isdark = useDarkStore((state) => state.dark).toString();
+ return ;
+ },
+);
diff --git a/langflow/src/frontend/src/icons/xAI/xAIIcon.jsx b/langflow/src/frontend/src/icons/xAI/xAIIcon.jsx
new file mode 100644
index 0000000..24d398d
--- /dev/null
+++ b/langflow/src/frontend/src/icons/xAI/xAIIcon.jsx
@@ -0,0 +1,19 @@
+import { stringToBool } from "@/utils/utils";
+
+const XAISVG = (props) => (
+
+ Grok
+
+
+);
+
+export default XAISVG;
diff --git a/langflow/src/frontend/src/icons/xAI/xai.svg b/langflow/src/frontend/src/icons/xAI/xai.svg
new file mode 100644
index 0000000..536e713
--- /dev/null
+++ b/langflow/src/frontend/src/icons/xAI/xai.svg
@@ -0,0 +1 @@
+Grok
\ No newline at end of file
diff --git a/langflow/src/frontend/src/index.tsx b/langflow/src/frontend/src/index.tsx
new file mode 100644
index 0000000..ca3d58b
--- /dev/null
+++ b/langflow/src/frontend/src/index.tsx
@@ -0,0 +1,19 @@
+import ReactDOM from "react-dom/client";
+import reportWebVitals from "./reportWebVitals";
+
+import "./style/classes.css";
+// @ts-ignore
+import "./style/index.css";
+// @ts-ignore
+import "./App.css";
+import "./style/applies.css";
+
+// @ts-ignore
+import App from "./App";
+
+const root = ReactDOM.createRoot(
+ document.getElementById("root") as HTMLElement,
+);
+
+root.render( );
+reportWebVitals();
diff --git a/langflow/src/frontend/src/logo.svg b/langflow/src/frontend/src/logo.svg
new file mode 100644
index 0000000..9dfc1c0
--- /dev/null
+++ b/langflow/src/frontend/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/csv-selected.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/csv-selected.tsx
new file mode 100644
index 0000000..e1c0e80
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/csv-selected.tsx
@@ -0,0 +1,40 @@
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../../../../../components/ui/select";
+
+export default function CsvSelect({ node, handleChangeSelect }): JSX.Element {
+ return (
+ <>
+
+ Expand the ouptut to see the CSV
+
+
+ CSV separator
+ handleChangeSelect(e)}
+ >
+
+
+
+
+
+ {node?.data?.node?.template?.separator?.options.map(
+ (separator) => (
+
+ {separator}
+
+ ),
+ )}
+
+
+
+
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/file-input.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/file-input.tsx
new file mode 100644
index 0000000..c48ae84
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/file-input.tsx
@@ -0,0 +1,161 @@
+import { Button } from "../../../../../components/ui/button";
+
+import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
+import { createFileUpload } from "@/helpers/create-file-upload";
+import useFileSizeValidator from "@/shared/hooks/use-file-size-validator";
+import useAlertStore from "@/stores/alertStore";
+import { useEffect, useState } from "react";
+import IconComponent from "../../../../../components/common/genericIconComponent";
+import {
+ ALLOWED_IMAGE_INPUT_EXTENSIONS,
+ BASE_URL_API,
+} from "../../../../../constants/constants";
+import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
+import { IOFileInputProps } from "../../../../../types/components";
+
+export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
+ //component to handle file upload from chatIO
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ const [isDragging, setIsDragging] = useState(false);
+ const [filePath, setFilePath] = useState("");
+ const [image, setImage] = useState(null);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { validateFileSize } = useFileSizeValidator(setErrorData);
+
+ useEffect(() => {
+ if (filePath) {
+ updateValue(filePath, "file");
+ }
+ }, [filePath]);
+
+ useEffect(() => {
+ if (field) {
+ const fileName = field.split("/")[1];
+ const flowFileId = currentFlowId.toString();
+ setImage(`${BASE_URL_API}files/images/${flowFileId}/${fileName}`);
+ }
+ }, []);
+
+ const dragOver = (e) => {
+ e.preventDefault();
+ if (e.dataTransfer.types.some((types) => types === "Files")) {
+ setIsDragging(true);
+ }
+ };
+
+ const dragEnter = (e) => {
+ if (e.dataTransfer.types.some((types) => types === "Files")) {
+ setIsDragging(true);
+ }
+ e.preventDefault();
+ };
+
+ const dragLeave = (e) => {
+ e.preventDefault();
+ setIsDragging(false);
+ };
+
+ const onDrop = (e) => {
+ e.preventDefault();
+
+ if (e.dataTransfer.files.length > 0) {
+ const file = e.dataTransfer.files[0];
+
+ upload(file);
+
+ const fileReader = new FileReader();
+ fileReader.onload = (event) => {
+ const imageDataUrl = event.target?.result as string;
+ setImage(imageDataUrl);
+ };
+ fileReader.readAsDataURL(file);
+ }
+ setIsDragging(false);
+ };
+
+ const { mutate } = usePostUploadFile();
+
+ const upload = async (file) => {
+ if (file) {
+ if (!validateFileSize(file)) {
+ return;
+ }
+ // Check if a file was selected
+ const fileReader = new FileReader();
+ fileReader.onload = (event) => {
+ const imageDataUrl = event.target?.result as string;
+ setImage(imageDataUrl);
+
+ // Display the image on the screen
+ const imgElement = document.createElement("img");
+ imgElement.src = imageDataUrl;
+ document.body.appendChild(imgElement); // Add the image to the body or replace this with your desired location
+ };
+ fileReader.readAsDataURL(file);
+ mutate(
+ { file, id: currentFlowId },
+ {
+ onSuccess: (data) => {
+ const { file_path } = data;
+ setFilePath(file_path);
+ },
+ onError: (error) => {
+ setErrorData({
+ title: "Error uploading file",
+ list: [error.response?.data?.detail],
+ });
+ console.error("Error occurred while uploading file");
+ },
+ },
+ );
+ }
+ };
+
+ const handleButtonClick = (): void => {
+ createFileUpload({
+ multiple: false,
+ accept: ALLOWED_IMAGE_INPUT_EXTENSIONS.join(","),
+ }).then((files) => upload(files[0]));
+ // Create a file input element
+ };
+
+ return (
+ <>
+
+ {!isDragging && (
+
+ Upload or drop your file
+
+ )}
+
+ {isDragging ? (
+ <>
+
+ "Drop your file here"
+ >
+ ) : image ? (
+
+ ) : (
+ <>
+
+ >
+ )}
+
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/json-input.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/json-input.tsx
new file mode 100644
index 0000000..928dd91
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/json-input.tsx
@@ -0,0 +1,45 @@
+import { IOJSONInputComponentType } from "@/types/components";
+import { useEffect, useRef } from "react";
+import JsonView from "react18-json-view";
+import { useDarkStore } from "../../../../../stores/darkStore";
+
+export default function IoJsonInput({
+ value = [],
+ onChange,
+ left,
+ output,
+}: IOJSONInputComponentType): JSX.Element {
+ useEffect(() => {
+ if (value) onChange(value);
+ }, [value]);
+ const isDark = useDarkStore((state) => state.dark);
+
+ const ref = useRef(null);
+ ref.current = value;
+
+ const getClassNames = () => {
+ if (!isDark && !left) return "json-view-playground-white";
+ if (!isDark && left) return "json-view-playground-white-left";
+ if (isDark && left) return "json-view-playground-dark-left";
+ if (isDark && !left) return "json-view-playground-dark";
+ };
+
+ return (
+
+ {
+ ref.current = edit["src"];
+ }}
+ onChange={(edit) => {
+ ref.current = edit["src"];
+ }}
+ src={ref.current}
+ />
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/key-pair-input.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/key-pair-input.tsx
new file mode 100644
index 0000000..cfde995
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/key-pair-input.tsx
@@ -0,0 +1,107 @@
+import _ from "lodash";
+import { useRef } from "react";
+import IconComponent from "../../../../../components/common/genericIconComponent";
+import { Input } from "../../../../../components/ui/input";
+import { classNames } from "../../../../../utils/utils";
+
+export type IOKeyPairInputProps = {
+ value: any;
+ onChange: (value: any) => void;
+ duplicateKey: boolean;
+ isList: boolean;
+ isInputField?: boolean;
+};
+
+const IOKeyPairInput = ({
+ value,
+ onChange,
+ duplicateKey,
+ isList = true,
+ isInputField,
+}: IOKeyPairInputProps) => {
+ const checkValueType = (value) => {
+ return Array.isArray(value) ? value : [value];
+ };
+
+ const ref = useRef([]);
+ ref.current =
+ !value || value?.length === 0 ? [{ "": "" }] : checkValueType(value);
+
+ const handleChangeKey = (event, idx) => {
+ const oldKey = Object.keys(ref.current[idx])[0];
+ const updatedObj = { [event.target.value]: ref.current[idx][oldKey] };
+ ref.current[idx] = updatedObj;
+ onChange(ref.current);
+ };
+
+ const handleChangeValue = (newValue, idx) => {
+ const key = Object.keys(ref.current[idx])[0];
+ ref.current[idx][key] = newValue;
+ onChange(ref.current);
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default IOKeyPairInput;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/session-selector.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/session-selector.tsx
new file mode 100644
index 0000000..40fecf1
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/components/session-selector.tsx
@@ -0,0 +1,218 @@
+import IconComponent from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+} from "@/components/ui/select-custom";
+import { useUpdateSessionName } from "@/controllers/API/queries/messages/use-rename-session";
+import useFlowStore from "@/stores/flowStore";
+import { cn } from "@/utils/utils";
+import React, { useEffect, useRef, useState } from "react";
+
+export default function SessionSelector({
+ deleteSession,
+ session,
+ toggleVisibility,
+ isVisible,
+ inspectSession,
+ updateVisibleSession,
+ selectedView,
+ setSelectedView,
+}: {
+ deleteSession: (session: string) => void;
+ session: string;
+ toggleVisibility: () => void;
+ isVisible: boolean;
+ inspectSession: (session: string) => void;
+ updateVisibleSession: (session: string) => void;
+ selectedView?: { type: string; id: string };
+ setSelectedView: (view: { type: string; id: string } | undefined) => void;
+}) {
+ const currentFlowId = useFlowStore((state) => state.currentFlow?.id);
+ const [isEditing, setIsEditing] = useState(false);
+ const [editedSession, setEditedSession] = useState(session);
+ const { mutate: updateSessionName } = useUpdateSessionName();
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ setEditedSession(session);
+ }, [session]);
+
+ const handleEditClick = (e?: React.MouseEvent) => {
+ e?.stopPropagation();
+ setIsEditing(true);
+ };
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ setEditedSession(e.target.value);
+ };
+
+ const handleConfirm = () => {
+ setIsEditing(false);
+ if (editedSession.trim() !== session) {
+ updateSessionName(
+ { old_session_id: session, new_session_id: editedSession.trim() },
+ {
+ onSuccess: () => {
+ if (isVisible) {
+ updateVisibleSession(editedSession);
+ }
+ if (
+ selectedView?.type === "Session" &&
+ selectedView?.id === session
+ ) {
+ setSelectedView({ type: "Session", id: editedSession });
+ }
+ },
+ },
+ );
+ }
+ };
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ setEditedSession(session);
+ };
+
+ const handleSelectChange = (value: string) => {
+ switch (value) {
+ case "rename":
+ handleEditClick();
+ break;
+ case "messageLogs":
+ inspectSession(session);
+ break;
+ case "delete":
+ deleteSession(session);
+ break;
+ }
+ };
+
+ const handleOnBlur = (e: React.FocusEvent) => {
+ if (
+ !e.relatedTarget ||
+ e.relatedTarget.getAttribute("data-confirm") !== "true"
+ ) {
+ handleCancel();
+ }
+ };
+
+ const onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ e.stopPropagation();
+ handleConfirm();
+ }
+ };
+
+ return (
+ {
+ if (isEditing) e.stopPropagation();
+ else toggleVisibility();
+ }}
+ className={cn(
+ "file-component-accordion-div group cursor-pointer rounded-md text-left text-[13px] hover:bg-secondary-hover",
+ isVisible ? "bg-secondary-hover font-semibold" : "font-normal",
+ )}
+ >
+
+
+ {isEditing ? (
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+ {session === currentFlowId ? "Default Session" : session}
+
+
+ )}
+
+
+
+ {
+ e.stopPropagation();
+ }}
+ onFocusCapture={() => {
+ inputRef.current?.focus();
+ }}
+ data-confirm="true"
+ className={cn(
+ "h-8 w-fit border-none bg-transparent p-2 focus:ring-0",
+ isVisible ? "visible" : "invisible group-hover:visible",
+ )}
+ >
+
+
+
+
+
+
+
+ Rename
+
+
+
+
+
+
+
+
+ Delete
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/io-field-view.tsx b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/io-field-view.tsx
new file mode 100644
index 0000000..5daf404
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/IOFieldView/io-field-view.tsx
@@ -0,0 +1,294 @@
+import useHandleNewValue from "@/CustomNodes/hooks/use-handle-new-value";
+import { AllNodeType } from "@/types/flow";
+import { cloneDeep } from "lodash";
+import { useState } from "react";
+import ImageViewer from "../../../../components/common/ImageViewer";
+import CsvOutputComponent from "../../../../components/core/csvOutputComponent";
+import DataOutputComponent from "../../../../components/core/dataOutputComponent";
+import InputListComponent from "../../../../components/core/parameterRenderComponent/components/inputListComponent";
+import PdfViewer from "../../../../components/core/pdfViewer";
+import { Textarea } from "../../../../components/ui/textarea";
+import { PDFViewConstant } from "../../../../constants/constants";
+import {
+ IOInputTypes,
+ IOOutputTypes,
+ InputOutput,
+} from "../../../../constants/enums";
+import TextOutputView from "../../../../shared/components/textOutputView";
+import useFlowStore from "../../../../stores/flowStore";
+import { IOFieldViewProps } from "../../../../types/components";
+import {
+ convertValuesToNumbers,
+ hasDuplicateKeys,
+} from "../../../../utils/reactflowUtils";
+import CsvSelect from "./components/csv-selected";
+import IOFileInput from "./components/file-input";
+import IoJsonInput from "./components/json-input";
+import IOKeyPairInput from "./components/key-pair-input";
+
+export default function IOFieldView({
+ type,
+ fieldType,
+ fieldId,
+ left,
+}: IOFieldViewProps): JSX.Element | undefined {
+ const nodes = useFlowStore((state) => state.nodes);
+ const setNode = useFlowStore((state) => state.setNode);
+ const flowPool = useFlowStore((state) => state.flowPool);
+ const node: AllNodeType | undefined = nodes.find(
+ (node) => node.id === fieldId,
+ );
+ const flowPoolNode = (flowPool[node!.id] ?? [])[
+ (flowPool[node!.id]?.length ?? 1) - 1
+ ];
+ const handleChangeSelect = (e) => {
+ if (node) {
+ let newNode = cloneDeep(node);
+ if (newNode.data.node?.template.separator) {
+ newNode.data.node.template.separator.value = e;
+ setNode(newNode.id, newNode);
+ }
+ }
+ };
+
+ const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
+
+ const textOutputValue =
+ (flowPool[node!.id] ?? [])[(flowPool[node!.id]?.length ?? 1) - 1]?.data
+ .results.text ?? "";
+
+ const { handleOnNewValue } = node?.data.node
+ ? useHandleNewValue({
+ node: node.data.node,
+ nodeId: node.id,
+ name: "input_value",
+ })
+ : { handleOnNewValue: (value: any, options?: any) => {} };
+
+ function handleOutputType() {
+ if (!node) return <>"No node found!">;
+ switch (type) {
+ case InputOutput.INPUT:
+ switch (fieldType) {
+ case IOInputTypes.TEXT:
+ return (
+ {
+ e.target.value;
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value =
+ e.target.value;
+ setNode(node.id, newNode);
+ }
+ }}
+ />
+ );
+ case IOInputTypes.FILE_LOADER:
+ return (
+ {
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["file_path"].value = e;
+ setNode(node.id, newNode);
+ }
+ }}
+ />
+ );
+
+ case IOInputTypes.KEYPAIR:
+ return (
+ {
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value = e;
+ setNode(node.id, newNode);
+ }
+ const valueToNumbers = convertValuesToNumbers(e);
+ setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
+ }}
+ duplicateKey={errorDuplicateKey}
+ isList={node.data.node!.template["input_value"]?.list ?? false}
+ isInputField
+ />
+ );
+
+ case IOInputTypes.JSON:
+ return (
+ {
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value = e;
+ setNode(node.id, newNode);
+ }
+ }}
+ left={left}
+ />
+ );
+
+ case IOInputTypes.STRING_LIST:
+ return (
+ <>
+
+ >
+ );
+
+ default:
+ return (
+ {
+ e.target.value;
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value =
+ e.target.value;
+ setNode(node.id, newNode);
+ }
+ }}
+ />
+ );
+ }
+ case InputOutput.OUTPUT:
+ switch (fieldType) {
+ case IOOutputTypes.TEXT:
+ return ;
+ case IOOutputTypes.PDF:
+ return left ? (
+ {PDFViewConstant}
+ ) : (
+
+ );
+ case IOOutputTypes.CSV:
+ return left ? (
+ <>
+
+ >
+ ) : (
+ <>
+
+ >
+ );
+ case IOOutputTypes.IMAGE:
+ return left ? (
+ Expand the view to see the image
+ ) : (
+
+ );
+
+ case IOOutputTypes.JSON:
+ return (
+ {
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value = e;
+ setNode(node.id, newNode);
+ }
+ }}
+ left={left}
+ output
+ />
+ );
+
+ case IOOutputTypes.KEY_PAIR:
+ return (
+ {
+ if (node) {
+ let newNode = cloneDeep(node);
+ newNode.data.node!.template["input_value"].value = e;
+ setNode(node.id, newNode);
+ }
+ const valueToNumbers = convertValuesToNumbers(e);
+ setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
+ }}
+ duplicateKey={errorDuplicateKey}
+ isList={node.data.node!.template["input_value"]?.list ?? false}
+ />
+ );
+
+ case IOOutputTypes.STRING_LIST:
+ return (
+ <>
+
+ >
+ );
+ case IOOutputTypes.DATA:
+ return (
+
+ artifact.data,
+ ) ?? [])
+ : [flowPoolNode?.data?.artifacts]
+ }
+ columnMode="union"
+ />
+
+ );
+
+ default:
+ return (
+
+ );
+ }
+ default:
+ break;
+ }
+ }
+ return handleOutputType();
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chat-view-wrapper.tsx b/langflow/src/frontend/src/modals/IOModal/components/chat-view-wrapper.tsx
new file mode 100644
index 0000000..6bcf576
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chat-view-wrapper.tsx
@@ -0,0 +1,107 @@
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+import { Separator } from "@/components/ui/separator";
+import { cn } from "@/utils/utils";
+import IconComponent from "../../../components/common/genericIconComponent";
+import { ChatViewWrapperProps } from "../types/chat-view-wrapper";
+import ChatView from "./chatView/chat-view";
+
+export const ChatViewWrapper = ({
+ selectedViewField,
+ visibleSession,
+ sessions,
+ sidebarOpen,
+ currentFlowId,
+ setSidebarOpen,
+ isPlayground,
+ setvisibleSession,
+ setSelectedViewField,
+ messagesFetched,
+ sessionId,
+ sendMessage,
+ canvasOpen,
+ setOpen,
+}: ChatViewWrapperProps) => {
+ return (
+
+
+ {visibleSession && sessions.length > 0 && sidebarOpen && (
+
+ {visibleSession === currentFlowId
+ ? "Default Session"
+ : `${visibleSession}`}
+
+ )}
+
+
+
setSidebarOpen(true)}
+ className="h-8 w-8"
+ >
+
+
+
Playground
+
+
+
+
+ {
+ setvisibleSession(undefined);
+ setSelectedViewField(undefined);
+ }}
+ >
+
+
+
+ {!isPlayground && }
+
+
+
+ {messagesFetched && (
+ {
+ setOpen(false);
+ }
+ }
+ />
+ )}
+
+
+ );
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chat-view.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chat-view.tsx
new file mode 100644
index 0000000..4ee3b3b
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chat-view.tsx
@@ -0,0 +1,228 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import { TextEffectPerChar } from "@/components/ui/textAnimation";
+import { track } from "@/customization/utils/analytics";
+import { useMessagesStore } from "@/stores/messagesStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { memo, useEffect, useMemo, useRef, useState } from "react";
+import useTabVisibility from "../../../../shared/hooks/use-tab-visibility";
+import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
+import useFlowStore from "../../../../stores/flowStore";
+import { ChatMessageType } from "../../../../types/chat";
+import { chatViewProps } from "../../../../types/components";
+import FlowRunningSqueleton from "../flow-running-squeleton";
+import ChatInput from "./chatInput/chat-input";
+import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
+import { useFileHandler } from "./chatInput/hooks/use-file-handler";
+import ChatMessage from "./chatMessage/chat-message";
+
+const MemoizedChatMessage = memo(ChatMessage, (prevProps, nextProps) => {
+ return (
+ prevProps.chat.message === nextProps.chat.message &&
+ prevProps.chat.id === nextProps.chat.id &&
+ prevProps.chat.session === nextProps.chat.session &&
+ prevProps.chat.content_blocks === nextProps.chat.content_blocks &&
+ prevProps.chat.properties === nextProps.chat.properties &&
+ prevProps.lastMessage === nextProps.lastMessage
+ );
+});
+
+export default function ChatView({
+ sendMessage,
+ visibleSession,
+ focusChat,
+ closeChat,
+}: chatViewProps): JSX.Element {
+ const flowPool = useFlowStore((state) => state.flowPool);
+ const inputs = useFlowStore((state) => state.inputs);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const messagesRef = useRef(null);
+ const [chatHistory, setChatHistory] = useState(
+ undefined,
+ );
+ const messages = useMessagesStore((state) => state.messages);
+ const nodes = useFlowStore((state) => state.nodes);
+ const chatInput = inputs.find((input) => input.type === "ChatInput");
+ const chatInputNode = nodes.find((node) => node.id === chatInput?.id);
+ const displayLoadingMessage = useMessagesStore(
+ (state) => state.displayLoadingMessage,
+ );
+
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+
+ const inputTypes = inputs.map((obj) => obj.type);
+ const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
+ const setChatValueStore = useUtilityStore((state) => state.setChatValueStore);
+ const isTabHidden = useTabVisibility();
+
+ //build chat history
+ useEffect(() => {
+ const messagesFromMessagesStore: ChatMessageType[] = messages
+ .filter(
+ (message) =>
+ message.flow_id === currentFlowId &&
+ (visibleSession === message.session_id || visibleSession === null),
+ )
+ .map((message) => {
+ let files = message.files;
+ // Handle the "[]" case, empty string, or already parsed array
+ if (Array.isArray(files)) {
+ // files is already an array, no need to parse
+ } else if (files === "[]" || files === "") {
+ files = [];
+ } else if (typeof files === "string") {
+ try {
+ files = JSON.parse(files);
+ } catch (error) {
+ console.error("Error parsing files:", error);
+ files = [];
+ }
+ }
+ return {
+ isSend: message.sender === "User",
+ message: message.text,
+ sender_name: message.sender_name,
+ files: files,
+ id: message.id,
+ timestamp: message.timestamp,
+ session: message.session_id,
+ edit: message.edit,
+ background_color: message.background_color || "",
+ text_color: message.text_color || "",
+ content_blocks: message.content_blocks || [],
+ category: message.category || "",
+ properties: message.properties || {},
+ };
+ });
+ const finalChatHistory = [...messagesFromMessagesStore].sort((a, b) => {
+ return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
+ });
+
+ if (messages.length === 0 && !isBuilding && chatInputNode && isTabHidden) {
+ setChatValueStore(
+ chatInputNode.data.node.template["input_value"].value ?? "",
+ );
+ } else {
+ isTabHidden ? setChatValueStore("") : null;
+ }
+
+ setChatHistory(finalChatHistory);
+ }, [flowPool, messages, visibleSession]);
+ useEffect(() => {
+ if (messagesRef.current) {
+ messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
+ }
+ }, []);
+
+ const ref = useRef(null);
+
+ useEffect(() => {
+ if (ref.current) {
+ ref.current.focus();
+ }
+ // trigger focus on chat when new session is set
+ }, [focusChat]);
+
+ function updateChat(
+ chat: ChatMessageType,
+ message: string,
+ stream_url?: string,
+ ) {
+ chat.message = message;
+ if (chat.componentId)
+ updateFlowPool(chat.componentId, {
+ message,
+ sender_name: chat.sender_name ?? "Bot",
+ sender: chat.isSend ? "User" : "Machine",
+ });
+ }
+
+ const { files, setFiles, handleFiles } = useFileHandler(currentFlowId);
+ const [isDragging, setIsDragging] = useState(false);
+
+ const { dragOver, dragEnter, dragLeave } = useDragAndDrop(setIsDragging);
+
+ const onDrop = (e) => {
+ e.preventDefault();
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
+ handleFiles(e.dataTransfer.files);
+ e.dataTransfer.clearData();
+ }
+ setIsDragging(false);
+ };
+
+ const flowRunningSkeletonMemo = useMemo(() => , []);
+
+ return (
+
+
+ {chatHistory &&
+ (isBuilding || chatHistory?.length > 0 ? (
+ <>
+ {chatHistory?.map((chat, index) => (
+
+ ))}
+ >
+ ) : (
+
+
+
+
+
+ New chat
+
+
+
+ Test your flow with a chat prompt
+
+
+
+
+
+ ))}
+
+ {displayLoadingMessage &&
+ !(chatHistory?.[chatHistory.length - 1]?.category === "error") &&
+ flowRunningSkeletonMemo}
+
+
+
+ {
+ sendMessage({ repeat, files });
+ track("Playground Message Sent");
+ }}
+ inputRef={ref}
+ files={files}
+ setFiles={setFiles}
+ isDragging={isDragging}
+ />
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/chat-input.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/chat-input.tsx
new file mode 100644
index 0000000..7de0862
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/chat-input.tsx
@@ -0,0 +1,261 @@
+import { Button } from "@/components/ui/button";
+import Loading from "@/components/ui/loading";
+import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
+import useFileSizeValidator from "@/shared/hooks/use-file-size-validator";
+import useAlertStore from "@/stores/alertStore";
+import useFlowStore from "@/stores/flowStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { useEffect, useRef } from "react";
+import ShortUniqueId from "short-unique-id";
+import {
+ ALLOWED_IMAGE_INPUT_EXTENSIONS,
+ CHAT_INPUT_PLACEHOLDER,
+ CHAT_INPUT_PLACEHOLDER_SEND,
+ FS_ERROR_TEXT,
+ SN_ERROR_TEXT,
+} from "../../../../../constants/constants";
+import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
+import {
+ ChatInputType,
+ FilePreviewType,
+} from "../../../../../types/components";
+import FilePreview from "../fileComponent/components/file-preview";
+import ButtonSendWrapper from "./components/button-send-wrapper";
+import TextAreaWrapper from "./components/text-area-wrapper";
+import UploadFileButton from "./components/upload-file-button";
+import useAutoResizeTextArea from "./hooks/use-auto-resize-text-area";
+import useFocusOnUnlock from "./hooks/use-focus-unlock";
+export default function ChatInput({
+ sendMessage,
+ inputRef,
+ noInput,
+ files,
+ setFiles,
+ isDragging,
+}: ChatInputType): JSX.Element {
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const fileInputRef = useRef(null);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { validateFileSize } = useFileSizeValidator(setErrorData);
+ const stopBuilding = useFlowStore((state) => state.stopBuilding);
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ const chatValue = useUtilityStore((state) => state.chatValueStore);
+
+ useFocusOnUnlock(isBuilding, inputRef);
+ useAutoResizeTextArea(chatValue, inputRef);
+
+ const { mutate } = usePostUploadFile();
+
+ const handleFileChange = async (
+ event: React.ChangeEvent | ClipboardEvent,
+ ) => {
+ let file: File | null = null;
+
+ if ("clipboardData" in event) {
+ const items = event.clipboardData?.items;
+ if (items) {
+ for (let i = 0; i < items.length; i++) {
+ const blob = items[i].getAsFile();
+ if (blob) {
+ file = blob;
+ break;
+ }
+ }
+ }
+ } else {
+ const fileInput = event.target as HTMLInputElement;
+ file = fileInput.files?.[0] ?? null;
+ }
+ if (file) {
+ const fileExtension = file.name.split(".").pop()?.toLowerCase();
+
+ if (!validateFileSize(file)) {
+ return;
+ }
+
+ if (
+ !fileExtension ||
+ !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
+ ) {
+ setErrorData({
+ title: "Error uploading file",
+ list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
+ });
+ return;
+ }
+
+ const uid = new ShortUniqueId();
+ const id = uid.randomUUID(10);
+
+ const type = file.type.split("/")[0];
+
+ setFiles((prevFiles) => [
+ ...prevFiles,
+ { file, loading: true, error: false, id, type },
+ ]);
+
+ mutate(
+ { file, id: currentFlowId },
+ {
+ onSuccess: (data) => {
+ setFiles((prev) => {
+ const newFiles = [...prev];
+ const updatedIndex = newFiles.findIndex((file) => file.id === id);
+ newFiles[updatedIndex].loading = false;
+ newFiles[updatedIndex].path = data.file_path;
+ return newFiles;
+ });
+ },
+ onError: (error) => {
+ setFiles((prev) => {
+ const newFiles = [...prev];
+ const updatedIndex = newFiles.findIndex((file) => file.id === id);
+ newFiles[updatedIndex].loading = false;
+ newFiles[updatedIndex].error = true;
+ return newFiles;
+ });
+ setErrorData({
+ title: "Error uploading file",
+ list: [error.response?.data?.detail],
+ });
+ },
+ },
+ );
+ }
+
+ if ("target" in event && event.target instanceof HTMLInputElement) {
+ event.target.value = "";
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener("paste", handleFileChange);
+ return () => {
+ document.removeEventListener("paste", handleFileChange);
+ };
+ }, [handleFileChange, currentFlowId, isBuilding]);
+
+ const send = () => {
+ sendMessage({
+ repeat: 1,
+ files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
+ });
+ setFiles([]);
+ };
+
+ const checkSendingOk = (event: React.KeyboardEvent) => {
+ return (
+ event.key === "Enter" &&
+ !isBuilding &&
+ !event.shiftKey &&
+ !event.nativeEvent.isComposing
+ );
+ };
+
+ const classNameFilePreview = `flex w-full items-center gap-2 py-2 overflow-auto custom-scroll`;
+
+ const handleButtonClick = () => {
+ fileInputRef.current!.click();
+ };
+
+ const handleDeleteFile = (file: FilePreviewType) => {
+ setFiles((prev: FilePreviewType[]) => prev.filter((f) => f.id !== file.id));
+ // TODO: delete file on backend
+ };
+
+ if (noInput) {
+ return (
+
+
+ {!isBuilding ? (
+
{
+ sendMessage({
+ repeat: 1,
+ });
+ }}
+ >
+ Run Flow
+
+ ) : (
+
+
+ Stop
+
+
+
+ )}
+
+
+ Add a{" "}
+
+ Chat Input
+ {" "}
+ component to your flow to send messages.
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {files.map((file) => (
+ {
+ handleDeleteFile(file);
+ }}
+ />
+ ))}
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/button-send-wrapper.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/button-send-wrapper.tsx
new file mode 100644
index 0000000..b5bbdc0
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/button-send-wrapper.tsx
@@ -0,0 +1,86 @@
+import Loading from "@/components/ui/loading";
+import useFlowStore from "@/stores/flowStore";
+import { Button } from "../../../../../../components/ui/button";
+import { Case } from "../../../../../../shared/components/caseComponent";
+import { FilePreviewType } from "../../../../../../types/components";
+import { classNames } from "../../../../../../utils/utils";
+
+const BUTTON_STATES = {
+ NO_INPUT: "bg-high-indigo text-background",
+ HAS_CHAT_VALUE: "text-primary",
+ SHOW_STOP:
+ "bg-muted hover:bg-secondary-hover dark:hover:bg-input text-foreground cursor-pointer",
+ DEFAULT:
+ "bg-primary text-primary-foreground hover:bg-primary-hover hover:text-secondary",
+};
+
+type ButtonSendWrapperProps = {
+ send: () => void;
+ noInput: boolean;
+ chatValue: string;
+ files: FilePreviewType[];
+};
+
+const ButtonSendWrapper = ({
+ send,
+ noInput,
+ chatValue,
+ files,
+}: ButtonSendWrapperProps) => {
+ const stopBuilding = useFlowStore((state) => state.stopBuilding);
+
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ const showStopButton = isBuilding || files.some((file) => file.loading);
+ const showSendButton =
+ !(isBuilding || files.some((file) => file.loading)) && !noInput;
+
+ const getButtonState = () => {
+ if (showStopButton) return BUTTON_STATES.SHOW_STOP;
+ if (noInput) return BUTTON_STATES.NO_INPUT;
+ if (chatValue) return BUTTON_STATES.DEFAULT;
+
+ return BUTTON_STATES.DEFAULT;
+ };
+
+ const buttonClasses = classNames("form-modal-send-button", getButtonState());
+
+ const handleClick = () => {
+ if (showStopButton && isBuilding) {
+ stopBuilding();
+ } else if (!showStopButton) {
+ send();
+ }
+ };
+
+ return (
+
+
+
+ Stop
+
+
+
+
+ {/*
+
+ */}
+
+
+
+ Send
+
+
+
+ );
+};
+
+export default ButtonSendWrapper;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/text-area-wrapper.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/text-area-wrapper.tsx
new file mode 100644
index 0000000..9060aa0
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/text-area-wrapper.tsx
@@ -0,0 +1,75 @@
+import { useUtilityStore } from "@/stores/utilityStore";
+import { useEffect } from "react";
+import { Textarea } from "../../../../../../components/ui/textarea";
+import { classNames } from "../../../../../../utils/utils";
+
+const TextAreaWrapper = ({
+ checkSendingOk,
+ send,
+ isBuilding,
+ noInput,
+ chatValue,
+ CHAT_INPUT_PLACEHOLDER,
+ CHAT_INPUT_PLACEHOLDER_SEND,
+ inputRef,
+ files,
+ isDragging,
+}) => {
+ const getPlaceholderText = (
+ isDragging: boolean,
+ noInput: boolean,
+ ): string => {
+ if (isDragging) {
+ return "Drop here";
+ } else if (noInput) {
+ return CHAT_INPUT_PLACEHOLDER;
+ } else {
+ return "Send a message...";
+ }
+ };
+
+ const fileClass = files.length > 0 ? "!rounded-t-none border-t-0" : "";
+
+ const setChatValueStore = useUtilityStore((state) => state.setChatValueStore);
+
+ const additionalClassNames =
+ "form-input block w-full border-0 custom-scroll focus:border-ring rounded-none shadow-none focus:ring-0 p-0 sm:text-sm !bg-transparent";
+
+ useEffect(() => {
+ if (!isBuilding && !noInput) {
+ inputRef.current?.focus();
+ }
+ }, [isBuilding, noInput]);
+
+ return (
+ {
+ if (checkSendingOk(event)) {
+ send();
+ }
+ }}
+ rows={1}
+ ref={inputRef}
+ disabled={isBuilding || noInput}
+ style={{
+ resize: "none",
+ bottom: `${inputRef?.current?.scrollHeight}px`,
+ maxHeight: "150px",
+ overflow: `${
+ inputRef.current && inputRef.current.scrollHeight > 150
+ ? "auto"
+ : "hidden"
+ }`,
+ }}
+ value={chatValue}
+ onChange={(event): void => {
+ setChatValueStore(event.target.value);
+ }}
+ className={classNames(fileClass, additionalClassNames)}
+ placeholder={getPlaceholderText(isDragging, noInput)}
+ />
+ );
+};
+
+export default TextAreaWrapper;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/upload-file-button.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/upload-file-button.tsx
new file mode 100644
index 0000000..7fcbbe3
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/upload-file-button.tsx
@@ -0,0 +1,42 @@
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+import { Button } from "../../../../../../components/ui/button";
+
+const UploadFileButton = ({
+ fileInputRef,
+ handleFileChange,
+ handleButtonClick,
+ isBuilding,
+}) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+export default UploadFileButton;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/helpers/get-class-file-preview.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/helpers/get-class-file-preview.ts
new file mode 100644
index 0000000..de54222
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/helpers/get-class-file-preview.ts
@@ -0,0 +1,5 @@
+export const getClassNamesFilePreview = (inputFocus) => {
+ return `flex w-full items-center gap-4 rounded-t-lg bg-background px-14 py-5 overflow-auto custom-scroll border ${
+ inputFocus ? "border-ring" : ""
+ }`;
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-auto-resize-text-area.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-auto-resize-text-area.ts
new file mode 100644
index 0000000..41428e4
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-auto-resize-text-area.ts
@@ -0,0 +1,17 @@
+import { useEffect } from "react";
+
+const useAutoResizeTextArea = (
+ value: string,
+ inputRef: React.RefObject,
+) => {
+ useEffect(() => {
+ if (inputRef.current && inputRef.current.scrollHeight! !== 0) {
+ inputRef.current.style!.height = "inherit"; // Reset the height
+ inputRef.current.style!.height = `${inputRef.current.scrollHeight!}px`; // Set it to the scrollHeight
+ }
+ }, [value]);
+
+ return inputRef;
+};
+
+export default useAutoResizeTextArea;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.ts
new file mode 100644
index 0000000..e6a2af2
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.ts
@@ -0,0 +1,28 @@
+const useDragAndDrop = (setIsDragging: (value: boolean) => void) => {
+ const dragOver = (e) => {
+ e.preventDefault();
+ if (e.dataTransfer.types.some((type) => type === "Files")) {
+ setIsDragging(true);
+ }
+ };
+
+ const dragEnter = (e) => {
+ if (e.dataTransfer.types.some((type) => type === "Files")) {
+ setIsDragging(true);
+ }
+ e.preventDefault();
+ };
+
+ const dragLeave = (e) => {
+ e.preventDefault();
+ setIsDragging(false);
+ };
+
+ return {
+ dragOver,
+ dragEnter,
+ dragLeave,
+ };
+};
+
+export default useDragAndDrop;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-handler.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-handler.ts
new file mode 100644
index 0000000..95359a2
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-handler.ts
@@ -0,0 +1,88 @@
+import { INVALID_FILE_SIZE_ALERT } from "@/constants/alerts_constants";
+import {
+ ALLOWED_IMAGE_INPUT_EXTENSIONS,
+ FS_ERROR_TEXT,
+ SN_ERROR_TEXT,
+} from "@/constants/constants";
+import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
+import useAlertStore from "@/stores/alertStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { FilePreviewType } from "@/types/components";
+import { useState } from "react";
+import ShortUniqueId from "short-unique-id";
+
+export const useFileHandler = (currentFlowId: string) => {
+ const [files, setFiles] = useState([]);
+ const { mutate } = usePostUploadFile();
+ const { setErrorData } = useAlertStore();
+ const maxFileSizeUpload = useUtilityStore((state) => state.maxFileSizeUpload);
+
+ const handleFiles = (uploadedFiles: FileList) => {
+ if (uploadedFiles) {
+ const file = uploadedFiles[0];
+ const fileExtension = file.name.split(".").pop()?.toLowerCase();
+ if (file.size > maxFileSizeUpload) {
+ setErrorData({
+ title: INVALID_FILE_SIZE_ALERT(maxFileSizeUpload / 1024 / 1024),
+ });
+ return;
+ }
+
+ if (
+ !fileExtension ||
+ !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
+ ) {
+ console.log("Error uploading file");
+ setErrorData({
+ title: "Error uploading file",
+ list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
+ });
+ return;
+ }
+ const uid = new ShortUniqueId();
+ const newId = uid.randomUUID(3);
+
+ const type = file.type.split("/")[0];
+ const blob = file;
+
+ setFiles((prevFiles) => [
+ ...prevFiles,
+ { file: blob, loading: true, error: false, id: newId, type },
+ ]);
+
+ mutate(
+ { file: blob, id: currentFlowId },
+ {
+ onSuccess: (data) => {
+ setFiles((prev) => {
+ const newFiles = [...prev];
+ const updatedIndex = newFiles.findIndex(
+ (file) => file.id === newId,
+ );
+ newFiles[updatedIndex].loading = false;
+ newFiles[updatedIndex].path = data.file_path;
+ return newFiles;
+ });
+ },
+ onError: (error) => {
+ setFiles((prev) => {
+ const newFiles = [...prev];
+ const updatedIndex = newFiles.findIndex(
+ (file) => file.id === newId,
+ );
+ newFiles[updatedIndex].loading = false;
+ newFiles[updatedIndex].error = true;
+ return newFiles;
+ });
+ setErrorData({
+ title: "Error uploading file",
+ list: [error.response?.data?.detail],
+ });
+ },
+ },
+ );
+ }
+ };
+
+ return { files, setFiles, handleFiles };
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-focus-unlock.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-focus-unlock.ts
new file mode 100644
index 0000000..09a9e15
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-focus-unlock.ts
@@ -0,0 +1,16 @@
+import { useEffect } from "react";
+
+const useFocusOnUnlock = (
+ isBuilding: boolean,
+ inputRef: React.RefObject,
+) => {
+ useEffect(() => {
+ if (!isBuilding && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isBuilding, inputRef]);
+
+ return inputRef;
+};
+
+export default useFocusOnUnlock;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.ts
new file mode 100644
index 0000000..7b7fcd5
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.ts
@@ -0,0 +1,54 @@
+import ShortUniqueId from "short-unique-id";
+import {
+ ALLOWED_IMAGE_INPUT_EXTENSIONS,
+ FS_ERROR_TEXT,
+ SN_ERROR_TEXT,
+} from "../../../../../../constants/constants";
+import useAlertStore from "../../../../../../stores/alertStore";
+import handleFileUpload from "../helpers/handle-file-upload";
+
+export const useHandleFileChange = (setFiles, currentFlowId) => {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const handleFileChange = async (
+ event: React.ChangeEvent,
+ ) => {
+ const fileInput = event.target;
+ const file = fileInput.files?.[0];
+ if (file) {
+ const fileExtension = file.name.split(".").pop()?.toLowerCase();
+
+ if (
+ !fileExtension ||
+ !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
+ ) {
+ setErrorData({
+ title: "Error uploading file",
+ list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
+ });
+ return;
+ }
+
+ const uid = new ShortUniqueId();
+ const id = uid.randomUUID(10);
+
+ const type = file.type.split("/")[0];
+ const blob = file;
+
+ setFiles((prevFiles) => [
+ ...prevFiles,
+ { file: blob, loading: true, error: false, id, type },
+ ]);
+
+ handleFileUpload(blob, currentFlowId, setFiles, id);
+ }
+
+ // Clear the file input value to ensure the change event is triggered even for the same file
+ fileInput.value = "";
+ };
+
+ return {
+ handleFileChange,
+ };
+};
+
+export default useHandleFileChange;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.ts
new file mode 100644
index 0000000..e0cc545
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.ts
@@ -0,0 +1,66 @@
+import useFlowStore from "@/stores/flowStore";
+import { AxiosResponse } from "axios";
+import { useEffect } from "react";
+import ShortUniqueId from "short-unique-id";
+import {
+ ALLOWED_IMAGE_INPUT_EXTENSIONS,
+ FS_ERROR_TEXT,
+ SN_ERROR_TEXT,
+} from "../../../../../../constants/constants";
+import useAlertStore from "../../../../../../stores/alertStore";
+import { UploadFileTypeAPI } from "../../../../../../types/api";
+
+const useUpload = (
+ uploadFile: (
+ file: File,
+ id: string,
+ ) => Promise>,
+ currentFlowId: string,
+ setFiles: any,
+) => {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ useEffect(() => {
+ const handlePaste = (event: ClipboardEvent): void => {
+ if (isBuilding) {
+ return;
+ }
+ const items = event.clipboardData?.items;
+ if (items) {
+ for (let i = 0; i < items.length; i++) {
+ const type = items[0].type.split("/")[0];
+ const uid = new ShortUniqueId();
+ const blob = items[i].getAsFile();
+ if (blob) {
+ const fileExtension = blob.name.split(".").pop()?.toLowerCase();
+
+ if (
+ !fileExtension ||
+ !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
+ ) {
+ setErrorData({
+ title: "Error uploading file",
+ list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
+ });
+ return;
+ }
+ const id = uid.randomUUID(3);
+ setFiles((prevFiles) => [
+ ...prevFiles,
+ { file: blob, loading: true, error: false, id, type },
+ ]);
+ }
+ }
+ }
+ };
+
+ document.addEventListener("paste", handlePaste);
+ return () => {
+ document.removeEventListener("paste", handlePaste);
+ };
+ }, [uploadFile, currentFlowId, isBuilding]);
+
+ return null;
+};
+
+export default useUpload;
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/chat-message.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/chat-message.tsx
new file mode 100644
index 0000000..4c0dfc5
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/chat-message.tsx
@@ -0,0 +1,446 @@
+import { ProfileIcon } from "@/components/core/appHeaderComponent/components/ProfileIcon";
+import { ContentBlockDisplay } from "@/components/core/chatComponents/ContentBlockDisplay";
+import { useUpdateMessage } from "@/controllers/API/queries/messages";
+import { CustomProfileIcon } from "@/customization/components/custom-profile-icon";
+import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import useFlowStore from "@/stores/flowStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { ChatMessageType } from "@/types/chat";
+import Convert from "ansi-to-html";
+import { useEffect, useRef, useState } from "react";
+import Robot from "../../../../../assets/robot.png";
+import IconComponent, {
+ ForwardedIconComponent,
+} from "../../../../../components/common/genericIconComponent";
+import SanitizedHTMLWrapper from "../../../../../components/common/sanitizedHTMLWrapper";
+import { EMPTY_INPUT_SEND_MESSAGE } from "../../../../../constants/constants";
+import useTabVisibility from "../../../../../shared/hooks/use-tab-visibility";
+import useAlertStore from "../../../../../stores/alertStore";
+import { chatMessagePropsType } from "../../../../../types/components";
+import { cn } from "../../../../../utils/utils";
+import { ErrorView } from "./components/content-view";
+import { MarkdownField } from "./components/edit-message";
+import EditMessageField from "./components/edit-message-field";
+import FileCardWrapper from "./components/file-card-wrapper";
+import { EditMessageButton } from "./components/message-options";
+import { convertFiles } from "./helpers/convert-files";
+
+export default function ChatMessage({
+ chat,
+ lastMessage,
+ updateChat,
+ closeChat,
+}: chatMessagePropsType): JSX.Element {
+ const convert = new Convert({ newline: true });
+ const [hidden, setHidden] = useState(true);
+ const [streamUrl, setStreamUrl] = useState(chat.stream_url);
+ const flow_id = useFlowsManagerStore((state) => state.currentFlowId);
+ const fitViewNode = useFlowStore((state) => state.fitViewNode);
+ // We need to check if message is not undefined because
+ // we need to run .toString() on it
+ const [chatMessage, setChatMessage] = useState(
+ chat.message ? chat.message.toString() : "",
+ );
+ const [isStreaming, setIsStreaming] = useState(false);
+ const eventSource = useRef(undefined);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const chatMessageRef = useRef(chatMessage);
+ const [editMessage, setEditMessage] = useState(false);
+ const [showError, setShowError] = useState(false);
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+
+ useEffect(() => {
+ const chatMessageString = chat.message ? chat.message.toString() : "";
+ setChatMessage(chatMessageString);
+ chatMessageRef.current = chatMessage;
+ }, [chat, isBuilding]);
+
+ const playgroundScrollBehaves = useUtilityStore(
+ (state) => state.playgroundScrollBehaves,
+ );
+ const setPlaygroundScrollBehaves = useUtilityStore(
+ (state) => state.setPlaygroundScrollBehaves,
+ );
+
+ // The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
+ // probably the message is empty when we have a stream_url
+ // what we need is to update the chat_message with the SSE data
+ const streamChunks = (url: string) => {
+ setIsStreaming(true); // Streaming starts
+ return new Promise((resolve, reject) => {
+ eventSource.current = new EventSource(url);
+ eventSource.current.onmessage = (event) => {
+ let parsedData = JSON.parse(event.data);
+ if (parsedData.chunk) {
+ setChatMessage((prev) => prev + parsedData.chunk);
+ }
+ };
+ eventSource.current.onerror = (event: any) => {
+ setIsStreaming(false);
+ eventSource.current?.close();
+ setStreamUrl(undefined);
+ if (JSON.parse(event.data)?.error) {
+ setErrorData({
+ title: "Error on Streaming",
+ list: [JSON.parse(event.data)?.error],
+ });
+ }
+ updateChat(chat, chatMessageRef.current);
+ reject(new Error("Streaming failed"));
+ };
+ eventSource.current.addEventListener("close", (event) => {
+ setStreamUrl(undefined); // Update state to reflect the stream is closed
+ eventSource.current?.close();
+ setIsStreaming(false);
+ resolve(true);
+ });
+ });
+ };
+
+ useEffect(() => {
+ if (streamUrl && !isStreaming) {
+ streamChunks(streamUrl)
+ .then(() => {
+ if (updateChat) {
+ updateChat(chat, chatMessageRef.current);
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }
+ }, [streamUrl, chatMessage]);
+ useEffect(() => {
+ return () => {
+ eventSource.current?.close();
+ };
+ }, []);
+
+ const isTabHidden = useTabVisibility();
+
+ useEffect(() => {
+ const element = document.getElementById("last-chat-message");
+ if (element && isTabHidden) {
+ if (playgroundScrollBehaves === "instant") {
+ element.scrollIntoView({ behavior: playgroundScrollBehaves });
+ setPlaygroundScrollBehaves("smooth");
+ } else {
+ setTimeout(() => {
+ element.scrollIntoView({ behavior: playgroundScrollBehaves });
+ }, 200);
+ }
+ }
+ }, [lastMessage, chat]);
+
+ useEffect(() => {
+ if (chat.category === "error") {
+ // Short delay before showing error to allow for loading animation
+ const timer = setTimeout(() => {
+ setShowError(true);
+ }, 50);
+ return () => clearTimeout(timer);
+ }
+ }, [chat.category]);
+
+ let decodedMessage = chatMessage ?? "";
+ try {
+ decodedMessage = decodeURIComponent(chatMessage);
+ } catch (e) {
+ // console.error(e);
+ }
+ const isEmpty = decodedMessage?.trim() === "";
+ const { mutate: updateMessageMutation } = useUpdateMessage();
+
+ const handleEditMessage = (message: string) => {
+ updateMessageMutation(
+ {
+ message: {
+ ...chat,
+ files: convertFiles(chat.files),
+ sender_name: chat.sender_name ?? "AI",
+ text: message,
+ sender: chat.isSend ? "User" : "Machine",
+ flow_id,
+ session_id: chat.session ?? "",
+ },
+ refetch: true,
+ },
+ {
+ onSuccess: () => {
+ updateChat(chat, message);
+ setEditMessage(false);
+ },
+ onError: () => {
+ setErrorData({
+ title: "Error updating messages.",
+ });
+ },
+ },
+ );
+ };
+
+ const handleEvaluateAnswer = (evaluation: boolean | null) => {
+ updateMessageMutation(
+ {
+ message: {
+ ...chat,
+ files: convertFiles(chat.files),
+ sender_name: chat.sender_name ?? "AI",
+ text: chat.message.toString(),
+ sender: chat.isSend ? "User" : "Machine",
+ flow_id,
+ session_id: chat.session ?? "",
+ properties: {
+ ...chat.properties,
+ positive_feedback: evaluation,
+ },
+ },
+ refetch: true,
+ },
+ {
+ onError: () => {
+ setErrorData({
+ title: "Error updating messages.",
+ });
+ },
+ },
+ );
+ };
+
+ const editedFlag = chat.edit ? (
+ (Edited)
+ ) : null;
+
+ if (chat.category === "error") {
+ const blocks = chat.content_blocks ?? [];
+
+ return (
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+ {!chat.isSend ? (
+
+ {chat.properties?.icon ? (
+ chat.properties.icon.match(
+ /[\u2600-\u27BF\uD83C-\uDBFF\uDC00-\uDFFF]/,
+ ) ? (
+
{chat.properties.icon}
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ ) : (
+
+ {chat.properties?.icon ? (
+ chat.properties.icon.match(
+ /[\u2600-\u27BF\uD83C-\uDBFF\uDC00-\uDFFF]/,
+ ) ? (
+
{chat.properties.icon}
+ ) : (
+
+ )
+ ) : !ENABLE_DATASTAX_LANGFLOW ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
+ {chat.sender_name}
+ {chat.properties?.source && (
+
+ {chat.properties?.source.source}
+
+ )}
+
+
+ {chat.content_blocks && chat.content_blocks.length > 0 && (
+
+ )}
+ {!chat.isSend ? (
+
+
+ {hidden && chat.thought && chat.thought !== "" && (
+
setHidden((prev) => !prev)}
+ className="form-modal-chat-icon-div"
+ >
+
+
+ )}
+ {chat.thought && chat.thought !== "" && !hidden && (
+
setHidden((prev) => !prev)}
+ />
+ )}
+ {chat.thought && chat.thought !== "" && !hidden && }
+
+
+
+ {chatMessage === "" && isBuilding && lastMessage ? (
+
+ ) : (
+
+ {editMessage ? (
+ {
+ handleEditMessage(message);
+ }}
+ onCancel={() => setEditMessage(false)}
+ />
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
+
+ ) : (
+
+
+ {editMessage ? (
+
{
+ handleEditMessage(message);
+ }}
+ onCancel={() => setEditMessage(false)}
+ />
+ ) : (
+ <>
+
+ {isEmpty ? EMPTY_INPUT_SEND_MESSAGE : decodedMessage}
+ {editedFlag}
+
+ >
+ )}
+ {chat.files && (
+
+ {chat.files?.map((file, index) => {
+ return ;
+ })}
+
+ )}
+
+
+ )}
+
+ {!editMessage && (
+
+
+ {
+ navigator.clipboard.writeText(chatMessage);
+ }}
+ onDelete={() => {}}
+ onEdit={() => setEditMessage(true)}
+ className="h-fit group-hover:visible"
+ isBotMessage={!chat.isSend}
+ onEvaluate={handleEvaluateAnswer}
+ evaluation={chat.properties?.positive_feedback}
+ />
+
+
+ )}
+
+
+
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/chat-logo-icon.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/chat-logo-icon.tsx
new file mode 100644
index 0000000..017ea07
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/chat-logo-icon.tsx
@@ -0,0 +1,14 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+
+export default function LogoIcon() {
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/code-block.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/code-block.tsx
new file mode 100644
index 0000000..033c3a5
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/code-block.tsx
@@ -0,0 +1,72 @@
+import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
+import { useState } from "react";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism";
+import { programmingLanguages } from "../../../../../../constants/constants";
+import { Props } from "../../../../../../types/components";
+
+export function CodeBlock({ language, value }: Props): JSX.Element {
+ const [isCopied, setIsCopied] = useState(false);
+
+ const copyToClipboard = (): void => {
+ if (!navigator.clipboard || !navigator.clipboard.writeText) {
+ return;
+ }
+
+ navigator.clipboard.writeText(value).then(() => {
+ setIsCopied(true);
+
+ setTimeout(() => {
+ setIsCopied(false);
+ }, 2000);
+ });
+ };
+ const downloadAsFile = (): void => {
+ const fileExtension = programmingLanguages[language] || ".file";
+ const suggestedFileName = `${"generated-code"}${fileExtension}`;
+ const fileName = window.prompt("enter file name", suggestedFileName);
+
+ if (!fileName) {
+ // user pressed cancel on prompt
+ return;
+ }
+
+ const blob = new Blob([value], { type: "text/plain" });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.download = fileName;
+ link.href = url;
+ link.style.display = "none";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ };
+ return (
+
+
+
{language}
+
+
+
+ {isCopied ? : }
+ {isCopied ? "Copied!" : "Copy Code"}
+
+
+
+
+
+
+
+
+ {value}
+
+
+ );
+}
+CodeBlock.displayName = "CodeBlock";
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/content-view.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/content-view.tsx
new file mode 100644
index 0000000..323d340
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/content-view.tsx
@@ -0,0 +1,197 @@
+import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
+import { TextShimmer } from "@/components/ui/TextShimmer";
+import { cn } from "@/utils/utils";
+import { AnimatePresence, motion } from "framer-motion";
+import Markdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
+import LogoIcon from "./chat-logo-icon";
+
+export const ErrorView = ({
+ closeChat,
+ fitViewNode,
+ chat,
+ showError,
+ lastMessage,
+ blocks,
+}: {
+ blocks: any;
+ showError: boolean;
+ lastMessage: boolean;
+ closeChat?: () => void;
+ fitViewNode: (id: string) => void;
+ chat: any;
+}) => {
+ return (
+ <>
+
+
+ {!showError && lastMessage ? (
+
+
+
+
+ Flow running...
+
+
+
+ ) : (
+
+
+ {blocks.map((block, blockIndex) => (
+
+ {block.contents.map((content, contentIndex) => {
+ if (content.type === "error") {
+ return (
+
+
+
+ {content.component && (
+ <>
+
+ An error occured in the{" "}
+ {
+ fitViewNode(
+ chat.properties?.source?.id ?? "",
+ );
+ closeChat?.();
+ }}
+ >
+ {content.component}
+ {" "}
+ Component, stopping your flow. See below for
+ more details.
+
+ >
+ )}
+
+
+
+ Error details:
+
+ {content.field && (
+
Field: {content.field}
+ )}
+ {content.reason && (
+
+ (
+
+ {props.children}
+
+ ),
+ p({ node, ...props }) {
+ return (
+
+ {props.children}
+
+ );
+ },
+ code: ({
+ node,
+ inline,
+ className,
+ children,
+ ...props
+ }) => {
+ let content = children as string;
+ if (
+ Array.isArray(children) &&
+ children.length === 1 &&
+ typeof children[0] === "string"
+ ) {
+ content = children[0] as string;
+ }
+ if (typeof content === "string") {
+ if (content.length) {
+ if (content[0] === "▍") {
+ return (
+
+ );
+ }
+ }
+
+ const match = /language-(\w+)/.exec(
+ className || "",
+ );
+
+ return !inline ? (
+
+ ) : (
+
+ {content}
+
+ );
+ }
+ },
+ }}
+ >
+ {content.reason}
+
+
+ )}
+ {content.solution && (
+
+
+ Steps to fix:
+
+
+ Check the component settings
+ Ensure all required fields are filled
+ Re-run your flow
+
+
+ )}
+
+
+ );
+ }
+ return null;
+ })}
+
+ ))}
+
+ )}
+
+
+ >
+ );
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message-field.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message-field.tsx
new file mode 100644
index 0000000..4b263ba
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message-field.tsx
@@ -0,0 +1,78 @@
+import { Button } from "@/components/ui/button";
+import { Textarea } from "@/components/ui/textarea";
+import { useEffect, useRef, useState } from "react";
+
+export default function EditMessageField({
+ message: initialMessage,
+ onEdit,
+ onCancel,
+}: {
+ message: string;
+ onEdit: (message: string) => void;
+ onCancel: () => void;
+}) {
+ const [message, setMessage] = useState(initialMessage);
+ const textareaRef = useRef(null);
+ // used before to onBlur function, leave it here because in the future we may want this functionality again
+ const [isButtonClicked, setIsButtonClicked] = useState(false);
+ const adjustTextareaHeight = () => {
+ if (textareaRef.current) {
+ textareaRef.current.style.height = "auto";
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight + 3}px`;
+ }
+ };
+ useEffect(() => {
+ adjustTextareaHeight();
+ }, []);
+
+ return (
+
+
{
+ // if (!isButtonClicked) {
+ // onCancel();
+ // }
+ // }}
+ value={message}
+ autoFocus={true}
+ onChange={(e) => setMessage(e.target.value)}
+ />
+
+
+
+ setIsButtonClicked(true)}
+ onClick={() => {
+ onEdit(message);
+ setIsButtonClicked(false);
+ }}
+ className="mt-2 bg-primary text-background hover:bg-primary-hover hover:text-secondary"
+ >
+ Save
+
+ setIsButtonClicked(true)}
+ onClick={() => {
+ onCancel();
+ setIsButtonClicked(false);
+ }}
+ className="mt-2 !bg-transparent text-foreground hover:!bg-secondary-hover"
+ >
+ Cancel
+
+
+
+ Editing messages will update the memory but won't restart the
+ conversation.
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message.tsx
new file mode 100644
index 0000000..ef0e311
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/edit-message.tsx
@@ -0,0 +1,82 @@
+import { EMPTY_OUTPUT_SEND_MESSAGE } from "@/constants/constants";
+import { cn } from "@/utils/utils";
+import Markdown from "react-markdown";
+import rehypeMathjax from "rehype-mathjax";
+import rehypeRaw from "rehype-raw";
+import remarkGfm from "remark-gfm";
+import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
+
+type MarkdownFieldProps = {
+ chat: any;
+ isEmpty: boolean;
+ chatMessage: string;
+ editedFlag: React.ReactNode;
+};
+
+export const MarkdownField = ({
+ chat,
+ isEmpty,
+ chatMessage,
+ editedFlag,
+}: MarkdownFieldProps) => {
+ return (
+
+
{props.children};
+ },
+ ol({ node, ...props }) {
+ return {props.children} ;
+ },
+ ul({ node, ...props }) {
+ return ;
+ },
+ pre({ node, ...props }) {
+ return <>{props.children}>;
+ },
+ code: ({ node, inline, className, children, ...props }) => {
+ let content = children as string;
+ if (
+ Array.isArray(children) &&
+ children.length === 1 &&
+ typeof children[0] === "string"
+ ) {
+ content = children[0] as string;
+ }
+ if (typeof content === "string") {
+ if (content.length) {
+ if (content[0] === "▍") {
+ return ;
+ }
+ }
+
+ const match = /language-(\w+)/.exec(className || "");
+
+ return !inline ? (
+
+ ) : (
+
+ {content}
+
+ );
+ }
+ },
+ }}
+ >
+ {isEmpty && !chat.stream_url ? EMPTY_OUTPUT_SEND_MESSAGE : chatMessage}
+
+ {editedFlag}
+
+ );
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/file-card-wrapper.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/file-card-wrapper.tsx
new file mode 100644
index 0000000..671e237
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/file-card-wrapper.tsx
@@ -0,0 +1,44 @@
+import { useState } from "react";
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+import FileCard from "../../fileComponent/components/file-card";
+import formatFileName from "../../fileComponent/utils/format-file-name";
+
+export default function FileCardWrapper({
+ index,
+ path,
+}: {
+ index: number;
+ path: { path: string; type: string; name: string } | string;
+}) {
+ const [show, setShow] = useState(true);
+ let name: string = "";
+ let type: string = "";
+ let pathString: string = "";
+ if (typeof path === "string") {
+ name = path.split("/").pop() || "";
+ type = path.split(".").pop() || "";
+ pathString = path;
+ } else {
+ name = path.name;
+ type = path.type;
+ pathString = path.path;
+ }
+
+ return (
+
+ setShow(!show)}
+ className="flex cursor-pointer gap-2 text-sm text-muted-foreground"
+ >
+ {formatFileName(name, 50)}
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/message-options.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/message-options.tsx
new file mode 100644
index 0000000..ebe168b
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/components/message-options.tsx
@@ -0,0 +1,109 @@
+import IconComponent from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/utils/utils";
+import { ButtonHTMLAttributes, useState } from "react";
+
+export function EditMessageButton({
+ onEdit,
+ onCopy,
+ onEvaluate,
+ isBotMessage,
+ evaluation,
+}: ButtonHTMLAttributes & {
+ onEdit: () => void;
+ onCopy: () => void;
+ onDelete: () => void;
+ onEvaluate?: (value: boolean | null) => void;
+ isBotMessage?: boolean;
+ evaluation?: boolean | null;
+}) {
+ const [isCopied, setIsCopied] = useState(false);
+
+ const handleCopy = () => {
+ onCopy();
+ setIsCopied(true);
+ setTimeout(() => setIsCopied(false), 2000);
+ };
+
+ const handleEvaluate = (value: boolean) => {
+ onEvaluate?.(evaluation === value ? null : value);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isBotMessage && (
+
+
+
+ handleEvaluate(true)}
+ className="h-8 w-8"
+ data-testid="helpful-button"
+ >
+
+
+
+
+
+
+
+ handleEvaluate(false)}
+ className="h-8 w-8"
+ data-testid="not-helpful-button"
+ >
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts
new file mode 100644
index 0000000..a9933b2
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/chatMessage/helpers/convert-files.ts
@@ -0,0 +1,20 @@
+export const convertFiles = (
+ files:
+ | (
+ | string
+ | {
+ path: string;
+ type: string;
+ name: string;
+ }
+ )[]
+ | undefined,
+) => {
+ if (!files) return [];
+ return files.map((file) => {
+ if (typeof file === "string") {
+ return file;
+ }
+ return file.path;
+ });
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/download-button.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/download-button.tsx
new file mode 100644
index 0000000..df49735
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/download-button.tsx
@@ -0,0 +1,30 @@
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+import { Button } from "../../../../../../components/ui/button";
+
+export default function DownloadButton({
+ isHovered,
+ handleDownload,
+}: {
+ isHovered: boolean;
+ handleDownload: () => void;
+}): JSX.Element | undefined {
+ if (isHovered) {
+ return (
+
+
+
+
+
+ );
+ }
+ return undefined;
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx
new file mode 100644
index 0000000..4e38858
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx
@@ -0,0 +1,80 @@
+import { useGetDownloadFileMutation } from "@/controllers/API/queries/files";
+import { useState } from "react";
+import { ForwardedIconComponent } from "../../../../../../components/common/genericIconComponent";
+import { BASE_URL_API } from "../../../../../../constants/constants";
+import { fileCardPropsType } from "../../../../../../types/components";
+import formatFileName from "../utils/format-file-name";
+import getClasses from "../utils/get-classes";
+import DownloadButton from "./download-button";
+
+const imgTypes = new Set(["png", "jpg", "jpeg", "gif", "webp", "image"]);
+
+export default function FileCard({
+ fileName,
+ path,
+ fileType,
+ showFile = true,
+}: fileCardPropsType): JSX.Element | undefined {
+ const [isHovered, setIsHovered] = useState(false);
+ const { mutate } = useGetDownloadFileMutation({
+ filename: fileName,
+ path: path,
+ });
+ function handleMouseEnter(): void {
+ setIsHovered(true);
+ }
+ function handleMouseLeave(): void {
+ setIsHovered(false);
+ }
+
+ const fileWrapperClasses = getClasses(isHovered);
+
+ const imgSrc = `${BASE_URL_API}files/images/${path}`;
+
+ if (showFile) {
+ if (imgTypes.has(fileType)) {
+ return (
+
+
+
+
mutate(undefined)}
+ />
+
+
+ );
+ }
+
+ return (
+ mutate(undefined)}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+
+
+
+ {formatFileName(fileName, 20)}
+ File
+
+
+
mutate(undefined)}
+ />
+
+ );
+ }
+ return undefined;
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-preview.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-preview.tsx
new file mode 100644
index 0000000..d420ea5
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-preview.tsx
@@ -0,0 +1,100 @@
+import IconComponent, {
+ ForwardedIconComponent,
+} from "../../../../../../components/common/genericIconComponent";
+import { Skeleton } from "../../../../../../components/ui/skeleton";
+import formatFileName from "../utils/format-file-name";
+
+const supImgFiles = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "image"];
+
+export default function FilePreview({
+ error,
+ file,
+ loading,
+ onDelete,
+}: {
+ loading: boolean;
+ file: File;
+ error: boolean;
+ onDelete: () => void;
+}) {
+ const fileType = file.type.toLowerCase();
+ const isImage = supImgFiles.some((type) => fileType.includes(type));
+
+ return (
+
+ {loading ? (
+ isImage ? (
+
+ ) : (
+
+ )
+ ) : error ? (
+
Error...
+ ) : (
+
+ {isImage ? (
+
+ ) : (
+
+
+
+ {formatFileName(file.name)}
+ File
+
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/format-file-name.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/format-file-name.tsx
new file mode 100644
index 0000000..94258b6
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/format-file-name.tsx
@@ -0,0 +1,14 @@
+export default function formatFileName(
+ name: string,
+ numberToTruncate: number = 25,
+): string {
+ if (name[numberToTruncate] === undefined) {
+ return name;
+ }
+ const fileExtension = name.split(".").pop(); // Get the file extension
+ const baseName = name.slice(0, name.lastIndexOf(".")); // Get the base name without the extension
+ if (baseName.length > 6) {
+ return `${baseName.slice(0, numberToTruncate)}...${fileExtension}`;
+ }
+ return name;
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/get-classes.tsx b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/get-classes.tsx
new file mode 100644
index 0000000..7a12a4e
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/chatView/fileComponent/utils/get-classes.tsx
@@ -0,0 +1,7 @@
+export default function getClasses(isHovered: boolean): string {
+ return `relative ${
+ false ? "h-20 w-20" : "h-20 w-80"
+ } cursor-pointer rounded-lg border border-ring bg-muted shadow transition duration-300 hover:drop-shadow-lg ${
+ isHovered ? "shadow-md" : ""
+ }`;
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/flow-running-squeleton.tsx b/langflow/src/frontend/src/modals/IOModal/components/flow-running-squeleton.tsx
new file mode 100644
index 0000000..0844a48
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/flow-running-squeleton.tsx
@@ -0,0 +1,17 @@
+import { TextShimmer } from "@/components/ui/TextShimmer";
+import LogoIcon from "./chatView/chatMessage/components/chat-logo-icon";
+
+export default function FlowRunningSqueleton() {
+ return (
+
+
+
+
+
+ Flow running...
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/selected-view-field.tsx b/langflow/src/frontend/src/modals/IOModal/components/selected-view-field.tsx
new file mode 100644
index 0000000..897bb80
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/selected-view-field.tsx
@@ -0,0 +1,64 @@
+import { InputOutput } from "@/constants/enums";
+import { cn } from "@/utils/utils";
+import IconComponent from "../../../components/common/genericIconComponent";
+import { SelectedViewFieldProps } from "../types/selected-view-field";
+import IOFieldView from "./IOFieldView/io-field-view";
+import SessionView from "./session-view";
+
+export const SelectedViewField = ({
+ selectedViewField,
+ setSelectedViewField,
+ haveChat,
+ inputs,
+ outputs,
+ sessions,
+ currentFlowId,
+ nodes,
+}: SelectedViewFieldProps) => {
+ return (
+ <>
+
+
+ {haveChat && (
+ setSelectedViewField(undefined)}>
+
+
+ )}
+ {
+ nodes.find((node) => node.id === selectedViewField?.id)?.data.node
+ .display_name
+ }
+
+
+ {inputs.some((input) => input.id === selectedViewField?.id) && (
+
+ )}
+ {outputs.some((output) => output.id === selectedViewField?.id) && (
+
+ )}
+ {sessions.some((session) => session === selectedViewField?.id) && (
+
+ )}
+
+
+ >
+ );
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/components/session-view.tsx b/langflow/src/frontend/src/modals/IOModal/components/session-view.tsx
new file mode 100644
index 0000000..a74c0c5
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/session-view.tsx
@@ -0,0 +1,117 @@
+import Loading from "@/components/ui/loading";
+import {
+ useDeleteMessages,
+ useUpdateMessage,
+} from "@/controllers/API/queries/messages";
+import { useIsFetching } from "@tanstack/react-query";
+import { NewValueParams, SelectionChangedEvent } from "ag-grid-community";
+import cloneDeep from "lodash/cloneDeep";
+import { useMemo, useState } from "react";
+import TableComponent from "../../../components/core/parameterRenderComponent/components/tableComponent";
+import useAlertStore from "../../../stores/alertStore";
+import { useMessagesStore } from "../../../stores/messagesStore";
+import { extractColumnsFromRows, messagesSorter } from "../../../utils/utils";
+
+export default function SessionView({
+ session,
+ id,
+}: {
+ session?: string;
+ id?: string;
+}) {
+ const messages = useMessagesStore((state) => state.messages);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const updateMessage = useMessagesStore((state) => state.updateMessage);
+ const deleteMessagesStore = useMessagesStore((state) => state.removeMessages);
+ const columns = extractColumnsFromRows(messages, "intersection");
+ const isFetching = useIsFetching({
+ queryKey: ["useGetMessagesQuery"],
+ exact: false,
+ });
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const { mutate: deleteMessages } = useDeleteMessages({
+ onSuccess: () => {
+ deleteMessagesStore(selectedRows);
+ setSelectedRows([]);
+ setSuccessData({
+ title: "Messages deleted successfully.",
+ });
+ },
+ onError: () => {
+ setErrorData({
+ title: "Error deleting messages.",
+ });
+ },
+ });
+
+ const { mutate: updateMessageMutation } = useUpdateMessage();
+
+ function handleUpdateMessage(event: NewValueParams) {
+ const newValue = event.newValue;
+ const field = event.column.getColId();
+ const row = cloneDeep(event.data);
+ const data = {
+ ...row,
+ [field]: newValue,
+ };
+ updateMessageMutation(
+ { message: data },
+ {
+ onSuccess: () => {
+ updateMessage(data);
+ // Set success message
+ setSuccessData({
+ title: "Messages updated successfully.",
+ });
+ },
+ onError: () => {
+ setErrorData({
+ title: "Error updating messages.",
+ });
+ event.data[field] = event.oldValue;
+ event.api.refreshCells();
+ },
+ },
+ );
+ }
+
+ const filteredMessages = useMemo(() => {
+ let filteredMessages = session
+ ? messages.filter((message) => message.session_id === session)
+ : messages;
+ filteredMessages = id
+ ? filteredMessages.filter((message) => message.flow_id === id)
+ : filteredMessages;
+ return filteredMessages;
+ }, [session, id, messages]);
+
+ function handleRemoveMessages() {
+ deleteMessages({ ids: selectedRows });
+ }
+
+ return isFetching > 0 ? (
+
+
+
+ ) : (
+ {
+ setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
+ }}
+ rowSelection="multiple"
+ suppressRowClickSelection={true}
+ pagination={true}
+ columnDefs={columns.sort(messagesSorter)}
+ rowData={filteredMessages}
+ />
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/components/sidebar-open-view.tsx b/langflow/src/frontend/src/modals/IOModal/components/sidebar-open-view.tsx
new file mode 100644
index 0000000..05e4cfa
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/components/sidebar-open-view.tsx
@@ -0,0 +1,79 @@
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+import IconComponent from "../../../components/common/genericIconComponent";
+import { SidebarOpenViewProps } from "../types/sidebar-open-view";
+import SessionSelector from "./IOFieldView/components/session-selector";
+
+export const SidebarOpenView = ({
+ sessions,
+ setSelectedViewField,
+ setvisibleSession,
+ handleDeleteSession,
+ visibleSession,
+ selectedViewField,
+}: SidebarOpenViewProps) => {
+ return (
+ <>
+
+
+
+
+
+
+ {
+ setvisibleSession(undefined);
+ setSelectedViewField(undefined);
+ }}
+ >
+
+
+
+
+
+
+
+ {sessions.map((session, index) => (
+ {
+ handleDeleteSession(session);
+ if (selectedViewField?.id === session) {
+ setSelectedViewField(undefined);
+ }
+ }}
+ updateVisibleSession={(session) => {
+ setvisibleSession(session);
+ }}
+ toggleVisibility={() => {
+ setvisibleSession(session);
+ }}
+ isVisible={visibleSession === session}
+ inspectSession={(session) => {
+ setSelectedViewField({
+ id: session,
+ type: "Session",
+ });
+ }}
+ />
+ ))}
+
+
+ >
+ );
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/new-modal.tsx b/langflow/src/frontend/src/modals/IOModal/new-modal.tsx
new file mode 100644
index 0000000..71dd947
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/new-modal.tsx
@@ -0,0 +1,334 @@
+import { EventDeliveryType } from "@/constants/enums";
+import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
+import {
+ useDeleteMessages,
+ useGetMessagesQuery,
+} from "@/controllers/API/queries/messages";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { useCallback, useEffect, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import ShadTooltip from "../../components/common/shadTooltipComponent";
+import { Button } from "../../components/ui/button";
+import useAlertStore from "../../stores/alertStore";
+import useFlowStore from "../../stores/flowStore";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import { useMessagesStore } from "../../stores/messagesStore";
+import { IOModalPropsType } from "../../types/components";
+import { cn } from "../../utils/utils";
+import BaseModal from "../baseModal";
+import { ChatViewWrapper } from "./components/chat-view-wrapper";
+import { SelectedViewField } from "./components/selected-view-field";
+import { SidebarOpenView } from "./components/sidebar-open-view";
+
+export default function IOModal({
+ children,
+ open,
+ setOpen,
+ disable,
+ isPlayground,
+ canvasOpen,
+}: IOModalPropsType): JSX.Element {
+ const allNodes = useFlowStore((state) => state.nodes);
+ const setIOModalOpen = useFlowsManagerStore((state) => state.setIOModalOpen);
+ const inputs = useFlowStore((state) => state.inputs).filter(
+ (input) => input.type !== "ChatInput",
+ );
+ const chatInput = useFlowStore((state) => state.inputs).find(
+ (input) => input.type === "ChatInput",
+ );
+ const outputs = useFlowStore((state) => state.outputs).filter(
+ (output) => output.type !== "ChatOutput",
+ );
+ const chatOutput = useFlowStore((state) => state.outputs).find(
+ (output) => output.type === "ChatOutput",
+ );
+ const nodes = useFlowStore((state) => state.nodes).filter(
+ (node) =>
+ inputs.some((input) => input.id === node.id) ||
+ outputs.some((output) => output.id === node.id),
+ );
+ const haveChat = chatInput || chatOutput;
+ const [selectedTab, setSelectedTab] = useState(
+ inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0,
+ );
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const deleteSession = useMessagesStore((state) => state.deleteSession);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const [sidebarOpen, setSidebarOpen] = useState(true);
+
+ const { mutate: deleteSessionFunction } = useDeleteMessages();
+ const [visibleSession, setvisibleSession] = useState(
+ currentFlowId,
+ );
+
+ useEffect(() => {
+ setIOModalOpen(open);
+ return () => {
+ setIOModalOpen(false);
+ };
+ }, [open]);
+
+ function handleDeleteSession(session_id: string) {
+ deleteSessionFunction(
+ {
+ ids: messages
+ .filter((msg) => msg.session_id === session_id)
+ .map((msg) => msg.id),
+ },
+ {
+ onSuccess: () => {
+ setSuccessData({
+ title: "Session deleted successfully.",
+ });
+ deleteSession(session_id);
+ if (visibleSession === session_id) {
+ setvisibleSession(undefined);
+ }
+ },
+ onError: () => {
+ setErrorData({
+ title: "Error deleting Session.",
+ });
+ },
+ },
+ );
+ }
+
+ function startView() {
+ if (!chatInput && !chatOutput) {
+ if (inputs.length > 0) {
+ return inputs[0];
+ } else {
+ return outputs[0];
+ }
+ } else {
+ return undefined;
+ }
+ }
+
+ const [selectedViewField, setSelectedViewField] = useState<
+ { type: string; id: string } | undefined
+ >(startView());
+
+ const buildFlow = useFlowStore((state) => state.buildFlow);
+ const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
+
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ const messages = useMessagesStore((state) => state.messages);
+ const [sessions, setSessions] = useState(
+ Array.from(
+ new Set(
+ messages
+ .filter((message) => message.flow_id === currentFlowId)
+ .map((message) => message.session_id),
+ ),
+ ),
+ );
+ const [sessionId, setSessionId] = useState(currentFlowId);
+ const { isFetched: messagesFetched } = useGetMessagesQuery(
+ {
+ mode: "union",
+ id: currentFlowId,
+ },
+ { enabled: open },
+ );
+
+ const chatValue = useUtilityStore((state) => state.chatValueStore);
+ const setChatValue = useUtilityStore((state) => state.setChatValueStore);
+ const config = useGetConfig();
+
+ function shouldStreamEvents() {
+ return config.data?.event_delivery === EventDeliveryType.STREAMING;
+ }
+
+ const sendMessage = useCallback(
+ async ({
+ repeat = 1,
+ files,
+ }: {
+ repeat: number;
+ files?: string[];
+ }): Promise => {
+ if (isBuilding) return;
+ setChatValue("");
+ for (let i = 0; i < repeat; i++) {
+ await buildFlow({
+ input_value: chatValue,
+ startNodeId: chatInput?.id,
+ files: files,
+ silent: true,
+ session: sessionId,
+ stream: shouldStreamEvents(),
+ }).catch((err) => {
+ console.error(err);
+ });
+ }
+ },
+ [isBuilding, setIsBuilding, chatValue, chatInput?.id, sessionId, buildFlow],
+ );
+
+ useEffect(() => {
+ setSelectedTab(inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0);
+ }, [allNodes.length]);
+
+ useEffect(() => {
+ const sessions = new Set();
+ messages
+ .filter((message) => message.flow_id === currentFlowId)
+ .forEach((row) => {
+ sessions.add(row.session_id);
+ });
+ setSessions((prev) => {
+ if (prev.length < Array.from(sessions).length) {
+ // set the new session as visible
+ setvisibleSession(
+ Array.from(sessions)[Array.from(sessions).length - 1],
+ );
+ }
+ return Array.from(sessions);
+ });
+ }, [messages]);
+
+ useEffect(() => {
+ if (!visibleSession) {
+ setSessionId(
+ `Session ${new Date().toLocaleString("en-US", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", hour12: false, second: "2-digit", timeZone: "UTC" })}`,
+ );
+ } else if (visibleSession) {
+ setSessionId(visibleSession);
+ if (selectedViewField?.type === "Session") {
+ setSelectedViewField({
+ id: visibleSession,
+ type: "Session",
+ });
+ }
+ }
+ }, [visibleSession]);
+
+ const setPlaygroundScrollBehaves = useUtilityStore(
+ (state) => state.setPlaygroundScrollBehaves,
+ );
+
+ useEffect(() => {
+ if (open) {
+ setPlaygroundScrollBehaves("instant");
+ }
+ }, [open]);
+
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth < 1024) {
+ // 1024px is Tailwind's 'lg' breakpoint
+ setSidebarOpen(false);
+ } else {
+ setSidebarOpen(true);
+ }
+ };
+
+ // Initial check
+ handleResize();
+
+ // Add event listener
+ window.addEventListener("resize", handleResize);
+
+ // Cleanup
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, []);
+
+ return (
+ sendMessage({ repeat: 1 })}
+ size="x-large"
+ className="!rounded-[12px] p-0"
+ >
+ {children}
+ {/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */}
+
+ {open && (
+
+
+
+
+
+ setSidebarOpen(!sidebarOpen)}
+ >
+
+
+
+ {sidebarOpen && (
+
Playground
+ )}
+
+ {sidebarOpen && (
+
+ )}
+
+
+
+ {selectedViewField && (
+
+ )}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts b/langflow/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts
new file mode 100644
index 0000000..31b101a
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/types/chat-view-wrapper.ts
@@ -0,0 +1,19 @@
+export type ChatViewWrapperProps = {
+ selectedViewField: { type: string; id: string } | undefined;
+ visibleSession: string | undefined;
+ sessions: string[];
+ sidebarOpen: boolean;
+ currentFlowId: string;
+ setSidebarOpen: (open: boolean) => void;
+ isPlayground: boolean | undefined;
+ setvisibleSession: (session: string | undefined) => void;
+ setSelectedViewField: (
+ field: { type: string; id: string } | undefined,
+ ) => void;
+ haveChat: { type: string; id: string; displayName: string } | undefined;
+ messagesFetched: boolean;
+ sessionId: string;
+ sendMessage: (options: { repeat: number; files?: string[] }) => Promise;
+ canvasOpen: boolean | undefined;
+ setOpen: (open: boolean) => void;
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/types/selected-view-field.ts b/langflow/src/frontend/src/modals/IOModal/types/selected-view-field.ts
new file mode 100644
index 0000000..b828e6f
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/types/selected-view-field.ts
@@ -0,0 +1,21 @@
+import { Node } from "reactflow";
+export type SelectedViewFieldProps = {
+ selectedViewField: { type: string; id: string } | undefined;
+ setSelectedViewField: (
+ field: { type: string; id: string } | undefined,
+ ) => void;
+ haveChat: { type: string; id: string; displayName: string } | undefined;
+ inputs: Array<{
+ type: string;
+ id: string;
+ displayName: string;
+ }>;
+ outputs: Array<{
+ type: string;
+ id: string;
+ displayName: string;
+ }>;
+ sessions: string[];
+ currentFlowId: string;
+ nodes: Node[];
+};
diff --git a/langflow/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts b/langflow/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts
new file mode 100644
index 0000000..047c6e4
--- /dev/null
+++ b/langflow/src/frontend/src/modals/IOModal/types/sidebar-open-view.ts
@@ -0,0 +1,10 @@
+export type SidebarOpenViewProps = {
+ sessions: string[];
+ setSelectedViewField: (
+ field: { type: string; id: string } | undefined,
+ ) => void;
+ setvisibleSession: (session: string | undefined) => void;
+ handleDeleteSession: (session: string) => void;
+ visibleSession: string | undefined;
+ selectedViewField: { type: string; id: string } | undefined;
+};
diff --git a/langflow/src/frontend/src/modals/apiModal/index.tsx b/langflow/src/frontend/src/modals/apiModal/index.tsx
new file mode 100644
index 0000000..842e5a7
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/index.tsx
@@ -0,0 +1,73 @@
+import { CustomAPIGenerator } from "@/customization/components/custom-api-generator";
+import { useCustomAPICode } from "@/customization/hooks/use-custom-api-code";
+import useAuthStore from "@/stores/authStore";
+import "ace-builds/src-noconflict/ext-language_tools";
+import "ace-builds/src-noconflict/mode-python";
+import "ace-builds/src-noconflict/theme-github";
+import "ace-builds/src-noconflict/theme-twilight";
+import { ReactNode, useEffect, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import CodeTabsComponent from "../../components/core/codeTabsComponent";
+import { EXPORT_CODE_DIALOG } from "../../constants/constants";
+import { useTweaksStore } from "../../stores/tweaksStore";
+import { FlowType } from "../../types/flow/index";
+import BaseModal from "../baseModal";
+
+export default function ApiModal({
+ flow,
+ children,
+ open: myOpen,
+ setOpen: mySetOpen,
+}: {
+ flow: FlowType;
+ children: ReactNode;
+ open?: boolean;
+ setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
+}) {
+ const autoLogin = useAuthStore((state) => state.autoLogin);
+ const [open, setOpen] =
+ mySetOpen !== undefined && myOpen !== undefined
+ ? [myOpen, mySetOpen]
+ : useState(false);
+ const [activeTab, setActiveTab] = useState("0");
+ const activeTweaks = useTweaksStore((state) => state.activeTweaks);
+ const setActiveTweaks = useTweaksStore((state) => state.setActiveTweaks);
+ const tabs = useTweaksStore((state) => state.tabs);
+ const initialSetup = useTweaksStore((state) => state.initialSetup);
+
+ const getCodes = useCustomAPICode();
+
+ useEffect(() => {
+ if (open) initialSetup(autoLogin ?? false, flow, getCodes);
+ setActiveTab("0");
+ }, [open]);
+
+ return (
+
+ {children}
+
+ API
+
+
+
+ {open && (
+ <>
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-changes-types.ts b/langflow/src/frontend/src/modals/apiModal/utils/get-changes-types.ts
new file mode 100644
index 0000000..9c0a5ce
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-changes-types.ts
@@ -0,0 +1,26 @@
+import { InputFieldType } from "@/types/api";
+import { convertArrayToObj } from "../../../utils/reactflowUtils";
+
+export const getChangesType = (
+ changes: string | string[] | boolean | number | Object[] | Object,
+ template: InputFieldType,
+) => {
+ if (typeof changes === "string" && template.type === "float") {
+ changes = parseFloat(changes);
+ }
+ if (typeof changes === "string" && template.type === "int") {
+ changes = parseInt(changes);
+ }
+ if (template.list === true && Array.isArray(changes)) {
+ changes = changes?.filter((x) => x !== "");
+ }
+
+ if (template.type === "dict" && Array.isArray(changes)) {
+ changes = convertArrayToObj(changes);
+ }
+
+ if (template.type === "NestedDict") {
+ changes = JSON.stringify(changes);
+ }
+ return changes;
+};
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx b/langflow/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx
new file mode 100644
index 0000000..ca00de7
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-curl-code.tsx
@@ -0,0 +1,64 @@
+import useFlowStore from "@/stores/flowStore";
+import { GetCodeType } from "@/types/tweaks";
+
+/**
+ * Function to get the curl code for the API
+ * @param {string} flowId - The id of the flow
+ * @param {boolean} isAuth - If the API is authenticated
+ * @returns {string} - The curl code
+ */
+export function getCurlRunCode({
+ flowId,
+ isAuth,
+ tweaksBuildedObject,
+ endpointName,
+ activeTweaks,
+}: GetCodeType): string {
+ let tweaksString = "{}";
+ const inputs = useFlowStore.getState().inputs;
+ const outputs = useFlowStore.getState().outputs;
+ const hasChatInput = inputs.some((input) => input.type === "ChatInput");
+ const hasChatOutput = outputs.some((output) => output.type === "ChatOutput");
+ if (tweaksBuildedObject)
+ tweaksString = JSON.stringify(tweaksBuildedObject, null, 2);
+ // show the endpoint name in the curl command if it exists
+ return `curl -X POST \\
+ "${window.location.protocol}//${window.location.host}/api/v1/run/${
+ endpointName || flowId
+ }?stream=false" \\
+ -H 'Content-Type: application/json'\\${
+ !isAuth ? `\n -H 'x-api-key: '\\` : ""
+ }
+ -d '{${!activeTweaks ? `"input_value": "message",` : ""}
+ "output_type": ${hasChatOutput ? '"chat"' : '"text"'},
+ "input_type": ${hasChatInput ? '"chat"' : '"text"'},
+ "tweaks": ${tweaksString}}'
+ `;
+}
+
+/**
+ * Generates a cURL command for making a POST request to a webhook endpoint.
+ *
+ * @param {Object} options - The options for generating the cURL command.
+ * @param {string} options.flowId - The ID of the flow.
+ * @param {boolean} options.isAuth - Indicates whether authentication is required.
+ * @param {string} options.endpointName - The name of the webhook endpoint.
+ * @returns {string} The cURL command.
+ */
+
+// KEEP THIS FOR LFOSS
+export function getCurlWebhookCode({
+ flowId,
+ isAuth,
+ endpointName,
+}: GetCodeType) {
+ return `curl -X POST \\
+ "${window.location.protocol}//${window.location.host}/api/v1/webhook/${
+ endpointName || flowId
+ }" \\
+ -H 'Content-Type: application/json'\\${
+ !isAuth ? `\n -H 'x-api-key: '\\` : ""
+ }
+ -d '{"any": "data"}'
+ `;
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx b/langflow/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx
new file mode 100644
index 0000000..6724664
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx
@@ -0,0 +1,46 @@
+import useFlowStore from "@/stores/flowStore";
+import { GetCodeType } from "@/types/tweaks";
+
+/**
+ * Function to generate JavaScript code for interfacing with an API using the LangflowClient class.
+ * @param {string} flowId - The id of the flow.
+ * @param {boolean} isAuth - Whether the API requires authentication.
+ * @param {any[]} tweaksBuildedObject - Customizations applied to the flow.
+ * @param {string} [endpointName] - Optional endpoint name.
+ * @returns {string} - The JavaScript code as a string.
+ */
+export default function getJsApiCode({
+ flowId,
+ isAuth,
+ tweaksBuildedObject,
+ endpointName,
+ activeTweaks,
+}: GetCodeType): string {
+ let tweaksString = "{}";
+ if (tweaksBuildedObject)
+ tweaksString = JSON.stringify(tweaksBuildedObject, null, 8);
+ const inputs = useFlowStore.getState().inputs;
+ const outputs = useFlowStore.getState().outputs;
+ const hasChatInput = inputs.some((input) => input.type === "ChatInput");
+ const hasChatOutput = outputs.some((output) => output.type === "ChatOutput");
+
+ return `${activeTweaks ? "" : 'let inputValue = ""; // Insert input value here\n\n'}fetch(
+ "${window.location.protocol}//${window.location.host}/api/v1/run/${endpointName || flowId}?stream=false",
+ {
+ method: "POST",
+ headers: {
+ "Authorization": "Bearer ",
+ "Content-Type": "application/json",${isAuth ? '\n\t\t\t"x-api-key": ' : ""}
+ },
+ body: JSON.stringify({${activeTweaks ? "" : "\n\t\t\tinput_value: inputValue, "}
+ output_type: ${hasChatOutput ? '"chat"' : '"text"'},
+ input_type: ${hasChatInput ? '"chat"' : '"text"'},
+ tweaks: ${tweaksString}
+ }),
+ },
+)
+ .then(res => res.json())
+ .then(data => console.log(data))
+ .catch(error => console.error('Error:', error));
+`;
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts b/langflow/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts
new file mode 100644
index 0000000..92f0041
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-nodes-with-default-value.ts
@@ -0,0 +1,30 @@
+import { AllNodeType } from "@/types/flow";
+import { cloneDeep } from "lodash";
+import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants";
+
+export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => {
+ const filteredNodes: AllNodeType[] = [];
+
+ nodes.forEach((node) => {
+ if (node?.data?.node?.template && node?.type === "genericNode") {
+ const templateKeys = Object.keys(node.data.node.template).filter(
+ (templateField) =>
+ templateField.charAt(0) !== "_" &&
+ node!.data!.node!.template[templateField]?.show &&
+ LANGFLOW_SUPPORTED_TYPES.has(
+ node!.data!.node!.template[templateField].type,
+ ) &&
+ templateField !== "code",
+ );
+ const newNode = cloneDeep(node);
+ if (newNode?.data?.node?.template) {
+ newNode.data.node.template = templateKeys.reduce((acc, key) => {
+ acc[key] = cloneDeep(node?.data?.node?.template[key]);
+ return acc;
+ }, {});
+ }
+ filteredNodes.push(newNode);
+ }
+ });
+ return filteredNodes;
+};
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx b/langflow/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx
new file mode 100644
index 0000000..0931d1e
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx
@@ -0,0 +1,116 @@
+import { GetCodeType } from "@/types/tweaks";
+
+/**
+ * Function to get the python code for the API
+ * @param {string} flowId - The id of the flow
+ * @param {boolean} isAuth - If the API is authenticated
+ * @param {any[]} tweaksBuildedObject - The tweaks
+ * @param {string} [endpointName] - The optional endpoint name
+ * @returns {string} - The python code
+ */
+export default function getPythonApiCode({
+ flowId,
+ tweaksBuildedObject,
+ endpointName,
+ activeTweaks,
+}: GetCodeType): string {
+ let tweaksString = "{}";
+ if (tweaksBuildedObject)
+ tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)
+ .replace(/true/g, "True")
+ .replace(/false/g, "False")
+ .replace(/null|undefined/g, "None");
+
+ return `import argparse
+import json
+from argparse import RawTextHelpFormatter
+import requests
+from typing import Optional
+import warnings
+try:
+ from langflow.load import upload_file
+except ImportError:
+ warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.")
+ upload_file = None
+
+BASE_API_URL = "${window.location.protocol}//${window.location.host}"
+FLOW_ID = "${flowId}"
+ENDPOINT = "${endpointName || ""}" ${
+ endpointName
+ ? `# The endpoint name of the flow`
+ : `# You can set a specific endpoint name in the flow settings`
+ }
+
+# You can tweak the flow by adding a tweaks dictionary
+# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
+TWEAKS = ${tweaksString}
+
+def run_flow(message: str,
+ endpoint: str,
+ output_type: str = "chat",
+ input_type: str = "chat",
+ tweaks: Optional[dict] = None,
+ api_key: Optional[str] = None) -> dict:
+ """
+ Run a flow with a given message and optional tweaks.
+
+ :param message: The message to send to the flow
+ :param endpoint: The ID or the endpoint name of the flow
+ :param tweaks: Optional tweaks to customize the flow
+ :return: The JSON response from the flow
+ """
+ api_url = f"{BASE_API_URL}/api/v1/run/{endpoint}"
+
+ payload = {
+ ${!activeTweaks ? `"input_value": message,` : ""}
+ "output_type": output_type,
+ "input_type": input_type,
+ }
+ headers = None
+ if tweaks:
+ payload["tweaks"] = tweaks
+ if api_key:
+ headers = {"x-api-key": api_key}
+ response = requests.post(api_url, json=payload, headers=headers)
+ return response.json()
+
+def main():
+ parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.\nRun it like: python .py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""",
+ formatter_class=RawTextHelpFormatter)
+ parser.add_argument("message", type=str, help="The message to send to the flow")
+ parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow")
+ parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS))
+ parser.add_argument("--api_key", type=str, help="API key for authentication", default=None)
+ parser.add_argument("--output_type", type=str, default="chat", help="The output type")
+ parser.add_argument("--input_type", type=str, default="chat", help="The input type")
+ parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None)
+ parser.add_argument("--components", type=str, help="Components to upload the file to", default=None)
+
+ args = parser.parse_args()
+ try:
+ tweaks = json.loads(args.tweaks)
+ except json.JSONDecodeError:
+ raise ValueError("Invalid tweaks JSON string")
+
+ if args.upload_file:
+ if not upload_file:
+ raise ImportError("Langflow is not installed. Please install it to use the upload_file function.")
+ elif not args.components:
+ raise ValueError("You need to provide the components to upload the file to.")
+ tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=args.endpoint, components=[args.components], tweaks=tweaks)
+
+ response = run_flow(
+ message=args.message,
+ endpoint=args.endpoint,
+ output_type=args.output_type,
+ input_type=args.input_type,
+ tweaks=tweaks,
+ api_key=args.api_key
+ )
+
+ print(json.dumps(response, indent=2))
+
+if __name__ == "__main__":
+ main()
+`;
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-python-code.tsx b/langflow/src/frontend/src/modals/apiModal/utils/get-python-code.tsx
new file mode 100644
index 0000000..5432d69
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-python-code.tsx
@@ -0,0 +1,28 @@
+import { GetCodeType } from "@/types/tweaks";
+
+/**
+ * Function to get the python code for the API
+ * @param {string} flow - The current flow
+ * @param {any[]} tweaksBuildedObject - The tweaks
+ * @returns {string} - The python code
+ */
+export default function getPythonCode({
+ flowName,
+ tweaksBuildedObject,
+ activeTweaks,
+}: GetCodeType): string {
+ let tweaksString = "{}";
+ if (tweaksBuildedObject)
+ tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)
+ .replace(/true/g, "True")
+ .replace(/false/g, "False")
+ .replace(/null|undefined/g, "None");
+
+ return `from langflow.load import run_flow_from_json
+TWEAKS = ${tweaksString}
+
+result = run_flow_from_json(flow="${flowName}.json",
+ ${!activeTweaks ? `input_value="message",\n ` : ""}session_id="", # provide a session id if you want to use session state
+ fallback_to_env_vars=True, # False by default
+ tweaks=TWEAKS)`;
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/get-widget-code.tsx b/langflow/src/frontend/src/modals/apiModal/utils/get-widget-code.tsx
new file mode 100644
index 0000000..dd3e76b
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/get-widget-code.tsx
@@ -0,0 +1,26 @@
+import { GetCodeType } from "@/types/tweaks";
+
+/**
+ * Function to get the widget code for the API
+ * @param {string} flow - The current flow.
+ * @returns {string} - The widget code
+ */
+export default function getWidgetCode({
+ flowId,
+ flowName,
+ isAuth,
+}: GetCodeType): string {
+ return `
+
+ `;
+}
diff --git a/langflow/src/frontend/src/modals/apiModal/utils/tabs-array.tsx b/langflow/src/frontend/src/modals/apiModal/utils/tabs-array.tsx
new file mode 100644
index 0000000..b4aa25a
--- /dev/null
+++ b/langflow/src/frontend/src/modals/apiModal/utils/tabs-array.tsx
@@ -0,0 +1,80 @@
+import { tabsArrayType } from "@/types/components";
+
+export function createTabsArray(
+ codes: { [key: string]: string },
+ includeTweaks = false,
+) {
+ const tabs: tabsArrayType[] = [];
+ if (codes.runCurlCode) {
+ tabs.push({
+ name: "cURL",
+ mode: "bash",
+ image: "https://curl.se/logo/curl-symbol-transparent.png",
+ language: "sh",
+ code: codes.runCurlCode,
+ hasTweaks: includeTweaks,
+ });
+ }
+ if (codes.webhookCurlCode) {
+ tabs.push({
+ name: "Webhook cURL",
+ mode: "bash",
+ image: "https://curl.se/logo/curl-symbol-transparent.png",
+ language: "sh",
+ code: codes.webhookCurlCode,
+ });
+ }
+ if (codes.pythonApiCode) {
+ tabs.push({
+ name: "Python API",
+ mode: "python",
+ image:
+ "https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
+ language: "py",
+ code: codes.pythonApiCode,
+ hasTweaks: includeTweaks,
+ });
+ }
+ if (codes.jsApiCode) {
+ tabs.push({
+ name: "JS API",
+ mode: "javascript",
+ image: "https://cdn-icons-png.flaticon.com/512/136/136530.png",
+ language: "js",
+ code: codes.jsApiCode,
+ hasTweaks: includeTweaks,
+ });
+ }
+ if (codes.pythonCode) {
+ tabs.push({
+ name: "Python Code",
+ mode: "python",
+ image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
+ language: "py",
+ code: codes.pythonCode,
+ hasTweaks: includeTweaks,
+ });
+ }
+ if (codes.widgetCode) {
+ tabs.push({
+ name: "Chat Widget HTML",
+ description:
+ "Insert this code anywhere in your <body> tag. To use with react and other libs, check our documentation .",
+ mode: "html",
+ image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
+ language: "html",
+ code: codes.widgetCode,
+ });
+ }
+ if (includeTweaks) {
+ tabs.push({
+ name: "Tweaks",
+ mode: "python",
+ image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
+ language: "py",
+ code: codes.tweaksCode,
+ });
+ }
+
+ return tabs;
+}
diff --git a/langflow/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts b/langflow/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts
new file mode 100644
index 0000000..23196ce
--- /dev/null
+++ b/langflow/src/frontend/src/modals/baseModal/helpers/switch-case-size.ts
@@ -0,0 +1,81 @@
+export const switchCaseModalSize = (size: string) => {
+ let minWidth: string;
+ let height: string;
+ switch (size) {
+ case "x-small":
+ minWidth = "min-w-[20vw]";
+ height = "";
+ break;
+ case "smaller":
+ minWidth = "min-w-[40vw]";
+ height = "h-[11rem]";
+ break;
+ case "smaller-h-full":
+ minWidth = "min-w-[40vw]";
+ height = "";
+ break;
+ case "small":
+ minWidth = "min-w-[40vw]";
+ height = "h-[40vh]";
+ break;
+ case "small-h-full":
+ minWidth = "min-w-[40vw]";
+ height = "";
+ break;
+ case "medium":
+ minWidth = "min-w-[60vw]";
+ height = "h-[60vh]";
+ break;
+ case "medium-tall":
+ minWidth = "min-w-[60vw]";
+ height = "h-[90vh]";
+ break;
+ case "medium-h-full":
+ minWidth = "min-w-[60vw]";
+ height = "";
+ break;
+ case "large":
+ minWidth = "min-w-[85vw]";
+ height = "h-[80vh]";
+ break;
+ case "templates":
+ minWidth = "w-[97vw] max-w-[1200px]";
+ height =
+ "min-h-[700px] lg:min-h-0 h-[90vh] md:h-[80vh] lg:h-[50vw] lg:max-h-[620px]";
+ break;
+ case "three-cards":
+ minWidth = "min-w-[1066px]";
+ height = "max-h-[94vh]";
+ break;
+ case "large-thin":
+ minWidth = "min-w-[65vw]";
+ height = "h-[90vh]";
+ break;
+
+ case "md-thin":
+ minWidth = "min-w-[85vw]";
+ height = "h-[90vh]";
+ break;
+
+ case "sm-thin":
+ minWidth = "min-w-[65vw]";
+ height = "h-[90vh]";
+ break;
+
+ case "large-h-full":
+ minWidth = "min-w-[80vw]";
+ height = "";
+ break;
+
+ case "x-large":
+ minWidth = "min-w-[95vw]";
+ height = "h-[95vh]";
+ break;
+
+ default:
+ minWidth = "min-w-[80vw]";
+ height = "h-[90vh]";
+ break;
+ }
+ return { minWidth, height };
+};
diff --git a/langflow/src/frontend/src/modals/baseModal/index.tsx b/langflow/src/frontend/src/modals/baseModal/index.tsx
new file mode 100644
index 0000000..dfe86a6
--- /dev/null
+++ b/langflow/src/frontend/src/modals/baseModal/index.tsx
@@ -0,0 +1,274 @@
+import { ReactNode, useEffect } from "react";
+
+import React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "../../components/ui/dialog";
+
+import {
+ Dialog as Modal,
+ DialogContent as ModalContent,
+} from "../../components/ui/dialog-with-no-close";
+
+import { DialogClose } from "@radix-ui/react-dialog";
+import * as Form from "@radix-ui/react-form";
+import { Button } from "../../components/ui/button";
+import { modalHeaderType } from "../../types/components";
+import { cn } from "../../utils/utils";
+import { switchCaseModalSize } from "./helpers/switch-case-size";
+
+type ContentProps = {
+ children: ReactNode;
+ overflowHidden?: boolean;
+ className?: string;
+};
+type HeaderProps = { children: ReactNode; description: string };
+type FooterProps = { children: ReactNode };
+type TriggerProps = {
+ children: ReactNode;
+ asChild?: boolean;
+ disable?: boolean;
+ className?: string;
+};
+
+const Content: React.FC = ({
+ children,
+ overflowHidden,
+ className,
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+const Trigger: React.FC = ({
+ children,
+ asChild,
+ disable,
+ className,
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const Header: React.FC<{
+ children: ReactNode;
+ description: string | JSX.Element | null;
+}> = ({ children, description }: modalHeaderType): JSX.Element => {
+ return (
+
+
+ {children}
+
+
+ {description}
+
+
+ );
+};
+
+const Footer: React.FC<{
+ children?: ReactNode;
+ submit?: {
+ label: string;
+ icon?: ReactNode;
+ loading?: boolean;
+ disabled?: boolean;
+ dataTestId?: string;
+ onClick?: () => void;
+ };
+ close?: boolean;
+ centered?: boolean;
+}> = ({ children, submit, close, centered }) => {
+ return (
+
+ {submit ? (
+
+ {children ??
}
+
+
+
+ Cancel
+
+
+
+ {submit.icon && submit.icon}
+ {submit.label}
+
+
+
+ ) : (
+ <>{children && children}>
+ )}
+ {close && (
+
+
+ Close
+
+
+ )}
+
+ );
+};
+interface BaseModalProps {
+ children:
+ | [
+ React.ReactElement,
+ React.ReactElement?,
+ React.ReactElement?,
+ React.ReactElement?,
+ ]
+ | React.ReactElement;
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
+ size?:
+ | "x-small"
+ | "smaller"
+ | "small"
+ | "medium"
+ | "medium-tall"
+ | "large"
+ | "three-cards"
+ | "large-thin"
+ | "large-h-full"
+ | "templates"
+ | "small-h-full"
+ | "medium-h-full"
+ | "md-thin"
+ | "sm-thin"
+ | "smaller-h-full"
+ | "medium-log"
+ | "x-large";
+ className?: string;
+ disable?: boolean;
+ onChangeOpenModal?: (open?: boolean) => void;
+ type?: "modal" | "dialog" | "full-screen";
+ onSubmit?: () => void;
+ onEscapeKeyDown?: (e: KeyboardEvent) => void;
+}
+function BaseModal({
+ className,
+ open,
+ setOpen,
+ children,
+ size = "large",
+ onChangeOpenModal,
+ type = "dialog",
+ onSubmit,
+ onEscapeKeyDown,
+}: BaseModalProps) {
+ const headerChild = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Header,
+ );
+ const triggerChild = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Trigger,
+ );
+ const ContentChild = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Content,
+ );
+ const ContentFooter = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Footer,
+ );
+
+ let { minWidth, height } = switchCaseModalSize(size);
+
+ useEffect(() => {
+ if (onChangeOpenModal) {
+ onChangeOpenModal(open);
+ }
+ }, [open]);
+
+ const modalContent = (
+ <>
+ {headerChild && headerChild}
+ {ContentChild}
+ {ContentFooter && ContentFooter}
+ >
+ );
+
+ const contentClasses = cn(
+ minWidth,
+ height,
+ "flex flex-col duration-300 overflow-hidden",
+ className,
+ );
+
+ //UPDATE COLORS AND STYLE CLASSSES
+ return (
+ <>
+ {type === "modal" ? (
+
+ {triggerChild}
+ {modalContent}
+
+ ) : type === "full-screen" ? (
+ {modalContent}
+ ) : (
+
+ {triggerChild}
+ event.preventDefault()}
+ onEscapeKeyDown={onEscapeKeyDown}
+ className={contentClasses}
+ >
+ {onSubmit ? (
+ {
+ event.preventDefault();
+ onSubmit();
+ }}
+ className="flex h-full flex-col gap-6"
+ >
+ {modalContent}
+
+ ) : (
+ modalContent
+ )}
+
+
+ )}
+ >
+ );
+}
+
+BaseModal.Content = Content;
+BaseModal.Header = Header;
+BaseModal.Trigger = Trigger;
+BaseModal.Footer = Footer;
+export default BaseModal;
diff --git a/langflow/src/frontend/src/modals/codeAreaModal/index.tsx b/langflow/src/frontend/src/modals/codeAreaModal/index.tsx
new file mode 100644
index 0000000..5154af1
--- /dev/null
+++ b/langflow/src/frontend/src/modals/codeAreaModal/index.tsx
@@ -0,0 +1,289 @@
+import "ace-builds/src-noconflict/ace";
+import "ace-builds/src-noconflict/ext-language_tools";
+import "ace-builds/src-noconflict/mode-python";
+import "ace-builds/src-noconflict/theme-github";
+import "ace-builds/src-noconflict/theme-twilight";
+// import "ace-builds/webpack-resolver";
+import { usePostValidateCode } from "@/controllers/API/queries/nodes/use-post-validate-code";
+import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code";
+import useFlowStore from "@/stores/flowStore";
+import { useEffect, useRef, useState } from "react";
+import AceEditor from "react-ace";
+import ReactAce from "react-ace/lib/ace";
+import IconComponent from "../../components/common/genericIconComponent";
+import { Button } from "../../components/ui/button";
+import { Input } from "../../components/ui/input";
+import {
+ BUG_ALERT,
+ CODE_ERROR_ALERT,
+ CODE_SUCCESS_ALERT,
+ FUNC_ERROR_ALERT,
+ IMPORT_ERROR_ALERT,
+} from "../../constants/alerts_constants";
+import {
+ CODE_PROMPT_DIALOG_SUBTITLE,
+ EDIT_CODE_TITLE,
+} from "../../constants/constants";
+import useAlertStore from "../../stores/alertStore";
+import { useDarkStore } from "../../stores/darkStore";
+import { CodeErrorDataTypeAPI } from "../../types/api";
+import { codeAreaModalPropsType } from "../../types/components";
+import BaseModal from "../baseModal";
+import ConfirmationModal from "../confirmationModal";
+
+export default function CodeAreaModal({
+ value,
+ setValue,
+ nodeClass,
+ setNodeClass,
+ children,
+ dynamic,
+ readonly = false,
+ open: myOpen,
+ setOpen: mySetOpen,
+ componentId,
+}: codeAreaModalPropsType): JSX.Element {
+ const [code, setCode] = useState(value);
+ const [open, setOpen] =
+ mySetOpen !== undefined && myOpen !== undefined
+ ? [myOpen, mySetOpen]
+ : useState(false);
+ const dark = useDarkStore((state) => state.dark);
+ const [height, setHeight] = useState(null);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const [openConfirmation, setOpenConfirmation] = useState(false);
+ const codeRef = useRef(null);
+ const { mutate, isPending } = usePostValidateCode();
+ const [error, setError] = useState<{
+ detail: CodeErrorDataTypeAPI;
+ } | null>(null);
+
+ const { mutate: validateComponentCode } = usePostValidateComponentCode();
+ const setNode = useFlowStore((state) => state.setNode);
+
+ useEffect(() => {
+ // if nodeClass.template has more fields other than code and dynamic is true
+ // do not run handleClick
+ if (dynamic && Object.keys(nodeClass!.template).length > 2) {
+ return;
+ }
+ }, []);
+
+ function processNonDynamicField() {
+ mutate(
+ { code },
+ {
+ onSuccess: (apiReturn) => {
+ if (apiReturn) {
+ let importsErrors = apiReturn.imports.errors;
+ let funcErrors = apiReturn.function.errors;
+ if (funcErrors.length === 0 && importsErrors.length === 0) {
+ setSuccessData({
+ title: CODE_SUCCESS_ALERT,
+ });
+ setOpen(false);
+ setValue(code);
+ // setValue(code);
+ } else {
+ if (funcErrors.length !== 0) {
+ setErrorData({
+ title: FUNC_ERROR_ALERT,
+ list: funcErrors,
+ });
+ }
+ if (importsErrors.length !== 0) {
+ setErrorData({
+ title: IMPORT_ERROR_ALERT,
+ list: importsErrors,
+ });
+ }
+ }
+ } else {
+ setErrorData({
+ title: BUG_ALERT,
+ });
+ }
+ },
+ onError: (error) => {
+ setErrorData({
+ title: CODE_ERROR_ALERT,
+ list: [error.response.data.detail],
+ });
+ },
+ },
+ );
+ }
+
+ function processDynamicField() {
+ validateComponentCode(
+ { code, frontend_node: nodeClass! },
+ {
+ onSuccess: ({ data, type }) => {
+ if (data && type) {
+ setValue(code);
+ setNodeClass(data, type);
+ setError({ detail: { error: undefined, traceback: undefined } });
+ setOpen(false);
+ }
+ },
+ onError: (error) => {
+ setError(error.response.data);
+ },
+ },
+ );
+ }
+
+ function processCode() {
+ if (!dynamic) {
+ processNonDynamicField();
+ } else {
+ processDynamicField();
+ }
+ }
+
+ useEffect(() => {
+ // Function to be executed after the state changes
+ const delayedFunction = setTimeout(() => {
+ if (error?.detail.error !== undefined) {
+ //trigger to update the height, does not really apply any height
+ setHeight("90%");
+ }
+ //600 to happen after the transition of 500ms
+ }, 600);
+
+ // Cleanup function to clear the timeout if the component unmounts or the state changes again
+ return () => {
+ clearTimeout(delayedFunction);
+ };
+ }, [error, setHeight]);
+
+ useEffect(() => {
+ if (!openConfirmation) {
+ codeRef.current?.editor.focus();
+ }
+ }, [openConfirmation]);
+
+ useEffect(() => {
+ setCode(value);
+ }, [value, open]);
+
+ return (
+ {
+ e.preventDefault();
+ if (code === value) {
+ setOpen(false);
+ } else {
+ if (
+ !(
+ codeRef.current?.editor.completer.popup &&
+ codeRef.current?.editor.completer.popup.isOpen
+ )
+ ) {
+ setOpenConfirmation(true);
+ }
+ }
+ }}
+ open={open}
+ setOpen={setOpen}
+ size="x-large"
+ >
+ {children}
+
+ {EDIT_CODE_TITLE}
+
+
+
+
+
+
+
{
+ setCode(value);
+ }}
+ className="h-full min-w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
+ />
+
+
+
+
+ {error?.detail?.error}
+
+
+
+ {error?.detail?.traceback}
+
+
+
+
+
+
+ Check & Save
+
+
+
+ {
+ setOpenConfirmation(false);
+ }}
+ onEscapeKeyDown={(e) => {
+ e.stopPropagation();
+ setOpenConfirmation(false);
+ }}
+ size="x-small"
+ icon="AlertTriangle"
+ confirmationText="Check & Save"
+ cancelText="Discard Changes"
+ open={openConfirmation}
+ onCancel={() => setOpen(false)}
+ onConfirm={() => {
+ processCode();
+ setOpenConfirmation(false);
+ }}
+ title="Caution"
+ >
+
+ Are you sure you want to exit without saving your changes?
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/confirmationModal/index.tsx b/langflow/src/frontend/src/modals/confirmationModal/index.tsx
new file mode 100644
index 0000000..992e0ec
--- /dev/null
+++ b/langflow/src/frontend/src/modals/confirmationModal/index.tsx
@@ -0,0 +1,141 @@
+import GenericIconComponent from "@/components/common/genericIconComponent";
+import { DialogClose } from "@radix-ui/react-dialog";
+import React, { useEffect, useState } from "react";
+import ShadTooltip from "../../components/common/shadTooltipComponent";
+import { Button } from "../../components/ui/button";
+import {
+ ConfirmationModalType,
+ ContentProps,
+ TriggerProps,
+} from "../../types/components";
+import BaseModal from "../baseModal";
+
+const Content: React.FC = ({ children }) => {
+ return {children}
;
+};
+const Trigger: React.FC = ({
+ children,
+ tooltipContent,
+ side,
+}: TriggerProps) => {
+ return tooltipContent ? (
+
+ {children}
+
+ ) : (
+ {children}
+ );
+};
+function ConfirmationModal({
+ title,
+ titleHeader,
+ modalContentTitle,
+ cancelText,
+ confirmationText,
+ children,
+ destructive = false,
+ destructiveCancel = false,
+ icon,
+ loading,
+ data,
+ index,
+ onConfirm,
+ open,
+ onClose,
+ onCancel,
+ ...props
+}: ConfirmationModalType) {
+ const [modalOpen, setModalOpen] = useState(open ?? false);
+ const [flag, setFlag] = useState(false);
+
+ useEffect(() => {
+ if (open) setModalOpen(open);
+ }, [open]);
+
+ useEffect(() => {
+ if (onClose && modalOpen === false && !flag) {
+ onClose();
+ } else if (flag) {
+ setFlag(false);
+ }
+ }, [modalOpen]);
+
+ const triggerChild = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Trigger,
+ );
+ const ContentChild = React.Children.toArray(children).find(
+ (child) => (child as React.ReactElement).type === Content,
+ );
+
+ const shouldShowConfirm = confirmationText && onConfirm;
+ const shouldShowCancel = cancelText;
+ const shouldShowFooter = shouldShowConfirm || shouldShowCancel;
+
+ const handleCancel = () => {
+ setFlag(true);
+ setModalOpen(false);
+ onCancel?.();
+ };
+
+ return (
+
+ {triggerChild}
+
+ {title}
+ {icon && (
+
+ )}
+
+
+ {modalContentTitle && modalContentTitle != "" && (
+ <>
+ {modalContentTitle}
+
+ >
+ )}
+ {ContentChild}
+
+
+ {shouldShowFooter ? (
+
+ {shouldShowConfirm && (
+ {
+ setFlag(true);
+ setModalOpen(false);
+ onConfirm(index, data);
+ }}
+ loading={loading}
+ data-testid="replace-button"
+ >
+ {confirmationText}
+
+ )}
+ {shouldShowCancel && (
+
+
+ {cancelText}
+
+
+ )}
+
+ ) : (
+ <>>
+ )}
+
+ );
+}
+ConfirmationModal.Content = Content;
+ConfirmationModal.Trigger = Trigger;
+
+export default ConfirmationModal;
diff --git a/langflow/src/frontend/src/modals/deleteConfirmationModal/index.tsx b/langflow/src/frontend/src/modals/deleteConfirmationModal/index.tsx
new file mode 100644
index 0000000..a0eb120
--- /dev/null
+++ b/langflow/src/frontend/src/modals/deleteConfirmationModal/index.tsx
@@ -0,0 +1,83 @@
+import { DialogClose } from "@radix-ui/react-dialog";
+import { Trash2 } from "lucide-react";
+import { Button } from "../../components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "../../components/ui/dialog";
+
+export default function DeleteConfirmationModal({
+ children,
+ onConfirm,
+ description,
+ asChild,
+ open,
+ setOpen,
+ note = "",
+}: {
+ children: JSX.Element;
+ onConfirm: (e: React.MouseEvent) => void;
+ description?: string;
+ asChild?: boolean;
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
+ note?: string;
+}) {
+ return (
+
+
+ {children}
+
+
+
+
+
+ Delete
+
+
+
+
+
+ Are you sure you want to delete the selected{" "}
+ {description ?? "component"}?
+ {note && (
+ <>
+ {note}
+
+ >
+ )}
+ Note: This action is irreversible.
+
+
+
+ e.stopPropagation()}
+ className="mr-1"
+ variant="outline"
+ >
+ Cancel
+
+
+
+ {
+ onConfirm(e);
+ }}
+ >
+ Delete
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/dictAreaModal/index.tsx b/langflow/src/frontend/src/modals/dictAreaModal/index.tsx
new file mode 100644
index 0000000..b62011e
--- /dev/null
+++ b/langflow/src/frontend/src/modals/dictAreaModal/index.tsx
@@ -0,0 +1,131 @@
+import "ace-builds/src-noconflict/ace";
+import "ace-builds/src-noconflict/ext-language_tools";
+import "ace-builds/src-noconflict/mode-python";
+import "ace-builds/src-noconflict/theme-github";
+import "ace-builds/src-noconflict/theme-twilight";
+// import "ace-builds/webpack-resolver";
+import { cloneDeep } from "lodash";
+import { useEffect, useState } from "react";
+import JsonView from "react18-json-view";
+import "react18-json-view/src/dark.css";
+import "react18-json-view/src/style.css";
+import IconComponent from "../../components/common/genericIconComponent";
+import { useDarkStore } from "../../stores/darkStore";
+import BaseModal from "../baseModal";
+
+export default function DictAreaModal({
+ children,
+ onChange,
+ value,
+ disabled = false,
+}: {
+ children: JSX.Element;
+ onChange?: (value: Object) => void;
+ value: Object;
+ disabled?: boolean;
+}): JSX.Element {
+ const [open, setOpen] = useState(false);
+ const isDark = useDarkStore((state) => state.dark);
+ const [componentValue, setComponentValue] = useState(value);
+
+ useEffect(() => {
+ setComponentValue(value);
+ }, [value, open]);
+
+ const handleSubmit = () => {
+ if (onChange) {
+ onChange(componentValue);
+ setOpen(false);
+ }
+ };
+
+ const handleJsonChange = (edit) => {
+ setComponentValue(edit.src);
+ };
+
+ const customizeCopy = (copy) => {
+ navigator.clipboard.writeText(JSON.stringify(copy));
+ };
+
+ const handleChangeType = (type: "array" | "object") => {
+ setComponentValue((value) => {
+ if (type === "array") {
+ if (value && Object.keys(value).length > 0) {
+ return [value];
+ }
+ return [];
+ }
+ if (value && Array.isArray(value) && value.length > 0) {
+ return value[0];
+ }
+ return {};
+ });
+ };
+
+ const IteractiveReader = () => {
+ return (
+
+ Customize your dictionary, adding or editing key-value pairs as needed.
+ Supports adding new{" "}
+ handleChangeType("object")}
+ className="cursor-pointer underline"
+ >
+ objects { }
+ {" "}
+ or{" "}
+ handleChangeType("array")}
+ className="cursor-pointer underline"
+ >
+ arrays [].
+
+
+ );
+ };
+
+ const renderHeader = () => (
+
+
+ {onChange ? "Edit Dictionary" : "View Dictionary"}
+
+
+
+ );
+
+ const renderContent = () => (
+
+
+
+
+
+ );
+
+ return (
+
+
+ {children}
+
+ {renderHeader()}
+ {renderContent()}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/editNodeModal/components/editNodeComponent/index.tsx b/langflow/src/frontend/src/modals/editNodeModal/components/editNodeComponent/index.tsx
new file mode 100644
index 0000000..53bf2f4
--- /dev/null
+++ b/langflow/src/frontend/src/modals/editNodeModal/components/editNodeComponent/index.tsx
@@ -0,0 +1,50 @@
+import TableComponent from "@/components/core/parameterRenderComponent/components/tableComponent";
+import { APIClassType } from "@/types/api";
+import { ColDef } from "ag-grid-community";
+import { useMemo } from "react";
+import useColumnDefs from "../../hooks/use-column-defs";
+import useRowData from "../../hooks/use-row-data";
+
+export function EditNodeComponent({
+ open,
+ nodeId,
+ nodeClass,
+ isTweaks,
+ autoHeight,
+ hideVisibility,
+}: {
+ open: boolean;
+ nodeId: string;
+ nodeClass: APIClassType;
+ isTweaks?: boolean;
+ autoHeight?: boolean;
+ hideVisibility?: boolean;
+}) {
+ const rowData = useRowData(nodeClass, open);
+
+ const columnDefs: ColDef[] = useColumnDefs(
+ nodeId,
+ open,
+ isTweaks,
+ hideVisibility,
+ );
+ return useMemo(
+ () => (
+
+
+ {nodeClass && (
+
+ )}
+
+
+ ),
+ [nodeClass],
+ );
+}
diff --git a/langflow/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts
new file mode 100644
index 0000000..0755a01
--- /dev/null
+++ b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.ts
@@ -0,0 +1,90 @@
+import TableAdvancedToggleCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableAdvancedToggleCellRender";
+import TableNodeCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableNodeCellRender";
+import { ColDef, ValueGetterParams } from "ag-grid-community";
+import { useMemo } from "react";
+
+const useColumnDefs = (
+ nodeId: string,
+ open: boolean,
+ isTweaks?: boolean,
+ hideVisibility?: boolean,
+) => {
+ const columnDefs: ColDef[] = useMemo(() => {
+ const colDefs: ColDef[] = [
+ {
+ headerName: "Field Name",
+ field: "display_name",
+ valueGetter: (params) => {
+ const templateParam = params.data;
+ return (
+ (templateParam.display_name
+ ? templateParam.display_name
+ : templateParam.name) ?? params.data.key
+ );
+ },
+ wrapText: true,
+ autoHeight: true,
+ flex: 1,
+ resizable: false,
+ cellClass: "no-border",
+ },
+ {
+ headerName: "Description",
+ field: "info",
+ tooltipField: "info",
+ wrapText: true,
+ autoHeight: true,
+ flex: 2,
+ resizable: false,
+ cellClass: "no-border",
+ },
+ {
+ headerName: "Value",
+ field: "value",
+ cellRenderer: TableNodeCellRender,
+ cellStyle: {
+ display: "flex",
+ "justify-content": "flex-start",
+ "align-items": "flex-start",
+ },
+ valueGetter: (params: ValueGetterParams) => {
+ return {
+ nodeId: nodeId,
+ parameterId: params.data.key,
+ isTweaks,
+ };
+ },
+ suppressKeyboardEvent: (params) =>
+ params.event.key === "a" &&
+ (params.event.ctrlKey || params.event.metaKey),
+ minWidth: 340,
+ autoHeight: true,
+ flex: 1,
+ resizable: false,
+ cellClass: "no-border",
+ },
+ ];
+ if (!hideVisibility) {
+ colDefs.push({
+ headerName: "Show",
+ field: "advanced",
+ cellRenderer: TableAdvancedToggleCellRender,
+ valueGetter: (params: ValueGetterParams) => {
+ return {
+ nodeId,
+ parameterId: params.data.key,
+ };
+ },
+ editable: false,
+ maxWidth: 80,
+ resizable: false,
+ cellClass: "no-border",
+ });
+ }
+ return colDefs;
+ }, [open]);
+
+ return columnDefs;
+};
+
+export default useColumnDefs;
diff --git a/langflow/src/frontend/src/modals/editNodeModal/hooks/use-handle-change-advanced.ts b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-handle-change-advanced.ts
new file mode 100644
index 0000000..fdb9102
--- /dev/null
+++ b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-handle-change-advanced.ts
@@ -0,0 +1,29 @@
+import { cloneDeep } from "lodash";
+import { NodeDataType } from "../../../types/flow";
+
+const useHandleChangeAdvanced = (
+ data: NodeDataType,
+ takeSnapshot: () => void,
+ setNode: (id: string, callback: (oldNode: any) => any) => void,
+ updateNodeInternals: (id: string) => void,
+) => {
+ const handleChangeAdvanced = (name) => {
+ if (!data.node) return;
+ takeSnapshot();
+
+ setNode(data.id, (oldNode) => {
+ let newNode = cloneDeep(oldNode);
+
+ newNode.data.node.template[name].advanced =
+ !newNode.data.node.template[name].advanced;
+
+ return newNode;
+ });
+
+ updateNodeInternals(data.id);
+ };
+
+ return { handleChangeAdvanced };
+};
+
+export default useHandleChangeAdvanced;
diff --git a/langflow/src/frontend/src/modals/editNodeModal/hooks/use-row-data.ts b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-row-data.ts
new file mode 100644
index 0000000..e6c42f2
--- /dev/null
+++ b/langflow/src/frontend/src/modals/editNodeModal/hooks/use-row-data.ts
@@ -0,0 +1,33 @@
+import sortFields from "@/CustomNodes/utils/sort-fields";
+import { useMemo } from "react";
+import { APIClassType } from "../../../types/api";
+
+const useRowData = (nodeClass: APIClassType, open: boolean) => {
+ const rowData = useMemo(() => {
+ return Object.keys(nodeClass.template)
+ .filter((key: string) => {
+ const templateParam = nodeClass.template[key] as any;
+ return (
+ key.charAt(0) !== "_" &&
+ templateParam.show &&
+ !(
+ (key === "code" && templateParam.type === "code") ||
+ (key.includes("code") && templateParam.proxy)
+ )
+ );
+ })
+ .sort((a, b) => sortFields(a, b, nodeClass.field_order ?? []))
+ .map((key: string) => {
+ const templateParam = nodeClass.template[key] as any;
+ return {
+ ...templateParam,
+ key: key,
+ id: key,
+ };
+ });
+ }, [open, nodeClass]);
+
+ return rowData;
+};
+
+export default useRowData;
diff --git a/langflow/src/frontend/src/modals/editNodeModal/index.tsx b/langflow/src/frontend/src/modals/editNodeModal/index.tsx
new file mode 100644
index 0000000..a9d85bf
--- /dev/null
+++ b/langflow/src/frontend/src/modals/editNodeModal/index.tsx
@@ -0,0 +1,60 @@
+import { APIClassType } from "@/types/api";
+import { customStringify } from "@/utils/reactflowUtils";
+import { useEffect, useState } from "react";
+import { Badge } from "../../components/ui/badge";
+import { Button } from "../../components/ui/button";
+import { useDarkStore } from "../../stores/darkStore";
+import { NodeDataType } from "../../types/flow";
+import BaseModal from "../baseModal";
+import { EditNodeComponent } from "./components/editNodeComponent";
+
+const EditNodeModal = ({
+ open,
+ setOpen,
+ data,
+}: {
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ data: NodeDataType;
+}) => {
+ const isDark = useDarkStore((state) => state.dark);
+
+ const [nodeClass, setNodeClass] = useState(data.node!);
+
+ useEffect(() => {
+ if (
+ customStringify(Object.keys(data?.node?.template ?? {})) ===
+ customStringify(Object.keys(nodeClass?.template ?? {}))
+ )
+ return;
+ setNodeClass(data.node!);
+ }, [data.node]);
+
+ return (
+
+
+ <>>
+
+
+
+ {data.node?.display_name ?? data.type}
+
+
+
+ ID: {data.id}
+
+
+
+
+
+
+
+
+ setOpen(false)}>Close
+
+
+
+ );
+};
+
+export default EditNodeModal;
diff --git a/langflow/src/frontend/src/modals/exportModal/index.tsx b/langflow/src/frontend/src/modals/exportModal/index.tsx
new file mode 100644
index 0000000..a2d71ff
--- /dev/null
+++ b/langflow/src/frontend/src/modals/exportModal/index.tsx
@@ -0,0 +1,115 @@
+import { track } from "@/customization/utils/analytics";
+import useFlowStore from "@/stores/flowStore";
+import { ReactNode, forwardRef, useEffect, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import EditFlowSettings from "../../components/core/editFlowSettingsComponent";
+import { Checkbox } from "../../components/ui/checkbox";
+import { API_WARNING_NOTICE_ALERT } from "../../constants/alerts_constants";
+import {
+ ALERT_SAVE_WITH_API,
+ EXPORT_DIALOG_SUBTITLE,
+ SAVE_WITH_API_CHECKBOX,
+} from "../../constants/constants";
+import useAlertStore from "../../stores/alertStore";
+import { useDarkStore } from "../../stores/darkStore";
+import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
+import BaseModal from "../baseModal";
+
+const ExportModal = forwardRef(
+ (props: { children: ReactNode }, ref): JSX.Element => {
+ const version = useDarkStore((state) => state.version);
+ const setNoticeData = useAlertStore((state) => state.setNoticeData);
+ const [checked, setChecked] = useState(false);
+ const currentFlow = useFlowStore((state) => state.currentFlow);
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ useEffect(() => {
+ setName(currentFlow?.name ?? "");
+ setDescription(currentFlow?.description ?? "");
+ }, [currentFlow?.name, currentFlow?.description]);
+ const [name, setName] = useState(currentFlow?.name ?? "");
+ const [description, setDescription] = useState(
+ currentFlow?.description ?? "",
+ );
+ const [open, setOpen] = useState(false);
+ return (
+ {
+ if (checked) {
+ downloadFlow(
+ {
+ id: currentFlow!.id,
+ data: currentFlow!.data!,
+ description,
+ name,
+ last_tested_version: version,
+ endpoint_name: currentFlow!.endpoint_name,
+ is_component: false,
+ tags: currentFlow!.tags,
+ },
+ name!,
+ description,
+ );
+ setNoticeData({
+ title: API_WARNING_NOTICE_ALERT,
+ });
+ } else
+ downloadFlow(
+ removeApiKeys({
+ id: currentFlow!.id,
+ data: currentFlow!.data!,
+ description,
+ name,
+ last_tested_version: version,
+ endpoint_name: currentFlow!.endpoint_name,
+ is_component: false,
+ tags: currentFlow!.tags,
+ }),
+ name!,
+ description,
+ );
+ setOpen(false);
+ track("Flow Exported", { flowId: currentFlow!.id });
+ }}
+ >
+ {props.children}
+
+ Export
+
+
+
+
+
+ {
+ setChecked(event);
+ }}
+ />
+
+ {SAVE_WITH_API_CHECKBOX}
+
+
+
+ {ALERT_SAVE_WITH_API}
+
+
+
+
+
+ );
+ },
+);
+export default ExportModal;
diff --git a/langflow/src/frontend/src/modals/flowLogsModal/index.tsx b/langflow/src/frontend/src/modals/flowLogsModal/index.tsx
new file mode 100644
index 0000000..0ec7f84
--- /dev/null
+++ b/langflow/src/frontend/src/modals/flowLogsModal/index.tsx
@@ -0,0 +1,87 @@
+import IconComponent from "@/components/common/genericIconComponent";
+import PaginatorComponent from "@/components/common/paginatorComponent";
+import TableComponent from "@/components/core/parameterRenderComponent/components/tableComponent";
+import { useGetTransactionsQuery } from "@/controllers/API/queries/transactions";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { FlowSettingsPropsType } from "@/types/components";
+import { ColDef, ColGroupDef } from "ag-grid-community";
+import { useCallback, useEffect, useState } from "react";
+import BaseModal from "../baseModal";
+
+export default function FlowLogsModal({
+ open,
+ setOpen,
+}: FlowSettingsPropsType): JSX.Element {
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(20);
+ const [columns, setColumns] = useState>([]);
+ const [rows, setRows] = useState([]);
+
+ const { data, isLoading, refetch } = useGetTransactionsQuery({
+ id: currentFlowId,
+ params: {
+ page: pageIndex,
+ size: pageSize,
+ },
+ mode: "union",
+ });
+
+ useEffect(() => {
+ if (data) {
+ const { columns, rows } = data;
+ setColumns(columns.map((col) => ({ ...col, editable: true })));
+ setRows(rows);
+ }
+ }, [data]);
+
+ useEffect(() => {
+ if (open) {
+ refetch();
+ }
+ }, [open]);
+
+ const handlePageChange = useCallback((newPageIndex, newPageSize) => {
+ setPageIndex(newPageIndex);
+ setPageSize(newPageSize);
+ }, []);
+
+ return (
+
+
+
+
+
+
+ {!isLoading && (data?.pagination.total ?? 0) >= 10 && (
+
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/flowSettingsModal/index.tsx b/langflow/src/frontend/src/modals/flowSettingsModal/index.tsx
new file mode 100644
index 0000000..8e27745
--- /dev/null
+++ b/langflow/src/frontend/src/modals/flowSettingsModal/index.tsx
@@ -0,0 +1,120 @@
+import useSaveFlow from "@/hooks/flows/use-save-flow";
+import useAlertStore from "@/stores/alertStore";
+import useFlowStore from "@/stores/flowStore";
+import { cloneDeep } from "lodash";
+import { useEffect, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import EditFlowSettings from "../../components/core/editFlowSettingsComponent";
+import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import { FlowSettingsPropsType } from "../../types/components";
+import { FlowType } from "../../types/flow";
+import { isEndpointNameValid } from "../../utils/utils";
+import BaseModal from "../baseModal";
+
+export default function FlowSettingsModal({
+ open,
+ setOpen,
+ flowData,
+ details,
+}: FlowSettingsPropsType): JSX.Element {
+ const saveFlow = useSaveFlow();
+ const currentFlow = useFlowStore((state) => state.currentFlow);
+ const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const flow = flowData ?? currentFlow;
+ useEffect(() => {
+ setName(flow?.name ?? "");
+ setDescription(flow?.description ?? "");
+ }, [flow?.name, flow?.description, open]);
+
+ const [name, setName] = useState(flow?.name ?? "");
+ const [description, setDescription] = useState(flow?.description ?? "");
+ const [endpoint_name, setEndpointName] = useState(flow?.endpoint_name ?? "");
+ const [isSaving, setIsSaving] = useState(false);
+ const [disableSave, setDisableSave] = useState(true);
+ const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
+ function handleClick(): void {
+ setIsSaving(true);
+ if (!flow) return;
+ const newFlow = cloneDeep(flow);
+ newFlow.name = name;
+ newFlow.description = description;
+ newFlow.endpoint_name =
+ endpoint_name && endpoint_name.length > 0 ? endpoint_name : null;
+ if (autoSaving) {
+ saveFlow(newFlow)
+ ?.then(() => {
+ setOpen(false);
+ setIsSaving(false);
+ setSuccessData({ title: "Changes saved successfully" });
+ })
+ .catch(() => {
+ setIsSaving(false);
+ });
+ } else {
+ setCurrentFlow(newFlow);
+ setOpen(false);
+ setIsSaving(false);
+ }
+ }
+
+ const [nameLists, setNameList] = useState([]);
+
+ useEffect(() => {
+ if (flows) {
+ const tempNameList: string[] = [];
+ flows.forEach((flow: FlowType) => {
+ tempNameList.push(flow.name);
+ });
+ setNameList(tempNameList.filter((name) => name !== flow!.name));
+ }
+ }, [flows]);
+
+ useEffect(() => {
+ if (
+ (!nameLists.includes(name) && flow?.name !== name) ||
+ flow?.description !== description ||
+ ((flow?.endpoint_name ?? "") !== endpoint_name &&
+ isEndpointNameValid(endpoint_name ?? "", 50))
+ ) {
+ setDisableSave(false);
+ } else {
+ setDisableSave(true);
+ }
+ }, [nameLists, flow, description, endpoint_name, name]);
+ return (
+
+
+ Details
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/newFlowModal/components/NewFlowCardComponent/index.tsx b/langflow/src/frontend/src/modals/newFlowModal/components/NewFlowCardComponent/index.tsx
new file mode 100644
index 0000000..9a9d74d
--- /dev/null
+++ b/langflow/src/frontend/src/modals/newFlowModal/components/NewFlowCardComponent/index.tsx
@@ -0,0 +1,38 @@
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import useAddFlow from "@/hooks/flows/use-add-flow";
+import { useParams } from "react-router-dom";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardTitle,
+} from "../../../../components/ui/card";
+
+export default function NewFlowCardComponent() {
+ const addFlow = useAddFlow();
+ const navigate = useCustomNavigate();
+ const { folderId } = useParams();
+
+ const handleClick = () => {
+ addFlow({ new_blank: true }).then((id) => {
+ navigate(`/flow/${id}${folderId ? `/folder/${folderId}` : ""}`);
+ });
+ track("New Flow Created", { template: "Blank Flow" });
+ };
+
+ return (
+
+
+
+
+
+ Blank Flow
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/newFlowModal/components/hooks/use-redirect-flow-card-click.tsx b/langflow/src/frontend/src/modals/newFlowModal/components/hooks/use-redirect-flow-card-click.tsx
new file mode 100644
index 0000000..26da57b
--- /dev/null
+++ b/langflow/src/frontend/src/modals/newFlowModal/components/hooks/use-redirect-flow-card-click.tsx
@@ -0,0 +1,23 @@
+import { useNavigate } from "react-router-dom";
+import { track } from "../../../../customization/utils/analytics";
+import useAddFlow from "../../../../hooks/flows/use-add-flow";
+import { FlowType } from "../../../../types/flow";
+import { updateIds } from "../../../../utils/reactflowUtils";
+
+export function useFlowCardClick() {
+ const navigate = useNavigate();
+ const addFlow = useAddFlow();
+
+ const handleFlowCardClick = async (flow: FlowType, folderIdUrl: string) => {
+ try {
+ updateIds(flow.data!);
+ const id = await addFlow({ flow });
+ navigate(`/flow/${id}/folder/${folderIdUrl}`);
+ track("New Flow Created", { template: `${flow.name} Template` });
+ } catch (error) {
+ console.error("Error handling flow card click:", error);
+ }
+ };
+
+ return handleFlowCardClick;
+}
diff --git a/langflow/src/frontend/src/modals/newFlowModal/components/undrawCards/index.tsx b/langflow/src/frontend/src/modals/newFlowModal/components/undrawCards/index.tsx
new file mode 100644
index 0000000..863e1b2
--- /dev/null
+++ b/langflow/src/frontend/src/modals/newFlowModal/components/undrawCards/index.tsx
@@ -0,0 +1,152 @@
+///
+import { useParams } from "react-router-dom";
+import BlogPost from "../../../../assets/undraw_blog_post_re_fy5x.svg?react";
+import ChatBot from "../../../../assets/undraw_chat_bot_re_e2gj.svg?react";
+import PromptChaining from "../../../../assets/undraw_cloud_docs_re_xjht.svg?react";
+import HierarchicalTasks from "../../../../assets/undraw_educator_re_ju47.svg?react";
+import ComplexAgent from "../../../../assets/undraw_firmware_re_fgdy.svg?react";
+import SequentialTasks from "../../../../assets/undraw_project_completed_re_jr7u.svg?react";
+import APIRequest from "../../../../assets/undraw_real_time_analytics_re_yliv.svg?react";
+import BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react";
+import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardTitle,
+} from "../../../../components/ui/card";
+import { useFolderStore } from "../../../../stores/foldersStore";
+import { UndrawCardComponentProps } from "../../../../types/components";
+import { useFlowCardClick } from "../hooks/use-redirect-flow-card-click";
+
+export default function UndrawCardComponent({
+ flow,
+}: UndrawCardComponentProps): JSX.Element {
+ const { folderId } = useParams();
+ const myCollectionId = useFolderStore((state) => state.myCollectionId);
+ const folderIdUrl = folderId ?? myCollectionId;
+
+ const handleFlowCardClick = useFlowCardClick();
+
+ function selectImage() {
+ switch (flow.name) {
+ case "Blog Writer":
+ return (
+
+ );
+ case "Basic Prompting (Hello, World)":
+ return (
+
+ );
+ case "Memory Chatbot":
+ return (
+
+ );
+ case "API requests":
+ return (
+
+ );
+ case "Document QA":
+ return (
+
+ );
+ case "Vector Store RAG":
+ return (
+
+ );
+ case "Simple Agent":
+ return (
+
+ );
+ case "Travel Planning Agents":
+ return (
+
+ );
+ case "Dynamic Agent":
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ }
+
+ return (
+ handleFlowCardClick(flow, folderIdUrl!)}
+ className="h-64 w-80 cursor-pointer bg-background pt-4"
+ >
+
+
+ {selectImage()}
+
+
+
+ {flow.name}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/nodeModal/components/modalField/index.tsx b/langflow/src/frontend/src/modals/nodeModal/components/modalField/index.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/langflow/src/frontend/src/modals/promptModal/index.tsx b/langflow/src/frontend/src/modals/promptModal/index.tsx
new file mode 100644
index 0000000..184d594
--- /dev/null
+++ b/langflow/src/frontend/src/modals/promptModal/index.tsx
@@ -0,0 +1,330 @@
+import { usePostValidatePrompt } from "@/controllers/API/queries/nodes/use-post-validate-prompt";
+import React, { useEffect, useRef, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import SanitizedHTMLWrapper from "../../components/common/sanitizedHTMLWrapper";
+import ShadTooltip from "../../components/common/shadTooltipComponent";
+import { Badge } from "../../components/ui/badge";
+import { Button } from "../../components/ui/button";
+import { Textarea } from "../../components/ui/textarea";
+import {
+ BUG_ALERT,
+ PROMPT_ERROR_ALERT,
+ PROMPT_SUCCESS_ALERT,
+ TEMP_NOTICE_ALERT,
+} from "../../constants/alerts_constants";
+import {
+ EDIT_TEXT_PLACEHOLDER,
+ INVALID_CHARACTERS,
+ MAX_WORDS_HIGHLIGHT,
+ PROMPT_DIALOG_SUBTITLE,
+ regexHighlight,
+} from "../../constants/constants";
+import useAlertStore from "../../stores/alertStore";
+import { PromptModalType } from "../../types/components";
+import { handleKeyDown } from "../../utils/reactflowUtils";
+import { classNames } from "../../utils/utils";
+import BaseModal from "../baseModal";
+import varHighlightHTML from "./utils/var-highlight-html";
+
+export default function PromptModal({
+ field_name = "",
+ value,
+ setValue,
+ nodeClass,
+ setNodeClass,
+ children,
+ disabled,
+ id = "",
+ readonly = false,
+}: PromptModalType): JSX.Element {
+ const [modalOpen, setModalOpen] = useState(false);
+ const [inputValue, setInputValue] = useState(value);
+ const [isEdit, setIsEdit] = useState(true);
+ const [wordsHighlight, setWordsHighlight] = useState>(new Set());
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const setNoticeData = useAlertStore((state) => state.setNoticeData);
+ const divRef = useRef(null);
+ const divRefPrompt = useRef(null);
+ const { mutate: postValidatePrompt } = usePostValidatePrompt();
+ const [clickPosition, setClickPosition] = useState({ x: 0, y: 0 });
+ const [scrollPosition, setScrollPosition] = useState(0);
+ const previewRef = useRef(null);
+ const textareaRef = useRef(null);
+
+ function checkVariables(valueToCheck: string): void {
+ const regex = /\{([^{}]+)\}/g;
+ const matches: string[] = [];
+ let match;
+ while ((match = regex.exec(valueToCheck))) {
+ matches.push(`{${match[1]}}`);
+ }
+
+ let invalid_chars: string[] = [];
+ let fixed_variables: string[] = [];
+ let input_variables = matches;
+ for (let variable of input_variables) {
+ let new_var = variable;
+ for (let char of INVALID_CHARACTERS) {
+ if (variable.includes(char)) {
+ invalid_chars.push(new_var);
+ }
+ }
+ fixed_variables.push(new_var);
+ if (new_var !== variable) {
+ const index = input_variables.indexOf(variable);
+ if (index !== -1) {
+ input_variables.splice(index, 1, new_var);
+ }
+ }
+ }
+
+ const filteredWordsHighlight = new Set(
+ matches.filter((word) => !invalid_chars.includes(word)),
+ );
+
+ setWordsHighlight(filteredWordsHighlight);
+ }
+
+ const coloredContent = (typeof inputValue === "string" ? inputValue : "")
+ .replace(//g, ">")
+ .replace(regexHighlight, (match, p1, p2) => {
+ // Decide which group was matched. If p1 is not undefined, do nothing
+ // we don't want to change the text. If p2 is not undefined, then we
+ // have a variable, so we should highlight it.
+ // ! This will not work with multiline or indented json yet
+ if (p1 !== undefined) {
+ return match;
+ } else if (p2 !== undefined) {
+ return varHighlightHTML({ name: p2 });
+ }
+
+ return match;
+ })
+ .replace(/\n/g, " ");
+
+ useEffect(() => {
+ if (inputValue && inputValue != "") {
+ checkVariables(inputValue);
+ }
+ }, [inputValue]);
+
+ useEffect(() => {
+ if (typeof value === "string") setInputValue(value);
+ }, [value, modalOpen]);
+
+ function getClassByNumberLength(): string {
+ let sumOfCaracteres: number = 0;
+ wordsHighlight.forEach((element) => {
+ sumOfCaracteres = sumOfCaracteres + element.replace(/[{}]/g, "").length;
+ });
+ return sumOfCaracteres > MAX_WORDS_HIGHLIGHT
+ ? "code-highlight"
+ : "code-nohighlight";
+ }
+
+ // Function need some review, working for now
+ function validatePrompt(closeModal: boolean): void {
+ //nodeClass is always null on tweaks
+ postValidatePrompt(
+ { name: field_name, template: inputValue, frontend_node: nodeClass! },
+ {
+ onSuccess: (apiReturn) => {
+ if (field_name === "") {
+ field_name = Array.isArray(
+ apiReturn?.frontend_node?.custom_fields?.[""],
+ )
+ ? (apiReturn?.frontend_node?.custom_fields?.[""][0] ?? "")
+ : (apiReturn?.frontend_node?.custom_fields?.[""] ?? "");
+ }
+ if (apiReturn) {
+ let inputVariables = apiReturn.input_variables ?? [];
+ if (
+ JSON.stringify(apiReturn?.frontend_node) !== JSON.stringify({})
+ ) {
+ setValue(inputValue);
+ apiReturn.frontend_node.template.template.value = inputValue;
+ if (setNodeClass) setNodeClass(apiReturn?.frontend_node);
+ setModalOpen(closeModal);
+ setIsEdit(false);
+ }
+ if (!inputVariables || inputVariables.length === 0) {
+ setNoticeData({
+ title: TEMP_NOTICE_ALERT,
+ });
+ } else {
+ setSuccessData({
+ title: PROMPT_SUCCESS_ALERT,
+ });
+ }
+ } else {
+ setIsEdit(true);
+ setErrorData({
+ title: BUG_ALERT,
+ });
+ }
+ },
+ onError: (error) => {
+ setIsEdit(true);
+ return setErrorData({
+ title: PROMPT_ERROR_ALERT,
+ list: [error.response.data.detail ?? ""],
+ });
+ },
+ },
+ );
+ }
+
+ const handlePreviewClick = (e: React.MouseEvent) => {
+ if (!isEdit && !readonly) {
+ const clickX = e.clientX;
+ const clickY = e.clientY;
+ setClickPosition({ x: clickX, y: clickY });
+ setScrollPosition(e.currentTarget.scrollTop);
+ setIsEdit(true);
+ }
+ };
+
+ useEffect(() => {
+ if (isEdit && textareaRef.current) {
+ textareaRef.current.focus();
+ textareaRef.current.scrollTop = scrollPosition;
+
+ const textArea = textareaRef.current;
+ const { x, y } = clickPosition;
+
+ // Use caretPositionFromPoint to get the closest text position. Does not work on Safari.
+ if ("caretPositionFromPoint" in document) {
+ let range = (document as any).caretPositionFromPoint(x, y)?.offset ?? 0;
+ if (range) {
+ const position = range;
+ textArea.setSelectionRange(position, position);
+ }
+ }
+ } else if (!isEdit && previewRef.current) {
+ previewRef.current.scrollTop = scrollPosition;
+ }
+ }, [isEdit, clickPosition, scrollPosition]);
+
+ return (
+ {}}
+ open={modalOpen}
+ setOpen={setModalOpen}
+ size="x-large"
+ >
+
+ {children}
+
+
+
+
+
+ Edit Prompt
+
+
+
+
+
+
+
+ {isEdit && !readonly ? (
+ {
+ setScrollPosition(textareaRef.current?.scrollTop || 0);
+ setIsEdit(false);
+ }}
+ autoFocus
+ onChange={(event) => {
+ setInputValue(event.target.value);
+ checkVariables(event.target.value);
+ }}
+ placeholder={EDIT_TEXT_PLACEHOLDER}
+ onKeyDown={(e) => {
+ handleKeyDown(e, inputValue, "");
+ }}
+ />
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ Prompt Variables:
+
+
+ {Array.from(wordsHighlight).map((word, index) => (
+
+
+
+
+ {word.replace(/[{}]/g, "").length > 59
+ ? word.replace(/[{}]/g, "").slice(0, 56) + "..."
+ : word.replace(/[{}]/g, "")}
+
+
+
+
+ ))}
+
+
+
+ Prompt variables can be created with any chosen name inside
+ curly brackets, e.g. {"{variable_name}"}
+
+
+
+
{
+ validatePrompt(false);
+ }}
+ type="submit"
+ >
+ Check & Save
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/promptModal/utils/var-highlight-html.tsx b/langflow/src/frontend/src/modals/promptModal/utils/var-highlight-html.tsx
new file mode 100644
index 0000000..6a3b27e
--- /dev/null
+++ b/langflow/src/frontend/src/modals/promptModal/utils/var-highlight-html.tsx
@@ -0,0 +1,6 @@
+import { IVarHighlightType } from "../../../types/components";
+
+export default function varHighlightHTML({ name }: IVarHighlightType): string {
+ const html = `{${name}} `;
+ return html;
+}
diff --git a/langflow/src/frontend/src/modals/saveChangesModal/index.tsx b/langflow/src/frontend/src/modals/saveChangesModal/index.tsx
new file mode 100644
index 0000000..8e30022
--- /dev/null
+++ b/langflow/src/frontend/src/modals/saveChangesModal/index.tsx
@@ -0,0 +1,72 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import Loading from "@/components/ui/loading";
+import { truncate } from "lodash";
+import { useState } from "react";
+import ConfirmationModal from "../confirmationModal";
+
+export function SaveChangesModal({
+ onSave,
+ onProceed,
+ onCancel,
+ flowName,
+ lastSaved,
+ autoSave,
+}: {
+ onSave: () => void;
+ onProceed: () => void;
+ onCancel: () => void;
+ flowName: string;
+ lastSaved: string | undefined;
+ autoSave: boolean;
+}): JSX.Element {
+ const [saving, setSaving] = useState(false);
+ return (
+ {
+ setSaving(true);
+ onSave();
+ }
+ }
+ onCancel={onProceed}
+ loading={autoSave ? true : saving}
+ size="x-small"
+ >
+
+ {autoSave ? (
+
+
+ Saving your changes...
+
+ ) : (
+ <>
+
+
+ Last saved: {lastSaved ?? "Never"}
+
+ Unsaved changes will be permanently lost.{" "}
+
+ Enable auto-saving
+ {" "}
+ to avoid losing progress.
+ >
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/secretKeyModal/index.tsx b/langflow/src/frontend/src/modals/secretKeyModal/index.tsx
new file mode 100644
index 0000000..6603cf8
--- /dev/null
+++ b/langflow/src/frontend/src/modals/secretKeyModal/index.tsx
@@ -0,0 +1,150 @@
+import * as Form from "@radix-ui/react-form";
+import { useEffect, useRef, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import { Button } from "../../components/ui/button";
+import { Input } from "../../components/ui/input";
+import { COPIED_NOTICE_ALERT } from "../../constants/alerts_constants";
+import { createApiKey } from "../../controllers/API";
+import useAlertStore from "../../stores/alertStore";
+import { ApiKeyType } from "../../types/components";
+import BaseModal from "../baseModal";
+
+export default function SecretKeyModal({
+ children,
+ data,
+ onCloseModal,
+}: ApiKeyType) {
+ const [open, setOpen] = useState(false);
+ const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
+ const [apiKeyValue, setApiKeyValue] = useState("");
+ const [renderKey, setRenderKey] = useState(false);
+ const [textCopied, setTextCopied] = useState(true);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (open) {
+ setRenderKey(false);
+ resetForm();
+ } else {
+ onCloseModal();
+ }
+ }, [open]);
+
+ function resetForm() {
+ setApiKeyName("");
+ setApiKeyValue("");
+ }
+
+ const handleCopyClick = async () => {
+ if (apiKeyValue) {
+ await navigator.clipboard.writeText(apiKeyValue);
+ inputRef?.current?.focus();
+ inputRef?.current?.select();
+ setSuccessData({
+ title: COPIED_NOTICE_ALERT,
+ });
+ setTextCopied(false);
+
+ setTimeout(() => {
+ setTextCopied(true);
+ }, 3000);
+ }
+ };
+
+ function handleAddNewKey() {
+ createApiKey(apiKeyName)
+ .then((res) => {
+ setApiKeyValue(res["api_key"]);
+ })
+ .catch((err) => {});
+ }
+
+ function handleSubmitForm() {
+ if (!renderKey) {
+ setRenderKey(true);
+ handleAddNewKey();
+ } else {
+ setOpen(false);
+ }
+ }
+
+ return (
+
+ {children}
+
+ {" "}
+ Please save this secret key somewhere safe and accessible. For
+ security reasons,{" "}
+ you won't be able to view it again through your
+ account. If you lose this secret key, you'll need to generate a
+ new one.
+ >
+ ) : (
+ <>Create a secret API Key to use Langflow API.>
+ )
+ }
+ >
+ Create API Key
+
+
+
+ {renderKey ? (
+ <>
+
+
+
+
+
+
{
+ handleCopyClick();
+ }}
+ data-testid="btn-copy-api-key"
+ unstyled
+ >
+ {textCopied ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ ) : (
+
+
+
+ {
+ setApiKeyName(value);
+ }}
+ placeholder="Insert a name for your API Key"
+ />
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/shareModal/index.tsx b/langflow/src/frontend/src/modals/shareModal/index.tsx
new file mode 100644
index 0000000..49b5f47
--- /dev/null
+++ b/langflow/src/frontend/src/modals/shareModal/index.tsx
@@ -0,0 +1,305 @@
+import useSaveFlow from "@/hooks/flows/use-save-flow";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { cloneDeep } from "lodash";
+import { ReactNode, useEffect, useMemo, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import { TagsSelector } from "../../components/common/tagsSelectorComponent";
+import EditFlowSettings from "../../components/core/editFlowSettingsComponent";
+import { Button } from "../../components/ui/button";
+import { Checkbox } from "../../components/ui/checkbox";
+import {
+ getStoreComponents,
+ saveFlowStore,
+ updateFlowStore,
+} from "../../controllers/API";
+import useAlertStore from "../../stores/alertStore";
+import { useDarkStore } from "../../stores/darkStore";
+import { useStoreStore } from "../../stores/storeStore";
+import { FlowType } from "../../types/flow";
+import {
+ downloadNode,
+ removeApiKeys,
+ removeFileNameFromComponents,
+} from "../../utils/reactflowUtils";
+import BaseModal from "../baseModal";
+import ConfirmationModal from "../confirmationModal";
+import ExportModal from "../exportModal";
+import getTagsIds from "./utils/get-tags-ids";
+
+export default function ShareModal({
+ component,
+ is_component,
+ children,
+ open,
+ setOpen,
+ disabled,
+}: {
+ children?: ReactNode;
+ is_component: boolean;
+ component: FlowType;
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
+ disabled?: boolean;
+}): JSX.Element {
+ const version = useDarkStore((state) => state.version);
+ const hasStore = useStoreStore((state) => state.hasStore);
+ const hasApiKey = useStoreStore((state) => state.hasApiKey);
+
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const [internalOpen, internalSetOpen] =
+ setOpen !== undefined && open !== undefined
+ ? [open, setOpen]
+ : useState(children ? false : true);
+ const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
+ const nameComponent = is_component ? "component" : "workflow";
+ const [sharePublic, setSharePublic] = useState(true);
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [unavaliableNames, setUnavaliableNames] = useState<
+ { id: string; name: string }[]
+ >([]);
+ const saveFlow = useSaveFlow();
+ const tags = useUtilityStore((state) => state.tags);
+
+ const [loadingNames, setLoadingNames] = useState(false);
+
+ const name = component?.name ?? "";
+ const description = component?.description ?? "";
+
+ useEffect(() => {
+ if (internalOpen) {
+ if (hasApiKey && hasStore) {
+ handleGetNames();
+ }
+ }
+ }, [internalOpen, hasApiKey, hasStore]);
+
+ async function handleGetNames() {
+ setLoadingNames(true);
+ const unavaliableNames: Array<{ id: string; name: string }> = [];
+ await getStoreComponents({
+ fields: ["name", "id", "is_component"],
+ filterByUser: true,
+ }).then((res) => {
+ res?.results?.forEach((element: any) => {
+ if ((element.is_component ?? false) === is_component)
+ unavaliableNames.push({ name: element.name, id: element.id });
+ });
+ setUnavaliableNames(unavaliableNames);
+ setLoadingNames(false);
+ });
+ }
+
+ const handleShareComponent = async (update = false) => {
+ //remove file names from flows before sharing
+ removeFileNameFromComponents(component);
+ const flow: FlowType = removeApiKeys({
+ id: component!.id,
+ data: component!.data,
+ description,
+ name,
+ last_tested_version: version,
+ is_component: is_component,
+ });
+
+ function successShare() {
+ if (!is_component) {
+ saveFlow(flow);
+ }
+ setSuccessData({
+ title: `${is_component ? "Component" : "Flow"} shared successfully!`,
+ });
+ }
+
+ if (!update)
+ saveFlowStore(
+ flow!,
+ getTagsIds(selectedTags, cloneDeep(tags) ?? []),
+ sharePublic,
+ ).then(successShare, (err) => {
+ setErrorData({
+ title: "Error sharing " + (is_component ? "component" : "flow"),
+ list: [err["response"]["data"]["detail"]],
+ });
+ });
+ else
+ updateFlowStore(
+ flow!,
+ getTagsIds(selectedTags, cloneDeep(tags) ?? []),
+ sharePublic,
+ unavaliableNames.find((e) => e.name === name)!.id,
+ ).then(successShare, (err) => {
+ setErrorData({
+ title: "Error sharing " + is_component ? "component" : "flow",
+ list: [err["response"]["data"]["detail"]],
+ });
+ });
+ };
+
+ const handleUpdateComponent = () => {
+ handleShareComponent(true);
+ internalSetOpen(false);
+ };
+
+ const handleExportComponent = () => {
+ component = removeApiKeys(component);
+ downloadNode(component);
+ };
+
+ let modalConfirmation = useMemo(() => {
+ return (
+ <>
+ {
+ handleUpdateComponent();
+ setOpenConfirmationModal(false);
+ }}
+ onCancel={() => {
+ setOpenConfirmationModal(false);
+ }}
+ >
+
+
+ It seems {name} already exists. Do you want to replace it with the
+ current?
+
+
+
+ Note: This action is irreversible.
+
+
+
+ >
+ );
+ }, [
+ unavaliableNames,
+ name,
+ loadingNames,
+ handleShareComponent,
+ openConfirmationModal,
+ ]);
+
+ return (
+ <>
+ {
+ const isNameAvailable = !unavaliableNames.some(
+ (element) => element.name === name,
+ );
+
+ if (isNameAvailable) {
+ handleShareComponent();
+ internalSetOpen(false);
+ } else {
+ setOpenConfirmationModal(true);
+ }
+ }}
+ >
+
+ {children ? children : <>>}
+
+
+ Share
+
+
+
+ {open && (
+ <>
+
+
+
+
+
+
+
+ {
+ setSharePublic(event);
+ }}
+ data-testid="public-checkbox"
+ />
+
+ Set {nameComponent} status to public
+
+
+
+ Attention: API keys in specified fields are automatically
+ removed upon sharing.
+
+ >
+ )}
+
+
+
+ <>
+ {!is_component && (
+
+ {
+ // (setOpen || internalSetOpen)(false);
+ }}
+ >
+
+ Export
+
+
+ )}
+ {is_component && (
+ {
+ internalSetOpen(false);
+ handleExportComponent();
+ }}
+ >
+
+ Export
+
+ )}
+ >
+
+
+ <>{modalConfirmation}>
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/modals/shareModal/utils/get-tags-ids.tsx b/langflow/src/frontend/src/modals/shareModal/utils/get-tags-ids.tsx
new file mode 100644
index 0000000..364d310
--- /dev/null
+++ b/langflow/src/frontend/src/modals/shareModal/utils/get-tags-ids.tsx
@@ -0,0 +1,8 @@
+export default function getTagsIds(
+ tags: string[],
+ tagListId: { name: string; id: string }[],
+) {
+ return tags
+ .map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
+ .map((tag) => tag!.id);
+}
diff --git a/langflow/src/frontend/src/modals/tableModal/index.tsx b/langflow/src/frontend/src/modals/tableModal/index.tsx
new file mode 100644
index 0000000..0834033
--- /dev/null
+++ b/langflow/src/frontend/src/modals/tableModal/index.tsx
@@ -0,0 +1,95 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import TableComponent, {
+ TableComponentProps,
+} from "@/components/core/parameterRenderComponent/components/tableComponent";
+import { TableOptionsTypeAPI } from "@/types/api";
+import { AgGridReact } from "ag-grid-react";
+import { ForwardedRef, forwardRef } from "react";
+import BaseModal from "../baseModal";
+
+interface TableModalProps extends TableComponentProps {
+ tableTitle: string;
+ description: string;
+ disabled?: boolean;
+ children: React.ReactNode;
+ tableOptions?: TableOptionsTypeAPI;
+ hideColumns?: boolean | string[];
+ tableIcon?: string;
+ open?: boolean;
+ setOpen?: (open: boolean) => void;
+ onSave?: () => void;
+ onCancel?: () => void;
+}
+
+const TableModal = forwardRef(
+ (
+ {
+ tableTitle,
+ description,
+ children,
+ disabled,
+ tableIcon,
+ open,
+ setOpen,
+ onSave,
+ onCancel,
+ ...props
+ }: TableModalProps,
+ ref: ForwardedRef,
+ ) => {
+ const handleSetOpen = (newOpen: boolean) => {
+ if (!newOpen && onCancel) {
+ onCancel();
+ }
+ if (setOpen) {
+ setOpen(newOpen);
+ }
+ };
+
+ const handleOnEscapeKeyDown = (e: KeyboardEvent) => {
+ const editingCells = (
+ ref as React.RefObject
+ )?.current?.api?.getEditingCells();
+
+ if (editingCells && editingCells.length > 0) {
+ e.preventDefault();
+ }
+ };
+
+ return (
+ {
+ handleOnEscapeKeyDown(e);
+ }}
+ disable={disabled}
+ open={open}
+ setOpen={(newOpen) => {
+ handleSetOpen(newOpen);
+ }}
+ >
+ {children}
+
+ {tableTitle}
+
+
+
+
+
+
+
+ );
+ },
+);
+
+export default TableModal;
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/GetStartedComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/GetStartedComponent/index.tsx
new file mode 100644
index 0000000..8ac7284
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/GetStartedComponent/index.tsx
@@ -0,0 +1,53 @@
+import BaseModal from "@/modals/baseModal";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { CardData } from "@/types/templates/types";
+import memoryChatbot from "../../../../assets/temp-pat-1.png";
+import vectorRag from "../../../../assets/temp-pat-2.png";
+import multiAgent from "../../../../assets/temp-pat-3.png";
+import memoryChatbotHorizontal from "../../../../assets/temp-pat-m-1.png";
+import vectorRagHorizontal from "../../../../assets/temp-pat-m-2.png";
+import multiAgentHorizontal from "../../../../assets/temp-pat-m-3.png";
+
+import TemplateGetStartedCardComponent from "../TemplateGetStartedCardComponent";
+
+export default function GetStartedComponent() {
+ const examples = useFlowsManagerStore((state) => state.examples);
+
+ // Define the card data
+ const cardData: CardData[] = [
+ {
+ bgImage: memoryChatbot,
+ bgHorizontalImage: memoryChatbotHorizontal,
+ icon: "MessagesSquare",
+ category: "prompting",
+ flow: examples.find((example) => example.name === "Basic Prompting"),
+ },
+ {
+ bgImage: vectorRag,
+ bgHorizontalImage: vectorRagHorizontal,
+ icon: "Database",
+ category: "RAG",
+ flow: examples.find((example) => example.name === "Vector Store RAG"),
+ },
+ {
+ bgImage: multiAgent,
+ bgHorizontalImage: multiAgentHorizontal,
+ icon: "Bot",
+ category: "Agents",
+ flow: examples.find((example) => example.name === "Simple Agent"),
+ },
+ ];
+
+ return (
+
+
+ Get started
+
+
+ {cardData.map((card, index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/TemplateCardComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/TemplateCardComponent/index.tsx
new file mode 100644
index 0000000..fa3f993
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/TemplateCardComponent/index.tsx
@@ -0,0 +1,69 @@
+import { convertTestName } from "@/components/common/storeCardComponent/utils/convert-test-name";
+import { swatchColors } from "@/utils/styleUtils";
+import { cn, getNumberFromString } from "@/utils/utils";
+import IconComponent, {
+ ForwardedIconComponent,
+} from "../../../../components/common/genericIconComponent";
+import { TemplateCardComponentProps } from "../../../../types/templates/types";
+
+export default function TemplateCardComponent({
+ example,
+ onClick,
+}: TemplateCardComponentProps) {
+ const swatchIndex =
+ (example.gradient && !isNaN(parseInt(example.gradient))
+ ? parseInt(example.gradient)
+ : getNumberFromString(example.gradient ?? example.name)) %
+ swatchColors.length;
+
+ const handleKeyDown = (e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onClick();
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {example.name}
+
+
+
+
+ {example.description}
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/TemplateCategoryComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/TemplateCategoryComponent/index.tsx
new file mode 100644
index 0000000..f8b2008
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/TemplateCategoryComponent/index.tsx
@@ -0,0 +1,21 @@
+import { TemplateCategoryProps } from "../../../../types/templates/types";
+import TemplateExampleCard from "../TemplateCardComponent";
+
+export function TemplateCategoryComponent({
+ examples,
+ onCardClick,
+}: TemplateCategoryProps) {
+ return (
+ <>
+
+ {examples.map((example, index) => (
+ onCardClick(example)}
+ />
+ ))}
+
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/TemplateContentComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/TemplateContentComponent/index.tsx
new file mode 100644
index 0000000..5b9d940
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/TemplateContentComponent/index.tsx
@@ -0,0 +1,119 @@
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import useAddFlow from "@/hooks/flows/use-add-flow";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import Fuse from "fuse.js";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { useParams } from "react-router-dom";
+import { ForwardedIconComponent } from "../../../../components/common/genericIconComponent";
+import { Input } from "../../../../components/ui/input";
+import { useFolderStore } from "../../../../stores/foldersStore";
+import { TemplateContentProps } from "../../../../types/templates/types";
+import { updateIds } from "../../../../utils/reactflowUtils";
+import { TemplateCategoryComponent } from "../TemplateCategoryComponent";
+
+export default function TemplateContentComponent({
+ currentTab,
+ categories,
+}: TemplateContentProps) {
+ const examples = useFlowsManagerStore((state) => state.examples).filter(
+ (example) =>
+ example.tags?.includes(currentTab ?? "") ||
+ currentTab === "all-templates",
+ );
+ const [searchQuery, setSearchQuery] = useState("");
+ const [filteredExamples, setFilteredExamples] = useState(examples);
+ const addFlow = useAddFlow();
+ const navigate = useCustomNavigate();
+ const { folderId } = useParams();
+ const myCollectionId = useFolderStore((state) => state.myCollectionId);
+ const scrollContainerRef = useRef(null);
+
+ const folderIdUrl = folderId ?? myCollectionId;
+
+ const fuse = useMemo(
+ () => new Fuse(examples, { keys: ["name", "description"] }),
+ [examples],
+ );
+
+ useEffect(() => {
+ // Reset search query when currentTab changes
+ setSearchQuery("");
+ }, [currentTab]);
+
+ useEffect(() => {
+ if (searchQuery === "") {
+ setFilteredExamples(examples);
+ } else {
+ const searchResults = fuse.search(searchQuery);
+ setFilteredExamples(searchResults.map((result) => result.item));
+ }
+ // Scroll to the top when search query changes
+ if (scrollContainerRef.current) {
+ scrollContainerRef.current.scrollTop = 0;
+ }
+ }, [searchQuery, currentTab]);
+
+ const handleCardClick = (example) => {
+ updateIds(example.data);
+ addFlow({ flow: example }).then((id) => {
+ navigate(`/flow/${id}/folder/${folderIdUrl}`);
+ });
+ track("New Flow Created", { template: `${example.name} Template` });
+ };
+
+ const handleClearSearch = () => {
+ setSearchQuery("");
+ if (searchInputRef.current) {
+ searchInputRef.current.focus();
+ }
+ };
+
+ const currentTabItem = categories.find((item) => item.id === currentTab);
+
+ const searchInputRef = useRef(null);
+
+ return (
+
+
+
+ setSearchQuery(e.target.value)}
+ ref={searchInputRef}
+ className="w-3/4 rounded-lg bg-background pl-8 lg:w-2/3"
+ />
+
+
+ {currentTabItem && filteredExamples.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsx
new file mode 100644
index 0000000..1bd07f9
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsx
@@ -0,0 +1,87 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import useAddFlow from "@/hooks/flows/use-add-flow";
+import { useFolderStore } from "@/stores/foldersStore";
+import { updateIds } from "@/utils/reactflowUtils";
+import { useParams } from "react-router-dom";
+import { CardData } from "../../../../types/templates/types";
+
+export default function TemplateGetStartedCardComponent({
+ bgImage,
+ bgHorizontalImage,
+ icon,
+ category,
+ flow,
+}: CardData) {
+ const addFlow = useAddFlow();
+ const navigate = useCustomNavigate();
+ const { folderId } = useParams();
+ const myCollectionId = useFolderStore((state) => state.myCollectionId);
+
+ const folderIdUrl = folderId ?? myCollectionId;
+
+ const handleClick = () => {
+ if (flow) {
+ updateIds(flow.data!);
+ addFlow({ flow }).then((id) => {
+ navigate(`/flow/${id}/folder/${folderIdUrl}`);
+ });
+ track("New Flow Created", { template: `${flow.name} Template` });
+ } else {
+ console.error(`Flow template not found`);
+ }
+ };
+
+ const handleKeyDown = (e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ handleClick();
+ }
+ };
+
+ return flow ? (
+
+
+
+
+
+
+
+
+
+ {category}
+
+
+
+
+ {flow.name}
+
+
+
+
+
+ {flow.description}
+
+
+
+ ) : (
+ <>>
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/components/navComponent/index.tsx b/langflow/src/frontend/src/modals/templatesModal/components/navComponent/index.tsx
new file mode 100644
index 0000000..3a1707c
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/components/navComponent/index.tsx
@@ -0,0 +1,88 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { convertTestName } from "@/components/common/storeCardComponent/utils/convert-test-name";
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarTrigger,
+} from "@/components/ui/sidebar";
+import { useIsMobile } from "../../../../hooks/use-mobile";
+
+import { cn } from "@/utils/utils";
+import { NavProps } from "../../../../types/templates/types";
+
+export function Nav({ categories, currentTab, setCurrentTab }: NavProps) {
+ const isMobile = useIsMobile();
+
+ return (
+
+
+
+
svg]:size-4 [&>svg]:shrink-0",
+ )}
+ />
+ svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ )}
+ >
+ Templates
+
+
+
+ {categories.map((category, index) => (
+
+
+ {category.title}
+
+
+
+ {category.items.map((link) => (
+
+ setCurrentTab(link.id)}
+ isActive={currentTab === link.id}
+ data-testid={`side_nav_options_${link.title.toLowerCase().replace(/\s+/g, "-")}`}
+ tooltip={link.title}
+ >
+
+
+ {link.title}
+
+
+
+ ))}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/templatesModal/index.tsx b/langflow/src/frontend/src/modals/templatesModal/index.tsx
new file mode 100644
index 0000000..e7d5778
--- /dev/null
+++ b/langflow/src/frontend/src/modals/templatesModal/index.tsx
@@ -0,0 +1,114 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Button } from "@/components/ui/button";
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import useAddFlow from "@/hooks/flows/use-add-flow";
+import { Category } from "@/types/templates/types";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import { newFlowModalPropsType } from "../../types/components";
+import BaseModal from "../baseModal";
+import GetStartedComponent from "./components/GetStartedComponent";
+import TemplateContentComponent from "./components/TemplateContentComponent";
+import { Nav } from "./components/navComponent";
+
+export default function TemplatesModal({
+ open,
+ setOpen,
+}: newFlowModalPropsType): JSX.Element {
+ const [currentTab, setCurrentTab] = useState("get-started");
+ const addFlow = useAddFlow();
+ const navigate = useCustomNavigate();
+ const { folderId } = useParams();
+
+ // Define categories and their items
+ const categories: Category[] = [
+ {
+ title: "Templates",
+ items: [
+ { title: "Get started", icon: "SquarePlay", id: "get-started" },
+ { title: "All templates", icon: "LayoutPanelTop", id: "all-templates" },
+ ],
+ },
+ {
+ title: "Use Cases",
+ items: [
+ { title: "Assistants", icon: "BotMessageSquare", id: "assistants" },
+ { title: "Classification", icon: "Tags", id: "classification" },
+ { title: "Coding", icon: "TerminalIcon", id: "coding" },
+ {
+ title: "Content Generation",
+ icon: "Newspaper",
+ id: "content-generation",
+ },
+ { title: "Q&A", icon: "Database", id: "q-a" },
+ // { title: "Summarization", icon: "Bot", id: "summarization" },
+ // { title: "Web Scraping", icon: "CodeXml", id: "web-scraping" },
+ ],
+ },
+ {
+ title: "Methodology",
+ items: [
+ { title: "Prompting", icon: "MessagesSquare", id: "chatbots" },
+ { title: "RAG", icon: "Database", id: "rag" },
+ { title: "Agents", icon: "Bot", id: "agents" },
+ ],
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ {currentTab === "get-started" ? (
+
+ ) : (
+ category.items)}
+ />
+ )}
+
+
+
+
Start from scratch
+
+ Begin with a fresh flow to build from scratch.
+
+
+
{
+ addFlow().then((id) => {
+ navigate(
+ `/flow/${id}${folderId ? `/folder/${folderId}` : ""}`,
+ );
+ });
+ track("New Flow Created", { template: "Blank Flow" });
+ }}
+ size="sm"
+ data-testid="blank-flow"
+ className="shrink-0"
+ >
+
+ Blank Flow
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/textAreaModal/index.tsx b/langflow/src/frontend/src/modals/textAreaModal/index.tsx
new file mode 100644
index 0000000..6bae59a
--- /dev/null
+++ b/langflow/src/frontend/src/modals/textAreaModal/index.tsx
@@ -0,0 +1,107 @@
+import { useEffect, useRef, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import { Button } from "../../components/ui/button";
+import { Textarea } from "../../components/ui/textarea";
+import {
+ EDIT_TEXT_PLACEHOLDER,
+ TEXT_DIALOG_SUBTITLE,
+} from "../../constants/constants";
+import { textModalPropsType } from "../../types/components";
+import { handleKeyDown } from "../../utils/reactflowUtils";
+import { classNames } from "../../utils/utils";
+import BaseModal from "../baseModal";
+
+export default function ComponentTextModal({
+ value,
+ setValue,
+ children,
+ disabled,
+ readonly = false,
+ password,
+ changeVisibility,
+}: textModalPropsType): JSX.Element {
+ const [modalOpen, setModalOpen] = useState(false);
+ const [inputValue, setInputValue] = useState(value);
+
+ const textRef = useRef(null);
+ useEffect(() => {
+ if (typeof value === "string") setInputValue(value);
+ }, [value, modalOpen]);
+
+ return (
+ {}}
+ open={modalOpen}
+ setOpen={setModalOpen}
+ size="x-large"
+ >
+
+ {children}
+
+
+
+
+
+ {TEXT_DIALOG_SUBTITLE}
+
+
+
+ {password !== undefined && (
+
+ {
+ if (changeVisibility) changeVisibility();
+ }}
+ >
+
+
+
+ )}
+
+
+
+
+ {
+ setInputValue(event.target.value);
+ }}
+ placeholder={EDIT_TEXT_PLACEHOLDER}
+ onKeyDown={(e) => {
+ handleKeyDown(e, value, "");
+ }}
+ readOnly={readonly}
+ id={"text-area-modal"}
+ data-testid={"text-area-modal"}
+ />
+
+
+
+
+ {
+ setValue(inputValue);
+ setModalOpen(false);
+ }}
+ type="submit"
+ >
+ Finish Editing
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/textModal/components/textEditorArea/index.tsx b/langflow/src/frontend/src/modals/textModal/components/textEditorArea/index.tsx
new file mode 100644
index 0000000..a064b66
--- /dev/null
+++ b/langflow/src/frontend/src/modals/textModal/components/textEditorArea/index.tsx
@@ -0,0 +1,35 @@
+import { Textarea } from "../../../../components/ui/textarea";
+
+const TextEditorArea = ({
+ left,
+ value,
+ resizable = true,
+ onChange,
+ readonly,
+}: {
+ left: boolean | undefined;
+ resizable?: boolean;
+ value: any;
+ onChange?: (string) => void;
+ readonly: boolean;
+}) => {
+ if (typeof value === "object" && Object.keys(value).includes("text")) {
+ value = value.text;
+ }
+ return (
+ {
+ if (onChange) onChange(e.target.value);
+ }}
+ />
+ );
+};
+
+export default TextEditorArea;
diff --git a/langflow/src/frontend/src/modals/textModal/index.tsx b/langflow/src/frontend/src/modals/textModal/index.tsx
new file mode 100644
index 0000000..4af102a
--- /dev/null
+++ b/langflow/src/frontend/src/modals/textModal/index.tsx
@@ -0,0 +1,80 @@
+import "ace-builds/src-noconflict/ace";
+import "ace-builds/src-noconflict/ext-language_tools";
+import "ace-builds/src-noconflict/mode-python";
+import "ace-builds/src-noconflict/theme-github";
+import "ace-builds/src-noconflict/theme-twilight";
+// import "ace-builds/webpack-resolver";
+import { useState } from "react";
+import "react18-json-view/src/dark.css";
+import "react18-json-view/src/style.css";
+import IconComponent from "../../components/common/genericIconComponent";
+import { Button } from "../../components/ui/button";
+import BaseModal from "../baseModal";
+import TextEditorArea from "./components/textEditorArea";
+
+export default function TextModal({
+ children,
+ value,
+ setValue,
+ editable = false,
+}: {
+ children: JSX.Element;
+ value: string;
+ setValue: (value: string) => void;
+ editable?: boolean;
+}): JSX.Element {
+ const [open, setOpen] = useState(false);
+ const [internalValue, setInternalValue] = useState(value);
+
+ const handleEscapeKeyDown = (event: KeyboardEvent) => {
+ setOpen(false);
+ event.stopPropagation();
+ };
+
+ return (
+
+ {children}
+
+ View Text
+
+
+
+
+
+ setInternalValue(text)}
+ value={internalValue}
+ resizable={false}
+ left={false}
+ />
+
+
+
+
+
+ {editable && (
+ {
+ setValue(internalValue);
+ setOpen(false);
+ }}
+ >
+ Save
+
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/modals/userManagementModal/index.tsx b/langflow/src/frontend/src/modals/userManagementModal/index.tsx
new file mode 100644
index 0000000..f4ebaf7
--- /dev/null
+++ b/langflow/src/frontend/src/modals/userManagementModal/index.tsx
@@ -0,0 +1,303 @@
+import * as Form from "@radix-ui/react-form";
+import { Eye, EyeOff } from "lucide-react";
+import { useContext, useEffect, useState } from "react";
+import { Button } from "../../components/ui/button";
+import { Checkbox } from "../../components/ui/checkbox";
+import { CONTROL_NEW_USER } from "../../constants/constants";
+import { AuthContext } from "../../contexts/authContext";
+import {
+ UserInputType,
+ UserManagementType,
+ inputHandlerEventType,
+} from "../../types/components";
+import { nodeIconsLucide } from "../../utils/styleUtils";
+import BaseModal from "../baseModal";
+
+export default function UserManagementModal({
+ title,
+ titleHeader,
+ cancelText,
+ confirmationText,
+ children,
+ icon,
+ data,
+ index,
+ onConfirm,
+ asChild,
+}: UserManagementType) {
+ const Icon: any = nodeIconsLucide[icon];
+ const [pwdVisible, setPwdVisible] = useState(false);
+ const [confirmPwdVisible, setConfirmPwdVisible] = useState(false);
+ const [open, setOpen] = useState(false);
+ const [password, setPassword] = useState(data?.password ?? "");
+ const [username, setUserName] = useState(data?.username ?? "");
+ const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
+ const [isActive, setIsActive] = useState(data?.is_active ?? false);
+ const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false);
+ const [inputState, setInputState] = useState(CONTROL_NEW_USER);
+ const { userData } = useContext(AuthContext);
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ useEffect(() => {
+ if (open) {
+ if (!data) {
+ resetForm();
+ } else {
+ setUserName(data.username);
+ setIsActive(data.is_active);
+ setIsSuperUser(data.is_superuser);
+
+ handleInput({ target: { name: "username", value: username } });
+ handleInput({ target: { name: "is_active", value: isActive } });
+ handleInput({ target: { name: "is_superuser", value: isSuperUser } });
+ }
+ }
+ }, [open]);
+
+ function resetForm() {
+ setPassword("");
+ setUserName("");
+ setConfirmPassword("");
+ setIsActive(false);
+ setIsSuperUser(false);
+ }
+
+ return (
+
+ {children}
+
+ {title}
+
+
+
+ {
+ if (password !== confirmPassword) {
+ event.preventDefault();
+ return;
+ }
+ resetForm();
+ onConfirm(1, inputState);
+ setOpen(false);
+ event.preventDefault();
+ }}
+ >
+
+
+
+
+ Username{" "}
+ *
+
+
+
+ {
+ handleInput({ target: { name: "username", value } });
+ setUserName(value);
+ }}
+ value={username}
+ className="primary-input"
+ required
+ placeholder="Username"
+ />
+
+
+ Please enter your username
+
+
+
+
+
+
+
+
+ Password{" "}
+
+ *
+
+ {pwdVisible && (
+ setPwdVisible(!pwdVisible)}
+ className="h-5 cursor-pointer"
+ strokeWidth={1.5}
+ />
+ )}
+ {!pwdVisible && (
+ setPwdVisible(!pwdVisible)}
+ className="h-5 cursor-pointer"
+ strokeWidth={1.5}
+ />
+ )}
+
+
+
+ {
+ handleInput({ target: { name: "password", value } });
+ setPassword(value);
+ }}
+ value={password}
+ className="primary-input"
+ required={data ? false : true}
+ type={pwdVisible ? "text" : "password"}
+ />
+
+
+
+ Please enter a password
+
+
+ {password != confirmPassword && (
+
+ Passwords do not match
+
+ )}
+
+
+
+
+
+
+
+ Confirm password{" "}
+
+ *
+
+ {confirmPwdVisible && (
+
+ setConfirmPwdVisible(!confirmPwdVisible)
+ }
+ className="h-5 cursor-pointer"
+ strokeWidth={1.5}
+ />
+ )}
+ {!confirmPwdVisible && (
+
+ setConfirmPwdVisible(!confirmPwdVisible)
+ }
+ className="h-5 cursor-pointer"
+ strokeWidth={1.5}
+ />
+ )}
+
+
+
+ {
+ setConfirmPassword(input.target.value);
+ }}
+ value={confirmPassword}
+ className="primary-input"
+ required={data ? false : true}
+ type={confirmPwdVisible ? "text" : "password"}
+ />
+
+
+ Please confirm your password
+
+
+
+
+
+
+
+
+ Active
+
+
+ {
+ handleInput({ target: { name: "is_active", value } });
+ setIsActive(value);
+ }}
+ />
+
+
+
+ {userData?.is_superuser && (
+
+
+
+ Superuser
+
+
+ {
+ handleInput({
+ target: { name: "is_superuser", value },
+ });
+ setIsSuperUser(value);
+ }}
+ />
+
+
+
+ )}
+
+
+
+
+ {
+ setOpen(false);
+ }}
+ className="mr-3"
+ >
+ {cancelText}
+
+
+
+ {confirmationText}
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/langflow/src/frontend/src/pages/AdminPage/LoginPage/index.tsx
new file mode 100644
index 0000000..4e83937
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AdminPage/LoginPage/index.tsx
@@ -0,0 +1,82 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import { useLoginUser } from "@/controllers/API/queries/auth";
+import { useContext, useState } from "react";
+import { Button } from "../../../components/ui/button";
+import { Input } from "../../../components/ui/input";
+import { SIGNIN_ERROR_ALERT } from "../../../constants/alerts_constants";
+import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
+import { AuthContext } from "../../../contexts/authContext";
+import useAlertStore from "../../../stores/alertStore";
+import { LoginType } from "../../../types/api";
+import {
+ inputHandlerEventType,
+ loginInputStateType,
+} from "../../../types/components";
+
+export default function LoginAdminPage() {
+ const [inputState, setInputState] =
+ useState(CONTROL_LOGIN_STATE);
+ const { login } = useContext(AuthContext);
+
+ const { password, username } = inputState;
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ const { mutate } = useLoginUser();
+
+ function signIn() {
+ const user: LoginType = {
+ username: username,
+ password: password,
+ };
+
+ mutate(user, {
+ onSuccess: (res) => {
+ login(res.access_token, "login", res.refresh_token);
+ },
+ onError: (error) => {
+ setErrorData({
+ title: SIGNIN_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ });
+ }
+
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/AdminPage/index.tsx b/langflow/src/frontend/src/pages/AdminPage/index.tsx
new file mode 100644
index 0000000..e209ce6
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AdminPage/index.tsx
@@ -0,0 +1,503 @@
+import PaginatorComponent from "@/components/common/paginatorComponent";
+import {
+ useAddUser,
+ useDeleteUsers,
+ useGetUsers,
+ useUpdateUser,
+} from "@/controllers/API/queries/auth";
+import CustomLoader from "@/customization/components/custom-loader";
+import { cloneDeep } from "lodash";
+import { useContext, useEffect, useRef, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import ShadTooltip from "../../components/common/shadTooltipComponent";
+import { Button } from "../../components/ui/button";
+import { CheckBoxDiv } from "../../components/ui/checkbox";
+import { Input } from "../../components/ui/input";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "../../components/ui/table";
+import {
+ USER_ADD_ERROR_ALERT,
+ USER_ADD_SUCCESS_ALERT,
+ USER_DEL_ERROR_ALERT,
+ USER_DEL_SUCCESS_ALERT,
+ USER_EDIT_ERROR_ALERT,
+ USER_EDIT_SUCCESS_ALERT,
+} from "../../constants/alerts_constants";
+import {
+ ADMIN_HEADER_DESCRIPTION,
+ ADMIN_HEADER_TITLE,
+ PAGINATION_PAGE,
+ PAGINATION_ROWS_COUNT,
+ PAGINATION_SIZE,
+} from "../../constants/constants";
+import { AuthContext } from "../../contexts/authContext";
+import ConfirmationModal from "../../modals/confirmationModal";
+import UserManagementModal from "../../modals/userManagementModal";
+import useAlertStore from "../../stores/alertStore";
+import { Users } from "../../types/api";
+import { UserInputType } from "../../types/components";
+
+export default function AdminPage() {
+ const [inputValue, setInputValue] = useState("");
+
+ const [size, setPageSize] = useState(PAGINATION_SIZE);
+ const [index, setPageIndex] = useState(PAGINATION_PAGE);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { userData } = useContext(AuthContext);
+ const [totalRowsCount, setTotalRowsCount] = useState(0);
+
+ const { mutate: mutateDeleteUser } = useDeleteUsers();
+ const { mutate: mutateUpdateUser } = useUpdateUser();
+ const { mutate: mutateAddUser } = useAddUser();
+
+ const userList = useRef([]);
+
+ useEffect(() => {
+ setTimeout(() => {
+ getUsers();
+ }, 500);
+ }, []);
+
+ const [filterUserList, setFilterUserList] = useState(userList.current);
+
+ const { mutate: mutateGetUsers, isPending, isIdle } = useGetUsers({});
+
+ function getUsers() {
+ mutateGetUsers(
+ {
+ skip: size * (index - 1),
+ limit: size,
+ },
+ {
+ onSuccess: (users) => {
+ setTotalRowsCount(users["total_count"]);
+ userList.current = users["users"];
+ setFilterUserList(users["users"]);
+ },
+ onError: () => {},
+ },
+ );
+ }
+
+ function handleChangePagination(pageIndex: number, pageSize: number) {
+ setPageSize(pageSize);
+ setPageIndex(pageIndex);
+
+ mutateGetUsers(
+ {
+ skip: pageSize * (pageIndex - 1),
+ limit: pageSize,
+ },
+ {
+ onSuccess: (users) => {
+ setTotalRowsCount(users["total_count"]);
+ userList.current = users["users"];
+ setFilterUserList(users["users"]);
+ },
+ },
+ );
+ }
+
+ function resetFilter() {
+ setPageIndex(PAGINATION_PAGE);
+ setPageSize(PAGINATION_SIZE);
+ getUsers();
+ }
+
+ function handleFilterUsers(input: string) {
+ setInputValue(input);
+
+ if (input === "") {
+ setFilterUserList(userList.current);
+ } else {
+ const filteredList = userList.current.filter((user: Users) =>
+ user.username.toLowerCase().includes(input.toLowerCase()),
+ );
+ setFilterUserList(filteredList);
+ }
+ }
+
+ function handleDeleteUser(user) {
+ mutateDeleteUser(
+ { user_id: user.id },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title: USER_DEL_SUCCESS_ALERT,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_DEL_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ },
+ );
+ }
+
+ function handleEditUser(userId, user) {
+ mutateUpdateUser(
+ { user_id: userId, user: user },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title: USER_EDIT_SUCCESS_ALERT,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_EDIT_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ },
+ );
+ }
+
+ function handleDisableUser(check, userId, user) {
+ const userEdit = cloneDeep(user);
+ userEdit.is_active = !check;
+
+ mutateUpdateUser(
+ { user_id: userId, user: userEdit },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title: USER_EDIT_SUCCESS_ALERT,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_EDIT_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ },
+ );
+ }
+
+ function handleSuperUserEdit(check, userId, user) {
+ const userEdit = cloneDeep(user);
+ userEdit.is_superuser = !check;
+
+ mutateUpdateUser(
+ { user_id: userId, user: userEdit },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title: USER_EDIT_SUCCESS_ALERT,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_EDIT_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ },
+ );
+ }
+
+ function handleNewUser(user: UserInputType) {
+ mutateAddUser(user, {
+ onSuccess: (res) => {
+ mutateUpdateUser(
+ {
+ user_id: res["id"],
+ user: {
+ is_active: user.is_active,
+ is_superuser: user.is_superuser,
+ },
+ },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title: USER_ADD_SUCCESS_ALERT,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_ADD_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ },
+ );
+ },
+ onError: (error) => {
+ setErrorData({
+ title: USER_ADD_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ });
+ }
+
+ return (
+ <>
+ {userData && (
+
+
+
+
+ {ADMIN_HEADER_TITLE}
+
+
+
+ {ADMIN_HEADER_DESCRIPTION}
+
+
+
+
handleFilterUsers(e.target.value)}
+ />
+ {inputValue.length > 0 ? (
+
{
+ setInputValue("");
+ setFilterUserList(userList.current);
+ }}
+ >
+
+
+ ) : (
+
+
+
+ )}
+
+
+ {
+ handleNewUser(user);
+ }}
+ asChild
+ >
+ New User
+
+
+
+ {isPending || isIdle ? (
+
+
+
+ ) : userList.current.length === 0 && !isIdle ? (
+ <>
+
+ No users registered.
+
+ >
+ ) : (
+ <>
+
+
+
+
+ Id
+ Username
+ Active
+ Superuser
+ Created At
+ Updated At
+
+
+
+ {!isPending && (
+
+ {filterUserList.map((user: UserInputType, index) => (
+
+
+
+ {user.id}
+
+
+
+
+
+ {user.username}
+
+
+
+
+ {
+ handleDisableUser(
+ user.is_active,
+ user.id,
+ user,
+ );
+ }}
+ >
+
+
+ Are you completely confident about the changes
+ you are making to this user?
+
+
+
+
+
+
+
+
+
+
+ {
+ handleSuperUserEdit(
+ user.is_superuser,
+ user.id,
+ user,
+ );
+ }}
+ >
+
+
+ Are you completely confident about the changes
+ you are making to this user?
+
+
+
+
+
+
+
+
+
+
+ {
+ new Date(user.create_at!)
+ .toISOString()
+ .split("T")[0]
+ }
+
+
+ {
+ new Date(user.updated_at!)
+ .toISOString()
+ .split("T")[0]
+ }
+
+
+
+ {
+ handleEditUser(user.id, editUser);
+ }}
+ >
+
+
+
+
+
+ {
+ handleDeleteUser(user);
+ }}
+ >
+
+
+ Are you sure you want to delete this user?
+ This action cannot be undone.
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ >
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/pages/AppAuthenticatedPage/index.tsx b/langflow/src/frontend/src/pages/AppAuthenticatedPage/index.tsx
new file mode 100644
index 0000000..3590230
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AppAuthenticatedPage/index.tsx
@@ -0,0 +1,8 @@
+import { useCustomPostAuth } from "@/customization/hooks/use-custom-post-auth";
+import { Outlet } from "react-router-dom";
+
+export function AppAuthenticatedPage() {
+ useCustomPostAuth();
+
+ return ;
+}
diff --git a/langflow/src/frontend/src/pages/AppInitPage/index.tsx b/langflow/src/frontend/src/pages/AppInitPage/index.tsx
new file mode 100644
index 0000000..be43fa5
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AppInitPage/index.tsx
@@ -0,0 +1,64 @@
+import { useGetAutoLogin } from "@/controllers/API/queries/auth";
+import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
+import { useGetBasicExamplesQuery } from "@/controllers/API/queries/flows/use-get-basic-examples";
+import { useGetTypes } from "@/controllers/API/queries/flows/use-get-types";
+import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
+import { useGetTagsQuery } from "@/controllers/API/queries/store";
+import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
+import { useGetVersionQuery } from "@/controllers/API/queries/version";
+import { CustomLoadingPage } from "@/customization/components/custom-loading-page";
+import { useCustomPrimaryLoading } from "@/customization/hooks/use-custom-primary-loading";
+import { useDarkStore } from "@/stores/darkStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { useEffect } from "react";
+import { Outlet } from "react-router-dom";
+import { LoadingPage } from "../LoadingPage";
+
+export function AppInitPage() {
+ const dark = useDarkStore((state) => state.dark);
+ const refreshStars = useDarkStore((state) => state.refreshStars);
+ const isLoading = useFlowsManagerStore((state) => state.isLoading);
+
+ const { isFetched: isLoaded } = useCustomPrimaryLoading();
+
+ const { isFetched } = useGetAutoLogin({ enabled: isLoaded });
+ useGetVersionQuery({ enabled: isFetched });
+ useGetConfig({ enabled: isFetched });
+ const { isFetched: typesLoaded } = useGetTypes({ enabled: isFetched });
+ useGetGlobalVariables({ enabled: typesLoaded });
+ useGetTagsQuery({ enabled: typesLoaded });
+ useGetFoldersQuery({
+ enabled: typesLoaded,
+ });
+ const { isFetched: isExamplesFetched } = useGetBasicExamplesQuery({
+ enabled: typesLoaded,
+ });
+
+ useEffect(() => {
+ if (isFetched) {
+ refreshStars();
+ }
+ }, [isFetched]);
+
+ useEffect(() => {
+ if (!dark) {
+ document.getElementById("body")!.classList.remove("dark");
+ } else {
+ document.getElementById("body")!.classList.add("dark");
+ }
+ }, [dark]);
+
+ return (
+ //need parent component with width and height
+ <>
+ {isLoaded ? (
+ (isLoading || !isFetched || !isExamplesFetched || !typesLoaded) && (
+
+ )
+ ) : (
+
+ )}
+ {isFetched && isExamplesFetched && typesLoaded && }
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx b/langflow/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx
new file mode 100644
index 0000000..5d652a7
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx
@@ -0,0 +1,35 @@
+import FetchErrorComponent from "@/components/common/fetchErrorComponent";
+import TimeoutErrorComponent from "@/components/common/timeoutErrorComponent";
+import {
+ FETCH_ERROR_DESCRIPION,
+ FETCH_ERROR_MESSAGE,
+ TIMEOUT_ERROR_DESCRIPION,
+ TIMEOUT_ERROR_MESSAGE,
+} from "@/constants/constants";
+
+export function GenericErrorComponent({ healthCheckTimeout, fetching, retry }) {
+ switch (healthCheckTimeout) {
+ case "serverDown":
+ return (
+
+ );
+ case "timeout":
+ return (
+
+ );
+ default:
+ return <>>;
+ }
+}
diff --git a/langflow/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts b/langflow/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts
new file mode 100644
index 0000000..4c49b1f
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts
@@ -0,0 +1,56 @@
+import { useGetHealthQuery } from "@/controllers/API/queries/health";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import useFlowStore from "@/stores/flowStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { useIsFetching, useIsMutating } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+import { useEffect, useState } from "react";
+
+export function useHealthCheck() {
+ const healthCheckMaxRetries = useFlowsManagerStore(
+ (state) => state.healthCheckMaxRetries,
+ );
+
+ const healthCheckTimeout = useUtilityStore(
+ (state) => state.healthCheckTimeout,
+ );
+
+ const isMutating = useIsMutating();
+ const isFetching = useIsFetching({
+ predicate: (query) => query.queryKey[0] !== "useGetHealthQuery",
+ });
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+
+ const disabled = isMutating || isFetching || isBuilding;
+
+ const {
+ isFetching: fetchingHealth,
+ isError: isErrorHealth,
+ error,
+ refetch,
+ } = useGetHealthQuery({ enableInterval: !disabled });
+ const [retryCount, setRetryCount] = useState(0);
+
+ useEffect(() => {
+ const isServerBusy =
+ (error as AxiosError)?.response?.status === 503 ||
+ (error as AxiosError)?.response?.status === 429;
+
+ if (isServerBusy && isErrorHealth && !disabled) {
+ const maxRetries = healthCheckMaxRetries;
+ if (retryCount < maxRetries) {
+ const delay = Math.pow(2, retryCount) * 1000;
+ const timer = setTimeout(() => {
+ refetch();
+ setRetryCount(retryCount + 1);
+ }, delay);
+
+ return () => clearTimeout(timer);
+ }
+ } else {
+ setRetryCount(0);
+ }
+ }, [isErrorHealth, retryCount, refetch]);
+
+ return { healthCheckTimeout, refetch, fetchingHealth };
+}
diff --git a/langflow/src/frontend/src/pages/AppWrapperPage/index.tsx b/langflow/src/frontend/src/pages/AppWrapperPage/index.tsx
new file mode 100644
index 0000000..e2286cd
--- /dev/null
+++ b/langflow/src/frontend/src/pages/AppWrapperPage/index.tsx
@@ -0,0 +1,33 @@
+import AlertDisplayArea from "@/alerts/displayArea";
+import CrashErrorComponent from "@/components/common/crashErrorComponent";
+import { ErrorBoundary } from "react-error-boundary";
+import { Outlet } from "react-router-dom";
+import { GenericErrorComponent } from "./components/GenericErrorComponent";
+import { useHealthCheck } from "./hooks/use-health-check";
+
+export function AppWrapperPage() {
+ const { healthCheckTimeout, fetchingHealth, refetch } = useHealthCheck();
+
+ return (
+
+
{
+ // any reset function
+ }}
+ FallbackComponent={CrashErrorComponent}
+ >
+ <>
+
+
+ >
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/DashboardWrapperPage/index.tsx b/langflow/src/frontend/src/pages/DashboardWrapperPage/index.tsx
new file mode 100644
index 0000000..d07dc9d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/DashboardWrapperPage/index.tsx
@@ -0,0 +1,16 @@
+import AppHeader from "@/components/core/appHeaderComponent";
+import useTheme from "@/customization/hooks/use-custom-theme";
+import { Outlet } from "react-router-dom";
+
+export function DashboardWrapperPage() {
+ useTheme();
+
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/DeleteAccountPage/index.tsx b/langflow/src/frontend/src/pages/DeleteAccountPage/index.tsx
new file mode 100644
index 0000000..cbe06ce
--- /dev/null
+++ b/langflow/src/frontend/src/pages/DeleteAccountPage/index.tsx
@@ -0,0 +1,62 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import { useState } from "react";
+import { Button } from "../../components/ui/button";
+import { Input } from "../../components/ui/input";
+import BaseModal from "../../modals/baseModal";
+
+export default function DeleteAccountPage() {
+ const [showConfirmation, setShowConfirmation] = useState(false);
+
+ const handleDeleteAccount = () => {
+ // Implement your account deletion logic here
+ // For example, make an API call to delete the account
+ // Upon successful deletion, you can redirect the user to another page
+ // Implement the logic to redirect the user after account deletion.
+ // For example, use react-router-dom's useHistory hook.
+ };
+
+ return (
+
+
+
+
+ Delete your account
+
+
+
+
+
+ Are you sure ?
+
+
+ setShowConfirmation(true)}
+ >
+ Delete account
+
+
+
+
+ handleDeleteAccount()}
+ >
+ Delete account
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx
new file mode 100644
index 0000000..8a0cfe9
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx
@@ -0,0 +1,41 @@
+import useFlowStore from "@/stores/flowStore";
+import { ConnectionLineComponentProps } from "@xyflow/react";
+
+const ConnectionLineComponent = ({
+ fromX,
+ fromY,
+ toX,
+ toY,
+ connectionLineStyle = {},
+}: ConnectionLineComponentProps): JSX.Element => {
+ const handleDragging = useFlowStore((state) => state.handleDragging);
+ const color = handleDragging?.color;
+ const accentColor = `hsl(var(--datatype-${color}))`;
+
+ return (
+
+
+
+
+ );
+};
+
+export default ConnectionLineComponent;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx
new file mode 100644
index 0000000..ef0c0f0
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx
@@ -0,0 +1,51 @@
+import { Disclosure } from "@headlessui/react";
+import IconComponent from "../../../../components/common/genericIconComponent";
+import { DisclosureComponentType } from "../../../../types/components";
+
+export default function DisclosureComponent({
+ button: { title, Icon, buttons = [] },
+ isChild = true,
+ children,
+ defaultOpen,
+}: DisclosureComponentType): JSX.Element {
+ return (
+
+ {({ open }) => (
+ <>
+
+
+
+ {/* BUG ON THIS ICON */}
+
+ {title}
+
+
+ {buttons.map((btn, index) => (
+
+ {btn.Icon}
+
+ ))}
+
+
+
+
+
+
+ {children}
+ >
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
new file mode 100644
index 0000000..68d9fa1
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -0,0 +1,657 @@
+import { DefaultEdge } from "@/CustomEdges";
+import NoteNode from "@/CustomNodes/NoteNode";
+
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import CanvasControls, {
+ CustomControlButton,
+} from "@/components/core/canvasControlsComponent";
+import FlowToolbar from "@/components/core/flowToolbarComponent";
+import { SidebarTrigger } from "@/components/ui/sidebar";
+import {
+ COLOR_OPTIONS,
+ NOTE_NODE_MIN_HEIGHT,
+ NOTE_NODE_MIN_WIDTH,
+} from "@/constants/constants";
+import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
+import CustomLoader from "@/customization/components/custom-loader";
+import { track } from "@/customization/utils/analytics";
+import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
+import useUploadFlow from "@/hooks/flows/use-upload-flow";
+import { useAddComponent } from "@/hooks/useAddComponent";
+import { nodeColorsName } from "@/utils/styleUtils";
+import { cn, isSupportedNodeTypes } from "@/utils/utils";
+import {
+ Background,
+ Connection,
+ Edge,
+ OnNodeDrag,
+ OnSelectionChangeParams,
+ Panel,
+ ReactFlow,
+ reconnectEdge,
+ SelectionDragHandler,
+} from "@xyflow/react";
+import _, { cloneDeep } from "lodash";
+import {
+ KeyboardEvent,
+ MouseEvent,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import { useHotkeys } from "react-hotkeys-hook";
+import GenericNode from "../../../../CustomNodes/GenericNode";
+import {
+ INVALID_SELECTION_ERROR_ALERT,
+ UPLOAD_ALERT_LIST,
+ UPLOAD_ERROR_ALERT,
+ WRONG_FILE_ERROR_ALERT,
+} from "../../../../constants/alerts_constants";
+import useAlertStore from "../../../../stores/alertStore";
+import useFlowStore from "../../../../stores/flowStore";
+import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
+import { useShortcutsStore } from "../../../../stores/shortcuts";
+import { useTypesStore } from "../../../../stores/typesStore";
+import { APIClassType } from "../../../../types/api";
+import { AllNodeType, EdgeType, NoteNodeType } from "../../../../types/flow";
+import {
+ generateFlow,
+ generateNodeFromFlow,
+ getNodeId,
+ isValidConnection,
+ scapeJSONParse,
+ updateIds,
+ validateSelection,
+} from "../../../../utils/reactflowUtils";
+import ConnectionLineComponent from "../ConnectionLineComponent";
+import SelectionMenu from "../SelectionMenuComponent";
+import UpdateAllComponents from "../UpdateAllComponents";
+import getRandomName from "./utils/get-random-name";
+import isWrappedWithClass from "./utils/is-wrapped-with-class";
+
+const nodeTypes = {
+ genericNode: GenericNode,
+ noteNode: NoteNode,
+};
+
+const edgeTypes = {
+ default: DefaultEdge,
+};
+
+export default function Page({
+ view,
+ setIsLoading,
+}: {
+ view?: boolean;
+ setIsLoading: (isLoading: boolean) => void;
+}): JSX.Element {
+ const uploadFlow = useUploadFlow();
+ const autoSaveFlow = useAutoSaveFlow();
+ const types = useTypesStore((state) => state.types);
+ const templates = useTypesStore((state) => state.templates);
+ const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
+ const reactFlowWrapper = useRef(null);
+ const setPositionDictionary = useFlowStore(
+ (state) => state.setPositionDictionary,
+ );
+ const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
+ const setReactFlowInstance = useFlowStore(
+ (state) => state.setReactFlowInstance,
+ );
+ const nodes = useFlowStore((state) => state.nodes);
+ const edges = useFlowStore((state) => state.edges);
+ const isEmptyFlow = useRef(nodes.length === 0);
+ const onNodesChange = useFlowStore((state) => state.onNodesChange);
+ const onEdgesChange = useFlowStore((state) => state.onEdgesChange);
+ const setNodes = useFlowStore((state) => state.setNodes);
+ const setEdges = useFlowStore((state) => state.setEdges);
+ const deleteNode = useFlowStore((state) => state.deleteNode);
+ const deleteEdge = useFlowStore((state) => state.deleteEdge);
+ const undo = useFlowsManagerStore((state) => state.undo);
+ const redo = useFlowsManagerStore((state) => state.redo);
+ const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
+ const paste = useFlowStore((state) => state.paste);
+ const lastCopiedSelection = useFlowStore(
+ (state) => state.lastCopiedSelection,
+ );
+ const setLastCopiedSelection = useFlowStore(
+ (state) => state.setLastCopiedSelection,
+ );
+ const onConnect = useFlowStore((state) => state.onConnect);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const updateCurrentFlow = useFlowStore((state) => state.updateCurrentFlow);
+ const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
+ const edgeUpdateSuccessful = useRef(true);
+
+ const position = useRef({ x: 0, y: 0 });
+ const [lastSelection, setLastSelection] =
+ useState(null);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ const [isAddingNote, setIsAddingNote] = useState(false);
+
+ const addComponent = useAddComponent();
+
+ const zoomLevel = reactFlowInstance?.getZoom();
+ const shadowBoxWidth = NOTE_NODE_MIN_WIDTH * (zoomLevel || 1);
+ const shadowBoxHeight = NOTE_NODE_MIN_HEIGHT * (zoomLevel || 1);
+ const shadowBoxBackgroundColor = COLOR_OPTIONS[Object.keys(COLOR_OPTIONS)[0]];
+
+ function handleGroupNode() {
+ takeSnapshot();
+ if (validateSelection(lastSelection!, edges).length === 0) {
+ const clonedNodes = cloneDeep(nodes);
+ const clonedEdges = cloneDeep(edges);
+ const clonedSelection = cloneDeep(lastSelection);
+ updateIds({ nodes: clonedNodes, edges: clonedEdges }, clonedSelection!);
+ const { newFlow } = generateFlow(
+ clonedSelection!,
+ clonedNodes,
+ clonedEdges,
+ getRandomName(),
+ );
+
+ const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
+
+ setNodes([
+ ...clonedNodes.filter(
+ (oldNodes) =>
+ !clonedSelection?.nodes.some(
+ (selectionNode) => selectionNode.id === oldNodes.id,
+ ),
+ ),
+ newGroupNode,
+ ]);
+ } else {
+ setErrorData({
+ title: INVALID_SELECTION_ERROR_ALERT,
+ list: validateSelection(lastSelection!, edges),
+ });
+ }
+ }
+
+ useEffect(() => {
+ const handleMouseMove = (event) => {
+ position.current = { x: event.clientX, y: event.clientY };
+ };
+
+ document.addEventListener("mousemove", handleMouseMove);
+
+ return () => {
+ document.removeEventListener("mousemove", handleMouseMove);
+ };
+ }, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
+
+ const { isFetching } = useGetBuildsQuery({ flowId: currentFlowId });
+
+ const showCanvas =
+ Object.keys(templates).length > 0 &&
+ Object.keys(types).length > 0 &&
+ !isFetching;
+
+ useEffect(() => {
+ setIsLoading(!showCanvas);
+ }, [showCanvas]);
+
+ useEffect(() => {
+ useFlowStore.setState({ autoSaveFlow });
+ }, [autoSaveFlow]);
+
+ function handleUndo(e: KeyboardEvent) {
+ if (!isWrappedWithClass(e, "noflow")) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ undo();
+ }
+ }
+
+ function handleRedo(e: KeyboardEvent) {
+ if (!isWrappedWithClass(e, "noflow")) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ redo();
+ }
+ }
+
+ function handleGroup(e: KeyboardEvent) {
+ if (selectionMenuVisible) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ handleGroupNode();
+ }
+ }
+
+ function handleDuplicate(e: KeyboardEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ (e as unknown as Event).stopImmediatePropagation();
+ const selectedNode = nodes.filter((obj) => obj.selected);
+ if (selectedNode.length > 0) {
+ paste(
+ { nodes: selectedNode, edges: [] },
+ {
+ x: position.current.x,
+ y: position.current.y,
+ },
+ );
+ }
+ }
+
+ function handleCopy(e: KeyboardEvent) {
+ const multipleSelection = lastSelection?.nodes
+ ? lastSelection?.nodes.length > 0
+ : false;
+ if (
+ !isWrappedWithClass(e, "noflow") &&
+ (isWrappedWithClass(e, "react-flow__node") || multipleSelection)
+ ) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ if (window.getSelection()?.toString().length === 0 && lastSelection) {
+ setLastCopiedSelection(_.cloneDeep(lastSelection));
+ }
+ }
+ }
+
+ function handleCut(e: KeyboardEvent) {
+ if (!isWrappedWithClass(e, "noflow")) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ if (window.getSelection()?.toString().length === 0 && lastSelection) {
+ setLastCopiedSelection(_.cloneDeep(lastSelection), true);
+ }
+ }
+ }
+
+ function handlePaste(e: KeyboardEvent) {
+ if (!isWrappedWithClass(e, "noflow")) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ if (
+ window.getSelection()?.toString().length === 0 &&
+ lastCopiedSelection
+ ) {
+ takeSnapshot();
+ paste(lastCopiedSelection, {
+ x: position.current.x,
+ y: position.current.y,
+ });
+ }
+ }
+ }
+
+ function handleDelete(e: KeyboardEvent) {
+ if (!isWrappedWithClass(e, "nodelete") && lastSelection) {
+ e.preventDefault();
+ (e as unknown as Event).stopImmediatePropagation();
+ takeSnapshot();
+ if (lastSelection.edges?.length) {
+ track("Component Connection Deleted");
+ }
+ if (lastSelection.nodes?.length) {
+ lastSelection.nodes.forEach((n) => {
+ track("Component Deleted", { componentType: n.data.type });
+ });
+ }
+ deleteNode(lastSelection.nodes.map((node) => node.id));
+ deleteEdge(lastSelection.edges.map((edge) => edge.id));
+ }
+ }
+
+ const undoAction = useShortcutsStore((state) => state.undo);
+ const redoAction = useShortcutsStore((state) => state.redo);
+ const redoAltAction = useShortcutsStore((state) => state.redoAlt);
+ const copyAction = useShortcutsStore((state) => state.copy);
+ const duplicate = useShortcutsStore((state) => state.duplicate);
+ const deleteAction = useShortcutsStore((state) => state.delete);
+ const groupAction = useShortcutsStore((state) => state.group);
+ const cutAction = useShortcutsStore((state) => state.cut);
+ const pasteAction = useShortcutsStore((state) => state.paste);
+ //@ts-ignore
+ useHotkeys(undoAction, handleUndo);
+ //@ts-ignore
+ useHotkeys(redoAction, handleRedo);
+ //@ts-ignore
+ useHotkeys(redoAltAction, handleRedo);
+ //@ts-ignore
+ useHotkeys(groupAction, handleGroup);
+ //@ts-ignore
+ useHotkeys(duplicate, handleDuplicate);
+ //@ts-ignore
+ useHotkeys(copyAction, handleCopy);
+ //@ts-ignore
+ useHotkeys(cutAction, handleCut);
+ //@ts-ignore
+ useHotkeys(pasteAction, handlePaste);
+ //@ts-ignore
+ useHotkeys(deleteAction, handleDelete);
+ //@ts-ignore
+ useHotkeys("delete", handleDelete);
+
+ const onConnectMod = useCallback(
+ (params: Connection) => {
+ takeSnapshot();
+ onConnect(params);
+ track("New Component Connection Added");
+ },
+ [takeSnapshot, onConnect],
+ );
+
+ const onNodeDragStart: OnNodeDrag = useCallback(() => {
+ // 👇 make dragging a node undoable
+
+ takeSnapshot();
+ // 👉 you can place your event handlers here
+ }, [takeSnapshot]);
+
+ const onNodeDragStop: OnNodeDrag = useCallback(() => {
+ // 👇 make moving the canvas undoable
+ autoSaveFlow();
+ updateCurrentFlow({ nodes });
+ setPositionDictionary({});
+ }, [
+ takeSnapshot,
+ autoSaveFlow,
+ nodes,
+ edges,
+ reactFlowInstance,
+ setPositionDictionary,
+ ]);
+
+ const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
+ // 👇 make dragging a selection undoable
+
+ takeSnapshot();
+ }, [takeSnapshot]);
+
+ const onDragOver = useCallback((event: React.DragEvent) => {
+ event.preventDefault();
+ if (event.dataTransfer.types.some((types) => isSupportedNodeTypes(types))) {
+ event.dataTransfer.dropEffect = "move";
+ } else {
+ event.dataTransfer.dropEffect = "copy";
+ }
+ }, []);
+
+ const onDrop = useCallback(
+ (event: React.DragEvent) => {
+ event.preventDefault();
+ const grabbingElement =
+ document.getElementsByClassName("cursor-grabbing");
+ if (grabbingElement.length > 0) {
+ document.body.removeChild(grabbingElement[0]);
+ }
+ if (event.dataTransfer.types.some((type) => isSupportedNodeTypes(type))) {
+ takeSnapshot();
+
+ const datakey = event.dataTransfer.types.find((type) =>
+ isSupportedNodeTypes(type),
+ );
+
+ // Extract the data from the drag event and parse it as a JSON object
+ const data: { type: string; node?: APIClassType } = JSON.parse(
+ event.dataTransfer.getData(datakey!),
+ );
+
+ addComponent(data.node!, data.type, {
+ x: event.clientX,
+ y: event.clientY,
+ });
+ } else if (event.dataTransfer.types.some((types) => types === "Files")) {
+ takeSnapshot();
+ const position = {
+ x: event.clientX,
+ y: event.clientY,
+ };
+ uploadFlow({
+ files: Array.from(event.dataTransfer.files!),
+ position: position,
+ }).catch((error) => {
+ setErrorData({
+ title: UPLOAD_ERROR_ALERT,
+ list: [(error as Error).message],
+ });
+ });
+ } else {
+ setErrorData({
+ title: WRONG_FILE_ERROR_ALERT,
+ list: [UPLOAD_ALERT_LIST],
+ });
+ }
+ },
+ [takeSnapshot, addComponent],
+ );
+
+ const onEdgeUpdateStart = useCallback(() => {
+ edgeUpdateSuccessful.current = false;
+ }, []);
+
+ const onEdgeUpdate = useCallback(
+ (oldEdge: EdgeType, newConnection: Connection) => {
+ if (isValidConnection(newConnection, nodes, edges)) {
+ edgeUpdateSuccessful.current = true;
+ oldEdge.data = {
+ targetHandle: scapeJSONParse(newConnection.targetHandle!),
+ sourceHandle: scapeJSONParse(newConnection.sourceHandle!),
+ };
+ setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
+ }
+ },
+ [setEdges],
+ );
+
+ const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
+ if (!edgeUpdateSuccessful.current) {
+ setEdges((eds) => eds.filter((edg) => edg.id !== edge.id));
+ }
+ edgeUpdateSuccessful.current = true;
+ }, []);
+
+ const [selectionEnded, setSelectionEnded] = useState(true);
+
+ const onSelectionEnd = useCallback(() => {
+ setSelectionEnded(true);
+ }, []);
+ const onSelectionStart = useCallback((event: MouseEvent) => {
+ event.preventDefault();
+ setSelectionEnded(false);
+ }, []);
+
+ // Workaround to show the menu only after the selection has ended.
+ useEffect(() => {
+ if (selectionEnded && lastSelection && lastSelection.nodes.length > 1) {
+ setSelectionMenuVisible(true);
+ } else {
+ setSelectionMenuVisible(false);
+ }
+ }, [selectionEnded, lastSelection]);
+
+ const onSelectionChange = useCallback(
+ (flow: OnSelectionChangeParams): void => {
+ setLastSelection(flow);
+ },
+ [],
+ );
+
+ const onPaneClick = useCallback(
+ (event: React.MouseEvent) => {
+ setFilterEdge([]);
+ if (isAddingNote) {
+ const shadowBox = document.getElementById("shadow-box");
+ if (shadowBox) {
+ shadowBox.style.display = "none";
+ }
+ const position = reactFlowInstance?.screenToFlowPosition({
+ x: event.clientX - shadowBoxWidth / 2,
+ y: event.clientY - shadowBoxHeight / 2,
+ });
+ const data = {
+ node: {
+ description: "",
+ display_name: "",
+ documentation: "",
+ template: {},
+ },
+ type: "note",
+ };
+ const newId = getNodeId(data.type);
+
+ const newNode: NoteNodeType = {
+ id: newId,
+ type: "noteNode",
+ position: position || { x: 0, y: 0 },
+ data: {
+ ...data,
+ id: newId,
+ },
+ };
+ setNodes((nds) => nds.concat(newNode));
+ setIsAddingNote(false);
+ }
+ },
+ [isAddingNote, setNodes, reactFlowInstance, getNodeId, setFilterEdge],
+ );
+
+ const handleEdgeClick = (event, edge) => {
+ const color =
+ nodeColorsName[edge?.data?.sourceHandle?.output_types[0]] || "cyan";
+
+ const accentColor = `hsl(var(--datatype-${color}))`;
+ reactFlowWrapper.current?.style.setProperty("--selected", accentColor);
+ };
+
+ useEffect(() => {
+ const handleGlobalMouseMove = (event) => {
+ if (isAddingNote) {
+ const shadowBox = document.getElementById("shadow-box");
+ if (shadowBox) {
+ shadowBox.style.display = "block";
+ shadowBox.style.left = `${event.clientX - shadowBoxWidth / 2}px`;
+ shadowBox.style.top = `${event.clientY - shadowBoxHeight / 2}px`;
+ }
+ }
+ };
+
+ document.addEventListener("mousemove", handleGlobalMouseMove);
+
+ return () => {
+ document.removeEventListener("mousemove", handleGlobalMouseMove);
+ };
+ }, [isAddingNote, shadowBoxWidth, shadowBoxHeight]);
+
+ const componentsToUpdate = useFlowStore((state) => state.componentsToUpdate);
+
+ return (
+
+ {showCanvas ? (
+
+
+ nodes={nodes}
+ edges={edges}
+ onNodesChange={onNodesChange}
+ onEdgesChange={onEdgesChange}
+ onConnect={onConnectMod}
+ disableKeyboardA11y={true}
+ onInit={setReactFlowInstance}
+ nodeTypes={nodeTypes}
+ onReconnect={onEdgeUpdate}
+ onReconnectStart={onEdgeUpdateStart}
+ onReconnectEnd={onEdgeUpdateEnd}
+ onNodeDragStart={onNodeDragStart}
+ onSelectionDragStart={onSelectionDragStart}
+ elevateEdgesOnSelect={true}
+ onSelectionEnd={onSelectionEnd}
+ onSelectionStart={onSelectionStart}
+ connectionRadius={30}
+ edgeTypes={edgeTypes}
+ connectionLineComponent={ConnectionLineComponent}
+ onDragOver={onDragOver}
+ onNodeDragStop={onNodeDragStop}
+ onDrop={onDrop}
+ onSelectionChange={onSelectionChange}
+ deleteKeyCode={[]}
+ fitView={isEmptyFlow.current ? false : true}
+ className="theme-attribution"
+ minZoom={0.01}
+ maxZoom={8}
+ zoomOnScroll={!view}
+ zoomOnPinch={!view}
+ panOnDrag={!view}
+ panActivationKeyCode={""}
+ proOptions={{ hideAttribution: true }}
+ onPaneClick={onPaneClick}
+ onEdgeClick={handleEdgeClick}
+ >
+
+ {!view && (
+ <>
+
+ {
+ setIsAddingNote(true);
+ const shadowBox = document.getElementById("shadow-box");
+ if (shadowBox) {
+ shadowBox.style.display = "block";
+ shadowBox.style.left = `${position.current.x - shadowBoxWidth / 2}px`;
+ shadowBox.style.top = `${position.current.y - shadowBoxHeight / 2}px`;
+ }
+ }}
+ iconClasses="text-primary"
+ testId="add_note"
+ />
+
+
+ >
+ )}
+ button]:border-0 [&>button]:bg-background hover:[&>button]:bg-accent",
+ "pointer-events-auto opacity-100 group-data-[open=true]/sidebar-wrapper:pointer-events-none group-data-[open=true]/sidebar-wrapper:-translate-x-full group-data-[open=true]/sidebar-wrapper:opacity-0",
+ )}
+ position="top-left"
+ >
+
+
+ Components
+
+
+
+
+
+ {
+ handleGroupNode();
+ }}
+ />
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/get-random-name.tsx b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/get-random-name.tsx
new file mode 100644
index 0000000..d3bcf4d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/get-random-name.tsx
@@ -0,0 +1,35 @@
+import { ADJECTIVES, NOUNS } from "../../../../../flow_constants";
+import { getRandomElement } from "../../../../../utils/reactflowUtils";
+import { toTitleCase } from "../../../../../utils/utils";
+
+export default function getRandomName(
+ retry: number = 0,
+ noSpace: boolean = false,
+ maxRetries: number = 3,
+): string {
+ const left: string[] = ADJECTIVES;
+ const right: string[] = NOUNS;
+
+ const lv = getRandomElement(left);
+ const rv = getRandomElement(right);
+
+ // Condition to avoid "boring wozniak"
+ if (lv === "boring" && rv === "wozniak") {
+ if (retry < maxRetries) {
+ return getRandomName(retry + 1, noSpace, maxRetries);
+ } else {
+ console.warn("Max retries reached, returning as is");
+ }
+ }
+
+ // Append a suffix if retrying and noSpace is true
+ if (retry > 0 && noSpace) {
+ const retrySuffix = Math.floor(Math.random() * 10);
+ return `${lv}_${rv}${retrySuffix}`;
+ }
+
+ // Construct the final name
+ let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
+ // Return title case final name
+ return toTitleCase(final_name);
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx
new file mode 100644
index 0000000..eff34d3
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class.tsx
@@ -0,0 +1,4 @@
+const isWrappedWithClass = (event: any, className: string | undefined) =>
+ event.target.closest(`.${className}`);
+
+export default isWrappedWithClass;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx
new file mode 100644
index 0000000..adf2286
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/ParentDisclosureComponent/index.tsx
@@ -0,0 +1,51 @@
+import { Disclosure } from "@headlessui/react";
+import IconComponent from "../../../../components/common/genericIconComponent";
+import { DisclosureComponentType } from "../../../../types/components";
+
+export default function ParentDisclosureComponent({
+ button: { title, Icon, buttons = [], beta },
+ children,
+ defaultOpen,
+ testId,
+}: DisclosureComponentType): JSX.Element {
+ return (
+
+ {({ open }) => (
+ <>
+
+
+
+
{title}
+ {beta && (
+
+ Beta
+
+ )}
+
+
+ {buttons.map((btn, index) => (
+
+ {btn.Icon}
+
+ ))}
+
+
+
+
+
+
+ {children}
+ >
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx
new file mode 100644
index 0000000..aa3b1a8
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/SelectionMenuComponent/index.tsx
@@ -0,0 +1,119 @@
+import { NodeToolbar } from "@xyflow/react";
+import { useEffect, useState } from "react";
+import { useHotkeys } from "react-hotkeys-hook";
+import ShadTooltip from "../../../../components/common/shadTooltipComponent";
+import { Button } from "../../../../components/ui/button";
+import { GradientGroup } from "../../../../icons/GradientSparkles";
+import useFlowStore from "../../../../stores/flowStore";
+import { validateSelection } from "../../../../utils/reactflowUtils";
+export default function SelectionMenu({
+ onClick,
+ nodes,
+ isVisible,
+ lastSelection,
+}) {
+ const edges = useFlowStore((state) => state.edges);
+ const unselectAll = useFlowStore((state) => state.unselectAll);
+ const [disable, setDisable] = useState(
+ lastSelection && edges.length > 0
+ ? validateSelection(lastSelection!, edges).length > 0
+ : false,
+ );
+ const [errors, setErrors] = useState([]);
+ const [isOpen, setIsOpen] = useState(false);
+ const [isTransitioning, setIsTransitioning] = useState(false);
+ const [lastNodes, setLastNodes] = useState(nodes);
+
+ useHotkeys("esc", unselectAll, { preventDefault: true });
+
+ useEffect(() => {
+ if (isOpen) {
+ setErrors(validateSelection(lastSelection!, edges));
+ return setDisable(validateSelection(lastSelection!, edges).length > 0);
+ }
+ setDisable(false);
+ }, [isOpen, setIsOpen]);
+
+ // nodes get saved to not be gone after the toolbar closes
+ useEffect(() => {
+ setLastNodes(nodes);
+ }, [isOpen]);
+
+ // transition starts after and ends before the toolbar closes
+ useEffect(() => {
+ if (isVisible) {
+ setIsOpen(true);
+ setTimeout(() => {
+ setIsTransitioning(true);
+ }, 50);
+ } else {
+ setIsTransitioning(false);
+ setTimeout(() => {
+ setIsOpen(false);
+ }, 500);
+ }
+ }, [isVisible]);
+
+ return (
+ 0 ? lastNodes.map((n) => n.id) : []
+ }
+ >
+
+
+ {errors.length > 0 ? (
+
+
+
+ Group
+
+
+ ) : (
+
+
+ Group
+
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx
new file mode 100644
index 0000000..2055cfa
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/UpdateAllComponents/index.tsx
@@ -0,0 +1,193 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Button } from "@/components/ui/button";
+import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code";
+import { processNodeAdvancedFields } from "@/CustomNodes/helpers/process-node-advanced-fields";
+import useUpdateAllNodes, {
+ UpdateNodesType,
+} from "@/CustomNodes/hooks/use-update-all-nodes";
+import useAlertStore from "@/stores/alertStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import useFlowStore from "@/stores/flowStore";
+import { useTypesStore } from "@/stores/typesStore";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { cn } from "@/utils/utils";
+import { useUpdateNodeInternals } from "@xyflow/react";
+import { useEffect, useMemo, useRef, useState } from "react";
+
+const ERROR_MESSAGE_UPDATING_COMPONENTS = "Error updating components";
+const ERROR_MESSAGE_UPDATING_COMPONENTS_LIST = [
+ "There was an error updating the components.",
+ "If the error persists, please report it on our Discord or GitHub.",
+];
+const ERROR_MESSAGE_EDGES_LOST =
+ "Some edges were lost after updating the components. Please review the flow and reconnect them.";
+
+export default function UpdateAllComponents({}: {}) {
+ const { componentsToUpdate, nodes, edges, setNodes } = useFlowStore();
+ const setDismissAll = useUtilityStore((state) => state.setDismissAll);
+ const templates = useTypesStore((state) => state.templates);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { mutateAsync: validateComponentCode } = usePostValidateComponentCode();
+ const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
+
+ const updateNodeInternals = useUpdateNodeInternals();
+ const updateAllNodes = useUpdateAllNodes(setNodes, updateNodeInternals);
+
+ const [loadingUpdate, setLoadingUpdate] = useState(false);
+ const [dismissed, setDismissed] = useState(false);
+
+ const edgesUpdateRef = useRef({
+ numberOfEdgesBeforeUpdate: 0,
+ updateComponent: false,
+ });
+
+ useMemo(() => {
+ if (
+ edgesUpdateRef.current.numberOfEdgesBeforeUpdate > 0 &&
+ edges.length !== edgesUpdateRef.current.numberOfEdgesBeforeUpdate &&
+ edgesUpdateRef.current.updateComponent
+ ) {
+ useAlertStore.getState().setNoticeData({
+ title: ERROR_MESSAGE_EDGES_LOST,
+ });
+
+ resetEdgesUpdateRef();
+ }
+ }, [edges]);
+
+ const getSuccessTitle = (updatedCount: number) => {
+ resetEdgesUpdateRef();
+ return `Successfully updated ${updatedCount} component${
+ updatedCount > 1 ? "s" : ""
+ }`;
+ };
+
+ const handleUpdateAllComponents = () => {
+ startEdgesUpdateRef();
+
+ setLoadingUpdate(true);
+ takeSnapshot();
+
+ let updatedCount = 0;
+ const updates: UpdateNodesType[] = [];
+
+ const updatePromises = componentsToUpdate.map((nodeId) => {
+ const node = nodes.find((n) => n.id === nodeId);
+ if (!node || node.type !== "genericNode") return Promise.resolve();
+
+ const thisNodeTemplate = templates[node.data.type]?.template;
+ if (!thisNodeTemplate?.code) return Promise.resolve();
+
+ const currentCode = thisNodeTemplate.code.value;
+
+ return new Promise((resolve) => {
+ validateComponentCode({
+ code: currentCode,
+ frontend_node: node.data.node!,
+ })
+ .then(({ data: resData, type }) => {
+ if (resData && type) {
+ const newNode = processNodeAdvancedFields(resData, edges, nodeId);
+
+ updates.push({
+ nodeId,
+ newNode,
+ code: currentCode,
+ name: "code",
+ type,
+ });
+
+ updatedCount++;
+ }
+ resolve(null);
+ })
+ .catch((error) => {
+ console.error(error);
+ resolve(null);
+ });
+ });
+ });
+
+ Promise.all(updatePromises)
+ .then(() => {
+ if (updatedCount > 0) {
+ updateAllNodes(updates);
+
+ useAlertStore.getState().setSuccessData({
+ title: getSuccessTitle(updatedCount),
+ });
+ }
+ })
+ .catch((error) => {
+ setErrorData({
+ title: ERROR_MESSAGE_UPDATING_COMPONENTS,
+ list: ERROR_MESSAGE_UPDATING_COMPONENTS_LIST,
+ });
+ console.error(error);
+ })
+ .finally(() => {
+ setLoadingUpdate(false);
+ });
+ };
+
+ const resetEdgesUpdateRef = () => {
+ edgesUpdateRef.current = {
+ numberOfEdgesBeforeUpdate: 0,
+ updateComponent: false,
+ };
+ };
+
+ const startEdgesUpdateRef = () => {
+ edgesUpdateRef.current = {
+ numberOfEdgesBeforeUpdate: edges.length,
+ updateComponent: true,
+ };
+ };
+
+ if (componentsToUpdate.length === 0) return null;
+
+ return (
+
+
+
+
+ {componentsToUpdate.length} component
+ {componentsToUpdate.length > 1 ? "s are" : " is"} ready to update
+
+
+
+ {
+ setDismissed(true);
+ setDismissAll(true);
+ e.stopPropagation();
+ }}
+ >
+ Dismiss
+
+
+ Update All
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/SidebarCategoryComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/SidebarCategoryComponent/index.tsx
new file mode 100644
index 0000000..dc7b47c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/SidebarCategoryComponent/index.tsx
@@ -0,0 +1,62 @@
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { nodeColors, nodeIconsLucide, nodeNames } from "@/utils/styleUtils";
+import { removeCountFromString } from "@/utils/utils";
+import DisclosureComponent from "../../DisclosureComponent";
+import SidebarDraggableComponent from "../sideBarDraggableComponent";
+import sensitiveSort from "../utils/sensitive-sort";
+
+export function SidebarCategoryComponent({
+ search,
+ getFilterEdge,
+ category,
+ name,
+ onDragStart,
+}) {
+ return (
+
+
+ {Object.keys(category)
+ .sort((a, b) =>
+ sensitiveSort(category[a].display_name, category[b].display_name),
+ )
+ .map((SBItemName: string, idx) => (
+
+
+ onDragStart(event, {
+ //split type to remove type in nodes saved with same name removing it's
+ type: removeCountFromString(SBItemName),
+ node: category[SBItemName],
+ })
+ }
+ color={nodeColors[name]}
+ itemName={SBItemName}
+ //convert error to boolean
+ error={!!category[SBItemName].error}
+ display_name={category[SBItemName].display_name}
+ official={
+ category[SBItemName].official === false ? false : true
+ }
+ />
+
+ ))}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
new file mode 100644
index 0000000..b28fd14
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
@@ -0,0 +1,343 @@
+import { ENABLE_INTEGRATIONS } from "@/customization/feature-flags";
+import { useStoreStore } from "@/stores/storeStore";
+import { cloneDeep } from "lodash";
+import { useEffect, useState } from "react";
+import IconComponent from "../../../../components/common/genericIconComponent";
+import { Input } from "../../../../components/ui/input";
+import { Separator } from "../../../../components/ui/separator";
+import {
+ BUNDLES_SIDEBAR_FOLDER_NAMES,
+ PRIORITY_SIDEBAR_ORDER,
+} from "../../../../constants/constants";
+import useAlertStore from "../../../../stores/alertStore";
+import useFlowStore from "../../../../stores/flowStore";
+import { useTypesStore } from "../../../../stores/typesStore";
+import { APIClassType, APIObjectType } from "../../../../types/api";
+import { nodeIconsLucide } from "../../../../utils/styleUtils";
+import ParentDisclosureComponent from "../ParentDisclosureComponent";
+import { SidebarCategoryComponent } from "./SidebarCategoryComponent";
+
+import { useUtilityStore } from "@/stores/utilityStore";
+import { SidebarFilterComponent } from "./sidebarFilterComponent";
+import { sortKeys } from "./utils";
+
+export default function ExtraSidebar(): JSX.Element {
+ const data = useTypesStore((state) => state.data);
+ const templates = useTypesStore((state) => state.templates);
+ const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
+ const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
+ const hasStore = useStoreStore((state) => state.hasStore);
+ const filterType = useFlowStore((state) => state.filterType);
+
+ const featureFlags = useUtilityStore((state) => state.featureFlags);
+
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const [dataFilter, setFilterData] = useState(data);
+ const [search, setSearch] = useState("");
+ function onDragStart(
+ event: React.DragEvent,
+ data: { type: string; node?: APIClassType },
+ ): void {
+ //start drag event
+ var crt = event.currentTarget.cloneNode(true);
+ crt.style.position = "absolute";
+ crt.style.width = "215px";
+ crt.style.top = "-500px";
+ crt.style.right = "-500px";
+ crt.classList.add("cursor-grabbing");
+ document.body.appendChild(crt);
+ event.dataTransfer.setDragImage(crt, 0, 0);
+ event.dataTransfer.setData("genericNode", JSON.stringify(data));
+ }
+ function normalizeString(str: string): string {
+ return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, "");
+ }
+
+ function searchInMetadata(metadata: any, searchTerm: string): boolean {
+ if (!metadata || typeof metadata !== "object") return false;
+
+ return Object.entries(metadata).some(([key, value]) => {
+ if (typeof value === "string") {
+ return (
+ normalizeString(key).includes(searchTerm) ||
+ normalizeString(value).includes(searchTerm)
+ );
+ }
+ if (typeof value === "object") {
+ return searchInMetadata(value, searchTerm);
+ }
+ return false;
+ });
+ }
+
+ function handleSearchInput(e: string) {
+ if (e === "") {
+ setFilterData(data);
+ return;
+ }
+
+ const searchTerm = normalizeString(e);
+
+ setFilterData((_) => {
+ let ret: APIObjectType = {};
+ Object.keys(data).forEach((d: keyof APIObjectType) => {
+ ret[d] = {};
+ let keys = Object.keys(data[d]).filter((nd) => {
+ const item = data[d][nd];
+ return (
+ normalizeString(nd).includes(searchTerm) ||
+ normalizeString(item.display_name).includes(searchTerm) ||
+ normalizeString(d.toString()).includes(searchTerm) ||
+ (item.metadata && searchInMetadata(item.metadata, searchTerm))
+ );
+ });
+ keys.forEach((element) => {
+ ret[d][element] = data[d][element];
+ });
+ });
+ return ret;
+ });
+ }
+
+ useEffect(() => {
+ // show components with error on load
+ let errors: string[] = [];
+ Object.keys(templates).forEach((component) => {
+ if (templates[component].error) {
+ errors.push(component);
+ }
+ });
+ if (errors.length > 0)
+ setErrorData({ title: " Components with errors: ", list: errors });
+ }, []);
+
+ function handleBlur() {
+ // check if search is search to reset fitler on click input
+ if ((!search && search === "") || search === "search") {
+ setFilterData(data);
+ setFilterEdge([]);
+ setSearch("");
+ }
+ }
+
+ useEffect(() => {
+ if (getFilterEdge.length !== 0) {
+ setSearch("");
+ }
+
+ if (getFilterEdge.length === 0 && search === "") {
+ setSearch("");
+ setFilterData(data);
+ }
+ }, [getFilterEdge, data]);
+
+ useEffect(() => {
+ handleSearchInput(search);
+ }, [data]);
+
+ useEffect(() => {
+ if (getFilterEdge?.length > 0) {
+ setFilterData((_) => {
+ let dataClone = cloneDeep(data);
+ let ret = {};
+ Object.keys(dataClone).forEach((d: keyof APIObjectType, i) => {
+ ret[d] = {};
+ if (getFilterEdge.some((x) => x.family === d)) {
+ ret[d] = dataClone[d];
+
+ const filtered = getFilterEdge
+ .filter((x) => x.family === d)
+ .pop()
+ .type.split(",");
+
+ for (let i = 0; i < filtered.length; i++) {
+ filtered[i] = filtered[i].trimStart();
+ }
+
+ if (filtered.some((x) => x !== "")) {
+ let keys = Object.keys(dataClone[d]).filter((nd) =>
+ filtered.includes(nd),
+ );
+ Object.keys(dataClone[d]).forEach((element) => {
+ if (!keys.includes(element)) {
+ delete ret[d][element];
+ }
+ });
+ }
+ }
+ });
+ setSearch("");
+ return ret;
+ });
+ }
+ }, [getFilterEdge, data]);
+
+ return (
+
+
+
handleBlur()}
+ value={search}
+ type="text"
+ name="search"
+ id="search"
+ placeholder="Search"
+ className="nopan nodelete nodrag noflow input-search"
+ onChange={(event) => {
+ handleSearchInput(event.target.value);
+ // Set search input state
+ setSearch(event.target.value);
+ }}
+ readOnly
+ onClick={() =>
+ document?.getElementById("search")?.removeAttribute("readonly")
+ }
+ />
+
{
+ if (search) {
+ setFilterData(data);
+ setSearch("");
+ }
+ }}
+ >
+
+
+
+
+
+
+
+
+ Components
+ {filterType && (
+ {
+ setFilterEdge([]);
+ setFilterData(data);
+ }}
+ />
+ )}
+
+
+
+ {Object.keys(dataFilter)
+ .sort(sortKeys)
+ .filter((x) => PRIORITY_SIDEBAR_ORDER.includes(x))
+ .map((SBSectionName: keyof APIObjectType, index) =>
+ Object.keys(dataFilter[SBSectionName]).length > 0 ? (
+
+ ) : (
+
+ ),
+ )}
+ {(ENABLE_INTEGRATIONS || featureFlags?.mvp_components) && (
+
+ {Object.keys(dataFilter)
+ .sort(sortKeys)
+ .filter((x) => BUNDLES_SIDEBAR_FOLDER_NAMES.includes(x))
+ .map((SBSectionName: keyof APIObjectType, index) =>
+ Object.keys(dataFilter[SBSectionName]).length > 0 ? (
+
+ ) : (
+
+ ),
+ )}
+
+ )}
+
+ {Object.keys(dataFilter)
+ .sort(sortKeys)
+ .filter(
+ (x) =>
+ !PRIORITY_SIDEBAR_ORDER.includes(x) &&
+ !BUNDLES_SIDEBAR_FOLDER_NAMES.includes(x),
+ )
+ .map((SBSectionName: keyof APIObjectType, index) =>
+ Object.keys(dataFilter[SBSectionName]).length > 0 ? (
+
+ ) : (
+
+ ),
+ )}
+ {hasStore && (
+
+
+ {/* BUG ON THIS ICON */}
+
+
+
+ Discover More
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx
new file mode 100644
index 0000000..1e85821
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx
@@ -0,0 +1,164 @@
+import useDeleteFlow from "@/hooks/flows/use-delete-flow";
+import { DragEventHandler, forwardRef, useRef, useState } from "react";
+import IconComponent from "../../../../../components/common/genericIconComponent";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+} from "../../../../../components/ui/select-custom";
+import { useDarkStore } from "../../../../../stores/darkStore";
+import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
+import { APIClassType } from "../../../../../types/api";
+import {
+ createFlowComponent,
+ downloadNode,
+ getNodeId,
+} from "../../../../../utils/reactflowUtils";
+import { removeCountFromString } from "../../../../../utils/utils";
+
+export const SidebarDraggableComponent = forwardRef(
+ (
+ {
+ sectionName,
+ display_name,
+ itemName,
+ error,
+ color,
+ onDragStart,
+ apiClass,
+ official,
+ }: {
+ sectionName: string;
+ apiClass: APIClassType;
+ display_name: string;
+ itemName: string;
+ error: boolean;
+ color: string;
+ onDragStart: DragEventHandler;
+ official: boolean;
+ },
+ ref,
+ ) => {
+ const [open, setOpen] = useState(false);
+ const { deleteFlow } = useDeleteFlow();
+ const flows = useFlowsManagerStore((state) => state.flows);
+
+ const version = useDarkStore((state) => state.version);
+ const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
+ const popoverRef = useRef(null);
+
+ const handlePointerDown = (e) => {
+ if (!open) {
+ const rect = popoverRef.current?.getBoundingClientRect() ?? {
+ left: 0,
+ top: 0,
+ };
+ setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
+ }
+ };
+
+ function handleSelectChange(value: string) {
+ switch (value) {
+ case "share":
+ break;
+ case "download":
+ const type = removeCountFromString(itemName);
+ downloadNode(
+ createFlowComponent(
+ { id: getNodeId(type), type, node: apiClass },
+ version,
+ ),
+ );
+ break;
+ case "delete":
+ const flowId = flows?.find((f) => f.name === display_name);
+ if (flowId) deleteFlow({ id: flowId.id });
+ break;
+ }
+ }
+ return (
+ setOpen(change)}
+ open={open}
+ key={itemName}
+ >
+ {
+ e.preventDefault();
+ setOpen(true);
+ }}
+ key={itemName}
+ data-tooltip-id={itemName}
+ >
+
{
+ document.body.removeChild(
+ document.getElementsByClassName("cursor-grabbing")[0],
+ );
+ }}
+ >
+
+
{display_name}
+
+
+
+
+
+
+ {" "}
+ Download{" "}
+
{" "}
+
+ {!official && (
+
+
+ {" "}
+ Delete{" "}
+
{" "}
+
+ )}
+
+
+
+
+
+
+ );
+ },
+);
+
+export default SidebarDraggableComponent;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarNoteComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarNoteComponent/index.tsx
new file mode 100644
index 0000000..2dc88b7
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarNoteComponent/index.tsx
@@ -0,0 +1,34 @@
+import { APIClassType } from "@/types/api";
+import IconComponent from "../../../../../components/common/genericIconComponent";
+export default function NoteDraggableComponent() {
+ function onDragStart(event: React.DragEvent): void {
+ const noteNode: APIClassType = {
+ description: "",
+ display_name: "",
+ documentation: "",
+ template: {},
+ };
+ event.dataTransfer.setData(
+ "noteNode",
+ JSON.stringify({ node: noteNode, type: "note" }),
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx
new file mode 100644
index 0000000..410cb45
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sidebarFilterComponent/index.tsx
@@ -0,0 +1,58 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+
+export function SidebarFilterComponent({
+ isInput,
+ type,
+ color,
+ resetFilters,
+}: {
+ isInput: boolean;
+ type: string;
+ color: string;
+ resetFilters: () => void;
+}) {
+ const tooltips = type.split("\n");
+ const plural = tooltips.length > 1 ? "s" : "";
+ return (
+
+
+
+
+ {isInput ? "Input" : "Output"}
+ {plural}:{" "}
+
+ {tooltips.join(", ")}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils.tsx
new file mode 100644
index 0000000..ee0509c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils.tsx
@@ -0,0 +1,26 @@
+import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
+
+export function sortKeys(a: string, b: string) {
+ // Define the order of specific keys
+
+ const indexA = PRIORITY_SIDEBAR_ORDER.indexOf(a.toLowerCase());
+ const indexB = PRIORITY_SIDEBAR_ORDER.indexOf(b.toLowerCase());
+
+ // Check if both keys are in the predefined order
+ if (indexA !== -1 && indexB !== -1) {
+ return indexA - indexB;
+ }
+
+ // If only 'a' is in the predefined order, it should come first
+ if (indexA !== -1) {
+ return -1;
+ }
+
+ // If only 'b' is in the predefined order, it should come first
+ if (indexB !== -1) {
+ return 1;
+ }
+
+ // If neither 'a' nor 'b' are in the predefined order, sort them alphabetically
+ return a.localeCompare(b);
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils/sensitive-sort.tsx b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils/sensitive-sort.tsx
new file mode 100644
index 0000000..97c6fe1
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/utils/sensitive-sort.tsx
@@ -0,0 +1,24 @@
+export default function sensitiveSort(a: string, b: string): number {
+ // Extract the name and number from each string using regular expressions
+ const regex = /(.+) \((\w+)\)/;
+ const matchA = a.match(regex);
+ const matchB = b.match(regex);
+
+ if (matchA && matchB) {
+ // Compare the names alphabetically
+ const nameA = matchA[1];
+ const nameB = matchB[1];
+ if (nameA !== nameB) {
+ return nameA.localeCompare(nameB);
+ }
+
+ // If the names are the same, compare the numbers numerically
+ const numberA = parseInt(matchA[2]);
+ const numberB = parseInt(matchB[2]);
+ return numberA - numberB;
+ } else {
+ // Handle cases where one or both strings do not match the expected pattern
+ // Simple strings are treated as pure alphabetical comparisons
+ return a.localeCompare(b);
+ }
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/bundleItems/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/bundleItems/index.tsx
new file mode 100644
index 0000000..6ef9240
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/bundleItems/index.tsx
@@ -0,0 +1,83 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import {
+ Disclosure,
+ DisclosureContent,
+ DisclosureTrigger,
+} from "@/components/ui/disclosure";
+import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
+import { memo } from "react";
+import SidebarItemsList from "../sidebarItemsList";
+
+export const BundleItem = memo(
+ ({
+ item,
+ isOpen,
+ onOpenChange,
+ dataFilter,
+ nodeColors,
+ chatInputAdded,
+ onDragStart,
+ sensitiveSort,
+ handleKeyDownInput,
+ }: {
+ item: any;
+ isOpen: boolean;
+ onOpenChange: (isOpen: boolean) => void;
+ dataFilter: any;
+ nodeColors: any;
+ chatInputAdded: any;
+ onDragStart: any;
+ sensitiveSort: any;
+ handleKeyDownInput: any;
+ }) => {
+ if (
+ !dataFilter[item.name] ||
+ Object.keys(dataFilter[item.name]).length === 0
+ ) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+ handleKeyDownInput(e, item.name)}
+ className="flex cursor-pointer items-center gap-2"
+ data-testid={`disclosure-bundles-${item.display_name.toLowerCase()}`}
+ >
+
+
+ {item.display_name}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ },
+);
+
+BundleItem.displayName = "BundleItem";
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryDisclouse/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryDisclouse/index.tsx
new file mode 100644
index 0000000..bdb1cf3
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryDisclouse/index.tsx
@@ -0,0 +1,97 @@
+import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
+import {
+ Disclosure,
+ DisclosureContent,
+ DisclosureTrigger,
+} from "@/components/ui/disclosure";
+import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
+import { APIClassType } from "@/types/api";
+import { memo, useCallback } from "react";
+import SidebarItemsList from "../sidebarItemsList";
+
+export const CategoryDisclosure = memo(function CategoryDisclosure({
+ item,
+ openCategories,
+ setOpenCategories,
+ dataFilter,
+ nodeColors,
+ chatInputAdded,
+ onDragStart,
+ sensitiveSort,
+}: {
+ item: any;
+ openCategories: string[];
+ setOpenCategories;
+ dataFilter: any;
+ nodeColors: any;
+ chatInputAdded: boolean;
+ onDragStart: (
+ event: React.DragEvent,
+ data: { type: string; node?: APIClassType },
+ ) => void;
+ sensitiveSort: (a: any, b: any) => number;
+}) {
+ const handleKeyDownInput = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ setOpenCategories((prev) =>
+ prev.includes(item.name)
+ ? prev.filter((cat) => cat !== item.name)
+ : [...prev, item.name],
+ );
+ }
+ },
+ [item.name, setOpenCategories],
+ );
+
+ return (
+ {
+ setOpenCategories((prev) =>
+ isOpen
+ ? [...prev, item.name]
+ : prev.filter((cat) => cat !== item.name),
+ );
+ }}
+ >
+
+
+
+
+
+
+ {item.display_name}
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+CategoryDisclosure.displayName = "CategoryDisclosure";
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryGroup/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryGroup/index.tsx
new file mode 100644
index 0000000..2e54e27
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/categoryGroup/index.tsx
@@ -0,0 +1,80 @@
+import {
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarMenu,
+} from "@/components/ui/sidebar";
+import { SIDEBAR_BUNDLES } from "@/utils/styleUtils";
+import { memo } from "react";
+import { CategoryGroupProps } from "../../types";
+import { CategoryDisclosure } from "../categoryDisclouse";
+
+export const CategoryGroup = memo(function CategoryGroup({
+ dataFilter,
+ sortedCategories,
+ CATEGORIES,
+ openCategories,
+ setOpenCategories,
+ search,
+ nodeColors,
+ chatInputAdded,
+ onDragStart,
+ sensitiveSort,
+}: CategoryGroupProps) {
+ return (
+
+
+
+ {Object.entries(dataFilter)
+ .filter(
+ ([categoryName, items]) =>
+ // filter out bundles
+ !SIDEBAR_BUNDLES.some((cat) => cat.name === categoryName) &&
+ categoryName !== "custom_component" &&
+ Object.keys(items).length > 0,
+ )
+ .sort(([aName], [bName]) => {
+ const categoryList =
+ search !== ""
+ ? sortedCategories
+ : CATEGORIES.map((c) => c.name);
+ const aIndex = categoryList.indexOf(aName);
+ const bIndex = categoryList.indexOf(bName);
+
+ // If neither is in CATEGORIES, keep their relative order
+ if (aIndex === -1 && bIndex === -1) return 0;
+ // If only a is not in CATEGORIES, put it after b
+ if (aIndex === -1) return 1;
+ // If only b is not in CATEGORIES, put it after a
+ if (bIndex === -1) return -1;
+ // If both are in CATEGORIES, sort by their index
+ return aIndex - bIndex;
+ })
+ .map(([categoryName]) => {
+ const item = CATEGORIES.find(
+ (cat) => cat.name === categoryName,
+ ) ?? {
+ name: categoryName,
+ icon: "folder",
+ display_name: categoryName,
+ };
+ return (
+
+ );
+ })}
+
+
+
+ );
+});
+
+CategoryGroup.displayName = "CategoryGroup";
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx
new file mode 100644
index 0000000..1eb49cd
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/emptySearchComponent/index.tsx
@@ -0,0 +1,23 @@
+const NoResultsMessage = ({
+ onClearSearch,
+ message = "No components found.",
+ clearSearchText = "Clear your search",
+ additionalText = "or filter and try a different query.",
+}) => {
+ return (
+
+ );
+};
+
+export default NoResultsMessage;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx
new file mode 100644
index 0000000..0d7573e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/featureTogglesComponent/index.tsx
@@ -0,0 +1,50 @@
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+
+const FeatureToggles = ({
+ showBeta,
+ setShowBeta,
+ showLegacy,
+ setShowLegacy,
+}) => {
+ const toggles = [
+ {
+ label: "Beta",
+ checked: showBeta,
+ onChange: setShowBeta,
+ badgeVariant: "pinkStatic" as const,
+ testId: "sidebar-beta-switch",
+ },
+ {
+ label: "Legacy",
+ checked: showLegacy,
+ onChange: setShowLegacy,
+ badgeVariant: "secondaryStatic" as const,
+ testId: "sidebar-legacy-switch",
+ },
+ ];
+
+ return (
+
+ {toggles.map((toggle) => (
+
+
+
+ Show
+
+ {toggle.label}
+
+
+
+
+
+ ))}
+
+ );
+};
+
+export default FeatureToggles;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/searchInput/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/searchInput/index.tsx
new file mode 100644
index 0000000..b657205
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/searchInput/index.tsx
@@ -0,0 +1,50 @@
+import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
+import { Input } from "@/components/ui/input";
+import { memo } from "react";
+import ShortcutDisplay from "../../../nodeToolbarComponent/shortcutDisplay";
+
+export const SearchInput = memo(function SearchInput({
+ searchInputRef,
+ isInputFocused,
+ search,
+ handleInputFocus,
+ handleInputBlur,
+ handleInputChange,
+}: {
+ searchInputRef: React.RefObject;
+ isInputFocused: boolean;
+ search: string;
+ handleInputFocus: (event: React.FocusEvent) => void;
+ handleInputBlur: (event: React.FocusEvent) => void;
+ handleInputChange: (event: React.ChangeEvent) => void;
+}) {
+ return (
+
+
+
+ {!isInputFocused && search === "" && (
+
+ Search{" "}
+
+
+
+
+ )}
+
+ );
+});
+
+SearchInput.displayName = "SearchInput";
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarBundles/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarBundles/index.tsx
new file mode 100644
index 0000000..b49c061
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarBundles/index.tsx
@@ -0,0 +1,81 @@
+import {
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarMenu,
+} from "@/components/ui/sidebar";
+import { memo, useMemo } from "react";
+import { BundleItem } from "../bundleItems";
+
+export const MemoizedSidebarGroup = memo(
+ ({
+ BUNDLES,
+ search,
+ sortedCategories,
+ dataFilter,
+ nodeColors,
+ chatInputAdded,
+ onDragStart,
+ sensitiveSort,
+ openCategories,
+ setOpenCategories,
+ handleKeyDownInput,
+ }: {
+ BUNDLES: any;
+ search: any;
+ sortedCategories: any;
+ dataFilter: any;
+ nodeColors: any;
+ chatInputAdded: any;
+ onDragStart: any;
+ sensitiveSort: any;
+ openCategories: any;
+ setOpenCategories: any;
+ handleKeyDownInput: any;
+ }) => {
+ // Memoize the sorted bundles calculation
+ const sortedBundles = useMemo(() => {
+ return BUNDLES.toSorted((a, b) => {
+ const referenceArray = search !== "" ? sortedCategories : BUNDLES;
+ return (
+ referenceArray.findIndex((value) => value === a.name) -
+ referenceArray.findIndex((value) => value === b.name)
+ );
+ });
+ }, [BUNDLES, search, sortedCategories]);
+
+ return (
+
+ Bundles
+
+
+ {sortedBundles.map((item) => (
+ {
+ setOpenCategories((prev) =>
+ isOpen
+ ? [...prev, item.name]
+ : prev.filter((cat) => cat !== item.name),
+ );
+ }}
+ dataFilter={dataFilter}
+ nodeColors={nodeColors}
+ chatInputAdded={chatInputAdded}
+ onDragStart={onDragStart}
+ sensitiveSort={sensitiveSort}
+ handleKeyDownInput={handleKeyDownInput}
+ />
+ ))}
+
+
+
+ );
+ },
+);
+
+MemoizedSidebarGroup.displayName = "MemoizedSidebarGroup";
+
+export default MemoizedSidebarGroup;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx
new file mode 100644
index 0000000..25af5b6
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarDraggableComponent/index.tsx
@@ -0,0 +1,246 @@
+import { convertTestName } from "@/components/common/storeCardComponent/utils/convert-test-name";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import useDeleteFlow from "@/hooks/flows/use-delete-flow";
+import { useAddComponent } from "@/hooks/useAddComponent";
+import { DragEventHandler, forwardRef, useRef, useState } from "react";
+import IconComponent, {
+ ForwardedIconComponent,
+} from "../../../../../../components/common/genericIconComponent";
+import ShadTooltip from "../../../../../../components/common/shadTooltipComponent";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+} from "../../../../../../components/ui/select-custom";
+import { useDarkStore } from "../../../../../../stores/darkStore";
+import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
+import { APIClassType } from "../../../../../../types/api";
+import {
+ createFlowComponent,
+ downloadNode,
+ getNodeId,
+} from "../../../../../../utils/reactflowUtils";
+import { cn, removeCountFromString } from "../../../../../../utils/utils";
+
+export const SidebarDraggableComponent = forwardRef(
+ (
+ {
+ sectionName,
+ display_name,
+ icon,
+ itemName,
+ error,
+ color,
+ onDragStart,
+ apiClass,
+ official,
+ beta,
+ legacy,
+ disabled,
+ disabledTooltip,
+ }: {
+ sectionName: string;
+ apiClass: APIClassType;
+ icon: string;
+ display_name: string;
+ itemName: string;
+ error: boolean;
+ color: string;
+ onDragStart: DragEventHandler;
+ official: boolean;
+ beta: boolean;
+ legacy: boolean;
+ disabled?: boolean;
+ disabledTooltip?: string;
+ },
+ ref,
+ ) => {
+ const [open, setOpen] = useState(false);
+ const { deleteFlow } = useDeleteFlow();
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const addComponent = useAddComponent();
+
+ const version = useDarkStore((state) => state.version);
+ const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
+ const popoverRef = useRef(null);
+
+ const handlePointerDown = (e) => {
+ if (!open) {
+ const rect = popoverRef.current?.getBoundingClientRect() ?? {
+ left: 0,
+ top: 0,
+ };
+ setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
+ }
+ };
+
+ function handleSelectChange(value: string) {
+ switch (value) {
+ case "download":
+ const type = removeCountFromString(itemName);
+ downloadNode(
+ createFlowComponent(
+ { id: getNodeId(type), type, node: apiClass },
+ version,
+ ),
+ );
+ break;
+ case "delete":
+ const flowId = flows?.find((f) => f.name === display_name);
+ if (flowId) deleteFlow({ id: flowId.id });
+ break;
+ }
+ }
+
+ const handleKeyDown = (e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ addComponent(apiClass, itemName);
+ }
+ };
+
+ return (
+ setOpen(change)}
+ open={open}
+ key={itemName}
+ >
+
+ {
+ e.preventDefault();
+ setOpen(true);
+ }}
+ key={itemName}
+ data-tooltip-id={itemName}
+ tabIndex={0}
+ onKeyDown={handleKeyDown}
+ className="m-[1px] rounded-md outline-none ring-ring focus-visible:ring-1"
+ >
+
{
+ if (
+ document.getElementsByClassName("cursor-grabbing").length > 0
+ ) {
+ document.body.removeChild(
+ document.getElementsByClassName("cursor-grabbing")[0],
+ );
+ }
+ }}
+ >
+
+
+
+
+ {display_name}
+
+
+ {beta && (
+
+ Beta
+
+ )}
+ {legacy && (
+
+ Legacy
+
+ )}
+
+
+ {!disabled && (
+
addComponent(apiClass, itemName)}
+ >
+
+
+ )}
+
+
+
+
+
+
+ {" "}
+ Download{" "}
+
{" "}
+
+ {!official && (
+
+
+ {" "}
+ Delete{" "}
+
{" "}
+
+ )}
+
+
+
+
+
+
+
+ );
+ },
+);
+
+export default SidebarDraggableComponent;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx
new file mode 100644
index 0000000..b970b3e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFooterButtons/index.tsx
@@ -0,0 +1,63 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Button } from "@/components/ui/button";
+import { SidebarMenuButton } from "@/components/ui/sidebar";
+import { CustomLink } from "@/customization/components/custom-link";
+
+const SidebarMenuButtons = ({
+ hasStore = false,
+ customComponent,
+ addComponent,
+ isLoading = false,
+}) => {
+ return (
+ <>
+ {hasStore && (
+
+
+
+
+
+ Discover more components
+
+
+
+
+
+ )}
+
+ {
+ if (customComponent) {
+ addComponent(customComponent, "CustomComponent");
+ }
+ }}
+ data-testid="sidebar-custom-component-button"
+ className="flex items-center gap-2"
+ >
+
+
+ New Custom Component
+
+
+
+ >
+ );
+};
+
+export default SidebarMenuButtons;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader/index.tsx
new file mode 100644
index 0000000..448085d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader/index.tsx
@@ -0,0 +1,92 @@
+import {
+ Disclosure,
+ DisclosureContent,
+ DisclosureTrigger,
+} from "@/components/ui/disclosure";
+
+import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+import { SidebarHeader, SidebarTrigger } from "@/components/ui/sidebar";
+import { memo } from "react";
+import { SidebarFilterComponent } from "../../../extraSidebarComponent/sidebarFilterComponent";
+import { SidebarHeaderComponentProps } from "../../types";
+import FeatureToggles from "../featureTogglesComponent";
+import { SearchInput } from "../searchInput";
+
+export const SidebarHeaderComponent = memo(function SidebarHeaderComponent({
+ showConfig,
+ setShowConfig,
+ showBeta,
+ setShowBeta,
+ showLegacy,
+ setShowLegacy,
+ searchInputRef,
+ isInputFocused,
+ search,
+ handleInputFocus,
+ handleInputBlur,
+ handleInputChange,
+ filterType,
+ setFilterEdge,
+ setFilterData,
+ data,
+}: SidebarHeaderComponentProps) {
+ return (
+
+
+
+
+
+
+
Components
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {filterType && (
+ {
+ setFilterEdge([]);
+ setFilterData(data);
+ }}
+ />
+ )}
+
+ );
+});
+
+SidebarHeaderComponent.displayName = "SidebarHeaderComponent";
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx
new file mode 100644
index 0000000..0456c1e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarItemsList/index.tsx
@@ -0,0 +1,59 @@
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { removeCountFromString } from "@/utils/utils";
+import SidebarDraggableComponent from "../sidebarDraggableComponent";
+
+const SidebarItemsList = ({
+ item,
+ dataFilter,
+ nodeColors,
+ chatInputAdded,
+ onDragStart,
+ sensitiveSort,
+}) => {
+ return (
+
+ {Object.keys(dataFilter[item.name])
+ .sort((a, b) => {
+ const itemA = dataFilter[item.name][a];
+ const itemB = dataFilter[item.name][b];
+ return itemA.score && itemB.score
+ ? itemA.score - itemB.score
+ : sensitiveSort(itemA.display_name, itemB.display_name);
+ })
+ .map((SBItemName, idx) => {
+ const currentItem = dataFilter[item.name][SBItemName];
+
+ return (
+
+
+ onDragStart(event, {
+ type: removeCountFromString(SBItemName),
+ node: currentItem,
+ })
+ }
+ color={nodeColors[item.name]}
+ itemName={SBItemName}
+ error={!!currentItem.error}
+ display_name={currentItem.display_name}
+ official={currentItem.official === false ? false : true}
+ beta={currentItem.beta ?? false}
+ legacy={currentItem.legacy ?? false}
+ disabled={SBItemName === "ChatInput" && chatInputAdded}
+ disabledTooltip="Chat input already added"
+ />
+
+ );
+ })}
+
+ );
+};
+
+export default SidebarItemsList;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts
new file mode 100644
index 0000000..63f910d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-beta-filter.ts
@@ -0,0 +1,12 @@
+import { APIDataType } from "@/types/api";
+
+export const applyBetaFilter = (filteredData: APIDataType) => {
+ return Object.fromEntries(
+ Object.entries(filteredData).map(([category, items]) => [
+ category,
+ Object.fromEntries(
+ Object.entries(items).filter(([_, value]) => !value.beta),
+ ),
+ ]),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts
new file mode 100644
index 0000000..5d981e9
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-edge-filter.ts
@@ -0,0 +1,25 @@
+import { APIDataType } from "@/types/api";
+
+export const applyEdgeFilter = (filteredData: APIDataType, getFilterEdge) => {
+ return Object.fromEntries(
+ Object.entries(filteredData).map(([family, familyData]) => {
+ const edgeFilter = getFilterEdge.find((x) => x.family === family);
+ if (!edgeFilter) return [family, {}];
+
+ const filteredTypes = edgeFilter.type
+ .split(",")
+ .map((t) => t.trim())
+ .filter((t) => t !== "");
+
+ if (filteredTypes.length === 0) return [family, familyData];
+
+ const filteredFamilyData = Object.fromEntries(
+ Object.entries(familyData).filter(([key]) =>
+ filteredTypes.includes(key),
+ ),
+ );
+
+ return [family, filteredFamilyData];
+ }),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts
new file mode 100644
index 0000000..d7ac6d5
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-legacy-filter.ts
@@ -0,0 +1,12 @@
+import { APIDataType } from "@/types/api";
+
+export const applyLegacyFilter = (filteredData: APIDataType) => {
+ return Object.fromEntries(
+ Object.entries(filteredData).map(([category, items]) => [
+ category,
+ Object.fromEntries(
+ Object.entries(items).filter(([_, value]) => !value.legacy),
+ ),
+ ]),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts
new file mode 100644
index 0000000..5f7e60e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/combined-results.ts
@@ -0,0 +1,19 @@
+import { APIDataType } from "@/types/api";
+import { FuseResult } from "fuse.js";
+
+export const combinedResultsFn = (
+ fuseResults: FuseResult[],
+ data: APIDataType,
+) => {
+ return Object.fromEntries(
+ Object.entries(data).map(([category]) => {
+ const categoryResults = fuseResults.filter(
+ (result) => result.item.category === category,
+ );
+ const filteredItems = Object.fromEntries(
+ categoryResults.map((result) => [result.item.key, result.item]),
+ );
+ return [category, filteredItems];
+ }),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts
new file mode 100644
index 0000000..fae3a67
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/filtered-data.ts
@@ -0,0 +1,21 @@
+import { APIDataType } from "@/types/api";
+
+export const filteredDataFn = (
+ data: APIDataType,
+ combinedResults,
+ traditionalResults,
+) => {
+ return Object.fromEntries(
+ Object.entries(data).map(([category, _]) => {
+ const fuseItems = combinedResults[category] || {};
+ const traditionalItems = traditionalResults[category] || {};
+
+ const mergedItems = {
+ ...fuseItems,
+ ...traditionalItems,
+ };
+
+ return [category, mergedItems];
+ }),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts
new file mode 100644
index 0000000..6e5434c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/normalize-string.ts
@@ -0,0 +1,3 @@
+export function normalizeString(str: string): string {
+ return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, "");
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts
new file mode 100644
index 0000000..c454c05
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/search-on-metadata.ts
@@ -0,0 +1,18 @@
+import { normalizeString } from "./normalize-string";
+
+export function searchInMetadata(metadata: any, searchTerm: string): boolean {
+ if (!metadata || typeof metadata !== "object") return false;
+
+ return Object.entries(metadata).some(([key, value]) => {
+ if (typeof value === "string") {
+ return (
+ normalizeString(key).includes(searchTerm) ||
+ normalizeString(value).includes(searchTerm)
+ );
+ }
+ if (typeof value === "object") {
+ return searchInMetadata(value, searchTerm);
+ }
+ return false;
+ });
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts
new file mode 100644
index 0000000..0db1822
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/traditional-search-metadata.ts
@@ -0,0 +1,19 @@
+import { APIDataType } from "@/types/api";
+import { searchInMetadata } from "./search-on-metadata";
+
+export const traditionalSearchMetadata = (
+ data: APIDataType,
+ searchTerm: string,
+) => {
+ return Object.fromEntries(
+ Object.entries(data).map(([category, items]) => {
+ const filteredItems = Object.fromEntries(
+ Object.entries(items).filter(
+ ([key, item]) =>
+ item.metadata && searchInMetadata(item.metadata, searchTerm),
+ ),
+ );
+ return [category, filteredItems];
+ }),
+ );
+};
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
new file mode 100644
index 0000000..2780e03
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
@@ -0,0 +1,402 @@
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ useSidebar,
+} from "@/components/ui/sidebar";
+import SkeletonGroup from "@/components/ui/skeletonGroup";
+import { useAddComponent } from "@/hooks/useAddComponent";
+import { useShortcutsStore } from "@/stores/shortcuts";
+import { useStoreStore } from "@/stores/storeStore";
+import { checkChatInput } from "@/utils/reactflowUtils";
+import {
+ nodeColors,
+ SIDEBAR_BUNDLES,
+ SIDEBAR_CATEGORIES,
+} from "@/utils/styleUtils";
+import Fuse from "fuse.js";
+import { cloneDeep } from "lodash";
+import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useHotkeys } from "react-hotkeys-hook";
+import useAlertStore from "../../../../stores/alertStore";
+import useFlowStore from "../../../../stores/flowStore";
+import { useTypesStore } from "../../../../stores/typesStore";
+import { APIClassType } from "../../../../types/api";
+import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
+import isWrappedWithClass from "../PageComponent/utils/is-wrapped-with-class";
+import { CategoryGroup } from "./components/categoryGroup";
+import NoResultsMessage from "./components/emptySearchComponent";
+import MemoizedSidebarGroup from "./components/sidebarBundles";
+import SidebarMenuButtons from "./components/sidebarFooterButtons";
+import { SidebarHeaderComponent } from "./components/sidebarHeader";
+import { applyBetaFilter } from "./helpers/apply-beta-filter";
+import { applyEdgeFilter } from "./helpers/apply-edge-filter";
+import { applyLegacyFilter } from "./helpers/apply-legacy-filter";
+import { combinedResultsFn } from "./helpers/combined-results";
+import { filteredDataFn } from "./helpers/filtered-data";
+import { normalizeString } from "./helpers/normalize-string";
+import { traditionalSearchMetadata } from "./helpers/traditional-search-metadata";
+
+const CATEGORIES = SIDEBAR_CATEGORIES;
+const BUNDLES = SIDEBAR_BUNDLES;
+
+interface FlowSidebarComponentProps {
+ showLegacy: boolean;
+ setShowLegacy: (value: boolean) => void;
+}
+
+export function FlowSidebarComponent({ isLoading }: { isLoading?: boolean }) {
+ const { data, templates } = useTypesStore(
+ useCallback(
+ (state) => ({
+ data: state.data,
+ templates: state.templates,
+ }),
+ [],
+ ),
+ );
+
+ const { getFilterEdge, setFilterEdge, filterType, nodes } = useFlowStore(
+ useCallback(
+ (state) => ({
+ getFilterEdge: state.getFilterEdge,
+ setFilterEdge: state.setFilterEdge,
+ filterType: state.filterType,
+ nodes: state.nodes,
+ }),
+ [],
+ ),
+ );
+
+ const hasStore = useStoreStore((state) => state.hasStore);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { setOpen } = useSidebar();
+ const addComponent = useAddComponent();
+
+ // State
+ const [dataFilter, setFilterData] = useState(data);
+ const [search, setSearch] = useState("");
+ const [fuse, setFuse] = useState | null>(null);
+ const [openCategories, setOpenCategories] = useState([]);
+ const [showConfig, setShowConfig] = useState(false);
+ const [showBeta, setShowBeta] = useState(true);
+ const [showLegacy, setShowLegacy] = useState(false);
+ const [isInputFocused, setIsInputFocused] = useState(false);
+
+ const searchInputRef = useRef(null);
+
+ const chatInputAdded = useMemo(() => checkChatInput(nodes), [nodes]);
+
+ const customComponent = useMemo(() => {
+ return data?.["custom_component"]?.["CustomComponent"] ?? null;
+ }, [data]);
+
+ const searchResults = useMemo(() => {
+ if (!search || !fuse) return null;
+
+ const searchTerm = normalizeString(search);
+ const fuseResults = fuse.search(search).map((result) => ({
+ ...result,
+ item: { ...result.item, score: result.score },
+ }));
+
+ return {
+ fuseResults,
+ fuseCategories: fuseResults.map((result) => result.item.category),
+ combinedResults: combinedResultsFn(fuseResults, data),
+ traditionalResults: traditionalSearchMetadata(data, searchTerm),
+ };
+ }, [search, fuse, data]);
+
+ const searchFilteredData = useMemo(() => {
+ if (!search || !searchResults) return cloneDeep(data);
+
+ return filteredDataFn(
+ data,
+ searchResults.combinedResults,
+ searchResults.traditionalResults,
+ );
+ }, [data, search, searchResults]);
+
+ const sortedCategories = useMemo(() => {
+ if (!searchResults || !searchFilteredData) return [];
+
+ return Object.keys(searchFilteredData).toSorted((a, b) =>
+ searchResults.fuseCategories.indexOf(b) <
+ searchResults.fuseCategories.indexOf(a)
+ ? 1
+ : -1,
+ );
+ }, [searchResults, searchFilteredData, CATEGORIES, BUNDLES]);
+
+ const finalFilteredData = useMemo(() => {
+ let filteredData = searchFilteredData;
+
+ if (getFilterEdge?.length > 0) {
+ filteredData = applyEdgeFilter(filteredData, getFilterEdge);
+ }
+
+ if (!showBeta) {
+ filteredData = applyBetaFilter(filteredData);
+ }
+
+ if (!showLegacy) {
+ filteredData = applyLegacyFilter(filteredData);
+ }
+
+ return filteredData;
+ }, [searchFilteredData, getFilterEdge, showBeta, showLegacy]);
+
+ const hasResults = useMemo(() => {
+ return Object.entries(dataFilter).some(
+ ([category, items]) =>
+ Object.keys(items).length > 0 &&
+ (CATEGORIES.find((c) => c.name === category) ||
+ BUNDLES.find((b) => b.name === category)),
+ );
+ }, [dataFilter]);
+
+ const handleKeyDownInput = useCallback(
+ (e: React.KeyboardEvent, name: string) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ setOpenCategories((prev) =>
+ prev.includes(name)
+ ? prev.filter((cat) => cat !== name)
+ : [...prev, name],
+ );
+ }
+ },
+ [],
+ );
+
+ const handleClearSearch = useCallback(() => {
+ setSearch("");
+ setFilterData(data);
+ setOpenCategories([]);
+ }, [data]);
+
+ const handleInputFocus = useCallback(() => {
+ setIsInputFocused(true);
+ }, []);
+
+ const handleInputBlur = useCallback(() => {
+ setIsInputFocused(false);
+ }, []);
+
+ const handleSearchInput = useCallback((value: string) => {
+ setSearch(value);
+ }, []);
+
+ const handleInputChange = useCallback(
+ (event: React.ChangeEvent) => {
+ handleSearchInput(event.target.value);
+ },
+ [handleSearchInput],
+ );
+
+ useEffect(() => {
+ if (filterType) {
+ setOpen(true);
+ }
+ }, [filterType, setOpen]);
+
+ useEffect(() => {
+ setFilterData(finalFilteredData);
+
+ if (search !== "" || filterType || getFilterEdge.length > 0) {
+ const newOpenCategories = Object.keys(finalFilteredData).filter(
+ (cat) => Object.keys(finalFilteredData[cat]).length > 0,
+ );
+ setOpenCategories(newOpenCategories);
+ }
+ }, [finalFilteredData, search, filterType, getFilterEdge]);
+
+ useEffect(() => {
+ const options = {
+ keys: ["display_name", "description", "type", "category"],
+ threshold: 0.2,
+ includeScore: true,
+ };
+
+ const fuseData = Object.entries(data).flatMap(([category, items]) =>
+ Object.entries(items).map(([key, value]) => ({
+ ...value,
+ category,
+ key,
+ })),
+ );
+
+ setFuse(new Fuse(fuseData, options));
+ }, [data]);
+
+ useEffect(() => {
+ if (getFilterEdge.length !== 0) {
+ setSearch("");
+ }
+ }, [getFilterEdge, data]);
+
+ useEffect(() => {
+ if (search === "" && getFilterEdge.length === 0) {
+ setOpenCategories([]);
+ }
+ }, [search, getFilterEdge]);
+
+ const searchComponentsSidebar = useShortcutsStore(
+ (state) => state.searchComponentsSidebar,
+ );
+
+ useHotkeys(
+ searchComponentsSidebar,
+ (e: KeyboardEvent) => {
+ if (isWrappedWithClass(e, "noflow")) return;
+ e.preventDefault();
+ searchInputRef.current?.focus();
+ setOpen(true);
+ },
+ {
+ preventDefault: true,
+ },
+ );
+
+ useHotkeys(
+ "esc",
+ (event) => {
+ event.preventDefault();
+ searchInputRef.current?.blur();
+ },
+ {
+ enableOnFormTags: true,
+ enabled: isInputFocused,
+ },
+ );
+
+ const onDragStart = useCallback(
+ (
+ event: React.DragEvent,
+ data: { type: string; node?: APIClassType },
+ ) => {
+ var crt = event.currentTarget.cloneNode(true);
+ crt.style.position = "absolute";
+ crt.style.width = "215px";
+ crt.style.top = "-500px";
+ crt.style.right = "-500px";
+ crt.classList.add("cursor-grabbing");
+ document.body.appendChild(crt);
+ event.dataTransfer.setDragImage(crt, 0, 0);
+ event.dataTransfer.setData("genericNode", JSON.stringify(data));
+ },
+ [],
+ );
+
+ const hasBundleItems = useMemo(
+ () =>
+ BUNDLES.some(
+ (item) =>
+ dataFilter[item.name] &&
+ Object.keys(dataFilter[item.name]).length > 0,
+ ),
+ [dataFilter],
+ );
+
+ return (
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ <>
+ {hasResults ? (
+ <>
+
+
+ {hasBundleItems && (
+
+ )}
+ >
+ ) : (
+
+ )}
+ >
+ )}
+
+
+
+
+
+ );
+}
+
+FlowSidebarComponent.displayName = "FlowSidebarComponent";
+
+export default memo(
+ FlowSidebarComponent,
+ (
+ prevProps: FlowSidebarComponentProps,
+ nextProps: FlowSidebarComponentProps,
+ ) => {
+ return (
+ prevProps.showLegacy === nextProps.showLegacy &&
+ prevProps.setShowLegacy === nextProps.setShowLegacy
+ );
+ },
+);
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/types/index.ts b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/types/index.ts
new file mode 100644
index 0000000..8214933
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/types/index.ts
@@ -0,0 +1,52 @@
+import { APIClassType, APIDataType } from "@/types/api";
+import { Dispatch, SetStateAction } from "react";
+
+export interface CategoryGroupProps {
+ dataFilter: APIDataType;
+ sortedCategories: string[];
+ CATEGORIES: {
+ display_name: string;
+ name: string;
+ icon: string;
+ }[];
+ openCategories: string[];
+ setOpenCategories: (categories: string[]) => void;
+ search: string;
+ nodeColors: {
+ [key: string]: string;
+ };
+ chatInputAdded: boolean;
+ onDragStart: (
+ event: React.DragEvent,
+ data: { type: string; node?: APIClassType },
+ ) => void;
+ sensitiveSort: (a: string, b: string) => number;
+}
+
+export interface SidebarHeaderComponentProps {
+ showConfig: boolean;
+ setShowConfig: (show: boolean) => void;
+ showBeta: boolean;
+ setShowBeta: (show: boolean) => void;
+ showLegacy: boolean;
+ setShowLegacy: (show: boolean) => void;
+ searchInputRef: React.RefObject;
+ isInputFocused: boolean;
+ search: string;
+ handleInputFocus: (event: React.FocusEvent) => void;
+ handleInputBlur: (event: React.FocusEvent) => void;
+ handleInputChange: (event: React.ChangeEvent) => void;
+ filterType:
+ | {
+ source: string | undefined;
+ sourceHandle: string | undefined;
+ target: string | undefined;
+ targetHandle: string | undefined;
+ type: string;
+ color: string;
+ }
+ | undefined;
+ setFilterEdge: (edge: any[]) => void;
+ setFilterData: Dispatch>;
+ data: APIDataType;
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-button.tsx b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-button.tsx
new file mode 100644
index 0000000..7472f72
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-button.tsx
@@ -0,0 +1,38 @@
+import { Button } from "@/components/ui/button";
+import { memo } from "react";
+
+import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { cn } from "@/utils/utils";
+import ShortcutDisplay from "../shortcutDisplay";
+
+export const ToolbarButton = memo(
+ ({
+ onClick,
+ icon,
+ label,
+ shortcut,
+ className,
+ dataTestId,
+ }: {
+ onClick: () => void;
+ icon: string;
+ label?: string;
+ shortcut?: any;
+ className?: string;
+ dataTestId?: string;
+ }) => (
+ } side="top">
+
+
+ {label && {label} }
+
+
+ ),
+);
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-modals.tsx b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-modals.tsx
new file mode 100644
index 0000000..af569f5
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/components/toolbar-modals.tsx
@@ -0,0 +1,146 @@
+import CodeAreaModal from "@/modals/codeAreaModal";
+import ConfirmationModal from "@/modals/confirmationModal";
+import EditNodeModal from "@/modals/editNodeModal";
+import ShareModal from "@/modals/shareModal";
+import { APIClassType } from "@/types/api";
+import { FlowType } from "@/types/flow";
+import { memo } from "react";
+
+interface ToolbarModalsProps {
+ // Modal visibility states
+ showModalAdvanced: boolean;
+ showconfirmShare: boolean;
+ showOverrideModal: boolean;
+ openModal: boolean;
+ hasCode: boolean;
+
+ // Setters for modal states
+ setShowModalAdvanced: (value: boolean) => void;
+ setShowconfirmShare: (value: boolean) => void;
+ setShowOverrideModal: (value: boolean) => void;
+ setOpenModal: (value: boolean) => void;
+
+ // Data and handlers
+ data: any;
+ flowComponent: FlowType;
+ handleOnNewValue: (value: string | string[]) => void;
+ handleNodeClass: (apiClassType: APIClassType, type: string) => void;
+ setToolMode: (value: boolean) => void;
+ setSuccessData: (data: { title: string }) => void;
+ addFlow: (params: { flow: FlowType; override: boolean }) => void;
+ name?: string;
+}
+
+const ToolbarModals = memo(
+ ({
+ showModalAdvanced,
+ showconfirmShare,
+ showOverrideModal,
+ openModal,
+ hasCode,
+ setShowModalAdvanced,
+ setShowconfirmShare,
+ setShowOverrideModal,
+ setOpenModal,
+ data,
+ flowComponent,
+ handleOnNewValue,
+ handleNodeClass,
+ setToolMode,
+ setSuccessData,
+ addFlow,
+ name = "code",
+ }: ToolbarModalsProps) => {
+ // Handlers for confirmation modal
+ const handleConfirm = () => {
+ addFlow({
+ flow: flowComponent,
+ override: true,
+ });
+ setSuccessData({ title: `${data.id} successfully overridden!` });
+ setShowOverrideModal(false);
+ };
+
+ const handleClose = () => {
+ setShowOverrideModal(false);
+ };
+
+ const handleCancel = () => {
+ addFlow({
+ flow: flowComponent,
+ override: true,
+ });
+ setSuccessData({ title: "New component successfully saved!" });
+ setShowOverrideModal(false);
+ };
+
+ return (
+ <>
+ {showModalAdvanced && (
+
+ )}
+
+ {showconfirmShare && (
+
+ )}
+
+ {showOverrideModal && (
+
+
+
+ It seems {data.node?.display_name} already exists. Do you want
+ to replace it with the current or create a new one?
+
+
+
+ )}
+
+ {hasCode && (
+
+ {openModal && (
+ {
+ handleNodeClass(apiClassType, type);
+ setToolMode(false);
+ }}
+ nodeClass={data.node}
+ value={data.node?.template[name]?.value ?? ""}
+ componentId={data.id}
+ >
+ <>>
+
+ )}
+
+ )}
+ >
+ );
+ },
+);
+
+ToolbarModals.displayName = "ToolbarModals";
+
+export default ToolbarModals;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.ts b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.ts
new file mode 100644
index 0000000..0d4ad5c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.ts
@@ -0,0 +1,144 @@
+import { useShortcutsStore } from "@/stores/shortcuts";
+import { useHotkeys } from "react-hotkeys-hook";
+import isWrappedWithClass from "../../PageComponent/utils/is-wrapped-with-class";
+
+export default function useShortcuts({
+ showOverrideModal,
+ showModalAdvanced,
+ openModal,
+ showconfirmShare,
+ FreezeAllVertices,
+ Freeze,
+ downloadFunction,
+ displayDocs,
+ saveComponent,
+ showAdvance,
+ handleCodeModal,
+ shareComponent,
+ ungroup,
+ minimizeFunction,
+ activateToolMode,
+ hasToolMode,
+}: {
+ showOverrideModal?: boolean;
+ showModalAdvanced?: boolean;
+ openModal?: boolean;
+ showconfirmShare?: boolean;
+ FreezeAllVertices?: () => void;
+ Freeze?: () => void;
+ downloadFunction?: () => void;
+ displayDocs?: () => void;
+ saveComponent?: () => void;
+ showAdvance?: () => void;
+ handleCodeModal?: () => void;
+ shareComponent?: () => void;
+ ungroup?: () => void;
+ minimizeFunction?: () => void;
+ activateToolMode?: () => void;
+ hasToolMode?: boolean;
+}) {
+ const advancedSettings = useShortcutsStore((state) => state.advancedSettings);
+ const minimize = useShortcutsStore((state) => state.minimize);
+ const componentShare = useShortcutsStore((state) => state.componentShare);
+ const save = useShortcutsStore((state) => state.saveComponent);
+ const docs = useShortcutsStore((state) => state.docs);
+ const code = useShortcutsStore((state) => state.code);
+ const group = useShortcutsStore((state) => state.group);
+ const download = useShortcutsStore((state) => state.download);
+ const freeze = useShortcutsStore((state) => state.freeze);
+ const freezeAll = useShortcutsStore((state) => state.freezePath);
+ const toolMode = useShortcutsStore((state) => state.toolMode);
+
+ function handleFreezeAll(e: KeyboardEvent) {
+ if (isWrappedWithClass(e, "noflow") || !FreezeAllVertices) return;
+ e.preventDefault();
+ FreezeAllVertices();
+ }
+
+ function handleFreeze(e: KeyboardEvent) {
+ if (isWrappedWithClass(e, "noflow") || !Freeze) return;
+ e.preventDefault();
+ Freeze();
+ }
+
+ function handleDownloadWShortcut(e: KeyboardEvent) {
+ if (!downloadFunction) return;
+ e.preventDefault();
+ downloadFunction();
+ }
+
+ function handleDocsWShortcut(e: KeyboardEvent) {
+ if (!displayDocs) return;
+ e.preventDefault();
+ displayDocs();
+ }
+
+ function handleSaveWShortcut(e: KeyboardEvent) {
+ if (
+ (isWrappedWithClass(e, "noflow") && !showOverrideModal) ||
+ !saveComponent
+ )
+ return;
+ e.preventDefault();
+ saveComponent();
+ }
+
+ function handleAdvancedWShortcut(e: KeyboardEvent) {
+ if ((isWrappedWithClass(e, "noflow") && !showModalAdvanced) || !showAdvance)
+ return;
+ e.preventDefault();
+ showAdvance();
+ }
+
+ function handleCodeWShortcut(e: KeyboardEvent) {
+ if ((isWrappedWithClass(e, "noflow") && !openModal) || !handleCodeModal)
+ return;
+ e.preventDefault();
+ handleCodeModal();
+ }
+
+ function handleShareWShortcut(e: KeyboardEvent) {
+ if (
+ (isWrappedWithClass(e, "noflow") && !showconfirmShare) ||
+ !shareComponent
+ )
+ return;
+ e.preventDefault();
+ shareComponent();
+ }
+
+ function handleGroupWShortcut(e: KeyboardEvent) {
+ if (isWrappedWithClass(e, "noflow") || !ungroup) return;
+ e.preventDefault();
+ ungroup();
+ }
+
+ function handleMinimizeWShortcut(e: KeyboardEvent) {
+ if (isWrappedWithClass(e, "noflow") || !minimizeFunction) return;
+ e.preventDefault();
+ minimizeFunction();
+ }
+
+ function handleToolModeWShortcut(e: KeyboardEvent, hasToolMode?: boolean) {
+ if (!hasToolMode) return;
+ if (isWrappedWithClass(e, "noflow") || !activateToolMode) return;
+ e.preventDefault();
+ activateToolMode();
+ }
+
+ useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault: true });
+ useHotkeys(group, handleGroupWShortcut, { preventDefault: true });
+ useHotkeys(componentShare, handleShareWShortcut, { preventDefault: true });
+ useHotkeys(code, handleCodeWShortcut, { preventDefault: true });
+ useHotkeys(advancedSettings, handleAdvancedWShortcut, {
+ preventDefault: true,
+ });
+ useHotkeys(save, handleSaveWShortcut, { preventDefault: true });
+ useHotkeys(docs, handleDocsWShortcut, { preventDefault: true });
+ useHotkeys(download, handleDownloadWShortcut, { preventDefault: true });
+ useHotkeys(freeze, handleFreeze);
+ useHotkeys(freezeAll, handleFreezeAll);
+ useHotkeys(toolMode, (e) => handleToolModeWShortcut(e, hasToolMode), {
+ preventDefault: true,
+ });
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
new file mode 100644
index 0000000..23f769f
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
@@ -0,0 +1,812 @@
+import { countHandlesFn } from "@/CustomNodes/helpers/count-handles";
+import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
+import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
+import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import ToggleShadComponent from "@/components/core/parameterRenderComponent/components/toggleShadComponent";
+import { Button } from "@/components/ui/button";
+import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
+import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
+import useAddFlow from "@/hooks/flows/use-add-flow";
+import { APIClassType } from "@/types/api";
+import { useUpdateNodeInternals } from "@xyflow/react";
+import _, { cloneDeep } from "lodash";
+import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
+import IconComponent from "../../../../components/common/genericIconComponent";
+import {
+ Select,
+ SelectContentWithoutPortal,
+ SelectItem,
+ SelectTrigger,
+} from "../../../../components/ui/select-custom";
+import useAlertStore from "../../../../stores/alertStore";
+import { useDarkStore } from "../../../../stores/darkStore";
+import useFlowStore from "../../../../stores/flowStore";
+import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
+import { useShortcutsStore } from "../../../../stores/shortcuts";
+import { useStoreStore } from "../../../../stores/storeStore";
+import { nodeToolbarPropsType } from "../../../../types/components";
+import { FlowType } from "../../../../types/flow";
+import {
+ checkHasToolMode,
+ createFlowComponent,
+ downloadNode,
+ expandGroupNode,
+ updateFlowPosition,
+} from "../../../../utils/reactflowUtils";
+import { cn, getNodeLength, openInNewTab } from "../../../../utils/utils";
+import { ToolbarButton } from "./components/toolbar-button";
+import ToolbarModals from "./components/toolbar-modals";
+import useShortcuts from "./hooks/use-shortcuts";
+import ShortcutDisplay from "./shortcutDisplay";
+import ToolbarSelectItem from "./toolbarSelectItem";
+
+const NodeToolbarComponent = memo(
+ ({
+ data,
+ deleteNode,
+ setShowNode,
+ numberOfOutputHandles,
+ showNode,
+ name = "code",
+ onCloseAdvancedModal,
+ updateNode,
+ isOutdated,
+ setOpenShowMoreOptions,
+ }: nodeToolbarPropsType): JSX.Element => {
+ const version = useDarkStore((state) => state.version);
+ const [showModalAdvanced, setShowModalAdvanced] = useState(false);
+ const [showconfirmShare, setShowconfirmShare] = useState(false);
+ const [showOverrideModal, setShowOverrideModal] = useState(false);
+ const [flowComponent, setFlowComponent] = useState(
+ createFlowComponent(cloneDeep(data), version),
+ );
+ const updateFreezeStatus = useFlowStore(
+ (state) => state.updateFreezeStatus,
+ );
+ const { hasStore, hasApiKey, validApiKey } = useStoreStore((state) => ({
+ hasStore: state.hasStore,
+ hasApiKey: state.hasApiKey,
+ validApiKey: state.validApiKey,
+ }));
+ const shortcuts = useShortcutsStore((state) => state.shortcuts);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const [openModal, setOpenModal] = useState(false);
+ const frozen = data.node?.frozen ?? false;
+ const currentFlow = useFlowStore((state) => state.currentFlow);
+ const updateNodeInternals = useUpdateNodeInternals();
+
+ const paste = useFlowStore((state) => state.paste);
+ const nodes = useFlowStore((state) => state.nodes);
+ const edges = useFlowStore((state) => state.edges);
+ const setNodes = useFlowStore((state) => state.setNodes);
+ const setEdges = useFlowStore((state) => state.setEdges);
+ const getNodePosition = useFlowStore((state) => state.getNodePosition);
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
+ const { mutate: FreezeAllVertices } = usePostRetrieveVertexOrder({
+ onSuccess: ({ vertices_to_run }) => {
+ updateFreezeStatus(vertices_to_run, !data.node?.frozen);
+ vertices_to_run.forEach((vertex) => {
+ updateNodeInternals(vertex);
+ });
+ },
+ });
+
+ const flowDataNodes = useMemo(
+ () => currentFlow?.data?.nodes,
+ [currentFlow],
+ );
+
+ const node = useMemo(
+ () => flowDataNodes?.find((n) => n.id === data.id),
+ [flowDataNodes, data.id],
+ );
+
+ const index = useMemo(
+ () => flowDataNodes?.indexOf(node!)!,
+ [flowDataNodes, node],
+ );
+
+ const postToolModeValue = usePostTemplateValue({
+ node: data.node!,
+ nodeId: data.id,
+ parameterId: "tool_mode",
+ });
+
+ const isSaved = flows?.some((flow) =>
+ Object.values(flow).includes(data.node?.display_name!),
+ );
+
+ const setNode = useFlowStore((state) => state.setNode);
+
+ const nodeLength = useMemo(() => getNodeLength(data), [data]);
+ const hasCode = useMemo(
+ () => Object.keys(data.node!.template).includes("code"),
+ [data.node],
+ );
+ const isGroup = useMemo(
+ () => (data.node?.flow ? true : false),
+ [data.node],
+ );
+
+ // Check if any of the data.node.template fields have tool_mode as True
+ // if so we can show the tool mode button
+ const hasToolMode = useMemo(
+ () => checkHasToolMode(data.node?.template ?? {}) && !isGroup,
+ [data.node?.template, isGroup],
+ );
+ const addFlow = useAddFlow();
+
+ const isMinimal = useMemo(
+ () => countHandlesFn(data) <= 1 && numberOfOutputHandles <= 1,
+ [data, numberOfOutputHandles],
+ );
+
+ const [toolMode, setToolMode] = useState(
+ () =>
+ data.node?.tool_mode ||
+ data.node?.outputs?.some(
+ (output) => output.name === "component_as_tool",
+ ) ||
+ false,
+ );
+
+ useEffect(() => {
+ if (data.node?.tool_mode !== undefined) {
+ setToolMode(
+ data.node?.tool_mode ||
+ data.node?.outputs?.some(
+ (output) => output.name === "component_as_tool",
+ ) ||
+ false,
+ );
+ }
+ }, [data.node?.tool_mode, data.node?.outputs]);
+
+ const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
+ data.id,
+ );
+
+ const handleNodeClass = (newNodeClass: APIClassType, type: string) => {
+ handleNodeClassHook(newNodeClass, type);
+ };
+
+ const handleActivateToolMode = () => {
+ const newValue = !toolMode;
+ setToolMode(newValue);
+ mutateTemplate(
+ newValue,
+ data.node!,
+ handleNodeClass,
+ postToolModeValue,
+ setErrorData,
+ "tool_mode",
+ () => updateNodeInternals(data.id),
+ newValue,
+ );
+ };
+
+ const handleMinimize = useCallback(() => {
+ if (isMinimal || !showNode) {
+ setShowNode(!showNode);
+ updateNodeInternals(data.id);
+ return;
+ }
+ setNoticeData({
+ title:
+ "Minimization only available for components with one handle or fewer.",
+ });
+ }, [isMinimal, showNode, data.id]);
+
+ const handleungroup = useCallback(() => {
+ if (isGroup) {
+ takeSnapshot();
+ expandGroupNode(
+ data.id,
+ updateFlowPosition(getNodePosition(data.id), data.node?.flow!),
+ data.node!.template,
+ nodes,
+ edges,
+ setNodes,
+ setEdges,
+ data.node?.outputs,
+ );
+ }
+ }, [
+ isGroup,
+ data.id,
+ data.node?.flow,
+ data.node?.template,
+ data.node?.outputs,
+ nodes,
+ edges,
+ setNodes,
+ setEdges,
+ takeSnapshot,
+ getNodePosition,
+ updateFlowPosition,
+ expandGroupNode,
+ ]);
+
+ const shareComponent = useCallback(() => {
+ if (hasApiKey || hasStore) {
+ setShowconfirmShare((state) => !state);
+ }
+ }, [hasApiKey, hasStore]);
+
+ const handleCodeModal = useCallback(() => {
+ if (!hasCode) {
+ setNoticeData({ title: `You can not access ${data.id} code` });
+ }
+ setOpenModal((state) => !state);
+ }, [hasCode, data.id]);
+
+ const saveComponent = useCallback(() => {
+ if (isSaved) {
+ setShowOverrideModal((state) => !state);
+ return;
+ }
+ addFlow({
+ flow: flowComponent,
+ override: false,
+ });
+ setSuccessData({ title: `${data.id} saved successfully` });
+ }, [isSaved, data.id, flowComponent, addFlow]);
+
+ const openDocs = useCallback(() => {
+ if (data.node?.documentation) {
+ return openInNewTab(data.node.documentation);
+ }
+ setNoticeData({
+ title: `${data.id} docs is not available at the moment.`,
+ });
+ }, [data.id, data.node?.documentation, openInNewTab]);
+
+ const freezeFunction = useCallback(() => {
+ setNode(data.id, (old) => ({
+ ...old,
+ data: {
+ ...old.data,
+ node: {
+ ...old.data.node,
+ frozen: old.data?.node?.frozen ? false : true,
+ },
+ },
+ }));
+ }, [data.id, setNode]);
+
+ useShortcuts({
+ showOverrideModal,
+ showModalAdvanced,
+ openModal,
+ showconfirmShare,
+ FreezeAllVertices: () => {
+ FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
+ },
+ Freeze: freezeFunction,
+ downloadFunction: () => downloadNode(flowComponent!),
+ displayDocs: openDocs,
+ saveComponent,
+ showAdvance: () => setShowModalAdvanced((state) => !state),
+ handleCodeModal,
+ shareComponent,
+ ungroup: handleungroup,
+ minimizeFunction: handleMinimize,
+ activateToolMode: handleActivateToolMode,
+ hasToolMode,
+ });
+
+ useEffect(() => {
+ if (!showModalAdvanced) {
+ onCloseAdvancedModal!(false);
+ }
+ }, [showModalAdvanced]);
+
+ const setLastCopiedSelection = useFlowStore(
+ (state) => state.setLastCopiedSelection,
+ );
+
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setNoticeData = useAlertStore((state) => state.setNoticeData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ useEffect(() => {
+ setFlowComponent(createFlowComponent(cloneDeep(data), version));
+ }, [
+ data,
+ data.node,
+ data.node?.display_name,
+ data.node?.description,
+ data.node?.template,
+ showModalAdvanced,
+ showconfirmShare,
+ ]);
+
+ const [selectedValue, setSelectedValue] = useState(null);
+
+ const handleSelectChange = useCallback(
+ (event) => {
+ setSelectedValue(event);
+
+ switch (event) {
+ case "save":
+ saveComponent();
+ break;
+ case "freeze":
+ freezeFunction();
+ break;
+ case "freezeAll":
+ FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id });
+ break;
+ case "code":
+ setOpenModal(!openModal);
+ break;
+ case "advanced":
+ setShowModalAdvanced(true);
+ break;
+ case "show":
+ takeSnapshot();
+ handleMinimize();
+ break;
+ case "Share":
+ shareComponent();
+ break;
+ case "Download":
+ downloadNode(flowComponent!);
+ break;
+ case "SaveAll":
+ addFlow({
+ flow: flowComponent,
+ override: false,
+ });
+ break;
+ case "documentation":
+ openDocs();
+ break;
+ case "disabled":
+ break;
+ case "ungroup":
+ handleungroup();
+ break;
+ case "override":
+ setShowOverrideModal(true);
+ break;
+ case "delete":
+ deleteNode(data.id);
+ break;
+ case "update":
+ updateNode();
+ break;
+ case "copy":
+ const node = nodes.filter((node) => node.id === data.id);
+ setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] });
+ break;
+ case "duplicate":
+ paste(
+ {
+ nodes: [nodes.find((node) => node.id === data.id)!],
+ edges: [],
+ },
+ {
+ x: 50,
+ y: 10,
+ paneX: nodes.find((node) => node.id === data.id)?.position.x,
+ paneY: nodes.find((node) => node.id === data.id)?.position.y,
+ },
+ );
+ break;
+ case "toolMode":
+ handleActivateToolMode();
+ break;
+ }
+
+ setSelectedValue(null);
+ },
+ [
+ saveComponent,
+ freezeFunction,
+ FreezeAllVertices,
+ setOpenModal,
+ setShowModalAdvanced,
+ handleMinimize,
+ shareComponent,
+ downloadNode,
+ addFlow,
+ openDocs,
+ handleungroup,
+ setShowOverrideModal,
+ deleteNode,
+ updateNode,
+ setLastCopiedSelection,
+ paste,
+ handleActivateToolMode,
+ toolMode,
+ ],
+ );
+
+ const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue({
+ node: data.node!,
+ nodeId: data.id,
+ name,
+ });
+
+ const handleOnNewValue = (value: string | string[]) => {
+ handleOnNewValueHook({ value });
+ };
+
+ const selectTriggerRef = useRef(null);
+
+ const handleButtonClick = () => {
+ (selectTriggerRef.current! as HTMLElement)?.click();
+ };
+
+ const handleOpenChange = (open: boolean) => {
+ setOpenShowMoreOptions && setOpenShowMoreOptions(open);
+ };
+
+ const renderToolbarButtons = useMemo(
+ () => (
+ <>
+ {hasCode && (
+ setOpenModal(true)}
+ shortcut={shortcuts.find((s) =>
+ s.name.toLowerCase().startsWith("code"),
+ )}
+ dataTestId="code-button-modal"
+ />
+ )}
+ {nodeLength > 0 && (
+ setShowModalAdvanced(true)}
+ shortcut={shortcuts.find((s) =>
+ s.name.toLowerCase().startsWith("advanced"),
+ )}
+ dataTestId="edit-button-modal"
+ />
+ )}
+ {!hasToolMode && (
+ {
+ takeSnapshot();
+ FreezeAllVertices({
+ flowId: currentFlowId,
+ stopNodeId: data.id,
+ });
+ }}
+ shortcut={shortcuts.find((s) =>
+ s.name.toLowerCase().startsWith("freeze path"),
+ )}
+ className={cn("node-toolbar-buttons", frozen && "text-blue-500")}
+ />
+ )}
+ {hasToolMode && (
+ name.toLowerCase() === "tool mode",
+ )!}
+ />
+ }
+ side="top"
+ >
+ {
+ event.preventDefault();
+ takeSnapshot();
+ handleSelectChange("toolMode");
+ }}
+ size="node-toolbar"
+ data-testid="tool-mode-button"
+ >
+
+ Tool Mode
+ {
+ takeSnapshot();
+ handleSelectChange("toolMode");
+ }}
+ disabled={false}
+ size="medium"
+ showToogle={false}
+ id="tool-mode-toggle"
+ />
+
+
+ )}
+ >
+ ),
+ [
+ hasCode,
+ nodeLength,
+ hasToolMode,
+ toolMode,
+ data.id,
+ takeSnapshot,
+ FreezeAllVertices,
+ currentFlowId,
+ shortcuts,
+ frozen,
+ handleSelectChange,
+ ],
+ );
+
+ return (
+ <>
+
+
+ {renderToolbarButtons}
+
+
+
+
+
+
+
+
+
+
+
+ {hasCode && (
+
+ obj.name === "Code")?.shortcut!
+ }
+ value={"Code"}
+ icon={"Code"}
+ dataTestId="code-button-modal"
+ />
+
+ )}
+ {nodeLength > 0 && (
+
+ obj.name === "Advanced Settings",
+ )?.shortcut!
+ }
+ value={"Controls"}
+ icon={"SlidersHorizontal"}
+ dataTestId="advanced-button-modal"
+ />
+
+ )}
+
+ obj.name === "Save Component")
+ ?.shortcut!
+ }
+ value={"Save"}
+ icon={"SaveAll"}
+ dataTestId="save-button-modal"
+ />
+
+
+ obj.name === "Duplicate")
+ ?.shortcut!
+ }
+ value={"Duplicate"}
+ icon={"Copy"}
+ dataTestId="copy-button-modal"
+ />
+
+
+ obj.name === "Copy")?.shortcut!
+ }
+ value={"Copy"}
+ icon={"Clipboard"}
+ dataTestId="copy-button-modal"
+ />
+
+ {isOutdated && (
+
+ obj.name === "Update")
+ ?.shortcut!
+ }
+ value={"Restore"}
+ icon={"RefreshCcwDot"}
+ dataTestId="update-button-modal"
+ />
+
+ )}
+ {hasStore && (
+
+ obj.name === "Component Share")
+ ?.shortcut!
+ }
+ value={"Share"}
+ icon={"Share3"}
+ dataTestId="share-button-modal"
+ />
+
+ )}
+
+
+ obj.name === "Docs")?.shortcut!
+ }
+ value={"Docs"}
+ icon={"FileText"}
+ dataTestId="docs-button-modal"
+ />
+
+ {(isMinimal || !showNode) && (
+
+ obj.name === "Minimize")
+ ?.shortcut!
+ }
+ value={showNode ? "Minimize" : "Expand"}
+ icon={showNode ? "Minimize2" : "Maximize2"}
+ />
+
+ )}
+ {isGroup && (
+
+ obj.name === "Group")?.shortcut!
+ }
+ value={"Ungroup"}
+ icon={"Ungroup"}
+ dataTestId="group-button-modal"
+ />
+
+ )}
+
+ obj.name === "Freeze")?.shortcut!
+ }
+ value={"Freeze"}
+ icon={"Snowflake"}
+ dataTestId="freeze-button"
+ style={`${frozen ? " text-ice" : ""} transition-all`}
+ />
+
+
+ obj.name === "Freeze Path")
+ ?.shortcut!
+ }
+ value={"Freeze Path"}
+ icon={"FreezeAll"}
+ dataTestId="freeze-path-button"
+ style={`${frozen ? " text-ice" : ""} transition-all`}
+ />
+
+
+ obj.name === "Download")
+ ?.shortcut!
+ }
+ value={"Download"}
+ icon={"Download"}
+ dataTestId="download-button-modal"
+ />
+
+
+
+ {" "}
+ Delete {" "}
+
+
+
+
+
+ {hasToolMode && (
+
+ obj.name === "Tool Mode")
+ ?.shortcut!
+ }
+ value={"Tool Mode"}
+ icon={"Hammer"}
+ dataTestId="tool-mode-button"
+ style={`${toolMode ? "text-primary" : ""} transition-all`}
+ />
+
+ )}
+
+
+
+
+
+
+ >
+ );
+ },
+);
+
+NodeToolbarComponent.displayName = "NodeToolbarComponent";
+
+export default NodeToolbarComponent;
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/shortcutDisplay/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/shortcutDisplay/index.tsx
new file mode 100644
index 0000000..8badd89
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/shortcutDisplay/index.tsx
@@ -0,0 +1,40 @@
+import RenderIcons from "@/components/common/renderIconComponent";
+import { cn } from "@/utils/utils";
+
+export default function ShortcutDisplay({
+ display_name,
+ shortcut,
+ sidebar = false,
+}: {
+ display_name?: string;
+ shortcut: string;
+ sidebar?: boolean;
+}): JSX.Element {
+ const fixedShortcut = shortcut?.split("+");
+ return (
+ <>
+ {sidebar ? (
+
+ {display_name && {display_name} }
+
+
+
+
+ ) : (
+
+ {display_name}
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/toolbarSelectItem/index.tsx b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/toolbarSelectItem/index.tsx
new file mode 100644
index 0000000..133e509
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/toolbarSelectItem/index.tsx
@@ -0,0 +1,34 @@
+import ForwardedIconComponent from "../../../../../components/common/genericIconComponent";
+import RenderIcons from "../../../../../components/common/renderIconComponent";
+import { toolbarSelectItemProps } from "../../../../../types/components";
+
+export default function ToolbarSelectItem({
+ value,
+ icon,
+ style,
+ dataTestId,
+ ping,
+ shortcut,
+}: toolbarSelectItemProps) {
+ const fixedShortcut = shortcut?.split("+");
+ return (
+
+
+
+ {value}
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/FlowPage/index.tsx b/langflow/src/frontend/src/pages/FlowPage/index.tsx
new file mode 100644
index 0000000..907ab66
--- /dev/null
+++ b/langflow/src/frontend/src/pages/FlowPage/index.tsx
@@ -0,0 +1,205 @@
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import useSaveFlow from "@/hooks/flows/use-save-flow";
+import { useIsMobile } from "@/hooks/use-mobile";
+import { SaveChangesModal } from "@/modals/saveChangesModal";
+import useAlertStore from "@/stores/alertStore";
+import { customStringify } from "@/utils/reactflowUtils";
+import { useEffect, useState } from "react";
+import { useBlocker, useParams } from "react-router-dom";
+import useFlowStore from "../../stores/flowStore";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import Page from "./components/PageComponent";
+import { FlowSidebarComponent } from "./components/flowSidebarComponent";
+
+export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+ const currentFlow = useFlowStore((state) => state.currentFlow);
+ const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const changesNotSaved =
+ customStringify(currentFlow) !== customStringify(currentSavedFlow) &&
+ (currentFlow?.data?.nodes?.length ?? 0) > 0;
+
+ const isBuilding = useFlowStore((state) => state.isBuilding);
+ const blocker = useBlocker(changesNotSaved || isBuilding);
+
+ const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
+ const { id } = useParams();
+ const navigate = useCustomNavigate();
+ const saveFlow = useSaveFlow();
+
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ const flowToCanvas = useFlowsManagerStore((state) => state.flowToCanvas);
+
+ const updatedAt = currentSavedFlow?.updated_at;
+ const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
+ const stopBuilding = useFlowStore((state) => state.stopBuilding);
+
+ const { mutateAsync: getFlow } = useGetFlow();
+
+ const handleSave = () => {
+ let saving = true;
+ let proceed = false;
+ setTimeout(() => {
+ saving = false;
+ if (proceed) {
+ blocker.proceed && blocker.proceed();
+ setSuccessData({
+ title: "Flow saved successfully!",
+ });
+ }
+ }, 1200);
+ saveFlow().then(() => {
+ if (!autoSaving || saving === false) {
+ blocker.proceed && blocker.proceed();
+ setSuccessData({
+ title: "Flow saved successfully!",
+ });
+ }
+ proceed = true;
+ });
+ };
+
+ const handleExit = () => {
+ if (isBuilding) {
+ // Do nothing, let the blocker handle it
+ } else if (changesNotSaved) {
+ if (blocker.proceed) blocker.proceed();
+ } else {
+ navigate("/all");
+ }
+ };
+
+ useEffect(() => {
+ const handleBeforeUnload = (event: BeforeUnloadEvent) => {
+ if (changesNotSaved || isBuilding) {
+ event.preventDefault();
+ event.returnValue = ""; // Required for Chrome
+ }
+ };
+
+ window.addEventListener("beforeunload", handleBeforeUnload);
+
+ return () => {
+ window.removeEventListener("beforeunload", handleBeforeUnload);
+ };
+ }, [changesNotSaved, isBuilding]);
+
+ // Set flow tab id
+ useEffect(() => {
+ const awaitgetTypes = async () => {
+ if (flows && currentFlowId === "") {
+ const isAnExistingFlow = flows.find((flow) => flow.id === id);
+
+ if (!isAnExistingFlow) {
+ navigate("/all");
+ return;
+ }
+
+ const isAnExistingFlowId = isAnExistingFlow.id;
+
+ flowToCanvas
+ ? setCurrentFlow(flowToCanvas)
+ : getFlowToAddToCanvas(isAnExistingFlowId);
+ }
+ };
+ awaitgetTypes();
+ }, [id, flows, currentFlowId, flowToCanvas]);
+
+ useEffect(() => {
+ setOnFlowPage(true);
+
+ return () => {
+ setOnFlowPage(false);
+ setCurrentFlow(undefined);
+ };
+ }, [id]);
+
+ useEffect(() => {
+ if (
+ blocker.state === "blocked" &&
+ autoSaving &&
+ changesNotSaved &&
+ !isBuilding
+ ) {
+ handleSave();
+ }
+ }, [blocker.state, isBuilding]);
+
+ useEffect(() => {
+ if (blocker.state === "blocked") {
+ if (isBuilding) {
+ stopBuilding();
+ } else if (!changesNotSaved) {
+ blocker.proceed && blocker.proceed();
+ }
+ }
+ }, [blocker.state, isBuilding]);
+
+ const getFlowToAddToCanvas = async (id: string) => {
+ const flow = await getFlow({ id: id });
+ setCurrentFlow(flow);
+ };
+
+ const isMobile = useIsMobile();
+
+ return (
+ <>
+
+ {blocker.state === "blocked" && (
+ <>
+ {!isBuilding && currentSavedFlow && (
+ blocker.reset?.()}
+ onProceed={handleExit}
+ flowName={currentSavedFlow.name}
+ lastSaved={
+ updatedAt
+ ? new Date(updatedAt).toLocaleString("en-US", {
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ month: "numeric",
+ day: "numeric",
+ })
+ : undefined
+ }
+ autoSave={autoSaving}
+ />
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/pages/LoadingPage/index.tsx b/langflow/src/frontend/src/pages/LoadingPage/index.tsx
new file mode 100644
index 0000000..57eba0c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/LoadingPage/index.tsx
@@ -0,0 +1,15 @@
+import LoadingComponent from "@/components/common/loadingComponent";
+import { cn } from "@/utils/utils";
+
+export function LoadingPage({ overlay = false }: { overlay?: boolean }) {
+ return (
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/LoginPage/index.tsx b/langflow/src/frontend/src/pages/LoginPage/index.tsx
new file mode 100644
index 0000000..342e33e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/LoginPage/index.tsx
@@ -0,0 +1,141 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import { useLoginUser } from "@/controllers/API/queries/auth";
+import { CustomLink } from "@/customization/components/custom-link";
+import * as Form from "@radix-ui/react-form";
+import { useContext, useState } from "react";
+import InputComponent from "../../components/core/parameterRenderComponent/components/inputComponent";
+import { Button } from "../../components/ui/button";
+import { Input } from "../../components/ui/input";
+import { SIGNIN_ERROR_ALERT } from "../../constants/alerts_constants";
+import { CONTROL_LOGIN_STATE } from "../../constants/constants";
+import { AuthContext } from "../../contexts/authContext";
+import useAlertStore from "../../stores/alertStore";
+import { LoginType } from "../../types/api";
+import {
+ inputHandlerEventType,
+ loginInputStateType,
+} from "../../types/components";
+
+export default function LoginPage(): JSX.Element {
+ const [inputState, setInputState] =
+ useState(CONTROL_LOGIN_STATE);
+
+ const { password, username } = inputState;
+ const { login } = useContext(AuthContext);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ const { mutate } = useLoginUser();
+
+ function signIn() {
+ const user: LoginType = {
+ username: username.trim(),
+ password: password.trim(),
+ };
+
+ mutate(user, {
+ onSuccess: (data) => {
+ login(data.access_token, "login", data.refresh_token);
+ },
+ onError: (error) => {
+ setErrorData({
+ title: SIGNIN_ERROR_ALERT,
+ list: [error["response"]["data"]["detail"]],
+ });
+ },
+ });
+ }
+
+ return (
+ {
+ if (password === "") {
+ event.preventDefault();
+ return;
+ }
+ signIn();
+ const data = Object.fromEntries(new FormData(event.currentTarget));
+ event.preventDefault();
+ }}
+ className="h-screen w-full"
+ >
+
+
+
+
+ Sign in to Langflow
+
+
+
+
+ Username *
+
+
+
+ {
+ handleInput({ target: { name: "username", value } });
+ }}
+ value={username}
+ className="w-full"
+ required
+ placeholder="Username"
+ />
+
+
+
+ Please enter your username
+
+
+
+
+
+
+ Password *
+
+
+ {
+ handleInput({ target: { name: "password", value } });
+ }}
+ value={password}
+ isForm
+ password={true}
+ required
+ placeholder="Password"
+ className="w-full"
+ />
+
+
+ Please enter your password
+
+
+
+
+
+
+ Sign in
+
+
+
+
+
+
+ Don't have an account? Sign Up
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/MainPage/components/dropdown/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/dropdown/index.tsx
new file mode 100644
index 0000000..907128b
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/dropdown/index.tsx
@@ -0,0 +1,108 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import useAlertStore from "@/stores/alertStore";
+import { FlowType } from "@/types/flow";
+import { downloadFlow } from "@/utils/reactflowUtils";
+import useDuplicateFlows from "../../hooks/use-handle-duplicate";
+import useSelectOptionsChange from "../../hooks/use-select-options-change";
+
+type DropdownComponentProps = {
+ flowData: FlowType;
+ setOpenDelete: (open: boolean) => void;
+ handlePlaygroundClick?: () => void;
+ handleEdit: () => void;
+};
+
+const DropdownComponent = ({
+ flowData,
+ setOpenDelete,
+ handleEdit,
+}: DropdownComponentProps) => {
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ const { handleDuplicate } = useDuplicateFlows({
+ selectedFlowsComponentsCards: [flowData.id],
+ allFlows: [flowData],
+ setSuccessData,
+ });
+
+ const handleExport = () => {
+ downloadFlow(flowData, flowData.name, flowData.description);
+ setSuccessData({ title: `${flowData.name} exported successfully` });
+ };
+ const { handleSelectOptionsChange } = useSelectOptionsChange(
+ [flowData.id],
+ setErrorData,
+ setOpenDelete,
+ handleDuplicate,
+ handleExport,
+ handleEdit,
+ );
+
+ return (
+ <>
+ {
+ e.stopPropagation();
+ handleSelectOptionsChange("edit");
+ }}
+ className="cursor-pointer"
+ data-testid="btn-edit-flow"
+ >
+
+ Edit details
+
+ {
+ e.stopPropagation();
+ handleSelectOptionsChange("export");
+ }}
+ className="cursor-pointer"
+ data-testid="btn-download-json"
+ >
+
+ Download
+
+ {
+ e.stopPropagation();
+ handleSelectOptionsChange("duplicate");
+ }}
+ className="cursor-pointer"
+ data-testid="btn-duplicate-flow"
+ >
+
+ Duplicate
+
+ {
+ e.stopPropagation();
+ setOpenDelete(true);
+ }}
+ className="cursor-pointer text-destructive"
+ >
+
+ Delete
+
+ >
+ );
+};
+
+export default DropdownComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/grid/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/grid/index.tsx
new file mode 100644
index 0000000..b1fb675
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/grid/index.tsx
@@ -0,0 +1,170 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import useDragStart from "@/components/core/cardComponent/hooks/use-on-drag-start";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import useDeleteFlow from "@/hooks/flows/use-delete-flow";
+import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
+import FlowSettingsModal from "@/modals/flowSettingsModal";
+import useAlertStore from "@/stores/alertStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { FlowType } from "@/types/flow";
+import { swatchColors } from "@/utils/styleUtils";
+import { cn, getNumberFromString } from "@/utils/utils";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import useDescriptionModal from "../../hooks/use-description-modal";
+import { useGetTemplateStyle } from "../../utils/get-template-style";
+import { timeElapsed } from "../../utils/time-elapse";
+import DropdownComponent from "../dropdown";
+
+const GridComponent = ({ flowData }: { flowData: FlowType }) => {
+ const navigate = useCustomNavigate();
+
+ const [openDelete, setOpenDelete] = useState(false);
+ const [openSettings, setOpenSettings] = useState(false);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const { deleteFlow } = useDeleteFlow();
+
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { folderId } = useParams();
+ const isComponent = flowData.is_component ?? false;
+ const setFlowToCanvas = useFlowsManagerStore(
+ (state) => state.setFlowToCanvas,
+ );
+
+ const { getIcon } = useGetTemplateStyle(flowData);
+
+ const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
+
+ const handleClick = async () => {
+ if (!isComponent) {
+ await setFlowToCanvas(flowData);
+ navigate(editFlowLink);
+ }
+ };
+
+ const handleDelete = () => {
+ deleteFlow({ id: [flowData.id] })
+ .then(() => {
+ setSuccessData({
+ title: "Selected items deleted successfully",
+ });
+ })
+ .catch(() => {
+ setErrorData({
+ title: "Error deleting items",
+ list: ["Please try again"],
+ });
+ });
+ };
+
+ const descriptionModal = useDescriptionModal(
+ [flowData?.id],
+ flowData.is_component ? "component" : "flow",
+ );
+
+ const { onDragStart } = useDragStart(flowData);
+
+ const swatchIndex =
+ (flowData.gradient && !isNaN(parseInt(flowData.gradient))
+ ? parseInt(flowData.gradient)
+ : getNumberFromString(flowData.gradient ?? flowData.id)) %
+ swatchColors.length;
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {flowData.name}
+
+
+ Edited {timeElapsed(flowData.updated_at)} ago
+
+
+
+
+
+
+
+
+
+ {
+ setOpenSettings(true);
+ }}
+ />
+
+
+
+
+
+
+ {flowData.description}
+
+
+
+ {openDelete && (
+
+ <>>
+
+ )}
+
+ >
+ );
+};
+
+export default GridComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/gridSkeleton/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/gridSkeleton/index.tsx
new file mode 100644
index 0000000..557dbdc
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/gridSkeleton/index.tsx
@@ -0,0 +1,34 @@
+import { Card } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+
+const GridSkeleton = () => {
+ return (
+
+
+ {/* Icon skeleton */}
+
+
+
+
+
+
+ {/* Title skeleton */}
+
+ {/* Time skeleton */}
+
+
+ {/* Dropdown button skeleton */}
+
+
+
+
+ {/* Description skeleton */}
+
+
+
+
+
+ );
+};
+
+export default GridSkeleton;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/header/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/header/index.tsx
new file mode 100644
index 0000000..065a539
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/header/index.tsx
@@ -0,0 +1,163 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import ShadTooltip from "@/components/common/shadTooltipComponent";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { SidebarTrigger } from "@/components/ui/sidebar";
+import { debounce } from "lodash";
+import { useCallback, useEffect, useState } from "react";
+
+interface HeaderComponentProps {
+ flowType: "flows" | "components";
+ setFlowType: (flowType: "flows" | "components") => void;
+ view: "list" | "grid";
+ setView: (view: "list" | "grid") => void;
+ setNewProjectModal: (newProjectModal: boolean) => void;
+ folderName?: string;
+ setSearch: (search: string) => void;
+ isEmptyFolder: boolean;
+}
+
+const HeaderComponent = ({
+ folderName = "",
+ flowType,
+ setFlowType,
+ view,
+ setView,
+ setNewProjectModal,
+ setSearch,
+ isEmptyFolder,
+}: HeaderComponentProps) => {
+ const [debouncedSearch, setDebouncedSearch] = useState("");
+
+ // Debounce the setSearch function from the parent
+ const debouncedSetSearch = useCallback(
+ debounce((value: string) => {
+ setSearch(value);
+ }, 1000),
+ [setSearch],
+ );
+
+ useEffect(() => {
+ debouncedSetSearch(debouncedSearch);
+
+ return () => {
+ debouncedSetSearch.cancel(); // Cleanup on unmount
+ };
+ }, [debouncedSearch, debouncedSetSearch]);
+
+ const handleSearch = (e: React.ChangeEvent) => {
+ setDebouncedSearch(e.target.value);
+ };
+
+ return (
+ <>
+
+ {!isEmptyFolder && (
+ <>
+
+
+ {["components", "flows"].map((type) => (
+
setFlowType(type as "flows" | "components")}
+ className={`border-b ${
+ flowType === type
+ ? "border-b-2 border-foreground text-foreground"
+ : "border-border text-muted-foreground hover:text-foreground"
+ } px-3 pb-2 text-sm`}
+ >
+
+ {type.charAt(0).toUpperCase() + type.slice(1)}
+
+
+ ))}
+
+ {/* Search and filters */}
+
+
+
+
+ {/* Sliding Indicator */}
+
+
+ {/* Buttons */}
+ {["list", "grid"].map((viewType) => (
+
setView(viewType as "list" | "grid")}
+ >
+
+
+ ))}
+
+
+
+ setNewProjectModal(true)}
+ id="new-project-btn"
+ data-testid="new-project-btn"
+ >
+
+
+ New Flow
+
+
+
+
+ >
+ )}
+ >
+ );
+};
+
+export default HeaderComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/inputSearchComponent/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/inputSearchComponent/index.tsx
new file mode 100644
index 0000000..ba54ee5
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/inputSearchComponent/index.tsx
@@ -0,0 +1,61 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Input } from "@/components/ui/input";
+import { ChangeEvent, KeyboardEvent } from "react";
+
+type InputSearchComponentProps = {
+ loading: boolean;
+ divClasses?: string;
+ onChange: (e: ChangeEvent) => void;
+ onClick?: () => void;
+ value: string;
+ onKeyDown: (e: KeyboardEvent) => void;
+};
+
+const InputSearchComponent = ({
+ loading,
+ divClasses,
+ onChange,
+ onClick,
+ value,
+ onKeyDown,
+}: InputSearchComponentProps) => {
+ const pagePath = window.location.pathname;
+
+ const getSearchPlaceholder = () => {
+ if (pagePath.includes("flows")) {
+ return "Search Flows";
+ } else if (pagePath.includes("components")) {
+ return "Search Components";
+ } else {
+ return "Search Flows and Components";
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
+export default InputSearchComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/list/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/list/index.tsx
new file mode 100644
index 0000000..ffc7e62
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/list/index.tsx
@@ -0,0 +1,186 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import useDragStart from "@/components/core/cardComponent/hooks/use-on-drag-start";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import useDeleteFlow from "@/hooks/flows/use-delete-flow";
+import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
+import FlowSettingsModal from "@/modals/flowSettingsModal";
+import useAlertStore from "@/stores/alertStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { FlowType } from "@/types/flow";
+import { swatchColors } from "@/utils/styleUtils";
+import { cn, getNumberFromString } from "@/utils/utils";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import useDescriptionModal from "../../hooks/use-description-modal";
+import { useGetTemplateStyle } from "../../utils/get-template-style";
+import { timeElapsed } from "../../utils/time-elapse";
+import DropdownComponent from "../dropdown";
+
+const ListComponent = ({ flowData }: { flowData: FlowType }) => {
+ const navigate = useCustomNavigate();
+
+ const [openDelete, setOpenDelete] = useState(false);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const { deleteFlow } = useDeleteFlow();
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { folderId } = useParams();
+ const [openSettings, setOpenSettings] = useState(false);
+ const isComponent = flowData.is_component ?? false;
+ const setFlowToCanvas = useFlowsManagerStore(
+ (state) => state.setFlowToCanvas,
+ );
+ const { getIcon } = useGetTemplateStyle(flowData);
+
+ const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
+
+ const handleClick = async () => {
+ if (!isComponent) {
+ await setFlowToCanvas(flowData);
+ navigate(editFlowLink);
+ }
+ };
+
+ const handleDelete = () => {
+ deleteFlow({ id: [flowData.id] })
+ .then(() => {
+ setSuccessData({
+ title: "Selected items deleted successfully",
+ });
+ })
+ .catch(() => {
+ setErrorData({
+ title: "Error deleting items",
+ list: ["Please try again"],
+ });
+ });
+ };
+
+ const { onDragStart } = useDragStart(flowData);
+
+ const descriptionModal = useDescriptionModal(
+ [flowData?.id],
+ flowData.is_component ? "component" : "flow",
+ );
+
+ const swatchIndex =
+ (flowData.gradient && !isNaN(parseInt(flowData.gradient))
+ ? parseInt(flowData.gradient)
+ : getNumberFromString(flowData.gradient ?? flowData.id)) %
+ swatchColors.length;
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {flowData.name}
+
+
+ Edited {timeElapsed(flowData.updated_at)} ago
+
+
+
+
+ {flowData.description}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ setOpenSettings(true);
+ }}
+ handlePlaygroundClick={() => {
+ // handlePlaygroundClick();
+ }}
+ />
+
+
+
+
+
+ {openDelete && (
+
+ <>>
+
+ )}
+
+ >
+ );
+};
+
+export default ListComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/listSkeleton/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/listSkeleton/index.tsx
new file mode 100644
index 0000000..63f7d70
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/listSkeleton/index.tsx
@@ -0,0 +1,33 @@
+import { Card } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+
+const ListSkeleton = () => {
+ return (
+
+ {/* left side */}
+
+ {/* Icon skeleton */}
+
+
+
+
+
+ {/* Title and time skeleton */}
+
+
+
+
+ {/* Description skeleton */}
+
+
+
+
+ {/* right side */}
+
+
+
+
+ );
+};
+
+export default ListSkeleton;
diff --git a/langflow/src/frontend/src/pages/MainPage/components/modalsComponent/index.tsx b/langflow/src/frontend/src/pages/MainPage/components/modalsComponent/index.tsx
new file mode 100644
index 0000000..e1203f2
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/components/modalsComponent/index.tsx
@@ -0,0 +1,41 @@
+// Modals.tsx
+import TemplatesModal from "@/modals/templatesModal";
+import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
+
+interface ModalsProps {
+ openModal: boolean;
+ setOpenModal: (value: boolean) => void;
+ openDeleteFolderModal: boolean;
+ setOpenDeleteFolderModal: (value: boolean) => void;
+ handleDeleteFolder: () => void;
+}
+
+const ModalsComponent = ({
+ openModal = false,
+ setOpenModal = () => {},
+ openDeleteFolderModal = false,
+ setOpenDeleteFolderModal = () => {},
+ handleDeleteFolder = () => {},
+}: ModalsProps) => (
+ <>
+ {openModal && }
+ {openDeleteFolderModal && (
+ {
+ handleDeleteFolder();
+ setOpenDeleteFolderModal(false);
+ }}
+ description="folder"
+ note={
+ "Deleting the selected folder will remove all associated flows and components."
+ }
+ >
+ <>>
+
+ )}
+ >
+);
+
+export default ModalsComponent;
diff --git a/langflow/src/frontend/src/pages/MainPage/constants.ts b/langflow/src/frontend/src/pages/MainPage/constants.ts
new file mode 100644
index 0000000..98c7e8c
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/constants.ts
@@ -0,0 +1,54 @@
+export const TEMPLATES_DATA = {
+ examples: [
+ {
+ name: "Basic Prompting (Hello, World)",
+ icon: "BotMessageSquare",
+ icon_bg_color: "bg-blue-500",
+ },
+ {
+ name: "Memory Chatbot",
+ icon: "MessagesSquare",
+ icon_bg_color: "bg-purple-500",
+ },
+ {
+ name: "Vector Store RAG",
+ icon: "Database",
+ icon_bg_color: "bg-green-500",
+ },
+ {
+ name: "Travel Planning Agents",
+ icon: "Plane",
+ icon_bg_color: "bg-yellow-500",
+ },
+ {
+ name: "Dynamic Agent",
+ icon: "Users",
+ icon_bg_color: "bg-red-500",
+ },
+ {
+ name: "Blog Writer",
+ icon: "FileText",
+ icon_bg_color: "bg-indigo-500",
+ },
+ {
+ name: "Sequential Tasks Agent",
+ icon: "ListOrdered",
+ icon_bg_color: "bg-pink-500",
+ },
+ {
+ name: "Hierarchical Tasks Agent",
+ icon: "GitFork",
+ icon_bg_color: "bg-orange-500",
+ },
+ {
+ name: "Simple Agent",
+ icon: "Users",
+ icon_bg_color: "bg-teal-500",
+ },
+ {
+ name: "Document QA",
+ icon: "FileText",
+ icon_bg_color: "bg-cyan-500",
+ },
+ ],
+};
diff --git a/langflow/src/frontend/src/pages/MainPage/entities/index.tsx b/langflow/src/frontend/src/pages/MainPage/entities/index.tsx
new file mode 100644
index 0000000..98271d8
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/entities/index.tsx
@@ -0,0 +1,44 @@
+import { FlowType } from "../../../types/flow";
+
+export type FolderType = {
+ name: string;
+ description: string;
+ id?: string | null;
+ parent_id: string;
+ flows: FlowType[];
+ components: string[];
+};
+
+export type PaginatedFolderType = {
+ folder: {
+ name: string;
+ description: string;
+ id?: string | null;
+ parent_id: string;
+ components: string[];
+ };
+ flows: {
+ items: FlowType[];
+ total: number;
+ page: number;
+ size: number;
+ pages: number;
+ };
+};
+
+export type AddFolderType = {
+ name: string;
+ description: string;
+ id?: string | null;
+ parent_id: string | null;
+ flows?: string[];
+ components?: string[];
+};
+
+export type StarterProjectsType = {
+ name?: string;
+ description?: string;
+ flows?: FlowType[];
+ id: string;
+ parent_id: string;
+};
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-description-modal.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-description-modal.ts
new file mode 100644
index 0000000..2af71e9
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-description-modal.ts
@@ -0,0 +1,35 @@
+import { useMemo } from "react";
+
+const useDescriptionModal = (
+ selectedFlowsComponentsCards: string[] | undefined,
+ type: string | undefined,
+) => {
+ const getDescriptionModal = useMemo(() => {
+ const getTypeLabel = (type) => {
+ const labels = {
+ all: "item",
+ component: "component",
+ flow: "flow",
+ };
+ return labels[type] || "";
+ };
+
+ const getPluralizedLabel = (type) => {
+ const labels = {
+ all: "items",
+ component: "components",
+ flow: "flows",
+ };
+ return labels[type] || "";
+ };
+
+ if (selectedFlowsComponentsCards?.length === 1) {
+ return getTypeLabel(type);
+ }
+ return getPluralizedLabel(type);
+ }, [selectedFlowsComponentsCards, type]);
+
+ return getDescriptionModal;
+};
+
+export default useDescriptionModal;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-dropdown-options.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-dropdown-options.ts
new file mode 100644
index 0000000..cb2f0e7
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-dropdown-options.ts
@@ -0,0 +1,43 @@
+import useUploadFlow from "@/hooks/flows/use-upload-flow";
+import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
+import useAlertStore from "../../../stores/alertStore";
+
+const useDropdownOptions = ({
+ navigate,
+ is_component,
+}: {
+ navigate: (url: string) => void;
+ is_component: boolean;
+}) => {
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const uploadFlow = useUploadFlow();
+ const handleImportFromJSON = () => {
+ uploadFlow({
+ isComponent: is_component,
+ })
+ .then((id) => {
+ setSuccessData({
+ title: `${is_component ? "Component" : "Flow"} uploaded successfully`,
+ });
+ if (!is_component) navigate("/flow/" + id);
+ })
+ .catch((error) => {
+ setErrorData({
+ title: CONSOLE_ERROR_MSG,
+ list: [error],
+ });
+ });
+ };
+
+ const dropdownOptions = [
+ {
+ name: "Import from JSON",
+ onBtnClick: handleImportFromJSON,
+ },
+ ];
+
+ return [...dropdownOptions];
+};
+
+export default useDropdownOptions;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-filtered-flows.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-filtered-flows.ts
new file mode 100644
index 0000000..ca9f95e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-filtered-flows.ts
@@ -0,0 +1,28 @@
+import cloneDeep from "lodash/cloneDeep";
+import { useEffect } from "react";
+import { FlowType } from "../../../types/flow";
+
+const useFilteredFlows = (
+ flowsFromFolder: FlowType[],
+ searchFlowsComponents: string,
+ setAllFlows: (value: any[]) => void,
+) => {
+ useEffect(() => {
+ const newFlows = cloneDeep(flowsFromFolder || []);
+ const filteredFlows = newFlows.filter(
+ (f) =>
+ f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) ||
+ f.description
+ .toLowerCase()
+ .includes(searchFlowsComponents.toLowerCase()),
+ );
+
+ if (searchFlowsComponents === "") {
+ setAllFlows(flowsFromFolder);
+ } else {
+ setAllFlows(filteredFlows);
+ }
+ }, [flowsFromFolder, searchFlowsComponents, setAllFlows]);
+};
+
+export default useFilteredFlows;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-duplicate.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-duplicate.ts
new file mode 100644
index 0000000..5de6ebc
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-duplicate.ts
@@ -0,0 +1,46 @@
+import { usePostAddFlow } from "@/controllers/API/queries/flows/use-post-add-flow";
+import { useFolderStore } from "@/stores/foldersStore";
+import { addVersionToDuplicates, createNewFlow } from "@/utils/reactflowUtils";
+import { useParams } from "react-router-dom";
+
+type UseDuplicateFlowsParams = {
+ selectedFlowsComponentsCards: string[];
+ allFlows: any[];
+ setSuccessData: (data: { title: string }) => void;
+};
+
+const useDuplicateFlows = ({
+ selectedFlowsComponentsCards,
+ allFlows,
+ setSuccessData,
+}: UseDuplicateFlowsParams) => {
+ const { mutateAsync: postAddFlow } = usePostAddFlow();
+ const { folderId } = useParams();
+ const myCollectionId = useFolderStore((state) => state.myCollectionId);
+
+ const handleDuplicate = async () => {
+ selectedFlowsComponentsCards.map(async (selectedFlow) => {
+ const currentFlow = allFlows.find((flow) => flow.id === selectedFlow);
+ const folder_id = folderId ?? myCollectionId ?? "";
+
+ const flowsToCheckNames = allFlows?.filter(
+ (f) => f.folder_id === folder_id,
+ );
+
+ const newFlow = createNewFlow(currentFlow.data, folder_id, currentFlow);
+
+ const newName = addVersionToDuplicates(newFlow, flowsToCheckNames ?? []);
+ newFlow.name = newName;
+ newFlow.folder_id = folder_id;
+
+ await postAddFlow(newFlow);
+ setSuccessData({
+ title: `${newFlow.is_component ? "Component" : "Flow"} duplicated successfully`,
+ });
+ });
+ };
+
+ return { handleDuplicate };
+};
+
+export default useDuplicateFlows;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-select-all.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-select-all.ts
new file mode 100644
index 0000000..5bd7f95
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-handle-select-all.ts
@@ -0,0 +1,30 @@
+import { useCallback } from "react";
+import { FlowType } from "../../../types/flow";
+
+const useSelectAll = (
+ flowsFromFolder: FlowType[],
+ getValues: () => Record,
+ setValue: (key: string, value: boolean) => void,
+) => {
+ const handleSelectAll = useCallback(
+ (select) => {
+ const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id);
+ if (select) {
+ Object.keys(getValues()).forEach((key) => {
+ if (!flowsFromFolderIds?.includes(key)) return;
+ setValue(key, true);
+ });
+ return;
+ }
+
+ Object.keys(getValues()).forEach((key) => {
+ setValue(key, false);
+ });
+ },
+ [flowsFromFolder, getValues, setValue],
+ );
+
+ return { handleSelectAll };
+};
+
+export default useSelectAll;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-on-file-drop.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-on-file-drop.ts
new file mode 100644
index 0000000..2b19761
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-on-file-drop.ts
@@ -0,0 +1,52 @@
+import useUploadFlow from "@/hooks/flows/use-upload-flow";
+import { useCallback, useRef } from "react";
+import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
+import useAlertStore from "../../../stores/alertStore";
+
+const useFileDrop = (type?: string) => {
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const uploadFlow = useUploadFlow();
+
+ const lastUploadTime = useRef(0);
+ const DEBOUNCE_INTERVAL = 1000;
+
+ const handleFileDrop = useCallback(
+ (e) => {
+ e.preventDefault();
+
+ if (e.dataTransfer.types.every((type) => type === "Files")) {
+ const currentTime = Date.now();
+
+ if (currentTime - lastUploadTime.current >= DEBOUNCE_INTERVAL) {
+ lastUploadTime.current = currentTime;
+
+ const files: File[] = Array.from(e.dataTransfer.files);
+
+ uploadFlow({
+ files,
+ isComponent:
+ type === "component" ? true : type === "flow" ? false : undefined,
+ })
+ .then(() => {
+ setSuccessData({
+ title: `All files uploaded successfully`,
+ });
+ })
+ .catch((error) => {
+ console.log(error);
+ setErrorData({
+ title: CONSOLE_ERROR_MSG,
+ list: [(error as Error).message],
+ });
+ });
+ }
+ }
+ },
+ [type, uploadFlow, setSuccessData, setErrorData],
+ );
+
+ return handleFileDrop;
+};
+
+export default useFileDrop;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-select-options-change.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-select-options-change.ts
new file mode 100644
index 0000000..41525a1
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-select-options-change.ts
@@ -0,0 +1,44 @@
+import { useCallback } from "react";
+
+const useSelectOptionsChange = (
+ selectedFlowsComponentsCards: string[] | undefined,
+ setErrorData: (data: { title: string; list: string[] }) => void,
+ setOpenDelete: (value: boolean) => void,
+ handleDuplicate: () => void,
+ handleExport: () => void,
+ handleEdit: () => void,
+) => {
+ const handleSelectOptionsChange = useCallback(
+ (action) => {
+ const hasSelected = selectedFlowsComponentsCards?.length! > 0;
+ if (!hasSelected) {
+ setErrorData({
+ title: "No items selected",
+ list: ["Please select items to delete"],
+ });
+ return;
+ }
+ if (action === "delete") {
+ setOpenDelete(true);
+ } else if (action === "duplicate") {
+ handleDuplicate();
+ } else if (action === "export") {
+ handleExport();
+ } else if (action === "edit") {
+ handleEdit();
+ }
+ },
+ [
+ selectedFlowsComponentsCards,
+ setErrorData,
+ setOpenDelete,
+ handleDuplicate,
+ handleExport,
+ handleEdit,
+ ],
+ );
+
+ return { handleSelectOptionsChange };
+};
+
+export default useSelectOptionsChange;
diff --git a/langflow/src/frontend/src/pages/MainPage/hooks/use-selected-flows.ts b/langflow/src/frontend/src/pages/MainPage/hooks/use-selected-flows.ts
new file mode 100644
index 0000000..e5ac9a9
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/hooks/use-selected-flows.ts
@@ -0,0 +1,20 @@
+import { useEffect } from "react";
+
+const useSelectedFlows = (
+ entireFormValues: Record | undefined,
+ setSelectedFlowsComponentsCards: (
+ selectedFlowsComponentsCards: string[],
+ ) => void,
+) => {
+ useEffect(() => {
+ if (!entireFormValues || Object.keys(entireFormValues).length === 0) return;
+
+ const selectedFlows = Object.keys(entireFormValues).filter((key) => {
+ return entireFormValues[key] === true;
+ });
+
+ setSelectedFlowsComponentsCards(selectedFlows);
+ }, [entireFormValues, setSelectedFlowsComponentsCards]);
+};
+
+export default useSelectedFlows;
diff --git a/langflow/src/frontend/src/pages/MainPage/pages/emptyFolder/index.tsx b/langflow/src/frontend/src/pages/MainPage/pages/emptyFolder/index.tsx
new file mode 100644
index 0000000..fb64056
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/pages/emptyFolder/index.tsx
@@ -0,0 +1,41 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Button } from "@/components/ui/button";
+import { useFolderStore } from "@/stores/foldersStore";
+
+type EmptyFolderProps = {
+ setOpenModal: (open: boolean) => void;
+};
+
+export const EmptyFolder = ({ setOpenModal }: EmptyFolderProps) => {
+ const folders = useFolderStore((state) => state.folders);
+
+ return (
+
+
+
+ {folders?.length > 1 ? "Empty folder" : "Start building"}
+
+
+ Begin with a template, or start from scratch.
+
+
setOpenModal(true)}
+ id="new-project-btn"
+ >
+
+ New Flow
+
+
+
+ );
+};
+
+export default EmptyFolder;
diff --git a/langflow/src/frontend/src/pages/MainPage/pages/emptyPage/index.tsx b/langflow/src/frontend/src/pages/MainPage/pages/emptyPage/index.tsx
new file mode 100644
index 0000000..4a370b1
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/pages/emptyPage/index.tsx
@@ -0,0 +1,78 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { Button } from "@/components/ui/button";
+import { useFolderStore } from "@/stores/foldersStore";
+
+type EmptyPageProps = {
+ setOpenModal: (open: boolean) => void;
+};
+
+export const EmptyPage = ({ setOpenModal }: EmptyPageProps) => {
+ const folders = useFolderStore((state) => state.folders);
+
+ return (
+
+
+
+
+
+ {folders?.length > 1 ? "Empty folder" : "Start building"}
+
+
+ Begin with a template, or start from scratch.
+
+
setOpenModal(true)}
+ id="new-project-btn"
+ >
+
+
+ New Flow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default EmptyPage;
diff --git a/langflow/src/frontend/src/pages/MainPage/pages/homePage/index.tsx b/langflow/src/frontend/src/pages/MainPage/pages/homePage/index.tsx
new file mode 100644
index 0000000..ef75653
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/pages/homePage/index.tsx
@@ -0,0 +1,192 @@
+import PaginatorComponent from "@/components/common/paginatorComponent";
+import CardsWrapComponent from "@/components/core/cardsWrapComponent";
+import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
+import { CustomBanner } from "@/customization/components/custom-banner";
+import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { useFolderStore } from "@/stores/foldersStore";
+import { useCallback, useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+import GridComponent from "../../components/grid";
+import GridSkeleton from "../../components/gridSkeleton";
+import HeaderComponent from "../../components/header";
+import ListComponent from "../../components/list";
+import ListSkeleton from "../../components/listSkeleton";
+import ModalsComponent from "../../components/modalsComponent";
+import useFileDrop from "../../hooks/use-on-file-drop";
+import EmptyFolder from "../emptyFolder";
+
+const HomePage = ({ type }) => {
+ const [view, setView] = useState<"grid" | "list">(() => {
+ const savedView = localStorage.getItem("view");
+ return savedView === "grid" || savedView === "list" ? savedView : "list";
+ });
+ const [newProjectModal, setNewProjectModal] = useState(false);
+ const { folderId } = useParams();
+ const [pageIndex, setPageIndex] = useState(1);
+ const [pageSize, setPageSize] = useState(12);
+ const [search, setSearch] = useState("");
+ const handleFileDrop = useFileDrop("flows");
+ const [flowType, setFlowType] = useState<"flows" | "components">(type);
+ const myCollectionId = useFolderStore((state) => state.myCollectionId);
+ const folders = useFolderStore((state) => state.folders);
+ const folderName =
+ folders.find((folder) => folder.id === folderId)?.name ??
+ folders[0]?.name ??
+ "";
+ const flows = useFlowsManagerStore((state) => state.flows);
+
+ const { data: folderData, isLoading } = useGetFolderQuery({
+ id: folderId ?? myCollectionId!,
+ page: pageIndex,
+ size: pageSize,
+ is_component: flowType === "components",
+ is_flow: flowType === "flows",
+ search,
+ });
+
+ const data = {
+ flows: folderData?.flows?.items ?? [],
+ name: folderData?.folder?.name ?? "",
+ description: folderData?.folder?.description ?? "",
+ parent_id: folderData?.folder?.parent_id ?? "",
+ components: folderData?.folder?.components ?? [],
+ pagination: {
+ page: folderData?.flows?.page ?? 1,
+ size: folderData?.flows?.size ?? 12,
+ total: folderData?.flows?.total ?? 0,
+ pages: folderData?.flows?.pages ?? 0,
+ },
+ };
+
+ useEffect(() => {
+ localStorage.setItem("view", view);
+ }, [view]);
+
+ const handlePageChange = useCallback((newPageIndex, newPageSize) => {
+ setPageIndex(newPageIndex);
+ setPageSize(newPageSize);
+ }, []);
+
+ const onSearch = useCallback((newSearch) => {
+ setSearch(newSearch);
+ setPageIndex(1);
+ }, []);
+
+ const isEmptyFolder =
+ flows?.find((flow) => flow.folder_id === (folderId ?? myCollectionId)) ===
+ undefined;
+
+ return (
+
+
+
+ {ENABLE_DATASTAX_LANGFLOW &&
}
+
+ {/* mt-10 to mt-8 for Datastax LF */}
+
+
+
+ {isEmptyFolder ? (
+
+ ) : (
+
+ {isLoading ? (
+ view === "grid" ? (
+
+
+
+
+ ) : (
+
+
+
+
+ )
+ ) : data && data.pagination.total > 0 ? (
+ view === "grid" ? (
+
+ {data.flows.map((flow) => (
+
+ ))}
+
+ ) : (
+
+ {data.flows.map((flow) => (
+
+ ))}
+
+ )
+ ) : flowType === "flows" ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+ {!isLoading && !isEmptyFolder && data.pagination.total >= 10 && (
+
+ )}
+
+
+
+ {}}
+ handleDeleteFolder={() => {}}
+ />
+
+ );
+};
+
+export default HomePage;
diff --git a/langflow/src/frontend/src/pages/MainPage/pages/index.tsx b/langflow/src/frontend/src/pages/MainPage/pages/index.tsx
new file mode 100644
index 0000000..6f09234
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/pages/index.tsx
@@ -0,0 +1,106 @@
+import CardsWrapComponent from "@/components/core/cardsWrapComponent";
+import SideBarFoldersButtonsComponent from "@/components/core/folderSidebarComponent/components/sideBarFolderButtons";
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { useDeleteFolders } from "@/controllers/API/queries/folders";
+import CustomLoader from "@/customization/components/custom-loader";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import useAlertStore from "@/stores/alertStore";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { useFolderStore } from "@/stores/foldersStore";
+import { useQueryClient } from "@tanstack/react-query";
+import { useEffect, useState } from "react";
+import { Outlet } from "react-router-dom";
+import ModalsComponent from "../components/modalsComponent";
+import useFileDrop from "../hooks/use-on-file-drop";
+import EmptyPage from "./emptyPage";
+
+export default function CollectionPage(): JSX.Element {
+ const [openModal, setOpenModal] = useState(false);
+ const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
+ const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
+ const navigate = useCustomNavigate();
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const examples = useFlowsManagerStore((state) => state.examples);
+ const handleFileDrop = useFileDrop("flow");
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const folderToEdit = useFolderStore((state) => state.folderToEdit);
+ const folders = useFolderStore((state) => state.folders);
+ const queryClient = useQueryClient();
+
+ useEffect(() => {
+ return () => queryClient.removeQueries({ queryKey: ["useGetFolder"] });
+ }, []);
+
+ const { mutate } = useDeleteFolders();
+
+ const handleDeleteFolder = () => {
+ mutate(
+ {
+ folder_id: folderToEdit?.id!,
+ },
+ {
+ onSuccess: () => {
+ setSuccessData({
+ title: "Folder deleted successfully.",
+ });
+ navigate("/all");
+ },
+ onError: (err) => {
+ console.error(err);
+ setErrorData({
+ title: "Error deleting folder.",
+ });
+ },
+ },
+ );
+ };
+
+ return (
+
+ {flows &&
+ examples &&
+ folders &&
+ (flows?.length !== examples?.length || folders?.length > 1) && (
+ {
+ navigate(`all/folder/${id}`);
+ }}
+ handleDeleteFolder={(item) => {
+ setFolderToEdit(item);
+ setOpenDeleteFolderModal(true);
+ }}
+ />
+ )}
+
+ {flows && examples && folders ? (
+
+
+ {flows?.length !== examples?.length || folders?.length > 1 ? (
+
+ ) : (
+
+ )}
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/MainPage/utils/get-name-by-type.ts b/langflow/src/frontend/src/pages/MainPage/utils/get-name-by-type.ts
new file mode 100644
index 0000000..a2a495f
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/utils/get-name-by-type.ts
@@ -0,0 +1,10 @@
+export const getNameByType = (type: string) => {
+ switch (type) {
+ case "all":
+ return "Component or Flow";
+ case "component":
+ return "Component";
+ default:
+ return "Flow";
+ }
+};
diff --git a/langflow/src/frontend/src/pages/MainPage/utils/get-template-style.ts b/langflow/src/frontend/src/pages/MainPage/utils/get-template-style.ts
new file mode 100644
index 0000000..2eecf47
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/utils/get-template-style.ts
@@ -0,0 +1,26 @@
+import { useTypesStore } from "@/stores/typesStore";
+import { FlowType } from "@/types/flow";
+import { nodeIconsLucide } from "@/utils/styleUtils";
+
+export const useGetTemplateStyle = (
+ flowData: FlowType,
+): { getIcon: () => string } => {
+ const getIcon = () => {
+ if (
+ flowData.is_component &&
+ flowData.data?.nodes[0].type === "genericNode"
+ ) {
+ const dataType = flowData.data?.nodes[0].data.type;
+ const isGroup = !!flowData.data?.nodes[0].data.node?.flow;
+ const icon = flowData.data?.nodes[0].data.node?.icon;
+ const types = useTypesStore((state) => state.types);
+ const name = nodeIconsLucide[dataType] ? dataType : types[dataType];
+ const iconName = icon || (isGroup ? "group_components" : name);
+ return iconName;
+ } else {
+ return flowData.icon ?? "Workflow";
+ }
+ };
+
+ return { getIcon };
+};
diff --git a/langflow/src/frontend/src/pages/MainPage/utils/handle-download-folder.ts b/langflow/src/frontend/src/pages/MainPage/utils/handle-download-folder.ts
new file mode 100644
index 0000000..6817622
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/utils/handle-download-folder.ts
@@ -0,0 +1,23 @@
+import { useFolderStore } from "../../../stores/foldersStore";
+import { downloadFlowsFromFolders } from "../services";
+
+export function handleDownloadFolderFn(folderId: string) {
+ downloadFlowsFromFolders(folderId).then((data) => {
+ const folders = useFolderStore.getState().folders;
+
+ const folder = folders.find((f) => f.id === folderId);
+
+ data.folder_name = folder?.name || "folder";
+ data.folder_description = folder?.description || "";
+
+ const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
+ JSON.stringify(data),
+ )}`;
+
+ const link = document.createElement("a");
+ link.href = jsonString;
+ link.download = `${data.folder_name}.json`;
+
+ link.click();
+ });
+}
diff --git a/langflow/src/frontend/src/pages/MainPage/utils/sort-flows.ts b/langflow/src/frontend/src/pages/MainPage/utils/sort-flows.ts
new file mode 100644
index 0000000..1b88251
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/utils/sort-flows.ts
@@ -0,0 +1,25 @@
+export const sortFlows = (flows, type) => {
+ const isComponent = type === "component";
+
+ const sortByDate = (a, b) => {
+ const dateA = a?.updated_at || a?.date_created;
+ const dateB = b?.updated_at || b?.date_created;
+
+ if (dateA && dateB) {
+ return new Date(dateB).getTime() - new Date(dateA).getTime();
+ } else if (dateA) {
+ return 1;
+ } else if (dateB) {
+ return -1;
+ } else {
+ return 0;
+ }
+ };
+
+ const filteredFlows =
+ type === "all"
+ ? flows
+ : flows?.filter((f) => (f?.is_component ?? false) === isComponent);
+
+ return filteredFlows?.sort(sortByDate) ?? [];
+};
diff --git a/langflow/src/frontend/src/pages/MainPage/utils/time-elapse.ts b/langflow/src/frontend/src/pages/MainPage/utils/time-elapse.ts
new file mode 100644
index 0000000..202f6e2
--- /dev/null
+++ b/langflow/src/frontend/src/pages/MainPage/utils/time-elapse.ts
@@ -0,0 +1,30 @@
+export const timeElapsed = (dateTimeString: string | undefined): string => {
+ if (!dateTimeString) {
+ return "";
+ }
+
+ const givenDate = new Date(dateTimeString);
+ const now = new Date();
+
+ let diffInMs = Math.abs(now.getTime() - givenDate.getTime());
+
+ const minutes = Math.floor(diffInMs / (1000 * 60));
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+ const months = Math.floor(days / 30); // Approximate
+ const years = Math.floor(months / 12);
+
+ if (years > 0) {
+ return years === 1 ? `${years} year` : `${years} years`;
+ } else if (months > 0) {
+ return months === 1 ? `${months} month` : `${months} months`;
+ } else if (days > 0) {
+ return days === 1 ? `${days} day` : `${days} days`;
+ } else if (hours > 0) {
+ return hours === 1 ? `${hours} hour` : `${hours} hours`;
+ } else if (minutes > 0) {
+ return minutes === 1 ? `${minutes} minute` : `${minutes} minutes`;
+ } else {
+ return "less than a minute";
+ }
+};
diff --git a/langflow/src/frontend/src/pages/Playground/index.tsx b/langflow/src/frontend/src/pages/Playground/index.tsx
new file mode 100644
index 0000000..9d38346
--- /dev/null
+++ b/langflow/src/frontend/src/pages/Playground/index.tsx
@@ -0,0 +1,75 @@
+import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import IOModal from "@/modals/IOModal/new-modal";
+import { useStoreStore } from "@/stores/storeStore";
+import { useEffect } from "react";
+import { useParams } from "react-router-dom";
+import { getComponent } from "../../controllers/API";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import cloneFLowWithParent from "../../utils/storeUtils";
+
+export default function PlaygroundPage() {
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+ const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const validApiKey = useStoreStore((state) => state.validApiKey);
+ const { id } = useParams();
+ const { mutateAsync: getFlow } = useGetFlow();
+
+ const navigate = useCustomNavigate();
+
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const setIsLoading = useFlowsManagerStore((state) => state.setIsLoading);
+
+ async function getFlowData() {
+ try {
+ const flow = await getFlow({ id: id! });
+ return flow;
+ } catch (error: any) {
+ if (error?.response?.status === 404) {
+ if (!validApiKey) {
+ return null;
+ }
+ try {
+ const res = await getComponent(id!);
+ const newFlow = cloneFLowWithParent(res, res.id, false, true);
+ return newFlow;
+ } catch (componentError) {
+ return null;
+ }
+ }
+ return null;
+ }
+ }
+
+ useEffect(() => {
+ const initializeFlow = async () => {
+ setIsLoading(true);
+ if (currentFlowId === "") {
+ const flow = await getFlowData();
+ if (flow) {
+ setCurrentFlow(flow);
+ } else {
+ navigate("/");
+ }
+ }
+ };
+
+ initializeFlow();
+ setIsLoading(false);
+ }, [id, validApiKey]);
+
+ useEffect(() => {
+ if (id) track("Playground Page Loaded", { flowId: id });
+ }, []);
+
+ return (
+
+ {currentSavedFlow && (
+ {}} isPlayground>
+ <>>
+
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/ProfileSettingsPage/index.tsx b/langflow/src/frontend/src/pages/ProfileSettingsPage/index.tsx
new file mode 100644
index 0000000..434a9c2
--- /dev/null
+++ b/langflow/src/frontend/src/pages/ProfileSettingsPage/index.tsx
@@ -0,0 +1,197 @@
+import {
+ useResetPassword,
+ useUpdateUser,
+} from "@/controllers/API/queries/auth";
+import * as Form from "@radix-ui/react-form";
+import { cloneDeep } from "lodash";
+import { useContext, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import Header from "../../components/headerComponent";
+import InputComponent from "../../components/inputComponent";
+import { Button } from "../../components/ui/button";
+import {
+ EDIT_PASSWORD_ALERT_LIST,
+ EDIT_PASSWORD_ERROR_ALERT,
+ SAVE_ERROR_ALERT,
+ SAVE_SUCCESS_ALERT,
+} from "../../constants/alerts_constants";
+import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
+import { AuthContext } from "../../contexts/authContext";
+import useAlertStore from "../../stores/alertStore";
+import {
+ inputHandlerEventType,
+ patchUserInputStateType,
+} from "../../types/components";
+import { gradients } from "../../utils/styleUtils";
+import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
+export default function ProfileSettingsPage(): JSX.Element {
+ const [inputState, setInputState] = useState(
+ CONTROL_PATCH_USER_STATE,
+ );
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { userData, setUserData } = useContext(AuthContext);
+ const { password, cnfPassword, gradient } = inputState;
+
+ const { mutate: mutateResetPassword } = useResetPassword();
+ const { mutate: mutatePatchUser } = useUpdateUser();
+
+ async function handlePatchUser() {
+ if (password !== cnfPassword) {
+ setErrorData({
+ title: EDIT_PASSWORD_ERROR_ALERT,
+ list: [EDIT_PASSWORD_ALERT_LIST],
+ });
+ return;
+ }
+ if (password !== "") {
+ mutateResetPassword(
+ { user_id: userData!.id, password: { password } },
+ {
+ onSuccess: successUpdates,
+ onError: errorUpdates,
+ },
+ );
+ }
+ if (gradient !== "") {
+ mutatePatchUser(
+ { user_id: userData!.id, user: { profile_image: gradient } },
+ {
+ onSuccess: successUpdates,
+ onError: errorUpdates,
+ },
+ );
+ }
+ }
+
+ const errorUpdates = (error) => {
+ setErrorData({
+ title: SAVE_ERROR_ALERT,
+ list: [(error as any).response.data.detail],
+ });
+ };
+
+ const successUpdates = () => {
+ if (gradient !== "") {
+ let newUserData = cloneDeep(userData);
+ newUserData!.profile_image = gradient;
+ setUserData(newUserData);
+ }
+ handleInput({ target: { name: "password", value: "" } });
+ handleInput({ target: { name: "cnfPassword", value: "" } });
+ setSuccessData({ title: SAVE_SUCCESS_ALERT });
+ };
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ Profile Settings
+
+
+
+ Change your profile settings like your password and your profile
+ picture.
+
+
{
+ handlePatchUser();
+ const data = Object.fromEntries(new FormData(event.currentTarget));
+ event.preventDefault();
+ }}
+ className="flex h-full flex-col px-6 pb-16"
+ >
+
+
+
+
+
+ Password{" "}
+
+ {
+ handleInput({ target: { name: "password", value } });
+ }}
+ value={password}
+ isForm
+ password={true}
+ placeholder="Password"
+ className="w-full"
+ />
+
+ Please enter your password
+
+
+
+
+
+
+ Confirm Password{" "}
+
+
+ {
+ handleInput({ target: { name: "cnfPassword", value } });
+ }}
+ value={cnfPassword}
+ isForm
+ password={true}
+ placeholder="Confirm Password"
+ className="w-full"
+ />
+
+
+ Please confirm your password
+
+
+
+
+
+
+ Profile Gradient{" "}
+
+
+
+ {
+ handleInput({ target: { name: "gradient", value } });
+ }}
+ />
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/index.tsx
new file mode 100644
index 0000000..f693601
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/index.tsx
@@ -0,0 +1,116 @@
+import SideBarButtonsComponent from "@/components/core/sidebarComponent";
+import { SidebarProvider } from "@/components/ui/sidebar";
+import {
+ ENABLE_DATASTAX_LANGFLOW,
+ ENABLE_PROFILE_ICONS,
+} from "@/customization/feature-flags";
+import useAuthStore from "@/stores/authStore";
+import { useStoreStore } from "@/stores/storeStore";
+import { Outlet } from "react-router-dom";
+import ForwardedIconComponent from "../../components/common/genericIconComponent";
+import PageLayout from "../../components/common/pageLayout";
+
+export default function SettingsPage(): JSX.Element {
+ const autoLogin = useAuthStore((state) => state.autoLogin);
+ const hasStore = useStoreStore((state) => state.hasStore);
+
+ // Hides the General settings if there is nothing to show
+ const showGeneralSettings = ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
+
+ const sidebarNavItems: {
+ href?: string;
+ title: string;
+ icon: React.ReactNode;
+ }[] = [];
+
+ if (showGeneralSettings) {
+ sidebarNavItems.push({
+ title: "General",
+ href: "/settings/general",
+ icon: (
+
+ ),
+ });
+ }
+
+ sidebarNavItems.push(
+ {
+ title: "Global Variables",
+ href: "/settings/global-variables",
+ icon: (
+
+ ),
+ },
+
+ {
+ title: "Shortcuts",
+ href: "/settings/shortcuts",
+ icon: (
+
+ ),
+ },
+ {
+ title: "Messages",
+ href: "/settings/messages",
+ icon: (
+
+ ),
+ },
+ );
+
+ if (!ENABLE_DATASTAX_LANGFLOW) {
+ const langflowItems = [
+ {
+ title: "Langflow API Keys",
+ href: "/settings/api-keys",
+ icon: (
+
+ ),
+ },
+ {
+ title: "Langflow Store",
+ href: "/settings/store",
+ icon: (
+
+ ),
+ },
+ ];
+
+ sidebarNavItems.splice(2, 0, ...langflowItems);
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/components/ApiKeyHeader/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/components/ApiKeyHeader/index.tsx
new file mode 100644
index 0000000..6f12aae
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/components/ApiKeyHeader/index.tsx
@@ -0,0 +1,41 @@
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+import { Button } from "../../../../../../components/ui/button";
+import { API_PAGE_PARAGRAPH } from "../../../../../../constants/constants";
+import SecretKeyModal from "../../../../../../modals/secretKeyModal";
+
+type ApiKeyHeaderComponentProps = {
+ selectedRows: string[];
+ fetchApiKeys: () => void;
+ userId: string;
+};
+const ApiKeyHeaderComponent = ({
+ selectedRows,
+ fetchApiKeys,
+ userId,
+}: ApiKeyHeaderComponentProps) => {
+ return (
+ <>
+
+
+
+ Langflow API Keys
+
+
+
{API_PAGE_PARAGRAPH}
+
+
+
+
+
+ Add New
+
+
+
+
+ >
+ );
+};
+export default ApiKeyHeaderComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/helpers/column-defs.ts b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/helpers/column-defs.ts
new file mode 100644
index 0000000..83fa218
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/helpers/column-defs.ts
@@ -0,0 +1,40 @@
+import TableAutoCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender";
+
+export const getColumnDefs = () => {
+ return [
+ {
+ headerCheckboxSelection: true,
+ checkboxSelection: true,
+ showDisabledCheckboxes: true,
+ headerName: "Name",
+ field: "name",
+ cellRenderer: TableAutoCellRender,
+ flex: 2,
+ },
+ {
+ headerName: "Key",
+ field: "api_key",
+ cellRenderer: TableAutoCellRender,
+ flex: 1,
+ },
+ {
+ headerName: "Created",
+ field: "created_at",
+ cellRenderer: TableAutoCellRender,
+ flex: 1,
+ },
+ {
+ headerName: "Last Used",
+ field: "last_used_at",
+ cellRenderer: TableAutoCellRender,
+ flex: 1,
+ },
+ {
+ headerName: "Total Uses",
+ field: "total_uses",
+ cellRenderer: TableAutoCellRender,
+ flex: 1,
+ resizable: false,
+ },
+ ];
+};
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx
new file mode 100644
index 0000000..fc63f8d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx
@@ -0,0 +1,110 @@
+import {
+ DEL_KEY_ERROR_ALERT,
+ DEL_KEY_ERROR_ALERT_PLURAL,
+ DEL_KEY_SUCCESS_ALERT,
+ DEL_KEY_SUCCESS_ALERT_PLURAL,
+} from "@/constants/alerts_constants";
+import {
+ IApiKeysDataArray,
+ useDeleteApiKey,
+ useGetApiKeysQuery,
+} from "@/controllers/API/queries/api-keys";
+import { SelectionChangedEvent } from "ag-grid-community";
+import { useContext, useEffect, useState } from "react";
+import TableComponent from "../../../../components/core/parameterRenderComponent/components/tableComponent";
+import { AuthContext } from "../../../../contexts/authContext";
+import useAlertStore from "../../../../stores/alertStore";
+import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
+import { getColumnDefs } from "./helpers/column-defs";
+
+export default function ApiKeysPage() {
+ const [loadingKeys, setLoadingKeys] = useState(true);
+ const [selectedRows, setSelectedRows] = useState([]);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { userData } = useContext(AuthContext);
+ const [userId, setUserId] = useState("");
+ const [keysList, setKeysList] = useState([]);
+ const { refetch } = useGetApiKeysQuery();
+
+ async function getApiKeysQuery() {
+ const { data } = await refetch();
+ if (data !== undefined) {
+ const updatedKeysList = data["api_keys"].map((apikey) => ({
+ ...apikey,
+ name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
+ last_used_at: apikey.last_used_at ?? "Never",
+ }));
+ setKeysList(updatedKeysList);
+ setUserId(data["user_id"]);
+ }
+ }
+
+ useEffect(() => {
+ if (userData) {
+ getApiKeysQuery();
+ }
+ }, [userData]);
+
+ function resetFilter() {
+ getApiKeysQuery();
+ }
+
+ const { mutate } = useDeleteApiKey();
+
+ function handleDeleteApi() {
+ for (let i = 0; i < selectedRows.length; i++) {
+ mutate(
+ { keyId: selectedRows[i] },
+ {
+ onSuccess: () => {
+ resetFilter();
+ setSuccessData({
+ title:
+ selectedRows.length === 1
+ ? DEL_KEY_SUCCESS_ALERT
+ : DEL_KEY_SUCCESS_ALERT_PLURAL,
+ });
+ },
+ onError: (error) => {
+ setErrorData({
+ title:
+ selectedRows.length === 1
+ ? DEL_KEY_ERROR_ALERT
+ : DEL_KEY_ERROR_ALERT_PLURAL,
+ list: [error?.response?.data?.detail],
+ });
+ },
+ },
+ );
+ }
+ }
+
+ const columnDefs = getColumnDefs();
+
+ return (
+
+
+
+
+
{
+ setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
+ }}
+ rowSelection="multiple"
+ suppressRowClickSelection={true}
+ pagination={true}
+ columnDefs={columnDefs}
+ rowData={keysList}
+ />
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/GeneralPageHeader/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/GeneralPageHeader/index.tsx
new file mode 100644
index 0000000..13e5161
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/GeneralPageHeader/index.tsx
@@ -0,0 +1,23 @@
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+
+const GeneralPageHeaderComponent = () => {
+ return (
+ <>
+
+
+
+ General
+
+
+
+ Manage settings related to Langflow and your account.
+
+
+
+ >
+ );
+};
+export default GeneralPageHeaderComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/PasswordForm/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/PasswordForm/index.tsx
new file mode 100644
index 0000000..dfc0898
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/PasswordForm/index.tsx
@@ -0,0 +1,93 @@
+import * as Form from "@radix-ui/react-form";
+import InputComponent from "../../../../../../components/core/parameterRenderComponent/components/inputComponent";
+import { Button } from "../../../../../../components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "../../../../../../components/ui/card";
+
+type PasswordFormComponentProps = {
+ password: string;
+ cnfPassword: string;
+ handleInput: (event: any) => void;
+ handlePatchPassword: (
+ password: string,
+ cnfPassword: string,
+ handleInput: any,
+ ) => void;
+};
+const PasswordFormComponent = ({
+ password,
+ cnfPassword,
+ handleInput,
+ handlePatchPassword,
+}: PasswordFormComponentProps) => {
+ return (
+ <>
+ {
+ handlePatchPassword(password, cnfPassword, handleInput);
+ event.preventDefault();
+ }}
+ >
+
+
+ Password
+
+ Type your new password and confirm it.
+
+
+
+
+
+ {
+ handleInput({ target: { name: "password", value } });
+ }}
+ value={password}
+ isForm
+ password={true}
+ placeholder="Password"
+ className="w-full"
+ />
+
+ Please enter your password
+
+
+
+ {
+ handleInput({
+ target: { name: "cnfPassword", value },
+ });
+ }}
+ value={cnfPassword}
+ isForm
+ password={true}
+ placeholder="Confirm Password"
+ className="w-full"
+ />
+
+
+ Please confirm your password
+
+
+
+
+
+
+ Save
+
+
+
+
+ >
+ );
+};
+export default PasswordFormComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfileGradientForm/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfileGradientForm/index.tsx
new file mode 100644
index 0000000..38fdb0e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfileGradientForm/index.tsx
@@ -0,0 +1,68 @@
+import * as Form from "@radix-ui/react-form";
+import GradientChooserComponent from "../../../../../../components/gradientChooserComponent";
+import { Button } from "../../../../../../components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "../../../../../../components/ui/card";
+import { gradients } from "../../../../../../utils/styleUtils";
+
+type ProfileGradientFormComponentProps = {
+ gradient: string;
+ handleInput: (event: any) => void;
+ handlePatchGradient: (gradient: string) => void;
+ userData: any;
+};
+const ProfileGradientFormComponent = ({
+ gradient,
+ handleInput,
+ handlePatchGradient,
+ userData,
+}: ProfileGradientFormComponentProps) => {
+ return (
+ <>
+ {
+ handlePatchGradient(gradient);
+ event.preventDefault();
+ }}
+ >
+
+
+ Profile Gradient
+
+ Choose the gradient that appears as your profile picture.
+
+
+
+
+ {
+ handleInput({ target: { name: "gradient", value } });
+ }}
+ />
+
+
+
+
+ Save
+
+
+
+
+ >
+ );
+};
+export default ProfileGradientFormComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/hooks/use-preload-images.ts b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/hooks/use-preload-images.ts
new file mode 100644
index 0000000..2b2c4a4
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/hooks/use-preload-images.ts
@@ -0,0 +1,43 @@
+import { useEffect } from "react";
+import { BASE_URL_API } from "../../../../../../../../../constants/constants";
+
+const usePreloadImages = (
+ setImagesLoaded: (value: boolean) => void,
+ loading: boolean,
+ profilePictures?: { [key: string]: string[] },
+) => {
+ const preloadImages = async (imageUrls) => {
+ return Promise.all(
+ imageUrls.map(
+ (src) =>
+ new Promise((resolve) => {
+ const img = new Image();
+ img.src = src;
+ img.onload = resolve;
+ img.onerror = resolve;
+ }),
+ ),
+ );
+ };
+
+ useEffect(() => {
+ if (loading || !profilePictures) return;
+ const imageArray: string[] = [];
+
+ Object.keys(profilePictures).flatMap((folder) =>
+ profilePictures[folder].map((path) =>
+ imageArray.push(
+ `${BASE_URL_API}files/profile_pictures/${folder}/${path}`,
+ ),
+ ),
+ );
+
+ preloadImages(imageArray).then(() => {
+ setImagesLoaded(true);
+ });
+ }, [profilePictures, loading]);
+
+ return;
+};
+
+export default usePreloadImages;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/index.tsx
new file mode 100644
index 0000000..24ef132
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent/index.tsx
@@ -0,0 +1,79 @@
+import { ProfilePicturesQueryResponse } from "@/controllers/API/queries/files";
+import { useEffect, useRef, useState } from "react";
+import { Button } from "../../../../../../../../components/ui/button";
+import Loading from "../../../../../../../../components/ui/loading";
+import { BASE_URL_API } from "../../../../../../../../constants/constants";
+import { useDarkStore } from "../../../../../../../../stores/darkStore";
+import { cn } from "../../../../../../../../utils/utils";
+import usePreloadImages from "./hooks/use-preload-images";
+
+type ProfilePictureChooserComponentProps = {
+ profilePictures?: ProfilePicturesQueryResponse;
+ loading: boolean;
+ value: string;
+ onChange: (value: string) => void;
+};
+
+export default function ProfilePictureChooserComponent({
+ profilePictures,
+ loading,
+ value,
+ onChange,
+}: ProfilePictureChooserComponentProps) {
+ const ref = useRef(null);
+ const dark = useDarkStore((state) => state.dark);
+ const [imagesLoaded, setImagesLoaded] = useState(false);
+
+ useEffect(() => {
+ if (value && ref) {
+ ref.current?.scrollIntoView({ behavior: "smooth", block: "center" });
+ }
+ }, [ref, value]);
+
+ usePreloadImages(setImagesLoaded, loading, profilePictures);
+
+ return (
+
+ {loading || !imagesLoaded ? (
+
+ ) : (
+ Object.keys(profilePictures!).map((folder, index) => (
+
+
+ {folder}
+
+
+
+ {profilePictures![folder].map((path, idx) => (
+
onChange(folder + "/" + path)}
+ className="shrink-0 px-0.5 py-2"
+ >
+
+
+ ))}
+
+
+
+ ))
+ )}
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/index.tsx
new file mode 100644
index 0000000..82fd2e9
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/components/ProfilePictureForm/index.tsx
@@ -0,0 +1,77 @@
+import {
+ ProfilePicturesQueryResponse,
+ useGetProfilePicturesQuery,
+} from "@/controllers/API/queries/files";
+import * as Form from "@radix-ui/react-form";
+import { UseQueryResult } from "@tanstack/react-query";
+import { Button } from "../../../../../../components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "../../../../../../components/ui/card";
+import { gradients } from "../../../../../../utils/styleUtils";
+import ProfilePictureChooserComponent from "./components/profilePictureChooserComponent";
+
+type ProfilePictureFormComponentProps = {
+ profilePicture: string;
+ handleInput: (event: any) => void;
+ handlePatchProfilePicture: (gradient: string) => void;
+ handleGetProfilePictures: UseQueryResult;
+ userData: any;
+};
+const ProfilePictureFormComponent = ({
+ profilePicture,
+ handleInput,
+ handlePatchProfilePicture,
+ handleGetProfilePictures,
+ userData,
+}: ProfilePictureFormComponentProps) => {
+ const { isLoading, data, isFetching } = useGetProfilePicturesQuery();
+
+ return (
+ {
+ handlePatchProfilePicture(profilePicture);
+ event.preventDefault();
+ }}
+ >
+
+
+ Profile Picture
+
+ Choose the image that appears as your profile picture.
+
+
+
+
+
{
+ handleInput({ target: { name: "profilePicture", value } });
+ }}
+ />
+
+
+
+
+ Save
+
+
+
+
+ );
+};
+export default ProfilePictureFormComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
new file mode 100644
index 0000000..719c551
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
@@ -0,0 +1,167 @@
+import {
+ EDIT_PASSWORD_ALERT_LIST,
+ EDIT_PASSWORD_ERROR_ALERT,
+ SAVE_ERROR_ALERT,
+ SAVE_SUCCESS_ALERT,
+} from "@/constants/alerts_constants";
+import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
+import {
+ useResetPassword,
+ useUpdateUser,
+} from "@/controllers/API/queries/auth";
+import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
+import { ENABLE_PROFILE_ICONS } from "@/customization/feature-flags";
+import useAuthStore from "@/stores/authStore";
+import { cloneDeep } from "lodash";
+import { useContext, useState } from "react";
+import { useParams } from "react-router-dom";
+import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
+import { AuthContext } from "../../../../contexts/authContext";
+import useAlertStore from "../../../../stores/alertStore";
+import { useStoreStore } from "../../../../stores/storeStore";
+import {
+ inputHandlerEventType,
+ patchUserInputStateType,
+} from "../../../../types/components";
+import useScrollToElement from "../hooks/use-scroll-to-element";
+import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
+import PasswordFormComponent from "./components/PasswordForm";
+import ProfilePictureFormComponent from "./components/ProfilePictureForm";
+
+export const GeneralPage = () => {
+ const { scrollId } = useParams();
+
+ const [inputState, setInputState] = useState(
+ CONTROL_PATCH_USER_STATE,
+ );
+
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { userData, setUserData } = useContext(AuthContext);
+ const { password, cnfPassword, profilePicture } = inputState;
+ const autoLogin = useAuthStore((state) => state.autoLogin);
+
+ const { storeApiKey } = useContext(AuthContext);
+ const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
+ const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
+ const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
+
+ const { mutate: mutateResetPassword } = useResetPassword();
+ const { mutate: mutatePatchUser } = useUpdateUser();
+
+ const handlePatchPassword = () => {
+ if (password !== cnfPassword) {
+ setErrorData({
+ title: EDIT_PASSWORD_ERROR_ALERT,
+ list: [EDIT_PASSWORD_ALERT_LIST],
+ });
+ return;
+ }
+
+ if (password !== "") {
+ mutateResetPassword(
+ { user_id: userData!.id, password: { password } },
+ {
+ onSuccess: () => {
+ handleInput({ target: { name: "password", value: "" } });
+ handleInput({ target: { name: "cnfPassword", value: "" } });
+ setSuccessData({ title: SAVE_SUCCESS_ALERT });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: SAVE_ERROR_ALERT,
+ list: [(error as any)?.response?.data?.detail],
+ });
+ },
+ },
+ );
+ }
+ };
+
+ const handleGetProfilePictures = useGetProfilePicturesQuery();
+
+ const handlePatchProfilePicture = (profile_picture) => {
+ if (profile_picture !== "") {
+ mutatePatchUser(
+ { user_id: userData!.id, user: { profile_image: profile_picture } },
+ {
+ onSuccess: () => {
+ let newUserData = cloneDeep(userData);
+ newUserData!.profile_image = profile_picture;
+ setUserData(newUserData);
+ setSuccessData({ title: SAVE_SUCCESS_ALERT });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: SAVE_ERROR_ALERT,
+ list: [(error as any)?.response?.data?.detail],
+ });
+ },
+ },
+ );
+ }
+ };
+
+ useScrollToElement(scrollId);
+
+ const { mutate } = usePostAddApiKey({
+ onSuccess: () => {
+ setSuccessData({ title: "API key saved successfully" });
+ setHasApiKey(true);
+ setValidApiKey(true);
+ setLoadingApiKey(false);
+ handleInput({ target: { name: "apikey", value: "" } });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: "API key save error",
+ list: [(error as any)?.response?.data?.detail],
+ });
+ setHasApiKey(false);
+ setValidApiKey(false);
+ setLoadingApiKey(false);
+ },
+ });
+
+ const handleSaveKey = (apikey: string) => {
+ if (apikey) {
+ mutate({ key: apikey });
+ storeApiKey(apikey);
+ }
+ };
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ return (
+
+
+
+
+ {ENABLE_PROFILE_ICONS && (
+
+ )}
+
+ {!autoLogin && (
+
+ )}
+
+
+ );
+};
+
+export default GeneralPage;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx
new file mode 100644
index 0000000..374e4da
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx
@@ -0,0 +1,155 @@
+import IconComponent, {
+ ForwardedIconComponent,
+} from "../../../../components/common/genericIconComponent";
+import { Button } from "../../../../components/ui/button";
+
+import Dropdown from "@/components/core/dropdownComponent";
+import GlobalVariableModal from "@/components/core/GlobalVariableModal/GlobalVariableModal";
+import TableComponent from "@/components/core/parameterRenderComponent/components/tableComponent";
+import {
+ useDeleteGlobalVariables,
+ useGetGlobalVariables,
+} from "@/controllers/API/queries/variables";
+import { GlobalVariable } from "@/types/global_variables";
+import {
+ ColDef,
+ RowClickedEvent,
+ SelectionChangedEvent,
+} from "ag-grid-community";
+import { useRef, useState } from "react";
+import { Badge } from "../../../../components/ui/badge";
+import useAlertStore from "../../../../stores/alertStore";
+
+export default function GlobalVariablesPage() {
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const [openModal, setOpenModal] = useState(false);
+ const initialData = useRef(undefined);
+ const BadgeRenderer = (props) => {
+ return props.value !== "" ? (
+
+
+ {props.value}
+
+
+ ) : (
+
+ );
+ };
+
+ const DropdownEditor = ({ options, value, onValueChange }) => {
+ return (
+
+
+
+ );
+ };
+ // Column Definitions: Defines the columns to be displayed.
+ const colDefs: ColDef[] = [
+ {
+ headerName: "Variable Name",
+ field: "name",
+ flex: 2,
+ }, //This column will be twice as wide as the others
+ {
+ headerName: "Type",
+ field: "type",
+ cellRenderer: BadgeRenderer,
+ cellEditor: DropdownEditor,
+ cellEditorParams: {
+ options: ["Generic", "Credential"],
+ },
+ flex: 1,
+ },
+ {
+ field: "value",
+ },
+ {
+ headerName: "Apply To Fields",
+ field: "default_fields",
+ valueFormatter: (params) => {
+ return params.value?.join(", ") ?? "";
+ },
+ flex: 1,
+ resizable: false,
+ },
+ ];
+
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ const { data: globalVariables } = useGetGlobalVariables();
+ const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables();
+
+ async function removeVariables() {
+ selectedRows.map(async (row) => {
+ const id = globalVariables?.find((variable) => variable.name === row)?.id;
+ mutateDeleteGlobalVariable(
+ { id },
+ {
+ onError: () => {
+ setErrorData({
+ title: `Error deleting variable`,
+ list: [`ID not found for variable: ${row}`],
+ });
+ },
+ },
+ );
+ });
+ }
+
+ function updateVariables(event: RowClickedEvent) {
+ initialData.current = event.data;
+ setOpenModal(true);
+ }
+
+ return (
+
+
+
+
+ Global Variables
+
+
+
+ Manage global variables and assign them to fields.
+
+
+
+
+
+
+ Add New
+
+
+
+
+
+
+
{
+ setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
+ }}
+ rowSelection="multiple"
+ onRowClicked={updateVariables}
+ suppressRowClickSelection={true}
+ pagination={true}
+ columnDefs={colDefs}
+ rowData={globalVariables ?? []}
+ onDelete={removeVariables}
+ />
+ {initialData.current && (
+
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/CellRenderWrapper/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/CellRenderWrapper/index.tsx
new file mode 100644
index 0000000..0f81391
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/CellRenderWrapper/index.tsx
@@ -0,0 +1,8 @@
+import RenderIcons from "@/components/common/renderIconComponent";
+import { CustomCellRendererProps } from "ag-grid-react";
+
+export default function CellRenderShortcuts(params: CustomCellRendererProps) {
+ const shortcut = params.value;
+ const splitShortcut = shortcut?.split("+");
+ return ;
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx
new file mode 100644
index 0000000..e5e12c2
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/EditShortcutButton/index.tsx
@@ -0,0 +1,194 @@
+import { useEffect, useState } from "react";
+import useAlertStore from "../../../../../stores/alertStore";
+
+import RenderKey from "@/components/common/renderIconComponent/components/renderKey";
+import ForwardedIconComponent from "../../../../../components/common/genericIconComponent";
+import { Button } from "../../../../../components/ui/button";
+import BaseModal from "../../../../../modals/baseModal";
+import { useShortcutsStore } from "../../../../../stores/shortcuts";
+import { toCamelCase, toTitleCase } from "../../../../../utils/utils";
+
+export default function EditShortcutButton({
+ children,
+ shortcut,
+ defaultShortcuts,
+ open,
+ setOpen,
+ disable,
+ setSelected,
+}: {
+ children: JSX.Element;
+ shortcut: string[];
+ defaultShortcuts: Array<{
+ name: string;
+ shortcut: string;
+ display_name: string;
+ }>;
+ open: boolean;
+ setOpen: (bool: boolean) => void;
+ disable?: boolean;
+ setSelected: (selected: string[]) => void;
+}): JSX.Element {
+ let shortcutInitialValue =
+ defaultShortcuts.length > 0
+ ? defaultShortcuts.find(
+ (s) => toCamelCase(s.name) === toCamelCase(shortcut[0]),
+ )?.shortcut
+ : "";
+ const [key, setKey] = useState(null);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+
+ function canEditCombination(newCombination: string): boolean {
+ let canSave = true;
+ defaultShortcuts.forEach(({ shortcut }) => {
+ if (shortcut.toLowerCase() === newCombination.toLowerCase()) {
+ canSave = false;
+ }
+ });
+ return canSave;
+ }
+
+ const setUniqueShortcut = useShortcutsStore(
+ (state) => state.updateUniqueShortcut,
+ );
+
+ function editCombination(): void {
+ if (key) {
+ if (canEditCombination(key)) {
+ const fixCombination = key.split(" ");
+ if (
+ fixCombination[0].toLowerCase().includes("ctrl") ||
+ fixCombination[0].toLowerCase().includes("cmd")
+ ) {
+ fixCombination[0] = "mod";
+ }
+ const newCombination = defaultShortcuts.map((s) => {
+ if (s.name === shortcut[0]) {
+ return {
+ name: s.name,
+ display_name: s.display_name,
+ shortcut: fixCombination.join("").toLowerCase(),
+ };
+ }
+ return {
+ name: s.name,
+ display_name: s.display_name,
+ shortcut: s.shortcut,
+ };
+ });
+ const shortcutName = toCamelCase(shortcut[0]);
+ setUniqueShortcut(shortcutName, fixCombination.join("").toLowerCase());
+ setShortcuts(newCombination);
+ localStorage.setItem(
+ "langflow-shortcuts",
+ JSON.stringify(newCombination),
+ );
+ setKey(null);
+ setOpen(false);
+ setSuccessData({
+ title: `${shortcut[0]} shortcut successfully changed`,
+ });
+ return;
+ }
+ }
+ setErrorData({
+ title: "Error saving key combination",
+ list: ["This combination already exists!"],
+ });
+ }
+
+ useEffect(() => {
+ if (!open) {
+ setKey(null);
+ setSelected([]);
+ }
+ }, [open, setOpen, key]);
+
+ function getFixedCombination({
+ oldKey,
+ key,
+ }: {
+ oldKey: string;
+ key: string;
+ }): string {
+ if (oldKey === null) {
+ return `${key.length > 0 ? toTitleCase(key) : toTitleCase(key)}`;
+ }
+ return `${
+ oldKey.length > 0 ? toTitleCase(oldKey) : oldKey.toUpperCase()
+ } + ${key.length > 0 ? toTitleCase(key) : key.toUpperCase()}`;
+ }
+
+ function checkForKeys(keys: string, keyToCompare: string): boolean {
+ const keysArr = keys.split(" ");
+ let hasNewKey = false;
+ return keysArr.some(
+ (k) => k.toLowerCase().trim() === keyToCompare.toLowerCase().trim(),
+ );
+ }
+
+ useEffect(() => {
+ function onKeyDown(e: KeyboardEvent) {
+ e.preventDefault();
+ let fixedKey = e.key;
+ if (e.key?.toLowerCase() === "control") {
+ fixedKey = "Ctrl";
+ }
+ if (e.key?.toLowerCase() === "meta") {
+ fixedKey = "Cmd";
+ }
+ if (e.key?.toLowerCase() === " ") {
+ fixedKey = "Space";
+ }
+ if (key) {
+ if (checkForKeys(key, fixedKey)) return;
+ }
+ setKey((oldKey) =>
+ getFixedCombination({ oldKey: oldKey!, key: fixedKey }),
+ );
+ }
+
+ document.addEventListener("keydown", onKeyDown);
+
+ return () => {
+ document.removeEventListener("keydown", onKeyDown);
+ };
+ }, [key, setKey]);
+
+ return (
+
+
+ Key Combination
+
+
+ {children}
+
+
+
+ {(key ?? shortcutInitialValue ?? "").split("+").map((k, i) => (
+
+ ))}
+
+
+
+
+
+ Apply
+
+ setKey(null)}
+ >
+ Reset
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx
new file mode 100644
index 0000000..263efc5
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx
@@ -0,0 +1,119 @@
+import { toCamelCase } from "@/utils/utils";
+import { ColDef } from "ag-grid-community";
+import { useEffect, useState } from "react";
+import ForwardedIconComponent from "../../../../components/common/genericIconComponent";
+import TableComponent from "../../../../components/core/parameterRenderComponent/components/tableComponent";
+import { Button } from "../../../../components/ui/button";
+import { defaultShortcuts } from "../../../../constants/constants";
+import { useShortcutsStore } from "../../../../stores/shortcuts";
+import CellRenderShortcuts from "./CellRenderWrapper";
+import EditShortcutButton from "./EditShortcutButton";
+
+export default function ShortcutsPage() {
+ const [selectedRows, setSelectedRows] = useState([]);
+ const shortcuts = useShortcutsStore((state) => state.shortcuts);
+ const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
+
+ // Column Definitions: Defines the columns to be displayed.
+ const colDefs: ColDef[] = [
+ {
+ headerName: "Functionality",
+ field: "display_name",
+ flex: 1,
+ editable: false,
+ resizable: false,
+ }, //This column will be twice as wide as the others
+ {
+ headerName: "Keyboard Shortcut",
+ field: "shortcut",
+ flex: 2,
+ editable: false,
+ resizable: false,
+ cellRenderer: CellRenderShortcuts,
+ },
+ ];
+
+ const [nodesRowData, setNodesRowData] = useState<
+ Array<{ name: string; shortcut: string }>
+ >([]);
+
+ useEffect(() => {
+ setNodesRowData(shortcuts);
+ }, [shortcuts]);
+
+ const [open, setOpen] = useState(false);
+ const updateUniqueShortcut = useShortcutsStore(
+ (state) => state.updateUniqueShortcut,
+ );
+
+ function handleRestore() {
+ setShortcuts(defaultShortcuts);
+ defaultShortcuts.forEach(({ name, shortcut }) => {
+ const fixedName = toCamelCase(name);
+ updateUniqueShortcut(fixedName, shortcut);
+ });
+ localStorage.removeItem("langflow-shortcuts");
+ }
+
+ return (
+
+
+
+
+ Shortcuts
+
+
+
+ Manage Shortcuts for quick access to frequently used actions.
+
+
+
+
+
+ {open && (
+
+
+
+ )}
+
+
+ Restore
+
+
+
+
+
+
+
+ {colDefs && nodesRowData.length > 0 && (
+
{
+ setSelectedRows([e.data.name]);
+ setOpen(true);
+ }}
+ />
+ )}
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/components/StoreApiKeyForm.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/components/StoreApiKeyForm.tsx
new file mode 100644
index 0000000..14b83e4
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/components/StoreApiKeyForm.tsx
@@ -0,0 +1,102 @@
+import * as Form from "@radix-ui/react-form";
+import InputComponent from "../../../../../components/core/parameterRenderComponent/components/inputComponent";
+import { Button } from "../../../../../components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "../../../../../components/ui/card";
+import {
+ CREATE_API_KEY,
+ INSERT_API_KEY,
+ INVALID_API_KEY,
+ NO_API_KEY,
+} from "../../../../../constants/constants";
+
+type StoreApiKeyFormComponentProps = {
+ apikey: string;
+ handleInput: (event: any) => void;
+ handleSaveKey: (apikey: string, handleInput: any) => void;
+ loadingApiKey: boolean;
+ validApiKey: boolean;
+ hasApiKey: boolean;
+};
+const StoreApiKeyFormComponent = ({
+ apikey,
+ handleInput,
+ handleSaveKey,
+ loadingApiKey,
+ validApiKey,
+ hasApiKey,
+}: StoreApiKeyFormComponentProps) => {
+ return (
+ <>
+ {
+ event.preventDefault();
+ handleSaveKey(apikey, handleInput);
+ }}
+ >
+
+
+ Store API Key
+
+ {(hasApiKey && !validApiKey
+ ? INVALID_API_KEY
+ : !hasApiKey
+ ? NO_API_KEY
+ : "") + INSERT_API_KEY}
+
+
+
+
+
+
+ {
+ handleInput({ target: { name: "apikey", value } });
+ }}
+ value={apikey}
+ isForm
+ password={true}
+ placeholder="Insert your API Key"
+ className="w-full"
+ />
+
+ Please enter your API Key
+
+
+
+
+ {CREATE_API_KEY}{" "}
+
+ langflow.store
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ >
+ );
+};
+export default StoreApiKeyFormComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/index.tsx
new file mode 100644
index 0000000..4d164d3
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/StoreApiKeyPage/index.tsx
@@ -0,0 +1,88 @@
+import ForwardedIconComponent from "@/components/common/genericIconComponent";
+import { CONTROL_PATCH_USER_STATE } from "@/constants/constants";
+import { AuthContext } from "@/contexts/authContext";
+import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
+import useAlertStore from "@/stores/alertStore";
+import { useStoreStore } from "@/stores/storeStore";
+import { inputHandlerEventType } from "@/types/components";
+import { useContext, useState } from "react";
+import { useParams } from "react-router-dom";
+import useScrollToElement from "../hooks/use-scroll-to-element";
+import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
+
+const StoreApiKeyPage = () => {
+ const { scrollId } = useParams();
+ const [inputState, setInputState] = useState(CONTROL_PATCH_USER_STATE);
+ const { storeApiKey } = useContext(AuthContext);
+ useScrollToElement(scrollId);
+
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const {
+ validApiKey,
+ hasApiKey,
+ loadingApiKey,
+ updateHasApiKey: setHasApiKey,
+ updateValidApiKey: setValidApiKey,
+ updateLoadingApiKey: setLoadingApiKey,
+ } = useStoreStore();
+
+ const { mutate: addApiKey } = usePostAddApiKey({
+ onSuccess: () => {
+ setSuccessData({ title: "API key saved successfully" });
+ setHasApiKey(true);
+ setValidApiKey(true);
+ setLoadingApiKey(false);
+ handleInput({ target: { name: "apikey", value: "" } });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: "API key save error",
+ list: [(error as any)?.response?.data?.detail],
+ });
+ setHasApiKey(false);
+ setValidApiKey(false);
+ setLoadingApiKey(false);
+ },
+ });
+
+ const handleSaveKey = (apikey: string) => {
+ if (apikey) {
+ addApiKey({ key: apikey });
+ storeApiKey(apikey);
+ }
+ };
+
+ const handleInput = ({ target: { name, value } }: inputHandlerEventType) => {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ };
+
+ return (
+
+
+
+
+ Langflow Store
+
+
+
+ Manage access to the Langflow Store.
+
+
+
+
+
+ );
+};
+
+export default StoreApiKeyPage;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/StorePage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/StorePage/index.tsx
new file mode 100644
index 0000000..b8605a8
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/StorePage/index.tsx
@@ -0,0 +1,71 @@
+import { CONTROL_PATCH_USER_STATE } from "@/constants/constants";
+import { AuthContext } from "@/contexts/authContext";
+import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
+import useAlertStore from "@/stores/alertStore";
+import { useStoreStore } from "@/stores/storeStore";
+import { inputHandlerEventType } from "@/types/components";
+import { useContext, useState } from "react";
+import { useParams } from "react-router-dom";
+import StoreApiKeyFormComponent from "../StoreApiKeyPage/components/StoreApiKeyForm";
+import useScrollToElement from "../hooks/use-scroll-to-element";
+
+const StoreApiKeyPage = () => {
+ const { scrollId } = useParams();
+ const [inputState, setInputState] = useState(CONTROL_PATCH_USER_STATE);
+ const { storeApiKey } = useContext(AuthContext);
+ useScrollToElement(scrollId);
+
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const {
+ validApiKey,
+ hasApiKey,
+ loadingApiKey,
+ updateHasApiKey: setHasApiKey,
+ updateValidApiKey: setValidApiKey,
+ updateLoadingApiKey: setLoadingApiKey,
+ } = useStoreStore();
+
+ const { mutate: addApiKey } = usePostAddApiKey({
+ onSuccess: () => {
+ setSuccessData({ title: "API key saved successfully" });
+ setHasApiKey(true);
+ setValidApiKey(true);
+ setLoadingApiKey(false);
+ handleInput({ target: { name: "apikey", value: "" } });
+ },
+ onError: (error) => {
+ setErrorData({
+ title: "API key save error",
+ list: [(error as any)?.response?.data?.detail],
+ });
+ setHasApiKey(false);
+ setValidApiKey(false);
+ setLoadingApiKey(false);
+ },
+ });
+
+ const handleSaveKey = (apikey: string) => {
+ if (apikey) {
+ addApiKey({ key: apikey });
+ storeApiKey(apikey);
+ }
+ };
+
+ const handleInput = ({ target: { name, value } }: inputHandlerEventType) => {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ };
+
+ return (
+
+ );
+};
+
+export default StoreApiKeyPage;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx
new file mode 100644
index 0000000..966d4bb
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/hooks/use-scroll-to-element.tsx
@@ -0,0 +1,13 @@
+import { useEffect } from "react";
+
+const useScrollToElement = (scrollId: string | null | undefined) => {
+ useEffect(() => {
+ const element = document.getElementById(scrollId ?? "null");
+ if (element) {
+ // Scroll smoothly to the top of the next section
+ element.scrollIntoView({ behavior: "smooth" });
+ }
+ }, [scrollId]);
+};
+
+export default useScrollToElement;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx
new file mode 100644
index 0000000..528f81b
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/components/headerMessages/index.tsx
@@ -0,0 +1,24 @@
+import ForwardedIconComponent from "../../../../../../components/common/genericIconComponent";
+
+const HeaderMessagesComponent = () => {
+ return (
+ <>
+
+
+
+ Messages
+
+
+
+ Inspect, edit and remove messages to explore and refine model
+ behaviors.
+
+
+
+ >
+ );
+};
+export default HeaderMessagesComponent;
diff --git a/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx
new file mode 100644
index 0000000..3cd5a0e
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx
@@ -0,0 +1,16 @@
+import { useGetMessagesQuery } from "@/controllers/API/queries/messages";
+import SessionView from "@/modals/IOModal/components/session-view";
+import HeaderMessagesComponent from "./components/headerMessages";
+
+export default function MessagesPage() {
+ useGetMessagesQuery({ mode: "union" });
+
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/SignUpPage/index.tsx b/langflow/src/frontend/src/pages/SignUpPage/index.tsx
new file mode 100644
index 0000000..c5fcbc7
--- /dev/null
+++ b/langflow/src/frontend/src/pages/SignUpPage/index.tsx
@@ -0,0 +1,204 @@
+import LangflowLogo from "@/assets/LangflowLogo.svg?react";
+import InputComponent from "@/components/core/parameterRenderComponent/components/inputComponent";
+import { useAddUser } from "@/controllers/API/queries/auth";
+import { CustomLink } from "@/customization/components/custom-link";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { track } from "@/customization/utils/analytics";
+import * as Form from "@radix-ui/react-form";
+import { FormEvent, useEffect, useState } from "react";
+import { Button } from "../../components/ui/button";
+import { Input } from "../../components/ui/input";
+import { SIGNUP_ERROR_ALERT } from "../../constants/alerts_constants";
+import {
+ CONTROL_INPUT_STATE,
+ SIGN_UP_SUCCESS,
+} from "../../constants/constants";
+import useAlertStore from "../../stores/alertStore";
+import {
+ UserInputType,
+ inputHandlerEventType,
+ signUpInputStateType,
+} from "../../types/components";
+
+export default function SignUp(): JSX.Element {
+ const [inputState, setInputState] =
+ useState(CONTROL_INPUT_STATE);
+
+ const [isDisabled, setDisableBtn] = useState(true);
+
+ const { password, cnfPassword, username } = inputState;
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const navigate = useCustomNavigate();
+
+ const { mutate: mutateAddUser } = useAddUser();
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ useEffect(() => {
+ if (password !== cnfPassword) return setDisableBtn(true);
+ if (password === "" || cnfPassword === "") return setDisableBtn(true);
+ if (username === "") return setDisableBtn(true);
+ setDisableBtn(false);
+ }, [password, cnfPassword, username, handleInput]);
+
+ function handleSignup(): void {
+ const { username, password } = inputState;
+ const newUser: UserInputType = {
+ username: username.trim(),
+ password: password.trim(),
+ };
+
+ mutateAddUser(newUser, {
+ onSuccess: (user) => {
+ track("User Signed Up", user);
+ setSuccessData({
+ title: SIGN_UP_SUCCESS,
+ });
+ navigate("/login");
+ },
+ onError: (error) => {
+ const {
+ response: {
+ data: { detail },
+ },
+ } = error;
+ setErrorData({
+ title: SIGNUP_ERROR_ALERT,
+ list: [detail],
+ });
+ },
+ });
+ }
+
+ return (
+ ) => {
+ if (password === "") {
+ event.preventDefault();
+ return;
+ }
+
+ const data = Object.fromEntries(new FormData(event.currentTarget));
+ event.preventDefault();
+ }}
+ className="h-screen w-full"
+ >
+
+
+
+
+ Sign up for Langflow
+
+
+
+
+ Username *
+
+
+
+ {
+ handleInput({ target: { name: "username", value } });
+ }}
+ value={username}
+ className="w-full"
+ required
+ placeholder="Username"
+ />
+
+
+
+ Please enter your username
+
+
+
+
+
+
+ Password *
+
+ {
+ handleInput({ target: { name: "password", value } });
+ }}
+ value={password}
+ isForm
+ password={true}
+ required
+ placeholder="Password"
+ className="w-full"
+ />
+
+
+ Please enter a password
+
+
+ {password != cnfPassword && (
+
+ Passwords do not match
+
+ )}
+
+
+
+
+
+ Confirm your password{" "}
+ *
+
+
+ {
+ handleInput({ target: { name: "cnfPassword", value } });
+ }}
+ value={cnfPassword}
+ isForm
+ password={true}
+ required
+ placeholder="Confirm your password"
+ className="w-full"
+ />
+
+
+ Please confirm your password
+
+
+
+
+
+ {
+ handleSignup();
+ }}
+ >
+ Sign up
+
+
+
+
+
+
+ Already have an account? Sign in
+
+
+
+
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/StorePage/index.tsx b/langflow/src/frontend/src/pages/StorePage/index.tsx
new file mode 100644
index 0000000..f9fd13d
--- /dev/null
+++ b/langflow/src/frontend/src/pages/StorePage/index.tsx
@@ -0,0 +1,397 @@
+import { uniqueId } from "lodash";
+import { useContext, useEffect, useState } from "react";
+import IconComponent from "../../components/common/genericIconComponent";
+import PageLayout from "../../components/common/pageLayout";
+import ShadTooltip from "../../components/common/shadTooltipComponent";
+import { SkeletonCardComponent } from "../../components/common/skeletonCardComponent";
+import { Button } from "../../components/ui/button";
+
+import PaginatorComponent from "@/components/common/paginatorComponent";
+import StoreCardComponent from "@/components/common/storeCardComponent";
+import { CustomLink } from "@/customization/components/custom-link";
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { useUtilityStore } from "@/stores/utilityStore";
+import { useParams } from "react-router-dom";
+import { TagsSelector } from "../../components/common/tagsSelectorComponent";
+import { Badge } from "../../components/ui/badge";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../../components/ui/select";
+import {
+ APIKEY_ERROR_ALERT,
+ COMPONENTS_ERROR_ALERT,
+ INVALID_API_ERROR_ALERT,
+ NOAPI_ERROR_ALERT,
+} from "../../constants/alerts_constants";
+import {
+ STORE_DESC,
+ STORE_PAGINATION_PAGE,
+ STORE_PAGINATION_ROWS_COUNT,
+ STORE_PAGINATION_SIZE,
+ STORE_TITLE,
+} from "../../constants/constants";
+import { AuthContext } from "../../contexts/authContext";
+import { getStoreComponents } from "../../controllers/API";
+import useAlertStore from "../../stores/alertStore";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import { useStoreStore } from "../../stores/storeStore";
+import { storeComponent } from "../../types/store";
+import { cn } from "../../utils/utils";
+import InputSearchComponent from "../MainPage/components/inputSearchComponent";
+
+export default function StorePage(): JSX.Element {
+ const hasApiKey = useStoreStore((state) => state.hasApiKey);
+ const validApiKey = useStoreStore((state) => state.validApiKey);
+ const loadingApiKey = useStoreStore((state) => state.loadingApiKey);
+
+ const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
+
+ const { apiKey } = useContext(AuthContext);
+
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const [loading, setLoading] = useState(true);
+ const { id } = useParams();
+ const [filteredCategories, setFilterCategories] = useState([]);
+ const [inputText, setInputText] = useState("");
+ const [searchData, setSearchData] = useState([]);
+ const [totalRowsCount, setTotalRowsCount] = useState(0);
+ const [pageSize, setPageSize] = useState(STORE_PAGINATION_SIZE);
+ const [pageIndex, setPageIndex] = useState(STORE_PAGINATION_PAGE);
+ const [pageOrder, setPageOrder] = useState("Popular");
+ const [tabActive, setTabActive] = useState("All");
+ const [searchNow, setSearchNow] = useState("");
+ const [selectFilter, setSelectFilter] = useState("all");
+
+ const tags = useUtilityStore((state) => state.tags);
+
+ const navigate = useCustomNavigate();
+
+ useEffect(() => {
+ if (!loadingApiKey) {
+ if (!hasApiKey) {
+ setErrorData({
+ title: APIKEY_ERROR_ALERT,
+ list: [NOAPI_ERROR_ALERT],
+ });
+ setLoading(false);
+ } else if (!validApiKey) {
+ setErrorData({
+ title: APIKEY_ERROR_ALERT,
+ list: [INVALID_API_ERROR_ALERT],
+ });
+ }
+ }
+ }, [loadingApiKey, validApiKey, hasApiKey, currentFlowId]);
+
+ useEffect(() => {
+ handleGetComponents();
+ }, [
+ tabActive,
+ pageOrder,
+ pageIndex,
+ pageSize,
+ filteredCategories,
+ selectFilter,
+ validApiKey,
+ hasApiKey,
+ apiKey,
+ searchNow,
+ loadingApiKey,
+ id,
+ ]);
+
+ function handleGetComponents() {
+ if (loadingApiKey) return;
+ setLoading(true);
+ getStoreComponents({
+ component_id: id,
+ page: pageIndex,
+ limit: pageSize,
+ is_component:
+ tabActive === "All" ? null : tabActive === "Flows" ? false : true,
+ sort: pageOrder === "Popular" ? "-count(downloads)" : "name",
+ tags: filteredCategories,
+ liked: selectFilter === "likedbyme" && validApiKey ? true : null,
+ isPrivate: null,
+ search: inputText === "" ? null : inputText,
+ filterByUser: selectFilter === "createdbyme" && validApiKey ? true : null,
+ })
+ .then((res) => {
+ if (!res?.authorized && validApiKey === true) {
+ setValidApiKey(false);
+ setSelectFilter("all");
+ } else {
+ if (res?.authorized) {
+ setValidApiKey(true);
+ }
+ setLoading(false);
+ setSearchData(res?.results ?? []);
+ setTotalRowsCount(
+ filteredCategories?.length === 0
+ ? Number(res?.count ?? 0)
+ : (res?.results?.length ?? 0),
+ );
+ }
+ })
+ .catch((err) => {
+ if (err.response?.status === 403 || err.response?.status === 401) {
+ setValidApiKey(false);
+ } else {
+ setSearchData([]);
+ setTotalRowsCount(0);
+ setLoading(false);
+ setErrorData({
+ title: COMPONENTS_ERROR_ALERT,
+ list: [err["response"]["data"]["detail"]],
+ });
+ }
+ });
+ }
+
+ function resetPagination() {
+ setPageIndex(STORE_PAGINATION_PAGE);
+ setPageSize(STORE_PAGINATION_SIZE);
+ }
+
+ return (
+ {
+ navigate("/settings/general/api");
+ }}
+ >
+
+ API Key
+
+ }
+ >
+
+
+
+
setInputText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ setSearchNow(uniqueId());
+ }
+ }}
+ onClick={() => {
+ setSearchNow(uniqueId());
+ }}
+ />
+
+ {
+ setTabActive("All");
+ }}
+ className={
+ (tabActive === "All"
+ ? "border-b-2 border-primary p-3"
+ : "border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
+ (loading ? " cursor-not-allowed" : "")
+ }
+ >
+ All
+
+ {
+ resetPagination();
+ setTabActive("Flows");
+ }}
+ className={
+ (tabActive === "Flows"
+ ? "border-b-2 border-primary p-3"
+ : "border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
+ (loading ? " cursor-not-allowed" : "")
+ }
+ >
+ Flows
+
+ {
+ resetPagination();
+ setTabActive("Components");
+ }}
+ className={
+ (tabActive === "Components"
+ ? "border-b-2 border-primary p-3"
+ : "border-b-2 border-transparent p-3 text-muted-foreground hover:text-primary") +
+ (loading ? " cursor-not-allowed" : "")
+ }
+ >
+ Components
+
+
+
+ Bundles
+
+
+
+
+
+
+
+
+
+
+
+
+ All
+
+ Created By Me
+
+
+ Liked By Me
+
+
+
+
+ {id === undefined ? (
+
+ ) : (
+
+
+
+
+ {id}
+
+ )}
+
+
+
+ {(!loading || searchData.length !== 0) && (
+ <>
+ {totalRowsCount} {totalRowsCount !== 1 ? "results" : "result"}
+ >
+ )}
+
+
+ {
+ setPageOrder(e);
+ }}
+ >
+
+
+
+
+ Popular
+ {/* Most Recent */}
+ Alphabetical
+
+
+
+
+
+ {!loading || searchData.length !== 0 ? (
+ searchData.map((item) => {
+ return (
+ <>
+
+ >
+ );
+ })
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+ {!loading && searchData?.length === 0 && (
+
+
+
+
+ {selectFilter != "all" ? (
+ <>
+ You haven't{" "}
+ {selectFilter === "createdbyme" ? "created" : "liked"}{" "}
+ anything with the selected filters yet.
+ >
+ ) : (
+ <>
+ There are no{" "}
+ {tabActive == "Flows" ? "Flows" : "Components"} with the
+ selected filters.
+ >
+ )}
+
+
+
+
+ )}
+
+ {!loading && searchData.length > 0 && (
+
+
{
+ setPageIndex(pageIndex);
+ setPageSize(pageSize);
+ }}
+ >
+
+ )}
+
+
+ );
+}
diff --git a/langflow/src/frontend/src/pages/ViewPage/index.tsx b/langflow/src/frontend/src/pages/ViewPage/index.tsx
new file mode 100644
index 0000000..86ce7bd
--- /dev/null
+++ b/langflow/src/frontend/src/pages/ViewPage/index.tsx
@@ -0,0 +1,38 @@
+import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
+import { useEffect } from "react";
+import { useParams } from "react-router-dom";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import Page from "../FlowPage/components/PageComponent";
+
+export default function ViewPage() {
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+
+ const { id } = useParams();
+ const navigate = useCustomNavigate();
+
+ const flows = useFlowsManagerStore((state) => state.flows);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+
+ // Set flow tab id
+ useEffect(() => {
+ const awaitgetTypes = async () => {
+ if (flows && currentFlowId === "") {
+ const isAnExistingFlow = flows.find((flow) => flow.id === id);
+
+ if (!isAnExistingFlow) {
+ navigate("/all");
+ return;
+ }
+
+ setCurrentFlow(isAnExistingFlow);
+ }
+ };
+ awaitgetTypes();
+ }, [id, flows]);
+
+ return (
+
+ );
+}
diff --git a/langflow/src/frontend/src/png.d.ts b/langflow/src/frontend/src/png.d.ts
new file mode 100644
index 0000000..cbfbd10
--- /dev/null
+++ b/langflow/src/frontend/src/png.d.ts
@@ -0,0 +1,4 @@
+declare module "*.png" {
+ const content: any;
+ export default content;
+}
diff --git a/langflow/src/frontend/src/reportWebVitals.ts b/langflow/src/frontend/src/reportWebVitals.ts
new file mode 100644
index 0000000..5fa3583
--- /dev/null
+++ b/langflow/src/frontend/src/reportWebVitals.ts
@@ -0,0 +1,15 @@
+import { ReportHandler } from "web-vitals";
+
+const reportWebVitals = (onPerfEntry?: ReportHandler) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/langflow/src/frontend/src/routes.tsx b/langflow/src/frontend/src/routes.tsx
new file mode 100644
index 0000000..6e133b9
--- /dev/null
+++ b/langflow/src/frontend/src/routes.tsx
@@ -0,0 +1,191 @@
+import { lazy } from "react";
+import {
+ createBrowserRouter,
+ createRoutesFromElements,
+ Outlet,
+ Route,
+} from "react-router-dom";
+import { ProtectedAdminRoute } from "./components/authorization/authAdminGuard";
+import { ProtectedRoute } from "./components/authorization/authGuard";
+import { ProtectedLoginRoute } from "./components/authorization/authLoginGuard";
+import { AuthSettingsGuard } from "./components/authorization/authSettingsGuard";
+import { StoreGuard } from "./components/authorization/storeGuard";
+import ContextWrapper from "./contexts";
+import { CustomNavigate } from "./customization/components/custom-navigate";
+import { BASENAME } from "./customization/config-constants";
+import { ENABLE_CUSTOM_PARAM } from "./customization/feature-flags";
+import { AppAuthenticatedPage } from "./pages/AppAuthenticatedPage";
+import { AppInitPage } from "./pages/AppInitPage";
+import { AppWrapperPage } from "./pages/AppWrapperPage";
+import { DashboardWrapperPage } from "./pages/DashboardWrapperPage";
+import FlowPage from "./pages/FlowPage";
+import LoginPage from "./pages/LoginPage";
+import CollectionPage from "./pages/MainPage/pages";
+import HomePage from "./pages/MainPage/pages/homePage";
+import SettingsPage from "./pages/SettingsPage";
+import ApiKeysPage from "./pages/SettingsPage/pages/ApiKeysPage";
+import GeneralPage from "./pages/SettingsPage/pages/GeneralPage";
+import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage";
+import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
+import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage";
+import StoreApiKeyPage from "./pages/SettingsPage/pages/StoreApiKeyPage";
+import StorePage from "./pages/StorePage";
+import ViewPage from "./pages/ViewPage";
+
+const AdminPage = lazy(() => import("./pages/AdminPage"));
+const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
+const DeleteAccountPage = lazy(() => import("./pages/DeleteAccountPage"));
+
+// const PlaygroundPage = lazy(() => import("./pages/Playground"));
+
+const SignUp = lazy(() => import("./pages/SignUpPage"));
+const router = createBrowserRouter(
+ createRoutesFromElements([
+
+
+
+ }
+ >
+ }>
+ }>
+
+
+
+ }
+ >
+ }>
+ }>
+ }>
+ }
+ />
+ }
+ >
+ }
+ />
+
+ }
+ >
+ }
+ />
+
+ }
+ >
+ }
+ />
+
+
+ }>
+ }
+ />
+ }
+ />
+ } />
+
+
+
+ }
+ />
+ } />
+ } />
+ } />
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ }>
+
+
+
+
+ }
+ />
+
+
+ }>
+ } />
+ } />
+
+ } />
+
+ {/*
+ } />
+ */}
+
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+ } />
+ ,
+ ]),
+ { basename: BASENAME || undefined },
+);
+
+export default router;
diff --git a/langflow/src/frontend/src/shared/components/caseComponent/index.tsx b/langflow/src/frontend/src/shared/components/caseComponent/index.tsx
new file mode 100644
index 0000000..c5330af
--- /dev/null
+++ b/langflow/src/frontend/src/shared/components/caseComponent/index.tsx
@@ -0,0 +1,15 @@
+import { memo } from "react";
+
+type BooleanLike = boolean | string | number | null | undefined;
+
+type Props = {
+ condition: (() => BooleanLike) | BooleanLike;
+ children: React.ReactNode | any;
+};
+
+export const Case = memo(({ condition, children }: Props) => {
+ const conditionResult =
+ typeof condition === "function" ? condition() : condition;
+
+ return conditionResult ? children : null;
+});
diff --git a/langflow/src/frontend/src/shared/components/suspenseImageComponent/index.tsx b/langflow/src/frontend/src/shared/components/suspenseImageComponent/index.tsx
new file mode 100644
index 0000000..9299b20
--- /dev/null
+++ b/langflow/src/frontend/src/shared/components/suspenseImageComponent/index.tsx
@@ -0,0 +1,43 @@
+type SuspenseImageComponentProps = { src: string };
+
+const imgCache = {
+ __cache: {},
+ read(src) {
+ if (!this.__cache[src]) {
+ this.__cache[src] = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => {
+ this.__cache[src] = true;
+ resolve(true);
+ };
+ img.onerror = () => {
+ delete this.__cache[src]; // Remove failed cache entry
+ reject(new Error("Image failed to load"));
+ };
+ img.src = src;
+ });
+ }
+ if (this.__cache[src] instanceof Promise) {
+ throw this.__cache[src];
+ }
+ return this.__cache[src];
+ },
+};
+
+const SuspenseImageComponent = ({
+ src,
+ ...rest
+}: SuspenseImageComponentProps) => {
+ try {
+ imgCache.read(src);
+ } catch (promise) {
+ if (promise instanceof Promise) {
+ throw promise;
+ }
+ throw new Error("Unexpected error in image loading");
+ }
+
+ return ;
+};
+
+export default SuspenseImageComponent;
diff --git a/langflow/src/frontend/src/shared/components/textOutputView/index.tsx b/langflow/src/frontend/src/shared/components/textOutputView/index.tsx
new file mode 100644
index 0000000..57ad7fe
--- /dev/null
+++ b/langflow/src/frontend/src/shared/components/textOutputView/index.tsx
@@ -0,0 +1,24 @@
+import { Textarea } from "../../../components/ui/textarea";
+
+const TextOutputView = ({
+ left,
+ value,
+}: {
+ left: boolean | undefined;
+ value: any;
+}) => {
+ if (typeof value === "object" && Object.keys(value).includes("text")) {
+ value = value.text;
+ }
+ return (
+
+ );
+};
+
+export default TextOutputView;
diff --git a/langflow/src/frontend/src/shared/hooks/use-alternate.tsx b/langflow/src/frontend/src/shared/hooks/use-alternate.tsx
new file mode 100644
index 0000000..7e1b720
--- /dev/null
+++ b/langflow/src/frontend/src/shared/hooks/use-alternate.tsx
@@ -0,0 +1,14 @@
+import { useCallback, useState } from "react";
+
+export const useAlternate = (
+ initialState: boolean = false,
+): [boolean, () => void, (value: boolean) => void] => {
+ const [switched, setSwitched] = useState(initialState);
+ const set = useCallback((value) => setSwitched(value), []);
+
+ const alternate = useCallback(
+ () => setSwitched((prevState) => !prevState),
+ [],
+ );
+ return [switched, alternate, set];
+};
diff --git a/langflow/src/frontend/src/shared/hooks/use-change-on-unfocus.tsx b/langflow/src/frontend/src/shared/hooks/use-change-on-unfocus.tsx
new file mode 100644
index 0000000..c0359d0
--- /dev/null
+++ b/langflow/src/frontend/src/shared/hooks/use-change-on-unfocus.tsx
@@ -0,0 +1,49 @@
+import { RefObject, useEffect } from "react";
+
+interface UseChangeOnUnfocusProps {
+ selected?: boolean;
+ value: T;
+ onChange?: (value: T) => void;
+ defaultValue: T;
+ shouldChangeValue?: (value: T) => boolean;
+ nodeRef: RefObject;
+ callback?: () => void;
+ callbackEscape?: () => void;
+}
+
+export function useChangeOnUnfocus({
+ selected,
+ value,
+ onChange,
+ defaultValue,
+ shouldChangeValue,
+ nodeRef,
+ callback,
+}: UseChangeOnUnfocusProps) {
+ useEffect(() => {
+ if (!selected) {
+ onChange?.(defaultValue);
+ }
+
+ const handleVisibilityChange = () => {
+ if (document.hidden && shouldChangeValue?.(value)) {
+ onChange?.(defaultValue);
+ callback?.();
+ }
+ };
+
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+
+ return () => {
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ };
+ }, [
+ selected,
+ value,
+ onChange,
+ defaultValue,
+ shouldChangeValue,
+ nodeRef,
+ callback,
+ ]);
+}
diff --git a/langflow/src/frontend/src/shared/hooks/use-file-size-validator.ts b/langflow/src/frontend/src/shared/hooks/use-file-size-validator.ts
new file mode 100644
index 0000000..502ac56
--- /dev/null
+++ b/langflow/src/frontend/src/shared/hooks/use-file-size-validator.ts
@@ -0,0 +1,22 @@
+import { INVALID_FILE_SIZE_ALERT } from "@/constants/alerts_constants";
+import { useUtilityStore } from "@/stores/utilityStore";
+
+const useFileSizeValidator = (
+ setErrorData: (newState: { title: string; list?: Array }) => void,
+) => {
+ const maxFileSizeUpload = useUtilityStore((state) => state.maxFileSizeUpload);
+
+ const validateFileSize = (file) => {
+ if (file.size > maxFileSizeUpload) {
+ setErrorData({
+ title: INVALID_FILE_SIZE_ALERT(maxFileSizeUpload / 1024 / 1024),
+ });
+ return false;
+ }
+ return true;
+ };
+
+ return { validateFileSize };
+};
+
+export default useFileSizeValidator;
diff --git a/langflow/src/frontend/src/shared/hooks/use-tab-visibility.ts b/langflow/src/frontend/src/shared/hooks/use-tab-visibility.ts
new file mode 100644
index 0000000..d8b510b
--- /dev/null
+++ b/langflow/src/frontend/src/shared/hooks/use-tab-visibility.ts
@@ -0,0 +1,21 @@
+import { useEffect, useState } from "react";
+
+const useTabVisibility = () => {
+ const [tabChanged, setTabChanged] = useState(true);
+
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ setTabChanged(document.hidden);
+ };
+
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+
+ return () => {
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ };
+ }, []);
+
+ return tabChanged;
+};
+
+export default useTabVisibility;
diff --git a/langflow/src/frontend/src/stores/alertStore.ts b/langflow/src/frontend/src/stores/alertStore.ts
new file mode 100644
index 0000000..027b706
--- /dev/null
+++ b/langflow/src/frontend/src/stores/alertStore.ts
@@ -0,0 +1,165 @@
+import { uniqueId } from "lodash";
+import { create } from "zustand";
+import { AlertItemType } from "../types/alerts";
+import { AlertStoreType } from "../types/zustand/alert";
+import { customStringify } from "../utils/reactflowUtils";
+
+const pushNotificationList = (
+ list: AlertItemType[],
+ notification: AlertItemType,
+) => {
+ list.unshift(notification);
+ return list;
+};
+
+const useAlertStore = create((set, get) => ({
+ errorData: { title: "", list: [] },
+ noticeData: { title: "", link: "" },
+ successData: { title: "" },
+ notificationCenter: false,
+ notificationList: [],
+ tempNotificationList: [],
+ setErrorData: (newState: { title: string; list?: Array }) => {
+ if (newState.title && newState.title !== "") {
+ set({
+ errorData: newState,
+ notificationCenter: true,
+ notificationList: [
+ {
+ type: "error",
+ title: newState.title,
+ list: newState.list,
+ id: uniqueId(),
+ },
+ ...get().notificationList,
+ ],
+ });
+ const tempList = get().tempNotificationList;
+ if (
+ !tempList.some((item) => {
+ return (
+ customStringify({
+ title: item.title,
+ type: item.type,
+ list: item.list,
+ }) === customStringify({ ...newState, type: "error" })
+ );
+ })
+ ) {
+ set({
+ tempNotificationList: [
+ {
+ type: "error",
+ title: newState.title,
+ list: newState.list,
+ id: uniqueId(),
+ },
+ ...get().tempNotificationList,
+ ],
+ });
+ }
+ }
+ },
+ setNoticeData: (newState: { title: string; link?: string }) => {
+ if (newState.title && newState.title !== "") {
+ set({
+ noticeData: newState,
+ notificationCenter: true,
+ notificationList: [
+ {
+ type: "notice",
+ title: newState.title,
+ link: newState.link,
+ id: uniqueId(),
+ },
+ ...get().notificationList,
+ ],
+ });
+ const tempList = get().tempNotificationList;
+ if (
+ !tempList.some((item) => {
+ return (
+ customStringify({
+ title: item.title,
+ type: item.type,
+ link: item.link,
+ }) === customStringify({ ...newState, type: "notice" })
+ );
+ })
+ ) {
+ set({
+ tempNotificationList: [
+ {
+ type: "notice",
+ title: newState.title,
+ link: newState.link,
+ id: uniqueId(),
+ },
+ ...get().tempNotificationList,
+ ],
+ });
+ }
+ }
+ },
+ setSuccessData: (newState: { title: string }) => {
+ if (newState.title && newState.title !== "") {
+ set({
+ successData: newState,
+ notificationCenter: true,
+ notificationList: [
+ {
+ type: "success",
+ title: newState.title,
+ id: uniqueId(),
+ },
+ ...get().notificationList,
+ ],
+ });
+ const tempList = get().tempNotificationList;
+ if (
+ !tempList.some((item) => {
+ return (
+ customStringify({ title: item.title, type: item.type }) ===
+ customStringify({ ...newState, type: "success" })
+ );
+ })
+ ) {
+ set({
+ tempNotificationList: [
+ {
+ type: "success",
+ title: newState.title,
+ id: uniqueId(),
+ },
+ ...get().tempNotificationList,
+ ],
+ });
+ }
+ }
+ },
+ setNotificationCenter: (newState: boolean) => {
+ set({ notificationCenter: newState });
+ },
+ clearNotificationList: () => {
+ set({ notificationList: [] });
+ },
+ removeFromNotificationList: (index: string) => {
+ set({
+ notificationList: get().notificationList.filter(
+ (item) => item.id !== index,
+ ),
+ });
+ },
+ clearTempNotificationList: () => {
+ set({ tempNotificationList: [] });
+ },
+ removeFromTempNotificationList: (index: string) => {
+ set({
+ tempNotificationList: get().tempNotificationList.filter(
+ (item) => item.id !== index,
+ ),
+ });
+ },
+}));
+
+export default useAlertStore;
diff --git a/langflow/src/frontend/src/stores/authStore.ts b/langflow/src/frontend/src/stores/authStore.ts
new file mode 100644
index 0000000..40282c6
--- /dev/null
+++ b/langflow/src/frontend/src/stores/authStore.ts
@@ -0,0 +1,41 @@
+// authStore.js
+import { LANGFLOW_ACCESS_TOKEN } from "@/constants/constants";
+import { AuthStoreType } from "@/types/zustand/auth";
+import { Cookies } from "react-cookie";
+import { create } from "zustand";
+
+const cookies = new Cookies();
+const useAuthStore = create((set, get) => ({
+ isAdmin: false,
+ isAuthenticated: !!cookies.get(LANGFLOW_ACCESS_TOKEN),
+ accessToken: cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null,
+ userData: null,
+ autoLogin: null,
+ apiKey: cookies.get("apikey_tkn_lflw"),
+ authenticationErrorCount: 0,
+
+ setIsAdmin: (isAdmin) => set({ isAdmin }),
+ setIsAuthenticated: (isAuthenticated) => set({ isAuthenticated }),
+ setAccessToken: (accessToken) => set({ accessToken }),
+ setUserData: (userData) => set({ userData }),
+ setAutoLogin: (autoLogin) => set({ autoLogin }),
+ setApiKey: (apiKey) => set({ apiKey }),
+ setAuthenticationErrorCount: (authenticationErrorCount) =>
+ set({ authenticationErrorCount }),
+
+ logout: async () => {
+ get().setIsAuthenticated(false);
+ get().setIsAdmin(false);
+
+ set({
+ isAdmin: false,
+ userData: null,
+ accessToken: null,
+ isAuthenticated: false,
+ autoLogin: false,
+ apiKey: null,
+ });
+ },
+}));
+
+export default useAuthStore;
diff --git a/langflow/src/frontend/src/stores/darkStore.ts b/langflow/src/frontend/src/stores/darkStore.ts
new file mode 100644
index 0000000..6e3c28a
--- /dev/null
+++ b/langflow/src/frontend/src/stores/darkStore.ts
@@ -0,0 +1,43 @@
+import { create } from "zustand";
+import { getRepoStars } from "../controllers/API";
+import { DarkStoreType } from "../types/zustand/dark";
+
+const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;
+
+export const useDarkStore = create((set, get) => ({
+ dark: JSON.parse(window.localStorage.getItem("isDark")!) ?? false,
+ stars: startedStars,
+ version: "",
+ setDark: (dark) => {
+ set(() => ({ dark: dark }));
+ window.localStorage.setItem("isDark", dark.toString());
+ },
+ refreshVersion: (v) => {
+ set(() => ({ version: v }));
+ },
+ refreshStars: () => {
+ if (import.meta.env.CI) {
+ window.localStorage.setItem("githubStars", "0");
+ set(() => ({ stars: 0, lastUpdated: new Date() }));
+ return;
+ }
+ let lastUpdated = window.localStorage.getItem("githubStarsLastUpdated");
+ let diff = 0;
+ // check if lastUpdated actually exists
+ if (lastUpdated !== null) {
+ diff = Math.abs(new Date().getTime() - new Date(lastUpdated).getTime());
+ }
+
+ // if lastUpdated is null or the difference is greater than 2 hours
+ if (lastUpdated === null || diff > 7200000) {
+ getRepoStars("langflow-ai", "langflow").then((res) => {
+ window.localStorage.setItem("githubStars", res?.toString() ?? "0");
+ window.localStorage.setItem(
+ "githubStarsLastUpdated",
+ new Date().toString(),
+ );
+ set(() => ({ stars: res, lastUpdated: new Date() }));
+ });
+ }
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/durationStore.ts b/langflow/src/frontend/src/stores/durationStore.ts
new file mode 100644
index 0000000..bc2512c
--- /dev/null
+++ b/langflow/src/frontend/src/stores/durationStore.ts
@@ -0,0 +1,38 @@
+import { create } from "zustand";
+
+interface DurationState {
+ durations: Record;
+ intervals: Record;
+ setDuration: (chatId: string, duration: number) => void;
+ incrementDuration: (chatId: string) => void;
+ clearInterval: (chatId: string) => void;
+ setInterval: (chatId: string, intervalId: NodeJS.Timeout) => void;
+}
+
+export const useDurationStore = create((set) => ({
+ durations: {},
+ intervals: {},
+ setDuration: (chatId, duration) =>
+ set((state) => ({
+ durations: { ...state.durations, [chatId]: duration },
+ })),
+ incrementDuration: (chatId) =>
+ set((state) => ({
+ durations: {
+ ...state.durations,
+ [chatId]: (state.durations[chatId] || 0) + 10,
+ },
+ })),
+ clearInterval: (chatId) =>
+ set((state) => {
+ if (state.intervals[chatId]) {
+ clearInterval(state.intervals[chatId]);
+ }
+ const { [chatId]: _, ...rest } = state.intervals;
+ return { intervals: rest };
+ }),
+ setInterval: (chatId, intervalId) =>
+ set((state) => ({
+ intervals: { ...state.intervals, [chatId]: intervalId },
+ })),
+}));
diff --git a/langflow/src/frontend/src/stores/flowStore.ts b/langflow/src/frontend/src/stores/flowStore.ts
new file mode 100644
index 0000000..64ef4ba
--- /dev/null
+++ b/langflow/src/frontend/src/stores/flowStore.ts
@@ -0,0 +1,969 @@
+import {
+ BROKEN_EDGES_WARNING,
+ componentsToIgnoreUpdate,
+} from "@/constants/constants";
+import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
+import {
+ track,
+ trackDataLoaded,
+ trackFlowBuild,
+} from "@/customization/utils/analytics";
+import { brokenEdgeMessage } from "@/utils/utils";
+import {
+ EdgeChange,
+ Node,
+ NodeChange,
+ addEdge,
+ applyEdgeChanges,
+ applyNodeChanges,
+} from "@xyflow/react";
+import { cloneDeep, zip } from "lodash";
+import { create } from "zustand";
+import {
+ FLOW_BUILD_SUCCESS_ALERT,
+ MISSED_ERROR_ALERT,
+} from "../constants/alerts_constants";
+import { BuildStatus } from "../constants/enums";
+import { LogsLogType, VertexBuildTypeAPI } from "../types/api";
+import { ChatInputType, ChatOutputType } from "../types/chat";
+import {
+ AllNodeType,
+ EdgeType,
+ NodeDataType,
+ sourceHandleType,
+ targetHandleType,
+} from "../types/flow";
+import { FlowStoreType, VertexLayerElementType } from "../types/zustand/flow";
+import { buildFlowVerticesWithFallback } from "../utils/buildUtils";
+import {
+ buildPositionDictionary,
+ checkChatInput,
+ cleanEdges,
+ detectBrokenEdgesEdges,
+ getHandleId,
+ getNodeId,
+ scapeJSONParse,
+ scapedJSONStringfy,
+ unselectAllNodesEdges,
+ updateGroupRecursion,
+ validateEdge,
+ validateNodes,
+} from "../utils/reactflowUtils";
+import { getInputsAndOutputs } from "../utils/storeUtils";
+import useAlertStore from "./alertStore";
+import { useDarkStore } from "./darkStore";
+import useFlowsManagerStore from "./flowsManagerStore";
+import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
+import { useTypesStore } from "./typesStore";
+
+// this is our useStore hook that we can use in our components to get parts of the store and call actions
+const useFlowStore = create((set, get) => ({
+ positionDictionary: {},
+ setPositionDictionary: (positionDictionary) => {
+ set({ positionDictionary });
+ },
+ isPositionAvailable: (position: { x: number; y: number }) => {
+ if (
+ get().positionDictionary[position.x] &&
+ get().positionDictionary[position.x] === position.y
+ ) {
+ return false;
+ }
+ return true;
+ },
+ fitViewNode: (nodeId) => {
+ if (get().reactFlowInstance && get().nodes.find((n) => n.id === nodeId)) {
+ get().reactFlowInstance?.fitView({ nodes: [{ id: nodeId }] });
+ }
+ },
+ autoSaveFlow: undefined,
+ componentsToUpdate: [],
+ setComponentsToUpdate: (change) => {
+ let newChange =
+ typeof change === "function" ? change(get().componentsToUpdate) : change;
+ set({ componentsToUpdate: newChange });
+ },
+ updateComponentsToUpdate: (nodes) => {
+ let outdatedNodes: string[] = [];
+ const templates = useTypesStore.getState().templates;
+ for (let i = 0; i < nodes.length; i++) {
+ let node = nodes[i];
+ if (node.type === "genericNode") {
+ const currentCode = templates[node.data?.type]?.template?.code?.value;
+ const thisNodesCode = node.data?.node!.template?.code?.value;
+ if (
+ currentCode &&
+ thisNodesCode &&
+ currentCode !== thisNodesCode &&
+ !node.data?.node?.edited &&
+ !componentsToIgnoreUpdate.includes(node.data?.type)
+ ) {
+ outdatedNodes.push(node.id);
+ }
+ }
+ }
+ set({ componentsToUpdate: outdatedNodes });
+ },
+ onFlowPage: false,
+ setOnFlowPage: (FlowPage) => set({ onFlowPage: FlowPage }),
+ flowState: undefined,
+ flowBuildStatus: {},
+ nodes: [],
+ edges: [],
+ isBuilding: false,
+ stopBuilding: () => {
+ get().buildController.abort();
+ get().updateEdgesRunningByNodes(
+ get().nodes.map((n) => n.id),
+ false,
+ );
+ set({ isBuilding: false });
+ get().revertBuiltStatusFromBuilding();
+ useAlertStore.getState().setErrorData({
+ title: "Build stopped",
+ });
+ },
+ isPending: true,
+ setHasIO: (hasIO) => {
+ set({ hasIO });
+ },
+ reactFlowInstance: null,
+ lastCopiedSelection: null,
+ flowPool: {},
+ setInputs: (inputs) => {
+ set({ inputs });
+ },
+ setOutputs: (outputs) => {
+ set({ outputs });
+ },
+ inputs: [],
+ outputs: [],
+ hasIO: get()?.inputs?.length > 0 || get()?.outputs?.length > 0,
+ setFlowPool: (flowPool) => {
+ set({ flowPool });
+ },
+ updateToolMode: (nodeId: string, toolMode: boolean) => {
+ get().setNode(nodeId, (node) => {
+ let newNode = cloneDeep(node);
+ if (newNode.type === "genericNode") {
+ newNode.data.node!.tool_mode = toolMode;
+ }
+ return newNode;
+ });
+ },
+ updateFreezeStatus: (nodeIds: string[], freeze: boolean) => {
+ get().setNodes((oldNodes) => {
+ const newNodes = cloneDeep(oldNodes);
+ return newNodes.map((node) => {
+ if (nodeIds.includes(node.id)) {
+ (node.data as NodeDataType).node!.frozen = freeze;
+ }
+ return node;
+ });
+ });
+ },
+ addDataToFlowPool: (data: VertexBuildTypeAPI, nodeId: string) => {
+ let newFlowPool = cloneDeep({ ...get().flowPool });
+ if (!newFlowPool[nodeId]) newFlowPool[nodeId] = [data];
+ else {
+ newFlowPool[nodeId].push(data);
+ }
+ get().setFlowPool(newFlowPool);
+ },
+ getNodePosition: (nodeId: string) => {
+ const node = get().nodes.find((node) => node.id === nodeId);
+ return node?.position || { x: 0, y: 0 };
+ },
+ updateFlowPool: (
+ nodeId: string,
+ data: VertexBuildTypeAPI | ChatOutputType | ChatInputType,
+ buildId?: string,
+ ) => {
+ let newFlowPool = cloneDeep({ ...get().flowPool });
+ if (!newFlowPool[nodeId]) {
+ return;
+ } else {
+ let index = newFlowPool[nodeId].length - 1;
+ if (buildId) {
+ index = newFlowPool[nodeId].findIndex((flow) => flow.id === buildId);
+ }
+ //check if the data is a flowpool object
+ if ((data as VertexBuildTypeAPI).valid !== undefined) {
+ newFlowPool[nodeId][index] = data as VertexBuildTypeAPI;
+ }
+ //update data results
+ else {
+ newFlowPool[nodeId][index].data.message = data as
+ | ChatOutputType
+ | ChatInputType;
+ }
+ }
+ get().setFlowPool(newFlowPool);
+ },
+ CleanFlowPool: () => {
+ get().setFlowPool({});
+ },
+ setPending: (isPending) => {
+ set({ isPending });
+ },
+ resetFlow: (flow) => {
+ const nodes = flow?.data?.nodes ?? [];
+ const edges = flow?.data?.edges ?? [];
+ let brokenEdges = detectBrokenEdgesEdges(nodes, edges);
+ if (brokenEdges.length > 0) {
+ useAlertStore.getState().setErrorData({
+ title: BROKEN_EDGES_WARNING,
+ list: brokenEdges.map((edge) => brokenEdgeMessage(edge)),
+ });
+ }
+ let newEdges = cleanEdges(nodes, edges);
+ const { inputs, outputs } = getInputsAndOutputs(nodes);
+ get().updateComponentsToUpdate(nodes);
+ unselectAllNodesEdges(nodes, edges);
+ set({
+ nodes,
+ edges: newEdges,
+ flowState: undefined,
+ inputs,
+ outputs,
+ hasIO: inputs.length > 0 || outputs.length > 0,
+ flowPool: {},
+ currentFlow: flow,
+ positionDictionary: {},
+ });
+ },
+ setIsBuilding: (isBuilding) => {
+ set({ isBuilding });
+ },
+ setFlowState: (flowState) => {
+ const newFlowState =
+ typeof flowState === "function" ? flowState(get().flowState) : flowState;
+
+ if (newFlowState !== get().flowState) {
+ set(() => ({
+ flowState: newFlowState,
+ }));
+ }
+ },
+ setReactFlowInstance: (newState) => {
+ set({ reactFlowInstance: newState });
+ },
+ onNodesChange: (changes: NodeChange[]) => {
+ set({
+ nodes: applyNodeChanges(changes, get().nodes),
+ });
+ },
+ onEdgesChange: (changes: EdgeChange[]) => {
+ set({
+ edges: applyEdgeChanges(changes, get().edges),
+ });
+ },
+ setNodes: (change) => {
+ let newChange = typeof change === "function" ? change(get().nodes) : change;
+ let newEdges = cleanEdges(newChange, get().edges);
+ const { inputs, outputs } = getInputsAndOutputs(newChange);
+ get().updateComponentsToUpdate(newChange);
+ set({
+ edges: newEdges,
+ nodes: newChange,
+ flowState: undefined,
+ inputs,
+ outputs,
+ hasIO: inputs.length > 0 || outputs.length > 0,
+ });
+ get().updateCurrentFlow({ nodes: newChange, edges: newEdges });
+ if (get().autoSaveFlow) {
+ get().autoSaveFlow!();
+ }
+ },
+ setEdges: (change) => {
+ let newChange = typeof change === "function" ? change(get().edges) : change;
+ set({
+ edges: newChange,
+ flowState: undefined,
+ });
+ get().updateCurrentFlow({ edges: newChange });
+ if (get().autoSaveFlow) {
+ get().autoSaveFlow!();
+ }
+ },
+ setNode: (
+ id: string,
+ change: AllNodeType | ((oldState: AllNodeType) => AllNodeType),
+ isUserChange: boolean = true,
+ callback?: () => void,
+ ) => {
+ let newChange =
+ typeof change === "function"
+ ? change(get().nodes.find((node) => node.id === id)!)
+ : change;
+
+ const newNodes = get().nodes.map((node) => {
+ if (node.id === id) {
+ if (isUserChange) {
+ if ((node.data as NodeDataType).node?.frozen) {
+ (newChange.data as NodeDataType).node!.frozen = false;
+ }
+ }
+ return newChange;
+ }
+ return node;
+ });
+
+ const newEdges = cleanEdges(newNodes, get().edges);
+
+ set((state) => {
+ if (callback) {
+ // Defer the callback execution to ensure it runs after state updates are fully applied.
+ queueMicrotask(callback);
+ }
+ return {
+ ...state,
+ nodes: newNodes,
+ edges: newEdges,
+ };
+ });
+ get().updateCurrentFlow({ nodes: newNodes, edges: newEdges });
+ if (get().autoSaveFlow) {
+ get().autoSaveFlow!();
+ }
+ },
+ getNode: (id: string) => {
+ return get().nodes.find((node) => node.id === id);
+ },
+ deleteNode: (nodeId) => {
+ const { filteredNodes, deletedNode } = get().nodes.reduce<{
+ filteredNodes: AllNodeType[];
+ deletedNode: AllNodeType | null;
+ }>(
+ (acc, node) => {
+ const isMatch =
+ typeof nodeId === "string"
+ ? node.id === nodeId
+ : nodeId.includes(node.id);
+
+ if (isMatch) {
+ acc.deletedNode = node;
+ } else {
+ acc.filteredNodes.push(node);
+ }
+
+ return acc;
+ },
+ { filteredNodes: [], deletedNode: null },
+ );
+
+ get().setNodes(filteredNodes);
+
+ if (deletedNode) {
+ track("Component Deleted", { componentType: deletedNode.data.type });
+ }
+ },
+ deleteEdge: (edgeId) => {
+ get().setEdges(
+ get().edges.filter((edge) =>
+ typeof edgeId === "string"
+ ? edge.id !== edgeId
+ : !edgeId.includes(edge.id),
+ ),
+ );
+ track("Component Connection Deleted", { edgeId });
+ },
+ paste: (selection, position) => {
+ if (
+ selection.nodes.some((node) => node.data.type === "ChatInput") &&
+ checkChatInput(get().nodes)
+ ) {
+ useAlertStore.getState().setNoticeData({
+ title: "You can only have one Chat Input component in a flow.",
+ });
+ selection.nodes = selection.nodes.filter(
+ (node) => node.data.type !== "ChatInput",
+ );
+ selection.edges = selection.edges.filter(
+ (edge) =>
+ selection.nodes.some((node) => edge.source === node.id) &&
+ selection.nodes.some((node) => edge.target === node.id),
+ );
+ }
+
+ let minimumX = Infinity;
+ let minimumY = Infinity;
+ let idsMap = {};
+ let newNodes: AllNodeType[] = get().nodes;
+ let newEdges = get().edges;
+ selection.nodes.forEach((node: Node) => {
+ if (node.position.y < minimumY) {
+ minimumY = node.position.y;
+ }
+ if (node.position.x < minimumX) {
+ minimumX = node.position.x;
+ }
+ });
+
+ const insidePosition = position.paneX
+ ? { x: position.paneX + position.x, y: position.paneY! + position.y }
+ : get().reactFlowInstance!.screenToFlowPosition({
+ x: position.x,
+ y: position.y,
+ });
+
+ let internalPostionDictionary = get().positionDictionary;
+ if (Object.keys(internalPostionDictionary).length === 0) {
+ internalPostionDictionary = buildPositionDictionary(get().nodes);
+ }
+ while (!get().isPositionAvailable(insidePosition)) {
+ insidePosition.x += 10;
+ insidePosition.y += 10;
+ }
+ internalPostionDictionary[insidePosition.x] = insidePosition.y;
+ get().setPositionDictionary(internalPostionDictionary);
+
+ selection.nodes.forEach((node: AllNodeType) => {
+ // Generate a unique node ID
+ let newId = getNodeId(node.data.type);
+ idsMap[node.id] = newId;
+
+ // Create a new node object with the correct type
+ const newNode = {
+ id: newId,
+ type: node.type as "genericNode" | "noteNode",
+ position: {
+ x: insidePosition.x + node.position!.x - minimumX,
+ y: insidePosition.y + node.position!.y - minimumY,
+ },
+ data: {
+ ...cloneDeep(node.data),
+ id: newId,
+ },
+ } as AllNodeType;
+
+ updateGroupRecursion(
+ newNode,
+ selection.edges,
+ useGlobalVariablesStore.getState().unavailableFields,
+ useGlobalVariablesStore.getState().globalVariablesEntries,
+ );
+
+ // Add the new node to the list of nodes in state
+ newNodes = newNodes
+ .map((node) => ({ ...node, selected: false }))
+ .concat({ ...newNode, selected: false });
+ });
+ get().setNodes(newNodes);
+
+ selection.edges.forEach((edge: EdgeType) => {
+ let source = idsMap[edge.source];
+ let target = idsMap[edge.target];
+ const sourceHandleObject: sourceHandleType = scapeJSONParse(
+ edge.sourceHandle!,
+ );
+ let sourceHandle = scapedJSONStringfy({
+ ...sourceHandleObject,
+ id: source,
+ });
+ sourceHandleObject.id = source;
+
+ const targetHandleObject: targetHandleType = scapeJSONParse(
+ edge.targetHandle!,
+ );
+ let targetHandle = scapedJSONStringfy({
+ ...targetHandleObject,
+ id: target,
+ });
+ targetHandleObject.id = target;
+
+ edge.data = {
+ sourceHandle: sourceHandleObject,
+ targetHandle: targetHandleObject,
+ };
+
+ let id = getHandleId(source, sourceHandle, target, targetHandle);
+ newEdges = addEdge(
+ {
+ source,
+ target,
+ sourceHandle,
+ targetHandle,
+ id,
+ data: cloneDeep(edge.data),
+ selected: false,
+ },
+ newEdges.map((edge) => ({ ...edge, selected: false })),
+ );
+ });
+ get().setEdges(newEdges);
+ },
+ setLastCopiedSelection: (newSelection, isCrop = false) => {
+ if (isCrop) {
+ const nodesIdsSelected = newSelection!.nodes.map((node) => node.id);
+ const edgesIdsSelected = newSelection!.edges.map((edge) => edge.id);
+
+ nodesIdsSelected.forEach((id) => {
+ get().deleteNode(id);
+ });
+
+ edgesIdsSelected.forEach((id) => {
+ get().deleteEdge(id);
+ });
+
+ const newNodes = get().nodes.filter(
+ (node) => !nodesIdsSelected.includes(node.id),
+ );
+ const newEdges = get().edges.filter(
+ (edge) => !edgesIdsSelected.includes(edge.id),
+ );
+
+ set({ nodes: newNodes, edges: newEdges });
+ }
+
+ set({ lastCopiedSelection: newSelection });
+ },
+ cleanFlow: () => {
+ set({
+ nodes: [],
+ edges: [],
+ flowState: undefined,
+ getFilterEdge: [],
+ });
+ },
+ setFilterEdge: (newState) => {
+ if (newState.length === 0) {
+ set({ filterType: undefined });
+ }
+ set({ getFilterEdge: newState });
+ },
+ getFilterEdge: [],
+ onConnect: (connection) => {
+ const dark = useDarkStore.getState().dark;
+ // const commonMarkerProps = {
+ // type: MarkerType.ArrowClosed,
+ // width: 20,
+ // height: 20,
+ // color: dark ? "#555555" : "#000000",
+ // };
+
+ // const inputTypes = INPUT_TYPES;
+ // const outputTypes = OUTPUT_TYPES;
+
+ // const findNode = useFlowStore
+ // .getState()
+ // .nodes.find(
+ // (node) => node.id === connection.source || node.id === connection.target
+ // );
+
+ // const sourceType = findNode?.data?.type;
+ // let isIoIn = false;
+ // let isIoOut = false;
+ // if (sourceType) {
+ // isIoIn = inputTypes.has(sourceType);
+ // isIoOut = outputTypes.has(sourceType);
+ // }
+
+ let newEdges: EdgeType[] = [];
+ get().setEdges((oldEdges) => {
+ newEdges = addEdge(
+ {
+ ...connection,
+ data: {
+ targetHandle: scapeJSONParse(connection.targetHandle!),
+ sourceHandle: scapeJSONParse(connection.sourceHandle!),
+ },
+ },
+ oldEdges,
+ );
+
+ return newEdges;
+ });
+ },
+ unselectAll: () => {
+ let newNodes = cloneDeep(get().nodes);
+ newNodes.forEach((node) => {
+ node.selected = false;
+ let newEdges = cleanEdges(newNodes, get().edges);
+ set({
+ nodes: newNodes,
+ edges: newEdges,
+ });
+ });
+ },
+ buildFlow: async ({
+ startNodeId,
+ stopNodeId,
+ input_value,
+ files,
+ silent,
+ session,
+ stream = true,
+ }: {
+ startNodeId?: string;
+ stopNodeId?: string;
+ input_value?: string;
+ files?: string[];
+ silent?: boolean;
+ session?: string;
+ stream?: boolean;
+ }) => {
+ get().setIsBuilding(true);
+ const currentFlow = useFlowsManagerStore.getState().currentFlow;
+ const setSuccessData = useAlertStore.getState().setSuccessData;
+ const setErrorData = useAlertStore.getState().setErrorData;
+ const setNoticeData = useAlertStore.getState().setNoticeData;
+
+ const edges = get().edges;
+ let error = false;
+ for (const edge of edges) {
+ const errors = validateEdge(edge, get().nodes, edges);
+ if (errors.length > 0) {
+ error = true;
+ setErrorData({
+ title: MISSED_ERROR_ALERT,
+ list: errors,
+ });
+ }
+ }
+ if (error) {
+ get().setIsBuilding(false);
+ throw new Error("Invalid components");
+ }
+
+ function validateSubgraph(nodes: string[]) {
+ const errorsObjs = validateNodes(
+ get().nodes.filter((node) => nodes.includes(node.id)),
+ get().edges,
+ );
+
+ const errors = errorsObjs.map((obj) => obj.errors).flat();
+ if (errors.length > 0) {
+ setErrorData({
+ title: MISSED_ERROR_ALERT,
+ list: errors,
+ });
+ get().setIsBuilding(false);
+ const ids = errorsObjs.map((obj) => obj.id).flat();
+
+ get().updateBuildStatus(ids, BuildStatus.ERROR);
+ throw new Error("Invalid components");
+ }
+ // get().updateEdgesRunningByNodes(nodes, true);
+ }
+ function handleBuildUpdate(
+ vertexBuildData: VertexBuildTypeAPI,
+ status: BuildStatus,
+ runId: string,
+ ) {
+ if (vertexBuildData && vertexBuildData.inactivated_vertices) {
+ get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
+ get().updateBuildStatus(
+ vertexBuildData.inactivated_vertices,
+ BuildStatus.INACTIVE,
+ );
+ }
+
+ if (vertexBuildData.next_vertices_ids) {
+ // next_vertices_ids is a list of vertices that are going to be built next
+ // verticesLayers is a list of list of vertices ids, where each list is a layer of vertices
+ // we want to add a new layer (next_vertices_ids) to the list of layers (verticesLayers)
+ // and the values of next_vertices_ids to the list of vertices ids (verticesIds)
+
+ // const nextVertices will be the zip of vertexBuildData.next_vertices_ids and
+ // vertexBuildData.top_level_vertices
+ // the VertexLayerElementType as {id: next_vertices_id, layer: top_level_vertex}
+
+ // next_vertices_ids should be next_vertices_ids without the inactivated vertices
+ const next_vertices_ids = vertexBuildData.next_vertices_ids.filter(
+ (id) => !vertexBuildData.inactivated_vertices?.includes(id),
+ );
+ const top_level_vertices = vertexBuildData.top_level_vertices.filter(
+ (vertex) => !vertexBuildData.inactivated_vertices?.includes(vertex),
+ );
+ let nextVertices: VertexLayerElementType[] = zip(
+ next_vertices_ids,
+ top_level_vertices,
+ ).map(([id, reference]) => ({ id: id!, reference }));
+
+ // Now we filter nextVertices to remove any vertices that are in verticesLayers
+ // because they are already being built
+ // each layer is a list of vertexlayerelementtypes
+ let lastLayer =
+ get().verticesBuild!.verticesLayers[
+ get().verticesBuild!.verticesLayers.length - 1
+ ];
+
+ nextVertices = nextVertices.filter(
+ (vertexElement) =>
+ !lastLayer.some(
+ (layerElement) =>
+ layerElement.id === vertexElement.id &&
+ layerElement.reference === vertexElement.reference,
+ ),
+ );
+ const newLayers = [
+ ...get().verticesBuild!.verticesLayers,
+ nextVertices,
+ ];
+ const newIds = [
+ ...get().verticesBuild!.verticesIds,
+ ...next_vertices_ids,
+ ];
+ if (
+ ENABLE_DATASTAX_LANGFLOW &&
+ vertexBuildData?.id?.includes("AstraDB")
+ ) {
+ const search_results: LogsLogType[] = Object.values(
+ vertexBuildData?.data?.logs?.search_results,
+ );
+ search_results.forEach((log) => {
+ if (
+ log.message.includes("Adding") &&
+ log.message.includes("documents") &&
+ log.message.includes("Vector Store")
+ ) {
+ trackDataLoaded(
+ get().currentFlow?.id,
+ get().currentFlow?.name,
+ "AstraDB Vector Store",
+ vertexBuildData?.id,
+ );
+ }
+ });
+ }
+ get().updateVerticesBuild({
+ verticesIds: newIds,
+ verticesLayers: newLayers,
+ runId: runId,
+ verticesToRun: get().verticesBuild!.verticesToRun,
+ });
+
+ get().updateBuildStatus(top_level_vertices, BuildStatus.TO_BUILD);
+ }
+
+ get().addDataToFlowPool(
+ { ...vertexBuildData, run_id: runId },
+ vertexBuildData.id,
+ );
+
+ useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
+ }
+ await buildFlowVerticesWithFallback({
+ session,
+ input_value,
+ files,
+ flowId: currentFlow!.id,
+ startNodeId,
+ stopNodeId,
+ onGetOrderSuccess: () => {
+ if (!silent) {
+ setNoticeData({ title: "Running components" });
+ }
+ },
+ onBuildComplete: (allNodesValid) => {
+ const nodeId = startNodeId || stopNodeId;
+ if (!silent) {
+ if (allNodesValid) {
+ setSuccessData({
+ title: nodeId
+ ? `${
+ get().nodes.find((node) => node.id === nodeId)?.data.node
+ ?.display_name
+ } built successfully`
+ : FLOW_BUILD_SUCCESS_ALERT,
+ });
+ }
+ }
+ get().updateEdgesRunningByNodes(
+ get().nodes.map((n) => n.id),
+ false,
+ );
+ get().setIsBuilding(false);
+ trackFlowBuild(get().currentFlow?.name ?? "Unknown", false, {
+ flowId: get().currentFlow?.id,
+ });
+ },
+ onBuildUpdate: handleBuildUpdate,
+ onBuildError: (title: string, list: string[], elementList) => {
+ const idList =
+ (elementList
+ ?.map((element) => element.id)
+ .filter(Boolean) as string[]) ?? get().nodes.map((n) => n.id);
+ useFlowStore.getState().updateBuildStatus(idList, BuildStatus.ERROR);
+ if (get().componentsToUpdate.length > 0)
+ setErrorData({
+ title:
+ "There are outdated components in the flow. The error could be related to them.",
+ });
+ get().updateEdgesRunningByNodes(
+ get().nodes.map((n) => n.id),
+ false,
+ );
+ setErrorData({ list, title });
+ get().setIsBuilding(false);
+ get().buildController.abort();
+ trackFlowBuild(get().currentFlow?.name ?? "Unknown", true, {
+ flowId: get().currentFlow?.id,
+ error: list,
+ });
+ },
+ onBuildStart: (elementList) => {
+ const idList = elementList
+ // reference is the id of the vertex or the id of the parent in a group node
+ .map((element) => element.reference)
+ .filter(Boolean) as string[];
+ get().updateBuildStatus(idList, BuildStatus.BUILDING);
+
+ const edges = get().edges;
+ const newEdges = edges.map((edge) => {
+ if (
+ edge.data?.targetHandle &&
+ idList.includes(edge.data.targetHandle.id ?? "")
+ ) {
+ edge.className = "ran";
+ }
+ return edge;
+ });
+ set({ edges: newEdges });
+ },
+ onValidateNodes: validateSubgraph,
+ nodes: get().nodes || undefined,
+ edges: get().edges || undefined,
+ logBuilds: get().onFlowPage,
+ stream,
+ });
+ get().setIsBuilding(false);
+ get().revertBuiltStatusFromBuilding();
+ },
+ getFlow: () => {
+ return {
+ nodes: get().nodes,
+ edges: get().edges,
+ viewport: get().reactFlowInstance?.getViewport()!,
+ };
+ },
+ updateEdgesRunningByNodes: (ids: string[], running: boolean) => {
+ const edges = get().edges;
+ const newEdges = edges.map((edge) => {
+ if (
+ edge.data?.sourceHandle &&
+ ids.includes(edge.data.sourceHandle.id ?? "")
+ ) {
+ edge.animated = running;
+ edge.className = running ? "running" : "";
+ } else {
+ edge.animated = false;
+ edge.className = "not-running";
+ }
+ return edge;
+ });
+ set({ edges: newEdges });
+ },
+ clearEdgesRunningByNodes: async (): Promise => {
+ return new Promise((resolve) => {
+ const edges = get().edges;
+ const newEdges = edges.map((edge) => {
+ edge.animated = false;
+ edge.className = "";
+ return edge;
+ });
+ set({ edges: newEdges });
+ resolve();
+ });
+ },
+
+ updateVerticesBuild: (
+ vertices: {
+ verticesIds: string[];
+ verticesLayers: VertexLayerElementType[][];
+ runId?: string;
+ verticesToRun: string[];
+ } | null,
+ ) => {
+ set({ verticesBuild: vertices });
+ },
+ verticesBuild: null,
+ addToVerticesBuild: (vertices: string[]) => {
+ const verticesBuild = get().verticesBuild;
+ if (!verticesBuild) return;
+ set({
+ verticesBuild: {
+ ...verticesBuild,
+ verticesIds: [...verticesBuild.verticesIds, ...vertices],
+ },
+ });
+ },
+ removeFromVerticesBuild: (vertices: string[]) => {
+ const verticesBuild = get().verticesBuild;
+ if (!verticesBuild) return;
+ set({
+ verticesBuild: {
+ ...verticesBuild,
+ // remove the vertices from the list of vertices ids
+ // that are going to be built
+ verticesIds: get().verticesBuild!.verticesIds.filter(
+ // keep the vertices that are not in the list of vertices to remove
+ (vertex) => !vertices.includes(vertex),
+ ),
+ },
+ });
+ },
+ updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
+ const newFlowBuildStatus = { ...get().flowBuildStatus };
+ nodeIdList.forEach((id) => {
+ newFlowBuildStatus[id] = {
+ status,
+ };
+ if (status == BuildStatus.BUILT) {
+ const timestamp_string = new Date(Date.now()).toLocaleString();
+ newFlowBuildStatus[id].timestamp = timestamp_string;
+ }
+ });
+ set({ flowBuildStatus: newFlowBuildStatus });
+ },
+ revertBuiltStatusFromBuilding: () => {
+ const newFlowBuildStatus = { ...get().flowBuildStatus };
+ Object.keys(newFlowBuildStatus).forEach((id) => {
+ if (newFlowBuildStatus[id].status === BuildStatus.BUILDING) {
+ newFlowBuildStatus[id].status = BuildStatus.BUILT;
+ }
+ });
+ set({ flowBuildStatus: newFlowBuildStatus });
+ },
+ currentFlow: undefined,
+ setCurrentFlow: (flow) => {
+ set({ currentFlow: flow });
+ },
+ updateCurrentFlow: ({ nodes, edges }) => {
+ set({
+ currentFlow: {
+ ...get().currentFlow!,
+ data: {
+ nodes: nodes ?? get().currentFlow?.data?.nodes ?? [],
+ edges: edges ?? get().currentFlow?.data?.edges ?? [],
+ viewport: get().currentFlow?.data?.viewport ?? {
+ x: 0,
+ y: 0,
+ zoom: 1,
+ },
+ },
+ },
+ });
+ },
+ buildController: new AbortController(),
+ setBuildController: (controller) => {
+ set({ buildController: controller });
+ },
+ handleDragging: undefined,
+ setHandleDragging: (handleDragging) => {
+ set({ handleDragging });
+ },
+
+ filterType: undefined,
+ setFilterType: (filterType) => {
+ set({ filterType });
+ },
+ currentBuildingNodeId: undefined,
+ setCurrentBuildingNodeId: (nodeIds) => {
+ set({ currentBuildingNodeId: nodeIds });
+ },
+}));
+
+export default useFlowStore;
diff --git a/langflow/src/frontend/src/stores/flowsManagerStore.ts b/langflow/src/frontend/src/stores/flowsManagerStore.ts
new file mode 100644
index 0000000..dbb7144
--- /dev/null
+++ b/langflow/src/frontend/src/stores/flowsManagerStore.ts
@@ -0,0 +1,142 @@
+import { SAVE_DEBOUNCE_TIME } from "@/constants/constants";
+import { cloneDeep } from "lodash";
+import { create } from "zustand";
+import { FlowType } from "../types/flow";
+import {
+ FlowsManagerStoreType,
+ UseUndoRedoOptions,
+} from "../types/zustand/flowsManager";
+import useFlowStore from "./flowStore";
+
+const defaultOptions: UseUndoRedoOptions = {
+ maxHistorySize: 100,
+ enableShortcuts: true,
+};
+
+const past = {};
+const future = {};
+
+const useFlowsManagerStore = create((set, get) => ({
+ IOModalOpen: false,
+ setIOModalOpen: (IOModalOpen: boolean) => {
+ set({ IOModalOpen });
+ },
+ healthCheckMaxRetries: 5,
+ setHealthCheckMaxRetries: (healthCheckMaxRetries: number) =>
+ set({ healthCheckMaxRetries }),
+ autoSaving: true,
+ setAutoSaving: (autoSaving: boolean) => set({ autoSaving }),
+ autoSavingInterval: SAVE_DEBOUNCE_TIME,
+ setAutoSavingInterval: (autoSavingInterval: number) =>
+ set({ autoSavingInterval }),
+ examples: [],
+ setExamples: (examples: FlowType[]) => {
+ set({ examples });
+ },
+ currentFlowId: "",
+ setCurrentFlow: (flow: FlowType | undefined) => {
+ set({
+ currentFlow: flow,
+ currentFlowId: flow?.id ?? "",
+ });
+ useFlowStore.getState().resetFlow(flow);
+ },
+ getFlowById: (id: string) => {
+ return get().flows?.find((flow) => flow.id === id);
+ },
+ flows: undefined,
+ setFlows: (flows: FlowType[]) => {
+ set({
+ flows,
+ currentFlow: flows.find((flow) => flow.id === get().currentFlowId),
+ });
+ },
+ currentFlow: undefined,
+ saveLoading: false,
+ setSaveLoading: (saveLoading: boolean) => set({ saveLoading }),
+ isLoading: false,
+ setIsLoading: (isLoading: boolean) => set({ isLoading }),
+ takeSnapshot: () => {
+ const currentFlowId = get().currentFlowId;
+ // push the current graph to the past state
+ const flowStore = useFlowStore.getState();
+ const newState = {
+ nodes: cloneDeep(flowStore.nodes),
+ edges: cloneDeep(flowStore.edges),
+ };
+ const pastLength = past[currentFlowId]?.length ?? 0;
+ if (
+ pastLength > 0 &&
+ JSON.stringify(past[currentFlowId][pastLength - 1]) ===
+ JSON.stringify(newState)
+ )
+ return;
+ if (pastLength > 0) {
+ past[currentFlowId] = past[currentFlowId].slice(
+ pastLength - defaultOptions.maxHistorySize + 1,
+ pastLength,
+ );
+
+ past[currentFlowId].push(newState);
+ } else {
+ past[currentFlowId] = [newState];
+ }
+
+ future[currentFlowId] = [];
+ },
+ undo: () => {
+ const newState = useFlowStore.getState();
+ const currentFlowId = get().currentFlowId;
+ const pastLength = past[currentFlowId]?.length ?? 0;
+ const pastState = past[currentFlowId]?.[pastLength - 1] ?? null;
+
+ if (pastState) {
+ past[currentFlowId] = past[currentFlowId].slice(0, pastLength - 1);
+
+ if (!future[currentFlowId]) future[currentFlowId] = [];
+ future[currentFlowId].push({
+ nodes: newState.nodes,
+ edges: newState.edges,
+ });
+
+ newState.setNodes(pastState.nodes);
+ newState.setEdges(pastState.edges);
+ }
+ },
+ redo: () => {
+ const newState = useFlowStore.getState();
+ const currentFlowId = get().currentFlowId;
+ const futureLength = future[currentFlowId]?.length ?? 0;
+ const futureState = future[currentFlowId]?.[futureLength - 1] ?? null;
+
+ if (futureState) {
+ future[currentFlowId] = future[currentFlowId].slice(0, futureLength - 1);
+
+ if (!past[currentFlowId]) past[currentFlowId] = [];
+ past[currentFlowId].push({
+ nodes: newState.nodes,
+ edges: newState.edges,
+ });
+
+ newState.setNodes(futureState.nodes);
+ newState.setEdges(futureState.edges);
+ }
+ },
+ searchFlowsComponents: "",
+ setSearchFlowsComponents: (searchFlowsComponents: string) => {
+ set({ searchFlowsComponents });
+ },
+ selectedFlowsComponentsCards: [],
+ setSelectedFlowsComponentsCards: (selectedFlowsComponentsCards: string[]) => {
+ set({ selectedFlowsComponentsCards });
+ },
+ flowToCanvas: null,
+ setFlowToCanvas: async (flowToCanvas: FlowType | null) => {
+ await new Promise((resolve) => {
+ set({ flowToCanvas });
+ resolve();
+ });
+ },
+}));
+
+export default useFlowsManagerStore;
diff --git a/langflow/src/frontend/src/stores/foldersStore.tsx b/langflow/src/frontend/src/stores/foldersStore.tsx
new file mode 100644
index 0000000..9e8ba36
--- /dev/null
+++ b/langflow/src/frontend/src/stores/foldersStore.tsx
@@ -0,0 +1,20 @@
+import { create } from "zustand";
+import { FoldersStoreType } from "../types/zustand/folders";
+
+export const useFolderStore = create((set, get) => ({
+ loadingById: false,
+ setMyCollectionId: (myCollectionId) => {
+ set({ myCollectionId });
+ },
+ myCollectionId: "",
+ folderToEdit: null,
+ setFolderToEdit: (folder) => set(() => ({ folderToEdit: folder })),
+ folderDragging: false,
+ setFolderDragging: (folder) => set(() => ({ folderDragging: folder })),
+ folderIdDragging: "",
+ setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })),
+ starterProjectId: "",
+ setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
+ folders: [],
+ setFolders: (folders) => set(() => ({ folders: folders })),
+}));
diff --git a/langflow/src/frontend/src/stores/globalVariablesStore/globalVariables.ts b/langflow/src/frontend/src/stores/globalVariablesStore/globalVariables.ts
new file mode 100644
index 0000000..8096a6b
--- /dev/null
+++ b/langflow/src/frontend/src/stores/globalVariablesStore/globalVariables.ts
@@ -0,0 +1,15 @@
+import { create } from "zustand";
+import { GlobalVariablesStore } from "../../types/zustand/globalVariables";
+
+export const useGlobalVariablesStore = create(
+ (set, get) => ({
+ unavailableFields: {},
+ setUnavailableFields: (fields) => {
+ set({ unavailableFields: fields });
+ },
+ globalVariablesEntries: undefined,
+ setGlobalVariablesEntries: (entries) => {
+ set({ globalVariablesEntries: entries });
+ },
+ }),
+);
diff --git a/langflow/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx b/langflow/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx
new file mode 100644
index 0000000..5519c7b
--- /dev/null
+++ b/langflow/src/frontend/src/stores/globalVariablesStore/utils/get-unavailable-fields.tsx
@@ -0,0 +1,15 @@
+import { GlobalVariable } from "@/types/global_variables";
+
+export default function getUnavailableFields(variables: GlobalVariable[]): {
+ [name: string]: string;
+} {
+ const unVariables: { [name: string]: string } = {};
+ variables.forEach((variable) => {
+ if (variable.default_fields) {
+ variable.default_fields!.forEach((field) => {
+ unVariables[field] = variable.name;
+ });
+ }
+ });
+ return unVariables;
+}
diff --git a/langflow/src/frontend/src/stores/locationStore.ts b/langflow/src/frontend/src/stores/locationStore.ts
new file mode 100644
index 0000000..a573903
--- /dev/null
+++ b/langflow/src/frontend/src/stores/locationStore.ts
@@ -0,0 +1,21 @@
+import { create } from "zustand";
+import { LocationStoreType } from "../types/zustand/location";
+
+export const useLocationStore = create((set, get) => ({
+ routeHistory: [],
+ setRouteHistory: (location) => {
+ let routeHistoryArray = get().routeHistory;
+ routeHistoryArray.push(location);
+
+ if (routeHistoryArray?.length > 100) {
+ routeHistoryArray.shift();
+ set({
+ routeHistory: routeHistoryArray,
+ });
+ }
+
+ set({
+ routeHistory: routeHistoryArray,
+ });
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/messagesStore.ts b/langflow/src/frontend/src/stores/messagesStore.ts
new file mode 100644
index 0000000..01227e2
--- /dev/null
+++ b/langflow/src/frontend/src/stores/messagesStore.ts
@@ -0,0 +1,89 @@
+import { create } from "zustand";
+import { MessagesStoreType } from "../types/zustand/messages";
+
+export const useMessagesStore = create((set, get) => ({
+ displayLoadingMessage: false,
+ deleteSession: (id) => {
+ set((state) => {
+ const updatedMessages = state.messages.filter(
+ (msg) => msg.session_id !== id,
+ );
+ return { messages: updatedMessages };
+ });
+ },
+ messages: [],
+ setMessages: (messages) => {
+ set(() => ({ messages: messages }));
+ },
+ addMessage: (message) => {
+ const existingMessage = get().messages.find((msg) => msg.id === message.id);
+ if (existingMessage) {
+ get().updateMessagePartial(message);
+ return;
+ }
+ if (message.sender === "Machine") {
+ set(() => ({ displayLoadingMessage: false }));
+ }
+ set(() => ({ messages: [...get().messages, message] }));
+ },
+ removeMessage: (message) => {
+ set(() => ({
+ messages: get().messages.filter((msg) => msg.id !== message.id),
+ }));
+ },
+ updateMessage: (message) => {
+ set(() => ({
+ messages: get().messages.map((msg) =>
+ msg.id === message.id ? message : msg,
+ ),
+ }));
+ },
+ updateMessagePartial: (message) => {
+ // search for the message and update it
+ // look for the message list backwards to find the message faster
+ set((state) => {
+ const updatedMessages = [...state.messages];
+ for (let i = state.messages.length - 1; i >= 0; i--) {
+ if (state.messages[i].id === message.id) {
+ updatedMessages[i] = { ...updatedMessages[i], ...message };
+ break;
+ }
+ }
+ return { messages: updatedMessages };
+ });
+ },
+ updateMessageText: (id, chunk) => {
+ set((state) => {
+ const updatedMessages = [...state.messages];
+ for (let i = state.messages.length - 1; i >= 0; i--) {
+ if (state.messages[i].id === id) {
+ updatedMessages[i] = {
+ ...updatedMessages[i],
+ text: updatedMessages[i].text + chunk,
+ };
+ break;
+ }
+ }
+ return { messages: updatedMessages };
+ });
+ },
+ clearMessages: () => {
+ set(() => ({ messages: [] }));
+ },
+ removeMessages: (ids) => {
+ return new Promise((resolve, reject) => {
+ try {
+ set((state) => {
+ const updatedMessages = state.messages.filter(
+ (msg) => !ids.includes(msg.id),
+ );
+ get().setMessages(updatedMessages);
+ resolve(updatedMessages);
+ return { messages: updatedMessages };
+ });
+ } catch (error) {
+ reject(error);
+ }
+ });
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/shortcuts.ts b/langflow/src/frontend/src/stores/shortcuts.ts
new file mode 100644
index 0000000..038172f
--- /dev/null
+++ b/langflow/src/frontend/src/stores/shortcuts.ts
@@ -0,0 +1,59 @@
+import { toCamelCase } from "@/utils/utils";
+import { create } from "zustand";
+import { defaultShortcuts } from "../constants/constants";
+import { shortcutsStoreType } from "../types/store";
+
+export const useShortcutsStore = create((set, get) => ({
+ shortcuts: defaultShortcuts,
+ setShortcuts: (newShortcuts) => {
+ set({ shortcuts: newShortcuts });
+ },
+ outputInspection: "o",
+ play: "p",
+ flow: "mod+shift+b",
+ undo: "mod+z",
+ redo: "mod+y",
+ redoAlt: "mod+shift+z",
+ openPlayground: "mod+k",
+ advancedSettings: "mod+shift+a",
+ minimize: "mod+.",
+ code: "space",
+ copy: "mod+c",
+ duplicate: "mod+d",
+ componentShare: "mod+shift+s",
+ docs: "mod+shift+d",
+ changesSave: "mod+s",
+ saveComponent: "mod+alt+s",
+ delete: "backspace",
+ group: "mod+g",
+ cut: "mod+x",
+ paste: "mod+v",
+ api: "r",
+ update: "mod+u",
+ download: "mod+j",
+ freeze: "mod+f",
+ freezePath: "mod+shift+f",
+ toolMode: "mod+shift+m",
+ toggleSidebar: "mod+b",
+ searchComponentsSidebar: "/",
+ updateUniqueShortcut: (name, combination) => {
+ set({
+ [name]: combination,
+ });
+ },
+ getShortcutsFromStorage: () => {
+ if (localStorage.getItem("langflow-shortcuts")) {
+ const savedShortcuts = localStorage.getItem("langflow-shortcuts");
+ const savedArr = JSON.parse(savedShortcuts!);
+ savedArr.forEach(({ name, shortcut }) => {
+ let shortcutName = toCamelCase(name);
+ set({
+ [shortcutName]: shortcut,
+ });
+ });
+ get().setShortcuts(JSON.parse(savedShortcuts!));
+ }
+ },
+}));
+
+useShortcutsStore.getState().getShortcutsFromStorage();
diff --git a/langflow/src/frontend/src/stores/storeStore.ts b/langflow/src/frontend/src/stores/storeStore.ts
new file mode 100644
index 0000000..a72e3ea
--- /dev/null
+++ b/langflow/src/frontend/src/stores/storeStore.ts
@@ -0,0 +1,36 @@
+import { ENABLE_LANGFLOW_STORE } from "@/customization/feature-flags";
+import { create } from "zustand";
+import { checkHasApiKey, checkHasStore } from "../controllers/API";
+import { StoreStoreType } from "../types/zustand/store";
+
+export const useStoreStore = create((set) => ({
+ hasStore: ENABLE_LANGFLOW_STORE,
+ validApiKey: false,
+ hasApiKey: false,
+ loadingApiKey: true,
+ checkHasStore: () => {
+ checkHasStore().then((res) => {
+ set({
+ hasStore: ENABLE_LANGFLOW_STORE && (res?.enabled ?? false),
+ });
+ });
+ },
+ updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),
+ updateLoadingApiKey: (loadingApiKey) =>
+ set(() => ({ loadingApiKey: loadingApiKey })),
+ updateHasApiKey: (hasApiKey) => set(() => ({ hasApiKey: hasApiKey })),
+ fetchApiData: async () => {
+ set({ loadingApiKey: true });
+ try {
+ const res = await checkHasApiKey();
+ set({
+ validApiKey: res?.is_valid ?? false,
+ hasApiKey: res?.has_api_key ?? false,
+ loadingApiKey: false,
+ });
+ } catch (e) {
+ set({ loadingApiKey: false });
+ console.log(e);
+ }
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/tweaksStore.ts b/langflow/src/frontend/src/stores/tweaksStore.ts
new file mode 100644
index 0000000..290ddff
--- /dev/null
+++ b/langflow/src/frontend/src/stores/tweaksStore.ts
@@ -0,0 +1,129 @@
+import { getChangesType } from "@/modals/apiModal/utils/get-changes-types";
+import { getNodesWithDefaultValue } from "@/modals/apiModal/utils/get-nodes-with-default-value";
+import { createTabsArray } from "@/modals/apiModal/utils/tabs-array";
+import { FlowType, NodeDataType } from "@/types/flow";
+import { GetCodesType } from "@/types/tweaks";
+import { customStringify } from "@/utils/reactflowUtils";
+import { create } from "zustand";
+import { TweaksStoreType } from "../types/zustand/tweaks";
+import useFlowStore from "./flowStore";
+
+export const useTweaksStore = create((set, get) => ({
+ activeTweaks: false,
+ setActiveTweaks: (activeTweaks: boolean) => {
+ set({ activeTweaks }), get().refreshTabs();
+ },
+ nodes: [],
+ setNodes: (change) => {
+ let newChange = typeof change === "function" ? change(get().nodes) : change;
+
+ set({
+ nodes: newChange,
+ });
+ get().refreshTabs();
+ },
+ setNode: (id, change) => {
+ let newChange =
+ typeof change === "function"
+ ? change(get().nodes.find((node) => node.id === id)!)
+ : change;
+ get().setNodes((oldNodes) =>
+ oldNodes.map((node) => {
+ if (node.id === id) {
+ if ((node.data as NodeDataType).node?.frozen) {
+ (newChange.data as NodeDataType).node!.frozen = false;
+ }
+ return newChange;
+ }
+ return node;
+ }),
+ );
+ },
+ getNode: (id: string) => {
+ return get().nodes.find((node) => node.id === id);
+ },
+ autoLogin: false,
+ flow: null,
+ getCodes: {},
+ initialSetup: (
+ autoLogin: boolean,
+ flow: FlowType,
+ getCodes: GetCodesType,
+ ) => {
+ useFlowStore.getState().unselectAll();
+ set({
+ nodes: getNodesWithDefaultValue(flow?.data?.nodes ?? []),
+ autoLogin,
+ flow,
+ getCodes,
+ });
+ get().refreshTabs();
+ },
+ tabs: [],
+ refreshTabs: () => {
+ const autoLogin = get().autoLogin;
+ const flow = get().flow;
+ const tweak = {};
+ const nodes = get().nodes;
+ const originalNodes = flow?.data?.nodes;
+ if (!flow) return;
+
+ nodes.forEach((node) => {
+ const originalNodeTemplate = originalNodes?.find((n) => n.id === node.id)
+ ?.data?.node?.template;
+ const nodeTemplate = node.data?.node?.template;
+ if (originalNodeTemplate && nodeTemplate && node.type === "genericNode") {
+ const currentTweak = {};
+ Object.keys(nodeTemplate).forEach((name) => {
+ if (
+ customStringify(nodeTemplate[name]) !==
+ customStringify(originalNodeTemplate[name]) ||
+ get().activeTweaks
+ ) {
+ currentTweak[name] = getChangesType(
+ nodeTemplate[name].value,
+ nodeTemplate[name],
+ );
+ }
+ });
+ tweak[node.id] = currentTweak;
+ }
+ });
+ const codesObj = {};
+ const getCodes = get().getCodes;
+
+ const props = {
+ flowId: flow?.id,
+ flowName: flow?.name,
+ isAuth: autoLogin,
+ tweaksBuildedObject: tweak,
+ endpointName: flow?.endpoint_name,
+ activeTweaks: get().activeTweaks,
+ };
+
+ if (getCodes) {
+ if (getCodes.getCurlRunCode) {
+ codesObj["runCurlCode"] = getCodes.getCurlRunCode(props);
+ }
+ if (getCodes.getCurlWebhookCode && !!flow.webhook) {
+ codesObj["webhookCurlCode"] = getCodes.getCurlWebhookCode(props);
+ }
+ if (getCodes.getJsApiCode) {
+ codesObj["jsApiCode"] = getCodes.getJsApiCode(props);
+ }
+ if (getCodes.getPythonApiCode) {
+ codesObj["pythonApiCode"] = getCodes.getPythonApiCode(props);
+ }
+ if (getCodes.getPythonCode) {
+ codesObj["pythonCode"] = getCodes.getPythonCode(props);
+ }
+ if (getCodes.getWidgetCode) {
+ codesObj["widgetCode"] = getCodes.getWidgetCode(props);
+ }
+ }
+
+ set({
+ tabs: createTabsArray(codesObj, nodes.length > 0),
+ });
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/typesStore.ts b/langflow/src/frontend/src/stores/typesStore.ts
new file mode 100644
index 0000000..93b2afb
--- /dev/null
+++ b/langflow/src/frontend/src/stores/typesStore.ts
@@ -0,0 +1,40 @@
+import { create } from "zustand";
+import { APIDataType } from "../types/api";
+import { TypesStoreType } from "../types/zustand/types";
+import {
+ extractFieldsFromComponenents,
+ templatesGenerator,
+ typesGenerator,
+} from "../utils/reactflowUtils";
+
+export const useTypesStore = create((set, get) => ({
+ ComponentFields: new Set(),
+ setComponentFields: (fields) => {
+ set({ ComponentFields: fields });
+ },
+ addComponentField: (field) => {
+ set({ ComponentFields: get().ComponentFields.add(field) });
+ },
+ types: {},
+ templates: {},
+ data: {},
+ setTypes: (data: APIDataType) => {
+ set((old) => ({
+ types: typesGenerator(data),
+ data: { ...old.data, ...data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...old.data,
+ ...data,
+ }),
+ templates: templatesGenerator(data),
+ }));
+ },
+ setTemplates: (newState: {}) => {
+ set({ templates: newState });
+ },
+ setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => {
+ let newChange = typeof change === "function" ? change(get().data) : change;
+ set({ data: newChange });
+ get().setComponentFields(extractFieldsFromComponenents(newChange));
+ },
+}));
diff --git a/langflow/src/frontend/src/stores/utilityStore.ts b/langflow/src/frontend/src/stores/utilityStore.ts
new file mode 100644
index 0000000..4b43085
--- /dev/null
+++ b/langflow/src/frontend/src/stores/utilityStore.ts
@@ -0,0 +1,38 @@
+import { Pagination, Tag } from "@/types/utils/types";
+import { UtilityStoreType } from "@/types/zustand/utility";
+import { create } from "zustand";
+
+export const useUtilityStore = create((set, get) => ({
+ dismissAll: false,
+ setDismissAll: (dismissAll: boolean) => set({ dismissAll }),
+ chatValueStore: "",
+ setChatValueStore: (value: string) => set({ chatValueStore: value }),
+ selectedItems: [],
+ setSelectedItems: (itemId) => {
+ if (get().selectedItems.includes(itemId)) {
+ set({
+ selectedItems: get().selectedItems.filter((item) => item !== itemId),
+ });
+ } else {
+ set({ selectedItems: get().selectedItems.concat(itemId) });
+ }
+ },
+ healthCheckTimeout: null,
+ setHealthCheckTimeout: (timeout: string | null) =>
+ set({ healthCheckTimeout: timeout }),
+ playgroundScrollBehaves: "instant",
+ setPlaygroundScrollBehaves: (behaves: ScrollBehavior) =>
+ set({ playgroundScrollBehaves: behaves }),
+ maxFileSizeUpload: 100 * 1024 * 1024, // 100MB in bytes
+ setMaxFileSizeUpload: (maxFileSizeUpload: number) =>
+ set({ maxFileSizeUpload: maxFileSizeUpload * 1024 * 1024 }),
+ flowsPagination: {
+ page: 1,
+ size: 10,
+ },
+ setFlowsPagination: (flowsPagination: Pagination) => set({ flowsPagination }),
+ tags: [],
+ setTags: (tags: Tag[]) => set({ tags }),
+ featureFlags: {},
+ setFeatureFlags: (featureFlags: Record) => set({ featureFlags }),
+}));
diff --git a/langflow/src/frontend/src/style/ag-theme-shadcn.css b/langflow/src/frontend/src/style/ag-theme-shadcn.css
new file mode 100644
index 0000000..2c2b1f5
--- /dev/null
+++ b/langflow/src/frontend/src/style/ag-theme-shadcn.css
@@ -0,0 +1,80 @@
+/* set the background color of many elements across the grid */
+.ag-theme-shadcn {
+ --ag-foreground-color: hsl(var(--foreground)) !important;
+ --ag-background-color: hsl(var(--background)) !important;
+ --ag-secondary-foreground-color: hsl(var(--secondary-foreground)) !important;
+ --ag-data-color: hsl(var(--foreground)) !important;
+ --ag-header-foreground-color: hsl(var(--muted-foreground)) !important;
+ --ag-header-background-color: hsl(var(--background)) !important;
+ --ag-tooltip-background-color: hsl(var(--muted)) !important;
+ --ag-disabled-foreground-color: hsl(var(--muted-foreground)) !important;
+ --ag-border-color: hsl(var(--border)) !important;
+ --ag-selected-row-background-color: hsl(var(--accent)) !important;
+ --ag-menu-background-color: hsl(var(--accent)) !important;
+ --ag-panel-background-color: hsl(var(--accent)) !important;
+ --ag-row-hover-color: hsl(var(--primary-foreground)) !important;
+ --ag-header-height: 2.5rem !important;
+}
+
+.ag-theme-shadcn .ag-paging-panel {
+ height: 3rem !important;
+}
+
+.ag-row .ag-cell {
+ align-content: center !important;
+}
+
+.ag-cell {
+ line-height: 1.25rem;
+ padding-top: 0.675rem;
+ padding-bottom: 0.675rem;
+}
+
+.ag-cell-wrapper > *:not(.ag-cell-value):not(.ag-group-value) {
+ align-items: self-start;
+ position: relative;
+ top: 2px;
+}
+
+.ag-cell-wrapper > *:not(.ag-cell-value):not(.ag-group-value) {
+ height: 0px;
+}
+
+.ag-cell-wrapper {
+ align-items: normal !important;
+}
+
+.ag-body-horizontal-scroll-viewport,
+.ag-body-vertical-scroll-viewport {
+ cursor: auto;
+}
+
+.ag-body-horizontal-scroll-viewport::-webkit-scrollbar,
+.ag-body-vertical-scroll-viewport::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-track,
+.ag-body-vertical-scroll-viewport::-webkit-scrollbar-track {
+ background-color: hsl(var(--muted));
+}
+
+.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb,
+.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb {
+ background-color: hsl(var(--border));
+ border-radius: 999px;
+}
+
+.ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb:hover,
+.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
+ background-color: hsl(var(--placeholder-foreground));
+}
+
+.ag-paging-page-size {
+ display: none;
+}
+
+.ag-row {
+ cursor: pointer;
+}
diff --git a/langflow/src/frontend/src/style/applies.css b/langflow/src/frontend/src/style/applies.css
new file mode 100644
index 0000000..435629e
--- /dev/null
+++ b/langflow/src/frontend/src/style/applies.css
@@ -0,0 +1,1471 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+
+ body {
+ @apply bg-background text-foreground;
+ font-feature-settings:
+ "rlig" 1,
+ "calt" 1;
+ font-family: Inter, system-ui, sans-serif;
+ }
+}
+
+@keyframes slideDown {
+ from {
+ height: 0;
+ }
+ to {
+ height: var(--radix-accordion-content-height);
+ }
+}
+
+@keyframes slideUp {
+ from {
+ height: var(--radix-accordion-content-height);
+ }
+ to {
+ height: 0;
+ }
+}
+
+@keyframes gradient-motion-start {
+ 0% {
+ stop-color: rgb(156, 138, 236);
+ }
+ 50% {
+ stop-color: rgb(255, 130, 184);
+ }
+ 80% {
+ stop-color: rgb(255, 165, 100);
+ }
+ 100% {
+ stop-color: rgb(156, 138, 236);
+ }
+}
+
+@keyframes gradient-motion-end {
+ 0% {
+ stop-color: rgb(156, 138, 236);
+ }
+ 50% {
+ stop-color: rgb(255, 165, 100);
+ }
+ 80% {
+ stop-color: rgb(255, 130, 184);
+ }
+ 100% {
+ stop-color: rgb(156, 138, 236);
+ }
+}
+
+@layer components {
+ .round-buttons-position {
+ @apply fixed right-4;
+ }
+ .side-bar-arrangement {
+ @apply flex h-full w-[14.5rem] flex-col overflow-hidden border-r scrollbar-hide;
+ }
+ .side-bar-search-div-placement {
+ @apply relative mx-auto flex items-center py-3;
+ }
+ .side-bar-components-icon {
+ @apply h-6 w-4 text-ring;
+ }
+ .side-bar-components-text {
+ @apply w-full truncate pr-1 text-xs text-foreground;
+ }
+ .side-bar-components-div-form {
+ @apply flex w-full items-center justify-between rounded-md rounded-l-none border border-l-0 border-dashed border-ring px-3 py-1 text-sm;
+ }
+ .side-bar-components-border {
+ @apply cursor-grab rounded-l-md border-l-8;
+ }
+ .side-bar-components-gap {
+ @apply flex flex-col gap-2 p-2;
+ }
+ .side-bar-components-div-arrangement {
+ @apply w-full overflow-auto pb-10 scrollbar-hide;
+ }
+ .search-icon {
+ @apply absolute inset-y-0 right-0 flex items-center py-1.5 pr-5;
+ }
+ .extra-side-bar-save-disable {
+ @apply text-muted-foreground;
+ }
+ .side-bar-button-size {
+ @apply h-5 w-5;
+ }
+ .side-bar-button-size:hover {
+ @apply hover:text-accent-foreground;
+ }
+ .side-bar-buttons-arrangement {
+ @apply mb-2 mt-2 flex w-full items-center justify-between gap-2 px-2;
+ }
+ .side-bar-button {
+ @apply flex w-full;
+ }
+ .button-disable {
+ @apply pointer-events-none;
+ }
+ /* Frozen state border */
+ .border-ring-frozen {
+ position: relative;
+ @apply rounded-md border shadow-frozen-ring;
+ }
+ .border-ring-frozen::before {
+ content: "";
+ position: absolute;
+ top: -2px; /* Adjust based on desired border width */
+ bottom: -2px;
+ left: -2px;
+ right: -2px;
+ /* use the frozen-blue color from tailwind */
+ border-radius: inherit;
+ pointer-events: none;
+ @apply border-2 border-frozen-blue;
+ }
+ .border-frozen {
+ @apply border shadow-frozen-ring;
+ }
+ .frozen {
+ position: relative;
+ overflow: hidden;
+ }
+ .frozen::before,
+ .frozen::after {
+ content: "";
+ position: absolute;
+ top: -10px;
+ bottom: -10px;
+ left: -10px;
+ right: -10px;
+ background: rgba(
+ 255,
+ 255,
+ 255,
+ 0.5
+ ); /* Reduced opacity for better readability */
+ border-radius: 10px;
+ pointer-events: none;
+ }
+ .frozen::before {
+ filter: blur(5px); /* Less blur for better readability */
+ }
+ .frozen::after {
+ filter: blur(10px); /* Less blur for better readability */
+ opacity: 0.2; /* Reduced opacity */
+ }
+ .extra-side-bar-buttons {
+ @apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground transition-all duration-500 ease-in-out;
+ }
+ .header-menubar-item {
+ @apply relative flex cursor-default cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50;
+ }
+ .extra-side-bar-buttons:hover {
+ @apply hover:bg-muted;
+ }
+ .button-div-style {
+ @apply flex gap-2;
+ }
+ .primary-input {
+ @apply form-input block w-full truncate rounded-md border border-border bg-background px-3 text-left text-sm placeholder:text-muted-foreground hover:border-muted-foreground focus:border-foreground focus:placeholder-transparent focus:ring-0 focus:ring-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted disabled:opacity-100 placeholder:disabled:text-muted-foreground;
+ }
+
+ .skeleton-card {
+ @apply flex h-48 flex-col gap-6 rounded-lg border bg-background p-4;
+ }
+
+ .skeleton-card-wrapper {
+ @apply flex items-center space-x-4;
+ }
+
+ .skeleton-card-text {
+ @apply flex flex-col gap-3;
+ }
+
+ /* The same as primary-input but no-truncate */
+ .textarea-primary {
+ @apply form-input block w-full rounded-md border border-border bg-background px-3 text-left shadow-sm placeholder:text-placeholder-foreground hover:border-muted focus:border-muted focus:placeholder-transparent focus:ring-[0.75px] focus:ring-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-secondary disabled:text-muted disabled:opacity-100 placeholder:disabled:text-muted-foreground sm:text-sm;
+ }
+
+ .input-edit-node {
+ @apply primary-input w-full pb-0.5 pt-0.5 text-left;
+ }
+ .input-search {
+ @apply primary-input mx-2 pr-7;
+ }
+ .input-disable {
+ @apply border-transparent bg-border placeholder:text-ring;
+ }
+ .input-dialog {
+ @apply cursor-pointer bg-transparent text-ring;
+ }
+ .message-button {
+ @apply message-button-position flex h-12 w-12 items-center justify-center rounded-full bg-border px-3 py-1 shadow-md transition-all;
+ }
+
+ .round-button-form {
+ @apply flex h-12 w-12 cursor-pointer justify-center rounded-full bg-border px-3 py-1 shadow-md;
+ }
+ .build-trigger-loading-icon {
+ @apply stroke-build-trigger;
+ }
+ .build-trigger-icon {
+ @apply w-6 fill-build-trigger stroke-build-trigger stroke-1;
+ }
+ .message-button-position {
+ @apply fixed right-4 top-20;
+ }
+ .message-button-icon {
+ @apply fill-medium-indigo stroke-medium-indigo stroke-1;
+ }
+ .disabled-message-button-icon {
+ @apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1;
+ }
+ .parent-disclosure-arrangement {
+ @apply flex w-full select-none items-center justify-between bg-background py-3 pl-5 pr-3;
+ }
+ .components-disclosure-arrangement {
+ @apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-primary-foreground px-3 py-2;
+ }
+ .components-disclosure-arrangement-child {
+ /* different color than the non child */
+ @apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-primary-foreground px-3 py-2;
+ }
+ .components-disclosure-title {
+ @apply flex items-center text-sm text-primary;
+ }
+ .components-disclosure-div {
+ @apply flex gap-2;
+ }
+ .flow-page-positioning {
+ @apply h-full w-full overflow-hidden;
+ }
+ .langflow-page-icon {
+ @apply absolute bottom-2 left-7 flex h-6 cursor-pointer flex-col items-center justify-start overflow-hidden rounded-lg bg-foreground px-2 text-center font-sans text-xs tracking-wide text-secondary transition-all duration-500 ease-in-out;
+ }
+
+ .langflow-page-icon:hover {
+ @apply hover:h-12;
+ }
+
+ .flex-max-width {
+ @apply flex w-full;
+ }
+
+ .loading-page-panel {
+ @apply flex h-full w-full items-center justify-center bg-background;
+ }
+
+ .main-page-panel {
+ @apply flex-max-width h-full flex-col overflow-auto bg-background px-16;
+ }
+
+ .admin-page-panel {
+ @apply flex-max-width h-full flex-col overflow-auto bg-background px-16;
+ }
+
+ .main-page-nav-arrangement {
+ @apply flex-max-width justify-between py-8 pb-2;
+ }
+
+ .main-page-nav-title {
+ @apply flex items-center justify-center gap-2 text-2xl font-semibold;
+ }
+
+ .main-page-nav-button {
+ @apply w-4;
+ }
+
+ .main-page-description-text {
+ @apply flex w-[60%] pb-4 text-muted-foreground;
+ }
+
+ .admin-page-description-text {
+ @apply flex w-[80%] pb-8 text-muted-foreground;
+ }
+
+ .main-page-flows-display {
+ @apply grid w-full gap-4 pb-7 md:grid-cols-2 lg:grid-cols-3;
+ }
+
+ .community-page-arrangement {
+ @apply flex-max-width h-full flex-col justify-between overflow-auto bg-background px-16;
+ }
+
+ .community-page-nav-arrangement {
+ @apply flex-max-width justify-between py-8 pb-2;
+ }
+
+ .community-page-nav-title {
+ @apply flex items-center justify-center gap-2 text-2xl font-semibold;
+ }
+
+ .community-page-nav-button {
+ @apply flex gap-2;
+ }
+
+ .community-page-description-text {
+ @apply flex w-[70%] pb-8 text-muted-foreground;
+ }
+
+ .community-pages-flows-panel {
+ @apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
+ }
+ .generic-node-div {
+ @apply flex flex-col justify-center bg-background transition-all;
+ }
+ .generic-node-div-title {
+ @apply flex flex-1 items-center gap-2 overflow-hidden;
+ }
+ .generic-node-title-arrangement {
+ @apply flex-max-width items-center truncate;
+ }
+ .generic-node-icon {
+ @apply h-12 w-12 rounded;
+ }
+ .generic-node-tooltip-div {
+ @apply ml-2 flex w-full truncate;
+ }
+ .generic-node-validation-div {
+ @apply max-h-96 overflow-auto;
+ }
+
+ .generic-node-status-position {
+ @apply h-5 w-5;
+ }
+
+ .generic-node-status-animation {
+ @apply hidden h-4 w-4 animate-spin rounded-full bg-ring opacity-0;
+ }
+ .generic-node-status {
+ @apply animate-wiggle opacity-100;
+ }
+ .green-status {
+ @apply generic-node-status text-status-green;
+ }
+ .gray-status {
+ @apply generic-node-status text-status-gray;
+ }
+
+ .red-status {
+ @apply generic-node-status text-status-red;
+ }
+ .yellow-status {
+ @apply generic-node-status text-status-yellow;
+ }
+ .inactive-status {
+ /* what colour for inactive status?
+ muted-foreground is too strong, maybe use a lighter shade of it?
+
+ */
+ @apply border-none ring grayscale;
+ }
+ .built-invalid-status {
+ @apply border-none ring ring-[#FF9090];
+ }
+ .built-invalid-status-dark {
+ @apply border-none ring ring-[#751C1C];
+ }
+ .building-status {
+ @apply border-blue-400;
+ }
+ .status-build-animation {
+ @apply opacity-0;
+ }
+ .status-div {
+ @apply absolute w-4 duration-200 ease-in-out;
+ }
+ .status-div:hover {
+ @apply hover:text-accent-foreground hover:transition-all;
+ }
+ .generic-node-desc {
+ @apply h-full w-full px-5 pb-4 text-foreground;
+ }
+ .generic-node-desc-text {
+ @apply w-full text-sm text-muted-foreground;
+ }
+
+ .alert-icon {
+ @apply h-5 w-5;
+ }
+ .alert-font-size {
+ @apply text-sm font-medium;
+ }
+
+ .error-build-message {
+ @apply mt-6 w-96 rounded-md bg-error-background p-4 shadow-xl;
+ }
+ .error-build-message-circle {
+ @apply alert-icon text-status-red;
+ }
+ .error-build-text {
+ @apply text-error-foreground word-break-break-word;
+ }
+ .error-build-foreground {
+ @apply error-build-text alert-font-size;
+ }
+ .error-build-message-div {
+ @apply error-build-text mt-2 text-sm;
+ }
+ .error-build-message-list {
+ @apply list-disc space-y-1 pl-5;
+ }
+
+ .success-alert {
+ @apply mt-6 w-96 rounded-md bg-success-background p-4 shadow-xl;
+ }
+ .success-alert-icon {
+ @apply alert-icon text-status-green;
+ }
+ .success-alert-message {
+ @apply alert-font-size text-success-foreground word-break-break-word;
+ }
+
+ .unused-side-bar-aside {
+ @apply flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500;
+ }
+ .unused-side-bar-arrangement {
+ @apply flex h-full w-52 flex-col items-start overflow-y-auto border bg-background scrollbar-hide;
+ }
+ .unused-side-bar-division {
+ @apply flex-max-width flex-grow flex-col;
+ }
+ .unused-side-bar-nav {
+ @apply flex-1 space-y-1;
+ }
+ .unused-side-bar-link {
+ @apply flex-max-width items-center rounded-md py-2 pl-2 text-sm font-medium;
+ }
+ .unused-side-bar-link-colors-true {
+ @apply bg-muted text-foreground;
+ }
+ .unused-side-bar-link-colors-false {
+ @apply bg-background text-muted-foreground hover:bg-muted hover:text-foreground;
+ }
+ .unused-side-bar-icon {
+ @apply mr-3 h-6 w-6 flex-shrink-0;
+ }
+ .unused-side-bar-icon-false {
+ @apply text-ring group-hover:text-accent-foreground;
+ }
+ .unused-side-bar-disclosure {
+ @apply unused-side-bar-link pr-1 text-left;
+ }
+ .unused-side-bar-disclosure:focus {
+ @apply focus:outline-none focus:ring-1 focus:ring-ring;
+ }
+ .unused-side-bar-disclosure-icon {
+ @apply unused-side-bar-icon text-ring group-hover:text-accent-foreground;
+ }
+ .unused-side-bar-svg-true {
+ @apply rotate-90 text-ring;
+ }
+ .unused-side-bar-svg {
+ @apply ml-3 h-5 w-5 flex-shrink-0 duration-150 ease-in-out group-hover:text-accent-foreground;
+ }
+ .unused-side-bar-disclosure-panel {
+ @apply flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium;
+ }
+
+ .code-area-component {
+ @apply pointer-events-none w-full cursor-not-allowed;
+ }
+ .code-area-input-positioning {
+ @apply flex-max-width items-center;
+ }
+ .code-area-external-link {
+ @apply ml-3 h-6 w-6;
+ }
+ .code-area-external-link:hover {
+ @apply hover:text-accent-foreground;
+ }
+ .dropdown-component-disabled {
+ @apply pointer-events-none cursor-not-allowed;
+ }
+ .dropdown-component-outline {
+ @apply input-edit-node relative pr-8;
+ }
+ .dropdown-component-false-outline {
+ @apply primary-input py-2 pl-3 pr-10 text-left;
+ }
+ .dropdown-component-display {
+ @apply block w-full truncate bg-background;
+ }
+ .dropdown-component-display-disabled {
+ @apply text-muted-foreground;
+ }
+ .dropdown-component-arrow {
+ @apply pointer-events-none absolute inset-y-0 flex items-center pr-2;
+ }
+ .dropdown-component-arrow-color {
+ @apply h-5 w-5 text-accent-foreground;
+ }
+ .dropdown-component-arrow-color-disable {
+ @apply h-5 w-5 text-muted-foreground;
+ }
+ .dropdown-component-options {
+ @apply z-10 mt-1 max-h-60 overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm;
+ }
+ .dropdown-component-true-options {
+ @apply dropdown-component-options lg:w-[32%];
+ }
+ .dropdown-component-false-options {
+ @apply dropdown-component-options w-full;
+ }
+ .dropdown-component-option {
+ @apply relative cursor-default select-none;
+ }
+ .dropdown-component-false-option {
+ @apply dropdown-component-option py-0.5 pl-3 pr-12;
+ }
+ .dropdown-component-true-option {
+ @apply dropdown-component-option py-2 pl-3 pr-9;
+ }
+ .dropdown-component-choosal {
+ @apply absolute inset-y-0 right-0 flex items-center pr-4;
+ }
+ .dropdown-component-check-icon {
+ @apply h-5 w-5 text-primary;
+ }
+
+ .multiselect-component-outline {
+ @apply input-edit-node relative pr-8;
+ }
+ .multiselect-component-false-outline {
+ @apply primary-input py-2 pl-3 pr-10 text-left;
+ }
+
+ .edit-flow-arrangement {
+ @apply flex justify-between;
+ }
+ .edit-flow-span {
+ @apply ml-10 animate-pulse text-status-red;
+ }
+
+ .float-component-pointer {
+ @apply pointer-events-none cursor-not-allowed;
+ }
+
+ .header-menu-bar {
+ @apply flex items-center rounded-md py-1 text-sm font-medium;
+ }
+
+ .header-menu-bar-display {
+ @apply flex max-w-[110px] cursor-pointer items-center gap-2 lg:max-w-[150px];
+ }
+
+ .header-menu-bar-display-2 {
+ @apply flex cursor-pointer items-center gap-2;
+ }
+ .header-menu-flow-name-2 {
+ @apply flex-1;
+ }
+ .header-menu-flow-name {
+ @apply flex-1 truncate;
+ }
+ .header-menu-options {
+ @apply mr-2 h-4 w-4;
+ }
+
+ .header-arrangement {
+ @apply flex-max-width h-[53px] items-center justify-between border-b border-border;
+ }
+
+ .header-start-display {
+ @apply flex items-center gap-2;
+ }
+ .header-end-division {
+ @apply flex justify-end px-2;
+ }
+ .header-end-display {
+ @apply ml-auto mr-2 flex items-center gap-5;
+ }
+ .header-github-link-box {
+ @apply inline-flex h-8 items-center justify-center rounded-md border border-input px-2 pr-0;
+ }
+
+ .header-waitlist-link-box {
+ @apply inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md border border-input px-2 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background disabled:pointer-events-none disabled:opacity-50;
+ }
+ .header-waitlist-link-box:hover {
+ @apply hover:bg-accent hover:text-accent-foreground;
+ }
+ .header-github-link {
+ @apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50;
+ }
+ .header-github-link:focus-visible {
+ @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2;
+ }
+ .header-github-link:hover {
+ @apply hover:bg-accent hover:text-accent-foreground;
+ }
+ .header-notifications-box {
+ @apply fixed left-0 top-0 h-screen w-screen;
+ }
+ .header-notifications {
+ @apply absolute right-[3px] h-1.5 w-1.5 rounded-full bg-destructive;
+ }
+ .input-component-div {
+ @apply pointer-events-none relative cursor-not-allowed;
+ }
+ .input-component-button {
+ @apply absolute inset-y-0 right-0 items-center text-muted-foreground;
+ }
+ .input-component-true-button {
+ @apply input-component-button pr-2;
+ }
+ .input-component-false-button {
+ @apply input-component-button px-4;
+ }
+ .input-component-true-svg {
+ @apply side-bar-button-size absolute bottom-0.5 right-2;
+ }
+ .input-component-false-svg {
+ @apply side-bar-button-size absolute bottom-2 right-3;
+ }
+
+ .input-file-component {
+ @apply flex-max-width items-center;
+ }
+
+ .toggle-component-switch {
+ @apply relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out;
+ }
+ .toggle-component-switch:focus {
+ @apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1;
+ }
+ .toggle-component-span {
+ @apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
+ }
+ .toggle-component-second-span {
+ @apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity;
+ }
+
+ .app-div {
+ @apply absolute bottom-5 left-5 flex flex-col-reverse;
+ }
+
+ .chat-input-modal-txtarea {
+ @apply form-input block w-full rounded-md border-ring pr-10 custom-scroll sm:text-sm;
+ }
+ .chat-input-modal-div {
+ @apply absolute bottom-0.5 right-3;
+ }
+ .chat-input-modal-lock {
+ @apply side-bar-button-size animate-pulse text-ring;
+ }
+ .chat-input-modal-send {
+ @apply side-bar-button-size text-ring hover:text-muted-foreground;
+ }
+
+ .code-block-modal {
+ @apply flex items-center justify-between px-4 py-1.5;
+ }
+ .code-block-modal-span {
+ @apply text-xs lowercase text-muted-foreground;
+ }
+ .code-block-modal-button {
+ @apply flex items-center gap-1.5 rounded bg-none p-1 text-xs text-muted-foreground;
+ }
+
+ .chat-message-modal {
+ @apply flex-max-width py-2 pl-2;
+ }
+ .chat-message-modal-div {
+ @apply my-3 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full;
+ }
+ .chat-message-modal-img {
+ @apply absolute scale-150 transition-opacity duration-500;
+ }
+ .chat-message-modal-display {
+ @apply flex-max-width items-center text-start;
+ }
+ .chat-message-modal-text {
+ @apply relative w-full text-start text-sm font-normal text-muted-foreground;
+ }
+ .chat-message-modal-icon-div {
+ @apply absolute -left-2 -top-1 cursor-pointer;
+ }
+ .chat-message-modal-thought {
+ @apply chat-message-modal-thought-cursor ml-3 h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
+ }
+ .chat-message-modal-thought-cursor {
+ @apply cursor-pointer overflow-scroll scrollbar-hide;
+ }
+ .chat-message-modal-markdown {
+ @apply w-full px-4 pb-3 pr-8 pt-3;
+ }
+ .chat-message-modal-markdown-span {
+ @apply mt-1 animate-pulse cursor-default;
+ }
+ .chat-message-modal-alert {
+ @apply px-3 text-start text-muted-foreground;
+ }
+
+ .file-card-modal-image-div {
+ @apply absolute right-0 top-0 rounded-bl-lg bg-muted px-1 text-sm font-bold text-foreground;
+ }
+ .file-card-modal-image-button {
+ @apply px-2 py-1 text-ring;
+ }
+ .file-card-modal-button {
+ @apply flex w-1/2 items-center justify-between rounded border border-ring bg-muted px-2 py-2 text-foreground shadow hover:drop-shadow-lg;
+ }
+ .file-card-modal-div {
+ @apply flex-max-width mr-2 items-center gap-2 text-current;
+ }
+ .file-card-modal-footer {
+ @apply flex flex-col items-start;
+ }
+ .file-card-modal-name {
+ @apply truncate text-sm text-current;
+ }
+ .file-card-modal-type {
+ @apply truncate text-xs text-ring;
+ }
+
+ .send-message-modal-transition {
+ @apply fixed inset-0 bg-black bg-opacity-80 backdrop-blur-sm transition-opacity;
+ }
+ .chat-modal-box {
+ @apply fixed inset-0 z-10 overflow-y-auto;
+ }
+ .chat-modal-box-div {
+ @apply flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0;
+ }
+ .chat-modal-dialog-panel {
+ @apply relative flex h-[95%] w-[690px] transform flex-col justify-between overflow-hidden rounded-lg bg-background text-left shadow-xl drop-shadow-2xl transition-all;
+ }
+ .chat-modal-dialog-panel-div {
+ @apply relative w-full p-4;
+ }
+ .chat-modal-dialog-trash-panel {
+ @apply absolute right-10 top-2 z-30 text-muted-foreground hover:text-status-red;
+ }
+ .chat-modal-dialog-x-panel {
+ @apply absolute right-2 top-1.5 z-30 text-muted-foreground hover:text-status-red;
+ }
+ .chat-modal-dialog-history {
+ @apply flex-max-width h-full flex-col items-center overflow-scroll border-t bg-background scrollbar-hide;
+ }
+ .chat-modal-dialog-span-box {
+ @apply flex-max-width h-full flex-col items-center justify-center text-center align-middle;
+ }
+ .chat-modal-dialog-desc {
+ @apply w-2/4 rounded-md border border-input bg-muted px-6 py-8;
+ }
+ .chat-modal-input-div {
+ @apply flex-max-width flex-col items-center justify-between border-t bg-background p-3;
+ }
+ .chat-modal-input {
+ @apply relative mt-1 w-full rounded-md shadow-sm;
+ }
+ .code-area-modal-editor-div {
+ @apply flex-max-width mt-2 h-full;
+ }
+ .code-area-modal-editor-box {
+ @apply h-[300px] w-full rounded-lg border border-ring custom-scroll;
+ }
+
+ .edit-node-modal-variable {
+ @apply h-5 w-5 stroke-2 pe-1 text-muted-foreground;
+ }
+ .edit-node-modal-span {
+ @apply text-sm font-semibold text-primary;
+ }
+ .edit-node-modal-arrangement {
+ @apply flex-max-width h-fit max-h-[400px];
+ }
+ .edit-node-modal-box {
+ @apply w-full rounded-lg border border-input bg-background;
+ }
+ .edit-node-modal-table {
+ @apply flex h-fit flex-col gap-5;
+ }
+ .edit-node-modal-table-header {
+ @apply h-10 border-input text-xs font-medium text-ring;
+ }
+ .edit-node-modal-table-cell {
+ @apply truncate p-0 text-center text-sm text-foreground sm:px-3;
+ }
+ .edit-node-modal-second-cell {
+ @apply w-[300px] p-0 text-center text-xs text-foreground;
+ }
+
+ .generic-modal-txtarea-div {
+ @apply flex-max-width mt-2 h-full;
+ }
+
+ .dialog-header-modal-div {
+ @apply absolute left-0 top-2 z-50 hidden pl-4 pt-4 sm:block;
+ }
+ .dialog-header-modal-button {
+ @apply rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2;
+ }
+
+ .dialog-modal-examples-div {
+ @apply h-full w-full overflow-y-auto scrollbar-hide;
+ }
+ .dialog-modal-example-true {
+ @apply mx-auto flex flex-row flex-wrap items-start justify-center overflow-auto;
+ }
+ .dialog-modal-example-false {
+ @apply flex flex-row items-center justify-center;
+ }
+ .dialog-modal-button-box-div {
+ @apply flex-max-width h-full items-center justify-evenly;
+ }
+ .document-icon {
+ @apply h-10 w-10 flex-shrink-0;
+ }
+ .loading-component-div {
+ @apply flex items-center justify-center align-middle;
+ }
+ .dialog-modal-footer {
+ @apply flex-max-width mt-2 items-center justify-center;
+ }
+ .dialog-modal-footer-link {
+ @apply flex items-center justify-center text-muted-foreground;
+ }
+
+ .node-modal-div {
+ @apply fixed inset-0 bg-ring bg-opacity-75 transition-opacity;
+ }
+ .node-modal-dialog-arrangement {
+ @apply fixed inset-0 z-10 overflow-y-auto;
+ }
+ .node-modal-dialog-div {
+ @apply flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0;
+ }
+ .node-modal-dialog-panel {
+ @apply relative flex h-[600px] w-[700px] transform flex-col justify-between overflow-hidden rounded-lg bg-background text-left shadow-xl transition-all sm:my-8;
+ }
+ .node-modal-dialog-panel-div {
+ @apply absolute right-0 top-0 z-50 hidden pr-4 pt-4 sm:block;
+ }
+ .node-modal-dialog-button {
+ @apply rounded-md text-ring hover:text-accent-foreground;
+ }
+ .node-modal-dialog-icon-div {
+ @apply flex-max-width h-full flex-col items-center justify-center;
+ }
+ .node-modal-icon-arrangement {
+ @apply flex-max-width z-10 justify-center pb-4 shadow-sm;
+ }
+ .node-modal-icon {
+ @apply mt-4 h-10 w-10 rounded p-1;
+ }
+ .node-modal-title-div {
+ @apply mt-4 text-center sm:ml-4 sm:text-left;
+ }
+ .node-modal-title {
+ @apply text-lg font-medium leading-10 text-foreground;
+ }
+ .node-modal-template-div {
+ @apply flex-max-width h-full flex-row items-center justify-center gap-4 bg-input p-4;
+ }
+ .node-modal-template {
+ @apply w-full rounded-lg bg-background px-4 shadow sm:p-4;
+ }
+ .node-modal-template-column {
+ @apply flex h-full flex-col gap-5;
+ }
+ .node-modal-button-box {
+ @apply flex-max-width flex-row-reverse bg-input px-4 pb-3;
+ }
+ .link-color {
+ @apply font-semibold text-foreground;
+ }
+ .node-modal-button {
+ @apply inline-flex w-full justify-center rounded-md border border-transparent bg-status-red px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring sm:ml-3 sm:w-auto sm:text-sm;
+ }
+ .node-modal-button:focus {
+ @apply focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1;
+ }
+
+ .prompt-modal-icon-box {
+ @apply mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-almost-light-blue sm:mx-0 sm:h-10 sm:w-10;
+ }
+ .prompt-modal-icon {
+ @apply h-6 w-6 text-primary;
+ }
+ .prompt-modal-txtarea-arrangement {
+ @apply flex-max-width h-full flex-row items-center justify-center gap-4 overflow-auto bg-accent p-4;
+ }
+ .prompt-modal-txtarea-box {
+ @apply h-full w-full overflow-hidden rounded-lg bg-background px-4 py-5 shadow sm:p-6;
+ }
+ .prompt-modal-txtarea {
+ @apply form-input h-full w-full rounded-lg border-ring;
+ }
+
+ .txtarea-modal-arrangement {
+ @apply flex h-full w-full flex-row items-center justify-center gap-4 bg-input p-4;
+ }
+ .txtarea-modal-box {
+ @apply w-full overflow-hidden rounded-lg bg-background px-4 py-5 shadow sm:p-6;
+ }
+ .txtarea-modal-input {
+ @apply form-input h-full w-full;
+ }
+
+ .api-modal-tabs {
+ @apply flex h-full flex-col overflow-hidden;
+ }
+ .api-modal-tablist-div {
+ @apply flex items-center justify-between px-2 py-2;
+ }
+ .api-modal-tabs-content {
+ @apply -mt-1 h-full w-full pb-4;
+ }
+ .api-modal-accordion-display {
+ @apply mt-2 flex h-full w-full;
+ }
+ .api-modal-table-arrangement {
+ @apply flex h-fit flex-col gap-5;
+ }
+
+ .icons-parameters-comp {
+ @apply h-6 w-6;
+ }
+
+ .form-modal-lock-true {
+ @apply bg-input text-primary;
+ }
+ .form-modal-no-input {
+ @apply bg-input text-center text-primary dark:bg-gray-700 dark:text-gray-300;
+ }
+ .form-modal-lock-false {
+ @apply text-primary;
+ }
+ .code-highlight {
+ @apply block w-full overflow-auto border-0 px-3 py-2 text-sm outline-0 word-break-break-word;
+ }
+
+ .code-nohighlight {
+ @apply block w-full overflow-auto border-0 px-3 py-2 text-sm outline-0 word-break-break-word;
+ }
+ .form-modal-lockchat {
+ @apply form-input block w-full rounded-md border border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-0 sm:text-sm;
+ }
+ .form-modal-send-icon-position {
+ @apply absolute bottom-2 right-4;
+ }
+ .form-modal-send-button {
+ @apply rounded-md p-1.5 px-2.5;
+ }
+ .form-modal-lock-icon {
+ @apply ml-1 mr-1 h-5 w-5 animate-pulse;
+ }
+ .form-modal-send-icon {
+ @apply mr-2 h-5 w-5 rotate-[44deg];
+ }
+ .form-modal-play-icon {
+ @apply mx-1 h-5 w-5 fill-current;
+ }
+ .form-modal-chat-position {
+ @apply flex-max-width px-2 py-6 pl-4 pr-9;
+ }
+ .form-modal-chatbot-icon {
+ @apply mb-3 ml-3 mr-6 mt-1 flex flex-col;
+ }
+ .form-modal-chat-image {
+ @apply flex flex-col items-center gap-1;
+ }
+ .form-modal-chat-img-box {
+ @apply relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl;
+ }
+ .form-modal-chat-bot-icon {
+ @apply form-modal-chat-img-box bg-chat-bot-icon;
+ }
+ .form-modal-chat-user-icon {
+ @apply form-modal-chat-img-box bg-chat-user-icon;
+ }
+ .form-modal-chat-icon-img {
+ @apply absolute scale-[60%];
+ }
+ .form-modal-chat-text-position {
+ @apply flex w-full flex-1 text-start;
+ }
+ .form-modal-chat-text {
+ @apply relative flex w-full flex-col text-start text-sm font-normal text-muted-foreground;
+ }
+ .form-modal-chat-icon-div {
+ @apply absolute -left-6 -top-3 cursor-pointer;
+ }
+ .form-modal-chat-icon {
+ @apply h-4 w-4 animate-bounce;
+ }
+ .form-modal-chat-thought-border {
+ @apply rounded-md border border-ring/60;
+ }
+ .form-modal-chat-thought-size {
+ @apply h-full w-[95%];
+ }
+ .form-modal-chat-thought {
+ @apply form-modal-chat-thought-border form-modal-chat-thought-size cursor-pointer overflow-scroll bg-background px-2 py-2 text-start text-primary scrollbar-hide;
+ }
+ .form-modal-markdown-span {
+ @apply mt-1 animate-pulse cursor-default;
+ }
+ .form-modal-initial-prompt-btn {
+ @apply mb-2 flex items-center gap-2 rounded-md border border-border bg-background px-4 py-2 text-sm font-semibold shadow-sm;
+ }
+ .form-modal-iv-size {
+ @apply mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide;
+ }
+ .file-component-arrangement {
+ @apply flex items-center py-2;
+ }
+ .file-component-variable {
+ @apply -ml-px mr-1 h-4 w-4 text-primary;
+ }
+ .file-component-variables-span {
+ @apply font-semibold text-primary;
+ }
+ .file-component-variables-title {
+ @apply flex items-center justify-between pt-2;
+ }
+ .file-component-variables-div {
+ @apply mr-2.5 flex items-center;
+ }
+ .file-component-variables-title-txt {
+ @apply text-sm font-medium text-primary;
+ }
+ .file-component-accordion-div {
+ @apply flex items-start gap-3;
+ }
+ .file-component-badge-div {
+ @apply flex-max-width items-center justify-between;
+ }
+ .file-component-tab-column {
+ @apply flex flex-col gap-2 p-1;
+ }
+ .tab-accordion-badge-div {
+ @apply flex flex-1 items-center justify-between py-4 text-sm font-normal text-muted-foreground transition-all;
+ }
+ .eraser-column-arrangement {
+ @apply flex-max-width flex-1 flex-col;
+ }
+ .eraser-size {
+ @apply relative flex h-full w-full flex-col rounded-md border bg-muted;
+ }
+ .eraser-position {
+ @apply absolute right-3 top-3 z-50;
+ }
+ .chat-message-div {
+ @apply flex-max-width h-full flex-col items-center overflow-scroll scrollbar-hide;
+ }
+ .chat-alert-box {
+ @apply flex-max-width h-full flex-col items-center justify-center text-center align-middle;
+ }
+ .langflow-chat-span {
+ @apply text-lg text-foreground;
+ }
+ .card-filter {
+ @apply bg-background bg-fixed opacity-20;
+ }
+ .langflow-chat-desc {
+ @apply w-2/4 rounded-md border border-border bg-muted px-6 py-8;
+ }
+ .langflow-chat-desc-span {
+ @apply text-base text-muted-foreground;
+ }
+ .langflow-chat-input-div {
+ @apply flex-max-width flex-col items-center justify-between px-8 pb-3;
+ }
+ .langflow-chat-input {
+ @apply relative w-full rounded-md shadow-sm;
+ }
+
+ .tooltip-fixed-width {
+ @apply max-h-[25vh] max-w-[30vw] overflow-auto;
+ }
+
+ .ace-editor-arrangement {
+ @apply flex-max-width h-full flex-col transition-all;
+ }
+ .ace-editor {
+ @apply h-full w-full rounded-lg border border-border custom-scroll;
+ }
+ .ace-editor-save-btn {
+ @apply flex-max-width h-fit justify-end;
+ }
+
+ .export-modal-save-api {
+ @apply font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70;
+ }
+
+ .beta-badge-wrapper {
+ @apply pointer-events-none absolute right-0 top-0 h-16 w-16 overflow-hidden rounded-tr-lg;
+ }
+ .beta-badge-content {
+ @apply mt-2 w-24 rotate-45 bg-beta-background text-center text-xs font-semibold text-beta-foreground;
+ }
+
+ .chat-message-highlight {
+ @apply rounded-md bg-indigo-100 px-0 font-semibold dark:bg-indigo-900;
+ }
+
+ .field-invalid {
+ @apply absolute text-[0.8rem] font-medium text-destructive;
+ }
+
+ .label-invalid {
+ @apply text-destructive;
+ }
+
+ .input-invalid {
+ @apply border-destructive focus:border-destructive focus:ring-destructive;
+ }
+
+ .fade-container {
+ @apply relative overflow-hidden;
+ }
+
+ .fade-container::before,
+ .fade-container::after {
+ @apply pointer-events-none absolute bottom-0 top-0 bg-gradient-to-r from-background to-transparent;
+ content: "";
+ width: 50px;
+ opacity: 0;
+ transition: opacity 0.3s;
+ }
+
+ .fade-container::after {
+ @apply right-0;
+ transform: rotate(180deg);
+ }
+
+ .fade-container.fade-left::before,
+ .fade-container.fade-right::after {
+ opacity: 1;
+ }
+
+ .fade-container-dark {
+ @apply relative overflow-hidden;
+ }
+
+ .fade-container-dark::before,
+ .fade-container-dark::after {
+ @apply pointer-events-none absolute bottom-0 top-0;
+ content: "";
+ width: 50px;
+ opacity: 0;
+ transition: opacity 0.3s;
+ background: linear-gradient(to right, black, transparent);
+ }
+
+ .fade-container-dark:after {
+ @apply right-0;
+ transform: rotate(180deg);
+ }
+
+ .fade-container-dark.fade-left::before,
+ .fade-container-dark.fade-right::after {
+ opacity: 1;
+ }
+
+ .scroll-container {
+ @apply flex overflow-x-scroll scrollbar-hide;
+ }
+
+ .store-beta-icon {
+ @apply relative bottom-3 left-1 ml-2 rounded-full bg-beta-background px-2 py-1 text-center text-xs font-semibold text-beta-foreground;
+ }
+ .color-option-container {
+ @apply w-fit;
+ }
+
+ .x-gradient {
+ @apply stroke-[url(#x-gradient)] bg-blend-hard-light;
+ }
+
+ .button-run-bg {
+ @apply flex h-7 w-7 cursor-pointer items-center justify-center rounded-sm bg-transparent p-0 hover:bg-muted hover:p-1;
+ }
+
+ .play-button-icon {
+ @apply h-4 w-4 transition-all group-hover/node:opacity-100;
+ }
+
+ .lucide-icon {
+ @apply h-5 w-5;
+ }
+
+ .bg-lucide-icon {
+ @apply flex h-8 w-8 items-center justify-center rounded-md bg-muted p-[6px];
+ }
+
+ .integration-icon {
+ @apply h-8 w-8 rounded-md;
+ }
+
+ .show-node-icon {
+ @apply absolute inset-x-6;
+ }
+
+ .disabled-state {
+ @apply pointer-events-none bg-secondary text-input;
+ }
+
+ .background-fade-input {
+ @apply absolute right-[0.9px] h-6 w-7 bg-background;
+ }
+
+ .gradient-fade-input {
+ @apply absolute right-7 h-6 w-12 rounded-l-xl;
+ }
+
+ .background-fade-input-edit-node {
+ @apply absolute right-[0.9px] h-4 w-7 bg-background;
+ }
+
+ .gradient-fade-input-edit-node {
+ @apply absolute right-7 h-4 w-12 rounded-l-xl bg-background;
+ }
+
+ .fade-top-position {
+ @apply top-[-35px];
+ }
+ .icon-top-position {
+ @apply top-[-30px];
+ }
+ .edit-node-icon-top-position {
+ @apply top-[-23px];
+ }
+ .popover-input {
+ @apply h-fit w-fit flex-1 border-none bg-transparent p-0 shadow-none outline-none ring-0 ring-offset-0 placeholder:text-muted-foreground focus:border-foreground focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50;
+ }
+
+ .beta-badge {
+ @apply flex h-6 w-11 items-center justify-center rounded-lg bg-accent-pink p-2 text-center text-xs font-medium text-accent-pink-foreground;
+ }
+
+ .icon-size {
+ @apply h-[18px] w-[18px];
+ }
+
+ .hit-area-icon {
+ @apply h-7 w-7 rounded-md;
+ }
+
+ .node-toolbar-buttons {
+ @apply flex w-max items-center gap-1 rounded-lg text-foreground;
+ }
+
+ .last-output-border {
+ @apply rounded-b-[12.5px];
+ }
+
+ .toolbar-wrapper {
+ @apply flex h-10 items-center gap-1 rounded-xl border border-border bg-background p-1 shadow-sm;
+ }
+
+ .playground-btn-flow-toolbar {
+ @apply relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out;
+ }
+
+ .input-slider-text {
+ @apply absolute right-3 w-14 -translate-y-3.5 cursor-text rounded-sm px-2 py-[1px] text-center hover:ring-[1px] hover:ring-slider-input-border;
+ }
+
+ .btn-add-input-list {
+ @apply flex h-8 w-full items-center justify-center rounded-md p-2 text-sm hover:bg-muted;
+ }
+}
+
+/* Gradient background */
+.text-container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ position: absolute;
+ top: 0;
+ left: 0;
+ justify-content: center;
+ align-items: center;
+}
+
+:root {
+ --color-bg1: rgb(255, 255, 255);
+ --color1: 255, 50, 118;
+ --color2: 244, 128, 255;
+ --color3: 117, 40, 252;
+ --color-interactive: 140, 100, 255;
+ --circle-size: 50%;
+ --blending: hard-light;
+}
+
+.dark {
+ --color-bg1: rgb(0, 0, 0);
+ --color1: 255, 50, 118;
+ --color2: 244, 128, 255;
+ --color3: 117, 40, 252;
+ --color-interactive: 140, 100, 255;
+ --circle-size: 60%;
+ --blending: hard-light;
+}
+
+@keyframes moveInCircle {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 50% {
+ transform: rotate(180deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.gradient-bg {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ overflow: hidden;
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==),
+ var(--color-bg1);
+ top: 0;
+ left: 0;
+ background-blend-mode: overlay;
+
+ svg {
+ display: none;
+ }
+
+ .gradients-container {
+ filter: url(#lf-balls) blur(40px);
+ width: 100%;
+ height: 100%;
+ }
+
+ .g1 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color1), 0.8) 0,
+ rgba(var(--color1), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 10%;
+ right: 20%;
+ transform-origin: 50% 100%;
+ animation: moveInCircle 10s linear infinite;
+ opacity: 1;
+ }
+
+ .g2 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color2), 0.8) 0,
+ rgba(var(--color2), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 10%;
+ left: 10%;
+
+ transform-origin: 50% 100%;
+ animation: moveInCircle 12s linear infinite;
+
+ opacity: 1;
+ }
+
+ .g3 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color3), 0.8) 0,
+ rgba(var(--color3), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 10%;
+ right: 30%;
+
+ transform-origin: 50% 100%;
+ animation: moveInCircle 11s linear infinite;
+
+ opacity: 1;
+ }
+
+ .g4 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color1), 0.8) 0,
+ rgba(var(--color1), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 5%;
+ right: 50%;
+
+ transform-origin: 50% 20%;
+ animation: moveInCircle 12s reverse linear infinite;
+
+ opacity: 0.7;
+ }
+
+ .g5 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color2), 0.8) 0,
+ rgba(var(--color2), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 10%;
+ left: 30%;
+
+ transform-origin: 50% 20%;
+ animation: moveInCircle 11s reverse linear infinite;
+ opacity: 1;
+ }
+
+ .g6 {
+ position: absolute;
+ background: radial-gradient(
+ circle at center,
+ rgba(var(--color3), 0.8) 0,
+ rgba(var(--color3), 0) 50%
+ )
+ no-repeat;
+ mix-blend-mode: var(--blending);
+
+ width: var(--circle-size);
+ height: var(--circle-size);
+ top: 10%;
+ right: 10%;
+
+ transform-origin: 50% 20%;
+ animation: moveInCircle 10s reverse linear infinite;
+
+ opacity: 1;
+ }
+}
diff --git a/langflow/src/frontend/src/style/classes.css b/langflow/src/frontend/src/style/classes.css
new file mode 100644
index 0000000..3334c52
--- /dev/null
+++ b/langflow/src/frontend/src/style/classes.css
@@ -0,0 +1,288 @@
+body {
+ margin: 0;
+ font-family:
+ "Inter",
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ "Roboto",
+ "Oxygen",
+ "Ubuntu",
+ "Cantarell",
+ "Fira Sans",
+ "Droid Sans",
+ "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
+pre {
+ font-family: inherit;
+}
+
+.react-flow__pane {
+ pointer-events: all;
+ cursor: default;
+}
+
+.AccordionContent {
+ overflow: hidden;
+}
+.AccordionContent[data-state="open"] {
+ animation: slideDown 300ms ease-out;
+}
+.AccordionContent[data-state="closed"] {
+ animation: slideUp 300ms ease-out;
+}
+
+.gradient-end {
+ animation: gradient-motion-end 3s infinite forwards;
+}
+.gradient-start {
+ animation: gradient-motion-start 4s infinite forwards;
+}
+
+input:-webkit-autofill,
+input:-webkit-autofill:hover,
+input:-webkit-autofill:focus,
+textarea:-webkit-autofill,
+textarea:-webkit-autofill:hover,
+textarea:-webkit-autofill:focus,
+select:-webkit-autofill,
+select:-webkit-autofill:hover,
+select:-webkit-autofill:focus {
+ -webkit-text-fill-color: black;
+ -webkit-box-shadow: 0 0 0px 1000px #fff6d0 inset;
+ box-shadow: 0 0 0px 1000px #fff6d0 inset;
+ color: black;
+}
+.ace_scrollbar::-webkit-scrollbar {
+ height: 8px;
+ width: 8px;
+}
+
+.ag-cell {
+ display: block;
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.ag-cell-focus {
+ border-radius: 0.375rem;
+ border: 1px solid #94a3b8 !important;
+ background-color: transparent !important;
+ text-align: left;
+ font-size: 0.875rem;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important;
+ outline: none !important;
+ outline-color: transparent !important;
+}
+
+.ag-cell-focus:focus {
+ border-color: #94a3b8 !important;
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05) !important;
+ background-color: transparent !important;
+ outline: none !important;
+ outline-color: transparent !important;
+}
+
+input .ag-cell-edit-input {
+ border: 1px solid transparent !important;
+ outline: none !important;
+ outline-color: transparent !important;
+}
+
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23666666'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
+ height: 16px;
+ width: 16px;
+ cursor: pointer;
+ display: block;
+}
+
+input[class^="ag-"]:not([type]),
+input[class^="ag-"][type="text"],
+input[class^="ag-"][type="number"],
+input[class^="ag-"][type="tel"],
+input[class^="ag-"][type="date"],
+input[class^="ag-"][type="datetime-local"],
+textarea[class^="ag-"] {
+ border: 1px solid transparent !important;
+ outline: none !important;
+ outline-color: transparent !important;
+ -webkit-box-shadow: none !important; /* Safari and older Chrome versions */
+ -moz-box-shadow: none !important; /* Older Firefox versions */
+ box-shadow: none !important; /* Standard syntax */
+}
+
+input[class^="ag-"][type="text"]:focus,
+input[class^="ag-"][type="number"]:focus,
+input[class^="ag-"][type="tel"]:focus,
+input[class^="ag-"][type="date"]:focus,
+input[class^="ag-"][type="datetime-local"]:focus,
+textarea[class^="ag-"]:focus {
+ border: 1px solid transparent !important;
+ outline-color: transparent !important;
+ outline: none !important;
+ -webkit-box-shadow: none !important; /* Safari and older Chrome versions */
+ -moz-box-shadow: none !important; /* Older Firefox versions */
+ box-shadow: none !important; /* Standard syntax */
+}
+
+.ace_scrollbar::-webkit-scrollbar-track {
+ background-color: hsl(var(--muted));
+}
+
+.ace_scrollbar::-webkit-scrollbar-thumb {
+ background-color: hsl(var(--border));
+ border-radius: 999px;
+}
+
+.ace_scrollbar::-webkit-scrollbar-thumb:hover {
+ background-color: hsl(var(--placeholder-foreground));
+ border-radius: 999px;
+}
+
+.json-view-playground-white-left {
+ background-color: #fff !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-dark {
+ background-color: #141924 !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-white {
+ background-color: #f8fafc !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-dark-left {
+ background-color: #0c101a !important;
+ height: fit-content !important;
+}
+
+.json-view {
+ height: 370px !important;
+ overflow-y: auto !important;
+ border-radius: 10px !important;
+ padding: 10px !important;
+}
+
+.json-view {
+ background-color: #f8fafc !important;
+}
+
+.dark .json-view {
+ background-color: #141924 !important;
+}
+
+.react-flow__node.dragging * {
+ cursor: grabbing !important;
+}
+
+.react-flow__handle-right {
+ right: 0 !important;
+ transform: translate(50%, -50%) !important;
+}
+
+.react-flow__handle-left {
+ left: 0 !important;
+ transform: translate(-50%, -50%) !important;
+}
+
+.react-flow__node-noteNode:not(.selected) {
+ z-index: -1 !important;
+}
+
+.card-shine-effect {
+ --shine-deg: 135deg;
+ position: relative;
+ overflow: hidden;
+ padding: 4rem 2rem;
+ max-width: 28rem;
+
+ background-repeat: no-repeat;
+ background-position:
+ 100% 100%,
+ 0 0;
+
+ background-image: linear-gradient(
+ var(--shine-deg),
+ transparent 20%,
+ transparent 40%,
+ rgba(255, 255, 255, 0.2) 50%,
+ rgba(255, 255, 255, 0.2) 55%,
+ transparent 60%,
+ transparent 100%
+ );
+
+ background-size:
+ 250% 250%,
+ 100% 100%;
+ transition: background-position 0s ease;
+}
+
+.card-shine-effect:hover {
+ background-position:
+ -100% -100%,
+ 0 0;
+ transition-duration: 1.5s;
+}
+
+span.token {
+ display: inline-block;
+ max-width: 100%;
+}
+
+pre code {
+ max-width: 100%;
+ display: inline-block;
+ width: 100%;
+ /* Background color */
+ background-color: hsl(var(--code-background)) !important;
+ color: hsl(var(--code-foreground)) !important;
+}
+
+pre {
+ /* Background color */
+ background-color: hsl(var(--code-background)) !important;
+}
+
+.prose li::marker {
+ color: inherit !important;
+}
+
+[type="search"]:not(:placeholder-shown)::-webkit-search-cancel-button {
+ opacity: 1 !important;
+}
+
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ height: 16px;
+ width: 16px;
+ margin-left: 0.4em;
+ position: relative;
+ right: -4px;
+ mask-image: url("data:image/svg+xml;utf8, ");
+ background-color: hsl(var(--foreground)) !important;
+ background-image: none;
+ cursor: pointer;
+}
+.react-flow__background pattern circle {
+ fill: hsl(var(--canvas-dot)) !important;
+}
+
+.markdown td {
+ min-width: 78px;
+}
diff --git a/langflow/src/frontend/src/style/index.css b/langflow/src/frontend/src/style/index.css
new file mode 100644
index 0000000..900f83a
--- /dev/null
+++ b/langflow/src/frontend/src/style/index.css
@@ -0,0 +1,333 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* TODO: Confirm that all colors here are found in tailwind config */
+
+@layer base {
+ :root {
+ --font-sans: "Inter", sans-serif;
+ --font-mono: "JetBrains Mono", monospace;
+ --font-chivo: "Chivo", sans-serif;
+
+ --foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --background: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --muted: 240 5% 96%; /* hsl(240, 5%, 96%) */
+ --muted-foreground: 240 4% 46%; /* hsl(240, 4%, 46%) */
+ --card: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --card-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --popover: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --popover-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --border: 240 6% 90%; /* hsl(240, 6%, 90%) */
+ --input: 240 6% 90%; /* hsl(240, 6%, 90%) */
+ --primary-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --primary: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --secondary: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --secondary-foreground: 240 4% 16%; /* hsl(240, 4%, 16%) */
+ --accent: 240 5% 96%; /* hsl(240, 5%, 96%) */
+ --accent-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --destructive: 0 72% 51%; /* hsl(0, 72%, 51%) */
+ --destructive-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --ring: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --primary-hover: 240 4% 16%; /* hsl(240, 4%, 16%) */
+ --secondary-hover: 240 6% 90%; /* hsl(240, 6%, 90%) */
+ --placeholder-foreground: 240 5% 65%; /* hsl(240, 5%, 65%) */
+ --canvas: 240 5% 96%; /* hsl(240, 5%, 96%) */
+ --canvas-dot: 240 5% 65%; /* hsl(240, 5%, 65%) */
+ --accent-emerald: 149 80% 90%; /* hsl(149, 80%, 90%) */
+ --accent-emerald-foreground: 161 41% 30%; /* hsl(161, 41%, 30%) */
+ --accent-emerald-hover: 152.4 76% 80.4%; /* hsl(152.4, 76%, 80.4%) */
+ --accent-indigo: 226 100% 94%; /* hsl(226, 100%, 94%) */
+ --accent-indigo-foreground: 243 75% 59%; /* hsl(243, 75%, 59%) */
+ --accent-pink: 326 78% 95%; /* hsl(326, 78%, 95%) */
+ --accent-pink-foreground: 333 71% 51%; /* hsl(333, 71%, 51%) */
+ --tooltip: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --tooltip-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+
+ --node-selected: 243 75% 59%;
+ --round-btn-shadow: #00000063;
+ --ice: #31a3cc;
+ --selected: #2196f3;
+
+ --error-background: #fef2f2;
+ --error-foreground: #991b1b;
+ --error: #991b1b;
+
+ --success-background: #f0fdf4;
+ --success-foreground: #14532d;
+
+ --info-background: #f0f4fd;
+ --info-foreground: #141653;
+
+ --high-indigo: #4338ca;
+ --medium-indigo: #6366f1;
+ --low-indigo: #e0e7ff;
+
+ --beta-background: rgb(219 234 254);
+ --beta-foreground: rgb(37 99 235);
+ --beta-foreground-soft: rgb(37 99 235 / 80%);
+
+ --chat-bot-icon: #afe6ef;
+ --chat-user-icon: #aface9;
+
+ --component-icon: #d8598a;
+ --flow-icon: #2f67d0;
+ --hover: #f2f4f5;
+ --disabled-run: #6366f1;
+
+ --filter-foreground: #4f46e5;
+ --filter-background: #eef2ff;
+ /* Colors that are shared in dark and light mode */
+ --blur-shared: #151923de;
+ --build-trigger: #dc735b;
+ --chat-trigger: #5c8be1;
+ --chat-trigger-disabled: #b4c3da;
+ --status-red: #ef4444;
+ --status-yellow: #eab308;
+ --chat-send: #000000;
+ --status-green: #4ade80;
+ --status-blue: #2563eb;
+ --status-gray: #6b7280;
+ --connection: #555;
+
+ --note-amber: 48 96.6% 76.7%; /* hsl(48, 96.6%, 76.7%) */
+ --note-neutral: 240 5.9% 90%; /* hsl(240, 5.9%, 90%) */
+ --note-rose: 352.7 96.1% 90%; /* hsl(352.7, 96.1%, 90%) */
+ --note-blue: 213.3 96.9% 87.3%; /* hsl(213.3, 96.9%, 87.3%) */
+ --note-lime: 80.9 88.5% 79.6%; /* hsl(80.9, 88.5%, 79.6%) */
+
+ --warning: 48 96.6% 76.7%; /* hsl(48, 96.6%, 76.7%) */
+ --warning-foreground: 240 6% 10%; /* hsl(240, 6%, 10%) */
+ --warning-text: 0 0% 100%; /* hsl(0, 0%, 100%) */
+
+ --error-red: 0, 86%, 97%; /*hsla(0, 86%, 97%)*/
+ --error-red-border: 0, 96%, 89%; /*hsla(0,96%,89%)*/
+
+ --code-background: 240 6% 10%;
+ --code-description-background: 240 6% 10%;
+ --code-foreground: 240 6% 90%;
+ --placeholder: 240 5% 64.9%;
+ --hard-zinc: 240 5.2% 33.9%;
+ --smooth-red: 0 93.3% 94.1%;
+ --radius: 0.5rem;
+
+ --datatype-pink: 333.3 71.4% 50.6%;
+ --datatype-pink-foreground: 325.7 77.8% 94.7%;
+
+ --datatype-rose: 346.8 77.2% 49.8%;
+ --datatype-rose-foreground: 355.6 100% 94.7%;
+
+ --datatype-yellow: 40.6 96.1% 40.4%;
+ --datatype-yellow-foreground: 54.9 96.7% 88%;
+
+ --datatype-blue: 221.2 83.2% 53.3%;
+ --datatype-blue-foreground: 214.3 94.6% 92.7%;
+
+ --datatype-gray: 215 13.8% 34.1%;
+ --datatype-gray-foreground: 220 14.3% 95.9%;
+
+ --datatype-lime: 84.8 85.2% 34.5%;
+ --datatype-lime-foreground: 79.6 89.1% 89.2%;
+
+ --datatype-red: 0 72.2% 50.6%;
+ --datatype-red-foreground: 0 93.3% 94.1%;
+
+ --datatype-violet: 262.1 83.3% 57.8%;
+ --datatype-violet-foreground: 251.4 91.3% 95.5%;
+
+ --datatype-emerald: 161.4 93.5% 30.4%;
+ --datatype-emerald-foreground: 149.3 80.4% 90%;
+
+ --datatype-fuchsia: 293.4 69.5% 48.8%;
+ --datatype-fuchsia-foreground: 287 100% 95.5%;
+
+ --datatype-purple: 271.5 81.3% 55.9%;
+ --datatype-purple-foreground: 268.7 100% 95.5%;
+
+ --datatype-cyan: 191.6 91.4% 36.5%;
+ --datatype-cyan-foreground: 185.1 95.9% 90.4%;
+
+ --datatype-indigo: 243.4 75.4% 58.6%;
+ --datatype-indigo-foreground: 226.5 100% 93.9%;
+
+ --datatype-orange: 20.5 90.2% 48.2%;
+ --datatype-orange-foreground: 34.3 100% 91.8%;
+
+ --node-ring: 240 6% 90%;
+
+ --neon-fuschia: 340 100% 60%; /* hsl(340, 100%, 60%) */
+ --digital-orchid: 295 100% 75%; /* hsl(295, 100%, 75%) */
+ --plasma-purple: 262 97% 57%; /* hsl(262, 97%, 57%) */
+ --electric-blue: 248 99% 53%; /* hsl(248, 99%, 53%) */
+ --holo-frost: 186 98% 80%; /* hsl(186, 98%, 80%) */
+ --terminal-green: 129 98% 80%; /* hsl(129, 98%, 80%) */
+ --cosmic-void: 258 95% 16%; /* hsl(258, 95%, 16%) */
+
+ --tool-mode-gradient-1: #f480ff;
+ --tool-mode-gradient-2: #ff3276;
+
+ --slider-input-border: #d4d4d8;
+
+ --zinc-foreground: 240 5.9% 90%;
+ }
+
+ .dark {
+ --foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --background: 240 6% 10%; /* hsl(240, 6%, 10%) */
+ --muted: 240 4% 16%; /* hsl(240, 4%, 16%) */
+ --muted-foreground: 240 5% 65%; /* hsl(240, 5%, 65%) */
+ --card: 240 6% 10%; /* hsl(240, 6%, 10%) */
+ --card-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --popover: 240 6% 10%; /* hsl(240, 6%, 10%) */
+ --popover-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --border: 240 5% 26%; /* hsl(240, 5%, 26%) */
+ --input: 240 5% 34%; /* hsl(240, 5%, 34%) */
+ --primary-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --primary: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --secondary: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --secondary-foreground: 240 6% 90%; /* hsl(240, 6%, 90%) */
+ --accent: 240 4% 16%; /* hsl(240, 4%, 16%) */
+ --accent-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --destructive: 0 84% 60%; /* hsl(0, 84%, 60%) */
+ --destructive-foreground: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --ring: 0 0% 100%; /* hsl(0, 0%, 100%) */
+ --primary-hover: 240 6% 90%; /* hsl(240, 6%, 90%) */
+ --secondary-hover: 240 4% 16%; /* hsl(240, 4%, 16%) */
+ --placeholder-foreground: 240 4% 46%; /* hsl(240, 4%, 46%) */
+ --canvas: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --canvas-dot: 240 5.3% 26.1%; /* hsl(240, 5.3%, 26.1%) */
+ --accent-amber: 26 90% 37%; /* hsl(26, 90%, 37%) */
+ --accent-amber-foreground: 26 90% 37%; /* hsl(26, 90%, 37%) */
+ --accent-emerald: 164 86% 16%; /* hsl(164, 86%, 16%) */
+ --accent-emerald-foreground: 158 64% 52%; /* hsl(158, 64%, 52%) */
+ --accent-emerald-hover: 163.1 88.1% 19.8%; /* hsl(163.1, 88.1%, 19.8%) */
+ --accent-indigo: 242 25% 34%; /* hsl(242, 25%, 34%) */
+ --accent-indigo-foreground: 234 89% 74%; /* hsl(234, 89%, 74%) */
+ --accent-pink: 336 69% 30%; /* hsl(336, 69%, 30%) */
+ --accent-pink-foreground: 329 86% 70%; /* hsl(329, 86%, 70%) */
+ --tooltip: 0 0% 100%; /* hsl(0, 0%, 100%) */
+
+ --tooltip-foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
+ --error-red: 0, 75%, 15%; /*hsla(0, 75%, 15%)*/
+ --error-red-border: 0, 70%, 35%; /*hsla(0,70%,35%)*/
+
+ --note-amber: 45.9 96.7% 64.5%; /* hsl(45.9, 96.7%, 64.5%) */
+ --note-neutral: 240 4.9% 83.9%; /* hsl(240, 4.9%, 83.9%) */
+ --note-rose: 352.6 95.7% 81.8%; /* hsl(352.6, 95.7%, 81.8%) */
+ --note-blue: 211.7 96.4% 78.4%; /* hsl(211.7, 96.4%, 78.4%) */
+ --note-lime: 82 84.5% 67.1%; /* hsl(82, 84.5%, 67.1%) */
+
+ --warning: 45.9 96.7% 64.5%; /* hsl(45.9, 96.7%, 64.5%) */
+ --warning-foreground: 240 6% 10%; /* hsl(240, 6%, 10%) */
+ --warning-text: 0 0% 100%; /* hsl(0, 0%, 100%) */
+
+ --node-selected: 234 89% 74%;
+
+ --ice: #60a5fa;
+ --hover: #1a202e;
+ --disabled-run: #6366f1;
+ --selected: #0369a1;
+
+ --filter-foreground: #eef2ff;
+ --filter-background: #4e46e599;
+
+ --round-btn-shadow: #00000063;
+
+ --success-background: #022c22;
+ --success-foreground: #ecfdf5;
+
+ --error-foreground: #fef2f2;
+ --error-background: #450a0a;
+ --error: #991b1b;
+
+ --info-foreground: #eff6ff;
+ --info-background: #172554;
+
+ --high-indigo: #4338ca;
+ --medium-indigo: #6366f1;
+ --low-indigo: #e0e7ff;
+
+ --component-icon: #c35f85;
+ --flow-icon: #2467e4;
+
+ --build-trigger: #dc735b;
+ --chat-trigger: #5c8be1;
+ --chat-trigger-disabled: #2d3b54;
+ --status-red: #ef4444;
+ --status-yellow: #eab308;
+ --chat-send: #ffffff;
+ --status-green: #4ade80;
+ --status-blue: #2563eb;
+ --connection: #6d6c6c;
+ --code-background: 240 6% 10%;
+ --code-description-background: 0 0% 0%;
+ --code-foreground: 240 6% 90%;
+
+ --beta-background: rgb(37 99 235);
+ --beta-foreground: rgb(219 234 254);
+ --beta-foreground-soft: rgb(219 234 254);
+
+ --chat-bot-icon: #235d70;
+ --chat-user-icon: #4f3d6e;
+
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ --placeholder: 240 5% 64.9%;
+ --hard-zinc: 240 5.2% 33.9%;
+ --smooth-red: 0 74.7% 15.5%;
+
+ --datatype-pink: 327.4 87.1% 81.8%;
+ --datatype-pink-foreground: 333.3 71.4% 50.6%;
+
+ --datatype-rose: 352.6 95.7% 81.8%;
+ --datatype-rose-foreground: 346.8 77.2% 49.8%;
+
+ --datatype-yellow: 50.4 97.8% 63.5%;
+ --datatype-yellow-foreground: 40.6 96.1% 40.4%;
+
+ --datatype-blue: 211.7 96.4% 78.4%;
+ --datatype-blue-foreground: 221.2 83.2% 53.3%;
+
+ --datatype-gray: 216 12.2% 83.9%;
+ --datatype-gray-foreground: 215 13.8% 34.1%;
+
+ --datatype-lime: 82 84.5% 67.1%;
+ --datatype-lime-foreground: 84.8 85.2% 34.5%;
+
+ --datatype-red: 0 93.5% 81.8%;
+ --datatype-red-foreground: 0 72.2% 50.6%;
+
+ --datatype-violet: 252.5 94.7% 85.1%;
+ --datatype-violet-foreground: 262.1 83.3% 57.8%;
+
+ --datatype-emerald: 156.2 71.6% 66.9%;
+ --datatype-emerald-foreground: 161.4 93.5% 30.4%;
+
+ --datatype-fuchsia: 291.1 93.1% 82.9%;
+ --datatype-fuchsia-foreground: 293.4 69.5% 48.8%;
+
+ --datatype-purple: 268.6 100% 91.8%;
+ --datatype-purple-foreground: 272.1 71.7% 47.1%;
+
+ --datatype-cyan: 187 92.4% 69%;
+ --datatype-cyan-foreground: 191.6 91.4% 36.5%;
+
+ --datatype-indigo: 229.7 93.5% 81.8%;
+ --datatype-indigo-foreground: 243.4 75.4% 58.6%;
+
+ --datatype-orange: 20.5 90.2% 48.2%;
+ --datatype-orange-foreground: 30.7 97.2% 72.4%;
+
+ --node-ring: 240 6% 90%;
+
+ --slider-input-border: #d4d4d8;
+
+ --zinc-foreground: 240 5.2% 33.9%;
+ }
+}
diff --git a/langflow/src/frontend/src/svg.d.ts b/langflow/src/frontend/src/svg.d.ts
new file mode 100644
index 0000000..1a3dd3c
--- /dev/null
+++ b/langflow/src/frontend/src/svg.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+ const content: any;
+ export default content;
+}
diff --git a/langflow/src/frontend/src/types/alerts/index.ts b/langflow/src/frontend/src/types/alerts/index.ts
new file mode 100644
index 0000000..8dfe0a1
--- /dev/null
+++ b/langflow/src/frontend/src/types/alerts/index.ts
@@ -0,0 +1,33 @@
+export type ErrorAlertType = {
+ title: string;
+ list: Array | undefined;
+ id: string;
+ removeAlert: (id: string) => void;
+};
+export type NoticeAlertType = {
+ title: string;
+ link?: string;
+ id: string;
+ removeAlert: (id: string) => void;
+};
+export type SuccessAlertType = {
+ title: string;
+ id: string;
+ removeAlert: (id: string) => void;
+};
+export type SingleAlertComponentType = {
+ dropItem: AlertItemType;
+ removeAlert: (index: string) => void;
+};
+export type AlertDropdownType = {
+ children: JSX.Element;
+ notificationRef?: React.RefObject;
+ onClose?: () => void;
+};
+export type AlertItemType = {
+ type: "notice" | "error" | "success";
+ title: string;
+ link?: string;
+ list?: Array;
+ id: string;
+};
diff --git a/langflow/src/frontend/src/types/api/index.ts b/langflow/src/frontend/src/types/api/index.ts
new file mode 100644
index 0000000..650f4f8
--- /dev/null
+++ b/langflow/src/frontend/src/types/api/index.ts
@@ -0,0 +1,323 @@
+import {
+ UseMutationOptions,
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult,
+} from "@tanstack/react-query";
+import { ChatInputType, ChatOutputType } from "../chat";
+import { FlowType } from "../flow";
+//kind and class are just representative names to represent the actual structure of the object received by the API
+export type APIDataType = { [key: string]: APIKindType };
+export type APIObjectType = { [key: string]: APIKindType };
+export type APIKindType = { [key: string]: APIClassType };
+export type APITemplateType = {
+ [key: string]: InputFieldType;
+};
+
+export type APICodeValidateType = {
+ imports: { errors: Array };
+ function: { errors: Array };
+};
+
+export type CustomFieldsType = {
+ [key: string]: Array;
+};
+
+export type CustomComponentRequest = {
+ data: APIClassType;
+ type: string;
+};
+
+export type APIClassType = {
+ base_classes?: Array;
+ description: string;
+ template: APITemplateType;
+ display_name: string;
+ icon?: string;
+ edited?: boolean;
+ is_input?: boolean;
+ is_output?: boolean;
+ conditional_paths?: Array;
+ input_types?: Array;
+ output_types?: Array;
+ custom_fields?: CustomFieldsType;
+ beta?: boolean;
+ legacy?: boolean;
+ documentation: string;
+ error?: string;
+ official?: boolean;
+ outputs?: Array;
+ frozen?: boolean;
+ lf_version?: string;
+ flow?: FlowType;
+ field_order?: string[];
+ tool_mode?: boolean;
+ [key: string]:
+ | Array
+ | string
+ | APITemplateType
+ | boolean
+ | FlowType
+ | CustomFieldsType
+ | boolean
+ | undefined
+ | Array<{ types: Array; selected?: string }>;
+};
+
+export type InputFieldType = {
+ type: string;
+ required: boolean;
+ placeholder?: string;
+ list: boolean;
+ show: boolean;
+ readonly: boolean;
+ password?: boolean;
+ multiline?: boolean;
+ value?: any;
+ dynamic?: boolean;
+ proxy?: { id: string; field: string };
+ input_types?: Array;
+ display_name?: string;
+ name?: string;
+ real_time_refresh?: boolean;
+ refresh_button?: boolean;
+ refresh_button_text?: string;
+ combobox?: boolean;
+ info?: string;
+ [key: string]: any;
+ icon?: string;
+ text?: string;
+};
+
+export type OutputFieldProxyType = {
+ id: string;
+ name: string;
+ nodeDisplayName: string;
+};
+
+export type OutputFieldType = {
+ types: Array;
+ selected?: string;
+ name: string;
+ display_name: string;
+ hidden?: boolean;
+ proxy?: OutputFieldProxyType;
+ allows_loop?: boolean;
+};
+export type errorsTypeAPI = {
+ function: { errors: Array };
+ imports: { errors: Array };
+};
+export type PromptTypeAPI = {
+ input_variables: Array;
+ frontend_node: APIClassType;
+};
+
+export type BuildStatusTypeAPI = {
+ built: boolean;
+};
+
+export type InitTypeAPI = {
+ flowId: string;
+};
+
+export type UploadFileTypeAPI = {
+ file_path: string;
+ flowId: string;
+};
+
+export type ProfilePicturesTypeAPI = {
+ files: string[];
+};
+
+export type LoginType = {
+ grant_type?: string;
+ username: string;
+ password: string;
+ scrope?: string;
+ client_id?: string;
+ client_secret?: string;
+};
+
+export type LoginAuthType = {
+ access_token: string;
+ refresh_token: string;
+ token_type?: string;
+};
+
+export type changeUser = {
+ username?: string;
+ is_active?: boolean;
+ is_superuser?: boolean;
+ password?: string;
+ profile_image?: string;
+};
+
+export type resetPasswordType = {
+ password?: string;
+ profile_image?: string;
+};
+
+export type Users = {
+ id: string;
+ username: string;
+ is_active: boolean;
+ is_superuser: boolean;
+ profile_image: string;
+ create_at: Date;
+ updated_at: Date;
+};
+
+export type Component = {
+ name: string;
+ description: string;
+ data: Object;
+ tags: [string];
+};
+
+export type VerticesOrderTypeAPI = {
+ ids: Array;
+ vertices_to_run: Array;
+ run_id: string;
+};
+
+export type VertexBuildTypeAPI = {
+ id: string;
+ inactivated_vertices: Array | null;
+ next_vertices_ids: Array;
+ top_level_vertices: Array;
+ run_id?: string;
+ valid: boolean;
+ data: VertexDataTypeAPI;
+ timestamp: string;
+ params: any;
+ messages: ChatOutputType[] | ChatInputType[];
+ artifacts: any | ChatOutputType | ChatInputType;
+};
+
+export type ErrorLogType = {
+ errorMessage: string;
+ stackTrace: string;
+};
+
+export type OutputLogType = {
+ message: any | ErrorLogType;
+ type: string;
+};
+export type LogsLogType = {
+ name: string;
+ message: any | ErrorLogType;
+ type: string;
+};
+
+// data is the object received by the API
+// it has results, artifacts, timedelta, duration
+export type VertexDataTypeAPI = {
+ results: { [key: string]: string };
+ outputs: { [key: string]: OutputLogType };
+ logs: { [key: string]: LogsLogType };
+ messages: ChatOutputType[] | ChatInputType[];
+ inactive?: boolean;
+ timedelta?: number;
+ duration?: string;
+ artifacts?: any | ChatOutputType | ChatInputType;
+ message?: ChatOutputType | ChatInputType;
+};
+
+export type CodeErrorDataTypeAPI = {
+ error: string | undefined;
+ traceback: string | undefined;
+};
+
+// the error above is inside this error.response.data.detail.error
+// which comes from a request to the API
+// to type the error we need to know the structure of the object
+
+// error that has a response, that has a data, that has a detail, that has an error
+export type ResponseErrorTypeAPI = {
+ response: { data: { detail: CodeErrorDataTypeAPI } };
+};
+export type ResponseErrorDetailAPI = {
+ response: { data: { detail: string } };
+};
+export type useQueryFunctionType = T extends undefined
+ ? (
+ options?: Omit