Initial push
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
|
**/build
|
||||||
|
**/dist
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:markdown/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
ignorePatterns: ['**/node_modules', '**/dist', '**/build', '**/package-lock.json'],
|
||||||
|
plugins: ['unused-imports'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'unused-imports/no-unused-imports': 'warn',
|
||||||
|
'unused-imports/no-unused-vars': ['warn', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }],
|
||||||
|
'no-undef': 'off',
|
||||||
|
'no-console': [process.env.CI ? 'error' : 'warn', { allow: ['warn', 'error', 'info'] }],
|
||||||
|
'prettier/prettier': 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: '[BUG]'
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Setup**
|
||||||
|
|
||||||
|
- OS: [e.g. iOS, Windows, Linux]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: '[FEATURE]'
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the feature you'd like**
|
||||||
|
A clear and concise description of what you would like Flowise to have.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: Node CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [ubuntu-latest]
|
||||||
|
node-version: [14.x, 16.x]
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- run: npm i -g yarn
|
||||||
|
|
||||||
|
- run: yarn install --ignore-engines
|
||||||
|
|
||||||
|
- run: yarn lint
|
||||||
|
|
||||||
|
- run: yarn build
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# editor
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
**/node_modules
|
||||||
|
**/package-lock.json
|
||||||
|
**/yarn.lock
|
||||||
|
|
||||||
|
## logs
|
||||||
|
**/*.log
|
||||||
|
|
||||||
|
## build
|
||||||
|
**/dist
|
||||||
|
**/build
|
||||||
|
|
||||||
|
## temp
|
||||||
|
**/tmp
|
||||||
|
**/temp
|
||||||
|
|
||||||
|
## test
|
||||||
|
**/coverage
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
## env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env
|
||||||
|
|
||||||
|
## turbo
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
## secrets
|
||||||
|
**/*.key
|
||||||
|
**/api.json
|
||||||
|
|
||||||
|
## compressed
|
||||||
|
**/*.tgz
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn quick # prettify
|
||||||
|
yarn lint-staged # eslint lint(also include prettify but prettify support more file extensions than eslint, so run prettify first)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
|
**/build
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
printWidth: 140,
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
trailingComma: 'none',
|
||||||
|
tabWidth: 4,
|
||||||
|
semi: false,
|
||||||
|
endOfLine: 'auto'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers 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, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at hello@flowiseai.com. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
<!-- markdownlint-disable MD030 -->
|
||||||
|
|
||||||
|
# Contributing to Flowise
|
||||||
|
|
||||||
|
We appreciate any form of contributions.
|
||||||
|
|
||||||
|
## ⭐ Star
|
||||||
|
|
||||||
|
Star and share the [Github Repo](https://github.com/FlowiseAI/Flowise).
|
||||||
|
|
||||||
|
## 🙋 Q&A
|
||||||
|
|
||||||
|
Search up for any questions in [Q&A section](https://github.com/FlowiseAI/Flowise/discussions/categories/q-a), if you can't find one, don't hesitate to create one. It might helps others that have similar question.
|
||||||
|
|
||||||
|
## 🙌 Share Chatflow
|
||||||
|
|
||||||
|
Yes! Sharing how you use Flowise is a way of contribution. Export your chatflow as JSON, attach a screenshot and share it in [Show and Tell section](https://github.com/FlowiseAI/Flowise/discussions/categories/show-and-tell).
|
||||||
|
|
||||||
|
## 💡 Ideas
|
||||||
|
|
||||||
|
Ideas are welcome such as new feature, apps integration, and blockchain networks. Submit in [Ideas section](https://github.com/FlowiseAI/Flowise/discussions/categories/ideas).
|
||||||
|
|
||||||
|
## 🐞 Report Bugs
|
||||||
|
|
||||||
|
Found an issue? [Report it](https://github.com/FlowiseAI/Flowise/issues/new/choose).
|
||||||
|
|
||||||
|
## 👨💻 Contribute to Code
|
||||||
|
|
||||||
|
Not sure what to contribute? Some ideas:
|
||||||
|
|
||||||
|
- Create new components from Langchain
|
||||||
|
- Update existing components such as extending functionality, fixing bugs
|
||||||
|
- Add new chatflow ideas
|
||||||
|
|
||||||
|
### Developers
|
||||||
|
|
||||||
|
Flowise has 3 different modules in a single mono repository.
|
||||||
|
|
||||||
|
- `server`: Node backend to serve API logics
|
||||||
|
- `ui`: React frontend
|
||||||
|
- `components`: Langchain components
|
||||||
|
|
||||||
|
#### Prerequisite
|
||||||
|
|
||||||
|
- Install Yarn
|
||||||
|
```bash
|
||||||
|
npm i -g yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step by step
|
||||||
|
|
||||||
|
1. Fork the official [Flowise Github Repository](https://github.com/FlowiseAI/Flowise).
|
||||||
|
|
||||||
|
2. Clone your forked repository.
|
||||||
|
|
||||||
|
3. Create a new branch, see [guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository). Naming conventions:
|
||||||
|
|
||||||
|
- For feature branch: `feature/<Your New Feature>`
|
||||||
|
- For bug fix branch: `bugfix/<Your New Bugfix>`.
|
||||||
|
|
||||||
|
4. Switch to the newly created branch.
|
||||||
|
|
||||||
|
5. Go into repository folder
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Flowise
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Install all dependencies of all modules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Build all the code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Start the app on [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
9. For development, run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Any changes made in `packages/ui` or `packages/server` will be reflected on [http://localhost:8080](http://localhost:8080)
|
||||||
|
|
||||||
|
For changes made in `packages/components`, run `yarn build` again to pickup the changes.
|
||||||
|
|
||||||
|
10. After making all the changes, run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
to make sure everything works fine in production.
|
||||||
|
|
||||||
|
11. Commit code and submit Pull Request from forked branch pointing to [Flowise master](https://github.com/FlowiseAI/Flowise/tree/master).
|
||||||
|
|
||||||
|
## 📖 Contribute to Docs
|
||||||
|
|
||||||
|
In-Progress
|
||||||
|
|
||||||
|
## 🏷️ Pull Request process
|
||||||
|
|
||||||
|
A member of the FlowiseAI team will automatically be notified/assigned when you open a pull request. You can also reach out to us on [Discord](https://discord.gg/GWcGczPk).
|
||||||
|
|
||||||
|
## 📃 Contributor License Agreement
|
||||||
|
|
||||||
|
Before we can merge your contribution you have to sign our [Contributor License Agreement (CLA)](https://cla-assistant.io/FlowiseAI/Flowise). The CLA contains the terms and conditions under which the contribution is submitted. You need to do this only once for your first pull request. Keep in mind that without a signed CLA we cannot merge your contribution.
|
||||||
|
|
||||||
|
## 📜 Code of Conduct
|
||||||
|
|
||||||
|
This project and everyone participating in it are governed by the Code of Conduct which can be found in the [file](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to hello@flowiseai.com.
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Build local monorepo image
|
||||||
|
# docker build --no-cache -t flowise .
|
||||||
|
# Run image
|
||||||
|
# docker run -d -p 3000:3000 flowise
|
||||||
|
FROM node:16
|
||||||
|
|
||||||
|
WORKDIR /usr/src/packages
|
||||||
|
|
||||||
|
# Copy root package.json and lockfile
|
||||||
|
COPY package.json ./
|
||||||
|
COPY yarn.lock ./
|
||||||
|
|
||||||
|
# Copy components package.json
|
||||||
|
COPY packages/components/package.json ./packages/components/package.json
|
||||||
|
|
||||||
|
# Copy ui package.json
|
||||||
|
COPY packages/ui/package.json ./packages/ui/package.json
|
||||||
|
|
||||||
|
# Copy server package.json
|
||||||
|
COPY packages/server/package.json ./packages/server/package.json
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
# Copy app source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "yarn", "start" ]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 FlowiseAI
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
<!-- markdownlint-disable MD030 -->
|
||||||
|
|
||||||
|
# Flowise - LangchainJS UI
|
||||||
|
|
||||||
|
<a href="https://github.com/FlowiseAI/Flowise">
|
||||||
|
<img width="100%" src="https://github.com/FlowiseAI/Flowise/blob/main/images/flowise.gif?raw=true"></a>
|
||||||
|
|
||||||
|
Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs)
|
||||||
|
|
||||||
|
## ⚡Quick Start
|
||||||
|
|
||||||
|
1. Install Flowise
|
||||||
|
```bash
|
||||||
|
npm install -g flowise
|
||||||
|
```
|
||||||
|
2. Start FlowiseAI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx flowise start
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
|
## 🐳 Docker
|
||||||
|
|
||||||
|
1. Go to `docker` folder at the root of the project
|
||||||
|
2. `docker-compose up -d`
|
||||||
|
3. This will automatically spins up mongodb and flowise containers
|
||||||
|
4. Open [http://localhost:3000](http://localhost:3000)
|
||||||
|
5. You can bring the containers down by `docker-compose stop`
|
||||||
|
|
||||||
|
## 👨💻 Developers
|
||||||
|
|
||||||
|
Flowise has 3 different modules in a single mono repository.
|
||||||
|
|
||||||
|
- `server`: Node backend to serve API logics
|
||||||
|
- `ui`: React frontend
|
||||||
|
- `components`: Langchain components
|
||||||
|
|
||||||
|
### Prerequisite
|
||||||
|
|
||||||
|
- Install Yarn
|
||||||
|
```bash
|
||||||
|
npm i -g yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/FlowiseAI/Flowise.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Go into repository folder
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Flowise
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install all dependencies of all modules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Build all the code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Start the app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now access the app on [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
|
6. For development build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Any code changes will reload the app automatically on [http://localhost:8080](http://localhost:8080)
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
Coming soon
|
||||||
|
|
||||||
|
## 💻 Cloud Hosted
|
||||||
|
|
||||||
|
Coming soon
|
||||||
|
|
||||||
|
## 🌐 Self Host
|
||||||
|
|
||||||
|
Coming soon
|
||||||
|
|
||||||
|
## 🙋 Support
|
||||||
|
|
||||||
|
Feel free to ask any questions, raise problems, and request new features in [discussion](https://github.com/FlowiseAI/Flowise/discussions)
|
||||||
|
|
||||||
|
## 🙌 Contributing
|
||||||
|
|
||||||
|
See [contributing guide](CONTRIBUTING.md). Reach out to us at [Discord](https://discord.gg/GWcGczPk) if you have any questions or issues.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
Source code in this repository is made available under the [MIT License](LICENSE.md).
|
||||||
|
After Width: | Height: | Size: 278 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
targets: {
|
||||||
|
node: 'current'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
FROM node:14.20.0-alpine
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
RUN apk add --no-cache python3 py3-pip
|
||||||
|
|
||||||
|
# You can install a specific version like: flowise@1.0.0
|
||||||
|
RUN npm install -g flowise
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
CMD "flowise"
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
version: '3.1'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo
|
||||||
|
ports:
|
||||||
|
- '27017:27017'
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_DATABASE=flowiseai
|
||||||
|
|
||||||
|
flowise:
|
||||||
|
image: flowiseai/flowise
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- PORT=${PORT}
|
||||||
|
ports:
|
||||||
|
- '${PORT}:${PORT}'
|
||||||
|
links:
|
||||||
|
- mongo
|
||||||
|
volumes:
|
||||||
|
- ~/.flowise:/root/.flowise
|
||||||
|
command: /bin/sh -c "sleep 3; flowise start"
|
||||||
|
After Width: | Height: | Size: 4.7 MiB |
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"name": "flowise",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"homepage": "https://flowiseai.com",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"flowise",
|
||||||
|
"ui",
|
||||||
|
"components"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev --parallel",
|
||||||
|
"start": "run-script-os",
|
||||||
|
"start:windows": "cd packages/server/bin && run start",
|
||||||
|
"start:default": "cd packages/server/bin && ./run start",
|
||||||
|
"clean": "npm exec -ws -- rimraf dist build",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
|
"test": "turbo run test",
|
||||||
|
"lint": "eslint \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||||
|
"lint-fix": "yarn lint --fix",
|
||||||
|
"quick": "pretty-quick --staged",
|
||||||
|
"postinstall": "husky install"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx,ts,tsx,json,md}": "eslint --fix"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "1.7.4",
|
||||||
|
"@babel/preset-env": "^7.19.4",
|
||||||
|
"@babel/preset-typescript": "7.18.6",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@typescript-eslint/typescript-estree": "^5.39.0",
|
||||||
|
"eslint": "^8.24.0",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
|
"eslint-plugin-markdown": "^3.0.0",
|
||||||
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
|
"eslint-plugin-react": "^7.26.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"husky": "^8.0.1",
|
||||||
|
"lint-staged": "^13.0.3",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"pretty-quick": "^3.1.3",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.15.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!-- markdownlint-disable MD030 -->
|
||||||
|
|
||||||
|
# Flowise Components
|
||||||
|
|
||||||
|
Apps integration for Flowise. Contain Nodes and Credentials.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i flowise-components
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md).
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import gulp from 'gulp'
|
||||||
|
|
||||||
|
const { src, dest } = gulp
|
||||||
|
|
||||||
|
function copyIcons() {
|
||||||
|
return src(['nodes/**/*.{jpg,png,svg}']).pipe(dest('dist/nodes'))
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = copyIcons
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
|
||||||
|
class MRLKAgentLLM implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'MRLK Agent for LLMs'
|
||||||
|
this.name = 'mrlkAgentLLM'
|
||||||
|
this.type = 'AgentExecutor'
|
||||||
|
this.category = 'Agents'
|
||||||
|
this.icon = 'agent.svg'
|
||||||
|
this.description = 'Agent that uses the ReAct Framework to decide what action to take, optimized to be used with LLMs'
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'Allowed Tools',
|
||||||
|
name: 'tools',
|
||||||
|
type: 'Tool',
|
||||||
|
list: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'LLM Model',
|
||||||
|
name: 'model',
|
||||||
|
type: 'BaseLanguageModel'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
return ['AgentExecutor']
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
|
const { initializeAgentExecutor } = await import('langchain/agents')
|
||||||
|
|
||||||
|
const model = nodeData.inputs?.model
|
||||||
|
const tools = nodeData.inputs?.tools
|
||||||
|
|
||||||
|
const executor = await initializeAgentExecutor(tools, model, 'zero-shot-react-description', true)
|
||||||
|
|
||||||
|
return executor
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(nodeData: INodeData, input: string): Promise<string> {
|
||||||
|
const executor = nodeData.instance
|
||||||
|
const result = await executor.call({ input })
|
||||||
|
|
||||||
|
return result?.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: MRLKAgentLLM }
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-robot" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M7 7h10a2 2 0 0 1 2 2v1l1 1v3l-1 1v3a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-3l-1 -1v-3l1 -1v-1a2 2 0 0 1 2 -2z"></path>
|
||||||
|
<path d="M10 16h4"></path>
|
||||||
|
<circle cx="8.5" cy="11.5" r=".5" fill="currentColor"></circle>
|
||||||
|
<circle cx="15.5" cy="11.5" r=".5" fill="currentColor"></circle>
|
||||||
|
<path d="M9 7l-1 -4"></path>
|
||||||
|
<path d="M15 7l1 -4"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 650 B |
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses } from '../../../src/utils'
|
||||||
|
|
||||||
|
class LLMChain_Chains implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
description: string
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'LLM Chain'
|
||||||
|
this.name = 'llmChain'
|
||||||
|
this.type = 'LLMChain'
|
||||||
|
this.icon = 'chain.svg'
|
||||||
|
this.category = 'Chains'
|
||||||
|
this.description = 'Chain to run queries against LLMs'
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'LLM',
|
||||||
|
name: 'llm',
|
||||||
|
type: 'BaseLanguageModel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Prompt',
|
||||||
|
name: 'prompt',
|
||||||
|
type: 'BasePromptTemplate'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
const { LLMChain } = await import('langchain/chains')
|
||||||
|
return getBaseClasses(LLMChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
|
const { LLMChain } = await import('langchain/chains')
|
||||||
|
|
||||||
|
const llm = nodeData.inputs?.llm
|
||||||
|
const prompt = nodeData.inputs?.prompt
|
||||||
|
|
||||||
|
const chain = new LLMChain({ llm, prompt })
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(nodeData: INodeData, input: string): Promise<string> {
|
||||||
|
const prompt = nodeData.instance.prompt.inputVariables // ["product"]
|
||||||
|
if (prompt.length > 1) throw new Error('Prompt can only contains 1 literal string {}. Multiples are found')
|
||||||
|
|
||||||
|
const chain = nodeData.instance
|
||||||
|
const res = await chain.run(input)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: LLMChain_Chains }
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-dna" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M14.828 14.828a4 4 0 1 0 -5.656 -5.656a4 4 0 0 0 5.656 5.656z"></path>
|
||||||
|
<path d="M9.172 20.485a4 4 0 1 0 -5.657 -5.657"></path>
|
||||||
|
<path d="M14.828 3.515a4 4 0 0 0 5.657 5.657"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses } from '../../../src/utils'
|
||||||
|
|
||||||
|
class OpenAI_LLMs implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
description: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'OpenAI'
|
||||||
|
this.name = 'openAI'
|
||||||
|
this.type = 'OpenAI'
|
||||||
|
this.icon = 'openai.png'
|
||||||
|
this.category = 'LLMs'
|
||||||
|
this.description = 'Wrapper around OpenAI large language models'
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'OpenAI Api Key',
|
||||||
|
name: 'openAIApiKey',
|
||||||
|
type: 'password'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Model Name',
|
||||||
|
name: 'modelName',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'text-davinci-003',
|
||||||
|
name: 'text-davinci-003'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'text-davinci-002',
|
||||||
|
name: 'text-davinci-002'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'text-curie-001',
|
||||||
|
name: 'text-curie-001'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'text-babbage-001',
|
||||||
|
name: 'text-babbage-001'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: 'text-davinci-003',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Temperature',
|
||||||
|
name: 'temperature',
|
||||||
|
type: 'number',
|
||||||
|
default: 0.7,
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
const { OpenAI } = await import('langchain/llms')
|
||||||
|
return getBaseClasses(OpenAI)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
|
const { OpenAI } = await import('langchain/llms')
|
||||||
|
|
||||||
|
const temperature = nodeData.inputs?.temperature as string
|
||||||
|
const modelName = nodeData.inputs?.modelName as string
|
||||||
|
const openAIApiKey = nodeData.inputs?.openAIApiKey as string
|
||||||
|
|
||||||
|
const model = new OpenAI({
|
||||||
|
temperature: parseInt(temperature, 10),
|
||||||
|
modelName,
|
||||||
|
openAIApiKey
|
||||||
|
})
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: OpenAI_LLMs }
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses, getInputVariables } from '../../../src/utils'
|
||||||
|
|
||||||
|
class PromptTemplate_Prompts implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Prompt Template'
|
||||||
|
this.name = 'promptTemplate'
|
||||||
|
this.type = 'PromptTemplate'
|
||||||
|
this.icon = 'prompt.svg'
|
||||||
|
this.category = 'Prompts'
|
||||||
|
this.description = 'Schema to represent a basic prompt for an LLM. Template can only contains 1 literal string {}'
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'Template',
|
||||||
|
name: 'template',
|
||||||
|
type: 'string',
|
||||||
|
rows: 5,
|
||||||
|
default: 'What is a good name for a company that makes {product}?',
|
||||||
|
placeholder: 'What is a good name for a company that makes {product}?'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
const { PromptTemplate } = await import('langchain/prompts')
|
||||||
|
return getBaseClasses(PromptTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
|
const { PromptTemplate } = await import('langchain/prompts')
|
||||||
|
|
||||||
|
const template = nodeData.inputs?.template as string
|
||||||
|
const inputVariables = getInputVariables(template)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prompt = new PromptTemplate({
|
||||||
|
template,
|
||||||
|
inputVariables: inputVariables
|
||||||
|
})
|
||||||
|
return prompt
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: PromptTemplate_Prompts }
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-terminal-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M8 9l3 3l-3 3"></path>
|
||||||
|
<path d="M13 15l3 0"></path>
|
||||||
|
<path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 465 B |
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { INode } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses } from '../../../src/utils'
|
||||||
|
|
||||||
|
class Calculator implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Calculator'
|
||||||
|
this.name = 'calculator'
|
||||||
|
this.type = 'Calculator'
|
||||||
|
this.icon = 'calculator.svg'
|
||||||
|
this.category = 'Tools'
|
||||||
|
this.description = 'Perform calculations on response'
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
const { Calculator } = await import('langchain/tools')
|
||||||
|
return getBaseClasses(Calculator)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<any> {
|
||||||
|
const { Calculator } = await import('langchain/tools')
|
||||||
|
return new Calculator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: Calculator }
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calculator" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M4 3m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path>
|
||||||
|
<path d="M8 7m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v1a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z"></path>
|
||||||
|
<path d="M8 14l0 .01"></path>
|
||||||
|
<path d="M12 14l0 .01"></path>
|
||||||
|
<path d="M16 14l0 .01"></path>
|
||||||
|
<path d="M8 17l0 .01"></path>
|
||||||
|
<path d="M12 17l0 .01"></path>
|
||||||
|
<path d="M16 17l0 .01"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 698 B |
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
|
import { getBaseClasses } from '../../../src/utils'
|
||||||
|
|
||||||
|
class SerpAPI implements INode {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
inputs: INodeParams[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.label = 'Serp API'
|
||||||
|
this.name = 'serpAPI'
|
||||||
|
this.type = 'SerpAPI'
|
||||||
|
this.icon = 'serp.png'
|
||||||
|
this.category = 'Tools'
|
||||||
|
this.description = 'Wrapper around SerpAPI - a real-time API to access Google search results'
|
||||||
|
this.inputs = [
|
||||||
|
{
|
||||||
|
label: 'Serp Api Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'password'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBaseClasses(): Promise<string[]> {
|
||||||
|
const { SerpAPI } = await import('langchain/tools')
|
||||||
|
return getBaseClasses(SerpAPI)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(nodeData: INodeData): Promise<any> {
|
||||||
|
const { SerpAPI } = await import('langchain/tools')
|
||||||
|
const apiKey = nodeData.inputs?.apiKey as string
|
||||||
|
return new SerpAPI(apiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { nodeClass: SerpAPI }
|
||||||
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "flowise-components",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Flowiseai Components",
|
||||||
|
"main": "dist/src/index",
|
||||||
|
"types": "dist/src/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && gulp",
|
||||||
|
"dev": "tsc --watch"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "https://flowiseai.com",
|
||||||
|
"author": {
|
||||||
|
"name": "Henry Heng",
|
||||||
|
"email": "henryheng@flowiseai.com"
|
||||||
|
},
|
||||||
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"dotenv": "^16.0.0",
|
||||||
|
"express": "^4.17.3",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"langchain": "^0.0.44",
|
||||||
|
"moment": "^2.29.3",
|
||||||
|
"node-fetch": "2",
|
||||||
|
"ws": "^8.9.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/gulp": "4.0.9",
|
||||||
|
"@types/ws": "^8.5.3",
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type NodeParamsType =
|
||||||
|
| 'asyncOptions'
|
||||||
|
| 'options'
|
||||||
|
| 'string'
|
||||||
|
| 'number'
|
||||||
|
| 'boolean'
|
||||||
|
| 'password'
|
||||||
|
| 'json'
|
||||||
|
| 'code'
|
||||||
|
| 'date'
|
||||||
|
| 'file'
|
||||||
|
| 'folder'
|
||||||
|
|
||||||
|
export type CommonType = string | number | boolean | undefined | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Others
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ICommonObject {
|
||||||
|
[key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAttachment {
|
||||||
|
content: string
|
||||||
|
contentType: string
|
||||||
|
size?: number
|
||||||
|
filename?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeOptionsValue {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeParams {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
type: NodeParamsType | string
|
||||||
|
default?: CommonType | ICommonObject | ICommonObject[]
|
||||||
|
description?: string
|
||||||
|
options?: Array<INodeOptionsValue>
|
||||||
|
optional?: boolean | INodeDisplay
|
||||||
|
rows?: number
|
||||||
|
list?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeExecutionData {
|
||||||
|
[key: string]: CommonType | CommonType[] | ICommonObject | ICommonObject[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeDisplay {
|
||||||
|
[key: string]: string[] | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeProperties {
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
icon: string
|
||||||
|
category: string
|
||||||
|
baseClasses: string[]
|
||||||
|
description?: string
|
||||||
|
filePath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INode extends INodeProperties {
|
||||||
|
inputs?: INodeParams[]
|
||||||
|
getBaseClasses?(): Promise<string[]>
|
||||||
|
getInstance?(nodeData: INodeData): Promise<string>
|
||||||
|
run?(nodeData: INodeData, input: string): Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeData extends INodeProperties {
|
||||||
|
inputs?: ICommonObject
|
||||||
|
instance?: any
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './Interface'
|
||||||
|
export * from './utils'
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||||
|
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||||
|
|
||||||
|
export const getBaseClasses = (targetClass: any) => {
|
||||||
|
const baseClasses: string[] = []
|
||||||
|
|
||||||
|
if (targetClass instanceof Function) {
|
||||||
|
let baseClass = targetClass
|
||||||
|
|
||||||
|
while (baseClass) {
|
||||||
|
const newBaseClass = Object.getPrototypeOf(baseClass)
|
||||||
|
if (newBaseClass && newBaseClass !== Object && newBaseClass.name) {
|
||||||
|
baseClass = newBaseClass
|
||||||
|
baseClasses.push(baseClass.name)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return baseClasses
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize axios query params
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {any} params
|
||||||
|
* @param {boolean} skipIndex // Set to true if you want same params to be: param=1¶m=2 instead of: param[0]=1¶m[1]=2
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function serializeQueryParams(params: any, skipIndex?: boolean): string {
|
||||||
|
const parts: any[] = []
|
||||||
|
|
||||||
|
const encode = (val: string) => {
|
||||||
|
return encodeURIComponent(val)
|
||||||
|
.replace(/%3A/gi, ':')
|
||||||
|
.replace(/%24/g, '$')
|
||||||
|
.replace(/%2C/gi, ',')
|
||||||
|
.replace(/%20/g, '+')
|
||||||
|
.replace(/%5B/gi, '[')
|
||||||
|
.replace(/%5D/gi, ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertPart = (key: string, val: any) => {
|
||||||
|
if (val instanceof Date) val = val.toISOString()
|
||||||
|
else if (val instanceof Object) val = JSON.stringify(val)
|
||||||
|
|
||||||
|
parts.push(encode(key) + '=' + encode(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(params).forEach(([key, val]) => {
|
||||||
|
if (val === null || typeof val === 'undefined') return
|
||||||
|
|
||||||
|
if (Array.isArray(val)) val.forEach((v, i) => convertPart(`${key}${skipIndex ? '' : `[${i}]`}`, v))
|
||||||
|
else convertPart(key, val)
|
||||||
|
})
|
||||||
|
|
||||||
|
return parts.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle error from try catch
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @param {any} error
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function handleErrorMessage(error: any): string {
|
||||||
|
let errorMessage = ''
|
||||||
|
|
||||||
|
if (error.message) {
|
||||||
|
errorMessage += error.message + '. '
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
if (error.response.data.error) {
|
||||||
|
if (typeof error.response.data.error === 'object') errorMessage += JSON.stringify(error.response.data.error) + '. '
|
||||||
|
else if (typeof error.response.data.error === 'string') errorMessage += error.response.data.error + '. '
|
||||||
|
} else if (error.response.data.msg) errorMessage += error.response.data.msg + '. '
|
||||||
|
else if (error.response.data.Message) errorMessage += error.response.data.Message + '. '
|
||||||
|
else if (typeof error.response.data === 'string') errorMessage += error.response.data + '. '
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errorMessage) errorMessage = 'Unexpected Error.'
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path of node modules package
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getNodeModulesPackagePath = (packageName: string): string => {
|
||||||
|
const checkPaths = [
|
||||||
|
path.join(__dirname, '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', packageName)
|
||||||
|
]
|
||||||
|
for (const checkPath of checkPaths) {
|
||||||
|
if (fs.existsSync(checkPath)) {
|
||||||
|
return checkPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get input variables
|
||||||
|
* @param {string} paramValue
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const getInputVariables = (paramValue: string): string[] => {
|
||||||
|
let returnVal = paramValue
|
||||||
|
const variableStack = []
|
||||||
|
const inputVariables = []
|
||||||
|
let startIdx = 0
|
||||||
|
const endIdx = returnVal.length - 1
|
||||||
|
|
||||||
|
while (startIdx < endIdx) {
|
||||||
|
const substr = returnVal.substring(startIdx, startIdx + 1)
|
||||||
|
|
||||||
|
// Store the opening double curly bracket
|
||||||
|
if (substr === '{') {
|
||||||
|
variableStack.push({ substr, startIdx: startIdx + 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the complete variable
|
||||||
|
if (substr === '}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{') {
|
||||||
|
const variableStartIdx = variableStack[variableStack.length - 1].startIdx
|
||||||
|
const variableEndIdx = startIdx
|
||||||
|
const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx)
|
||||||
|
inputVariables.push(variableFullPath)
|
||||||
|
variableStack.pop()
|
||||||
|
}
|
||||||
|
startIdx += 1
|
||||||
|
}
|
||||||
|
return inputVariables
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
|
||||||
|
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
|
||||||
|
"target": "ES2020", // or higher
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"useUnknownInCatchVariables": false,
|
||||||
|
"declaration": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node16"
|
||||||
|
},
|
||||||
|
"include": ["src", "nodes"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<!-- markdownlint-disable MD030 -->
|
||||||
|
|
||||||
|
# Flowise - LangchainJS UI
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Drag & drop UI to build your customized LLM flow using [LangchainJS](https://github.com/hwchase17/langchainjs)
|
||||||
|
|
||||||
|
## ⚡Quick Start
|
||||||
|
|
||||||
|
1. Install Flowise
|
||||||
|
```bash
|
||||||
|
npm install -g flowise
|
||||||
|
```
|
||||||
|
2. Start Flowise
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx flowise start
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open [http://localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
Coming Soon
|
||||||
|
|
||||||
|
## 💻 Cloud Hosted
|
||||||
|
|
||||||
|
Coming Soon
|
||||||
|
|
||||||
|
## 🌐 Self Host
|
||||||
|
|
||||||
|
Coming Soon
|
||||||
|
|
||||||
|
## 🙋 Support
|
||||||
|
|
||||||
|
Feel free to ask any questions, raise problems, and request new features in [discussion](https://github.com/FlowiseAI/Flowise/discussions)
|
||||||
|
|
||||||
|
## 🙌 Contributing
|
||||||
|
|
||||||
|
See [contributing guide](https://github.com/FlowiseAI/Flowise/blob/master/CONTRIBUTING.md). Reach out to us at [Discord](https://discord.gg/GWcGczPk) if you have any questions or issues.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: '../../babel.config.js'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const oclif = require('@oclif/core')
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const project = path.join(__dirname, '..', 'tsconfig.json')
|
||||||
|
|
||||||
|
// In dev mode -> use ts-node and dev plugins
|
||||||
|
process.env.NODE_ENV = 'development'
|
||||||
|
|
||||||
|
require('ts-node').register({ project })
|
||||||
|
|
||||||
|
// In dev mode, always show stack traces
|
||||||
|
oclif.settings.debug = true
|
||||||
|
|
||||||
|
// Start the CLI
|
||||||
|
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
node "%~dp0\dev" %*
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const oclif = require('@oclif/core')
|
||||||
|
|
||||||
|
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
node "%~dp0\run" %*
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"ignore": ["**/*.spec.ts", ".git", "node_modules"],
|
||||||
|
"watch": ["commands", "index.ts", "src"],
|
||||||
|
"exec": "yarn oclif-dev",
|
||||||
|
"ext": "ts"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "flowise",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Flowiseai Server",
|
||||||
|
"main": "dist/index",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"bin": {
|
||||||
|
"flowise": "./bin/run"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"dist",
|
||||||
|
"npm-shrinkwrap.json",
|
||||||
|
"oclif.manifest.json",
|
||||||
|
"oauth2.html",
|
||||||
|
".env"
|
||||||
|
],
|
||||||
|
"oclif": {
|
||||||
|
"bin": "flowise",
|
||||||
|
"commands": "./dist/commands"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "run-script-os",
|
||||||
|
"start:windows": "cd bin && run start",
|
||||||
|
"start:default": "cd bin && ./run start",
|
||||||
|
"dev": "concurrently \"yarn watch\" \"nodemon\"",
|
||||||
|
"oclif-dev": "run-script-os",
|
||||||
|
"oclif-dev:windows": "cd bin && dev start",
|
||||||
|
"oclif-dev:default": "cd bin && ./dev start",
|
||||||
|
"postpack": "shx rm -f oclif.manifest.json",
|
||||||
|
"prepack": "yarn build && oclif manifest && oclif readme",
|
||||||
|
"typeorm": "typeorm-ts-node-commonjs",
|
||||||
|
"watch": "tsc --watch",
|
||||||
|
"version": "oclif readme && git add README.md"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "https://flowiseai.com",
|
||||||
|
"author": {
|
||||||
|
"name": "Henry Heng",
|
||||||
|
"email": "henryheng@flowiseai.com"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.15.0"
|
||||||
|
},
|
||||||
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
|
"dependencies": {
|
||||||
|
"@oclif/core": "^1.13.10",
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.0.0",
|
||||||
|
"express": "^4.17.3",
|
||||||
|
"flowise-components": "*",
|
||||||
|
"flowise-ui": "*",
|
||||||
|
"moment-timezone": "^0.5.34",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
|
"typeorm": "^0.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.12",
|
||||||
|
"concurrently": "^7.1.0",
|
||||||
|
"nodemon": "^2.0.15",
|
||||||
|
"oclif": "^3",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
|
"shx": "^0.3.3",
|
||||||
|
"ts-node": "^10.7.0",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'reflect-metadata'
|
||||||
|
import path from 'path'
|
||||||
|
import { DataSource } from 'typeorm'
|
||||||
|
import { ChatFlow } from './entity/ChatFlow'
|
||||||
|
import { ChatMessage } from './entity/ChatMessage'
|
||||||
|
import { getUserHome } from './utils'
|
||||||
|
|
||||||
|
let appDataSource: DataSource
|
||||||
|
|
||||||
|
export const init = async (): Promise<void> => {
|
||||||
|
const homePath = path.join(getUserHome(), '.flowise')
|
||||||
|
|
||||||
|
appDataSource = new DataSource({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: path.resolve(homePath, 'database.sqlite'),
|
||||||
|
synchronize: true,
|
||||||
|
entities: [ChatFlow, ChatMessage],
|
||||||
|
migrations: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataSource(): DataSource {
|
||||||
|
if (appDataSource === undefined) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
return appDataSource
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { INode, INodeData } from 'flowise-components'
|
||||||
|
|
||||||
|
export type MessageType = 'apiMessage' | 'userMessage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databases
|
||||||
|
*/
|
||||||
|
export interface IChatFlow {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
flowData: string
|
||||||
|
deployed: boolean
|
||||||
|
updatedDate: Date
|
||||||
|
createdDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChatMessage {
|
||||||
|
id: string
|
||||||
|
role: MessageType
|
||||||
|
content: string
|
||||||
|
chatflowid: string
|
||||||
|
createdDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IComponentNodesPool {
|
||||||
|
[key: string]: INode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVariableDict {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeDependencies {
|
||||||
|
[key: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeDirectedGraph {
|
||||||
|
[key: string]: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReactFlowNode {
|
||||||
|
id: string
|
||||||
|
position: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
type: string
|
||||||
|
data: INodeData
|
||||||
|
positionAbsolute: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
z: number
|
||||||
|
handleBounds: {
|
||||||
|
source: any
|
||||||
|
target: any
|
||||||
|
}
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
selected: boolean
|
||||||
|
dragging: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReactFlowEdge {
|
||||||
|
source: string
|
||||||
|
sourceHandle: string
|
||||||
|
target: string
|
||||||
|
targetHandle: string
|
||||||
|
type: string
|
||||||
|
id: string
|
||||||
|
data: {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReactFlowObject {
|
||||||
|
nodes: IReactFlowNode[]
|
||||||
|
edges: IReactFlowEdge[]
|
||||||
|
viewport: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
zoom: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IExploredNode {
|
||||||
|
[key: string]: {
|
||||||
|
remainingLoop: number
|
||||||
|
lastSeenDepth: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeQueue {
|
||||||
|
nodeId: string
|
||||||
|
depth: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IncomingInput {
|
||||||
|
question: string
|
||||||
|
history: string[]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { IComponentNodesPool } from './Interface'
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import { Dirent } from 'fs'
|
||||||
|
import { getNodeModulesPackagePath } from './utils'
|
||||||
|
import { promises } from 'fs'
|
||||||
|
|
||||||
|
export class NodesPool {
|
||||||
|
componentNodes: IComponentNodesPool = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize to get all nodes
|
||||||
|
*/
|
||||||
|
async initialize() {
|
||||||
|
const packagePath = getNodeModulesPackagePath('flowise-components')
|
||||||
|
const nodesPath = path.join(packagePath, 'dist', 'nodes')
|
||||||
|
const nodeFiles = await this.getFiles(nodesPath)
|
||||||
|
return Promise.all(
|
||||||
|
nodeFiles.map(async (file) => {
|
||||||
|
if (file.endsWith('.js')) {
|
||||||
|
const nodeModule = await require(file)
|
||||||
|
try {
|
||||||
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
newNodeInstance.filePath = file
|
||||||
|
|
||||||
|
const baseClasses = await newNodeInstance.getBaseClasses!.call(newNodeInstance)
|
||||||
|
newNodeInstance.baseClasses = baseClasses
|
||||||
|
|
||||||
|
this.componentNodes[newNodeInstance.name] = newNodeInstance
|
||||||
|
|
||||||
|
// Replace file icon with absolute path
|
||||||
|
if (
|
||||||
|
newNodeInstance.icon &&
|
||||||
|
(newNodeInstance.icon.endsWith('.svg') ||
|
||||||
|
newNodeInstance.icon.endsWith('.png') ||
|
||||||
|
newNodeInstance.icon.endsWith('.jpg'))
|
||||||
|
) {
|
||||||
|
const filePath = file.replace(/\\/g, '/').split('/')
|
||||||
|
filePath.pop()
|
||||||
|
const nodeIconAbsolutePath = `${filePath.join('/')}/${newNodeInstance.icon}`
|
||||||
|
this.componentNodes[newNodeInstance.name].icon = nodeIconAbsolutePath
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function to get node files
|
||||||
|
* @param {string} dir
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
async getFiles(dir: string): Promise<string[]> {
|
||||||
|
const dirents = await promises.readdir(dir, { withFileTypes: true })
|
||||||
|
const files = await Promise.all(
|
||||||
|
dirents.map((dirent: Dirent) => {
|
||||||
|
const res = path.resolve(dir, dirent.name)
|
||||||
|
return dirent.isDirectory() ? this.getFiles(res) : res
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return Array.prototype.concat(...files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Command, Flags } from '@oclif/core'
|
||||||
|
import path from 'path'
|
||||||
|
import * as Server from '../index'
|
||||||
|
import * as DataSource from '../DataSource'
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
|
||||||
|
|
||||||
|
enum EXIT_CODE {
|
||||||
|
SUCCESS = 0,
|
||||||
|
FAILED = 1
|
||||||
|
}
|
||||||
|
let processExitCode = EXIT_CODE.SUCCESS
|
||||||
|
|
||||||
|
export default class Start extends Command {
|
||||||
|
static flags = {
|
||||||
|
mongourl: Flags.string()
|
||||||
|
}
|
||||||
|
|
||||||
|
static args = []
|
||||||
|
|
||||||
|
async stopProcess() {
|
||||||
|
console.info('Shutting down Flowise...')
|
||||||
|
try {
|
||||||
|
// Shut down the app after timeout if it ever stuck removing pools
|
||||||
|
setTimeout(() => {
|
||||||
|
console.info('Flowise was forced to shut down after 30 secs')
|
||||||
|
process.exit(processExitCode)
|
||||||
|
}, 30000)
|
||||||
|
|
||||||
|
// Removing pools
|
||||||
|
const serverApp = Server.getInstance()
|
||||||
|
if (serverApp) await serverApp.stopApp()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error shutting down Flowise...', error)
|
||||||
|
}
|
||||||
|
process.exit(processExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(): Promise<void> {
|
||||||
|
process.on('SIGTERM', this.stopProcess)
|
||||||
|
process.on('SIGINT', this.stopProcess)
|
||||||
|
|
||||||
|
// Prevent throw new Error from crashing the app
|
||||||
|
// TODO: Get rid of this and send proper error message to ui
|
||||||
|
process.on('uncaughtException', (err) => {
|
||||||
|
console.error('uncaughtException: ', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { flags } = await this.parse(Start)
|
||||||
|
if (flags.mongourl) process.env.MONGO_URL = flags.mongourl
|
||||||
|
|
||||||
|
await (async () => {
|
||||||
|
try {
|
||||||
|
this.log('Starting Flowise...')
|
||||||
|
await DataSource.init()
|
||||||
|
await Server.start()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error starting Flowise...', error)
|
||||||
|
processExitCode = EXIT_CODE.FAILED
|
||||||
|
// @ts-ignore
|
||||||
|
process.emit('SIGINT')
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
import { IChatFlow } from '../Interface'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ChatFlow implements IChatFlow {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
flowData: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
deployed: boolean
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdDate: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedDate: Date
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm'
|
||||||
|
import { IChatMessage, MessageType } from '../Interface'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ChatMessage implements IChatMessage {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
role: MessageType
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column()
|
||||||
|
chatflowid: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
content: string
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdDate: Date
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
export const workflow1 = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
width: 200,
|
||||||
|
height: 66,
|
||||||
|
id: 'promptTemplate_0',
|
||||||
|
position: {
|
||||||
|
x: 295.0571878493141,
|
||||||
|
y: 108.66221078850214
|
||||||
|
},
|
||||||
|
type: 'customNode',
|
||||||
|
data: {
|
||||||
|
label: 'Prompt Template',
|
||||||
|
name: 'promptTemplate',
|
||||||
|
type: 'PromptTemplate',
|
||||||
|
inputAnchors: [],
|
||||||
|
outputAnchors: [
|
||||||
|
{
|
||||||
|
id: 'promptTemplate_0-output-0'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selected: false,
|
||||||
|
inputs: {
|
||||||
|
template: 'What is a good name for a company that makes {product}?',
|
||||||
|
inputVariables: '["product"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
positionAbsolute: {
|
||||||
|
x: 295.0571878493141,
|
||||||
|
y: 108.66221078850214
|
||||||
|
},
|
||||||
|
dragging: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 200,
|
||||||
|
height: 66,
|
||||||
|
id: 'openAI_0',
|
||||||
|
position: {
|
||||||
|
x: 774,
|
||||||
|
y: 97.75
|
||||||
|
},
|
||||||
|
type: 'customNode',
|
||||||
|
data: {
|
||||||
|
label: 'OpenAI',
|
||||||
|
name: 'openAI',
|
||||||
|
type: 'OpenAI',
|
||||||
|
inputAnchors: [],
|
||||||
|
outputAnchors: [
|
||||||
|
{
|
||||||
|
id: 'openAI_0-output-0'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selected: false,
|
||||||
|
inputs: {
|
||||||
|
modelName: 'text-davinci-003',
|
||||||
|
temperature: '0.7',
|
||||||
|
openAIApiKey: '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'
|
||||||
|
},
|
||||||
|
calls: {
|
||||||
|
prompt: 'Hi, how are you?'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
positionAbsolute: {
|
||||||
|
x: 774,
|
||||||
|
y: 97.75
|
||||||
|
},
|
||||||
|
dragging: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 200,
|
||||||
|
height: 66,
|
||||||
|
id: 'llmChain_0',
|
||||||
|
position: {
|
||||||
|
x: 1034.233162523021,
|
||||||
|
y: 97.59868104260748
|
||||||
|
},
|
||||||
|
type: 'customNode',
|
||||||
|
data: {
|
||||||
|
label: 'LLM Chain',
|
||||||
|
name: 'llmChain',
|
||||||
|
type: 'LLMChain',
|
||||||
|
inputAnchors: [
|
||||||
|
{
|
||||||
|
id: 'llmChain_0-input-0'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
outputAnchors: [
|
||||||
|
{
|
||||||
|
id: 'llmChain_0-output-0'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selected: false,
|
||||||
|
inputs: {
|
||||||
|
llm: '{{openAI_0.data.instance}}',
|
||||||
|
prompt: '{{promptTemplate_0.data.instance}}'
|
||||||
|
},
|
||||||
|
calls: {
|
||||||
|
variable: '{"product":"colorful socks"}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: false,
|
||||||
|
positionAbsolute: {
|
||||||
|
x: 1034.233162523021,
|
||||||
|
y: 97.59868104260748
|
||||||
|
},
|
||||||
|
dragging: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
source: 'nodeJS_0',
|
||||||
|
sourceHandle: 'nodeJS_0-output-0',
|
||||||
|
target: 'nodeJS_1',
|
||||||
|
targetHandle: 'nodeJS_1-input-0',
|
||||||
|
type: 'buttonedge',
|
||||||
|
id: 'nodeJS_0-nodeJS_0-output-0-nodeJS_1-nodeJS_1-input-0',
|
||||||
|
data: {
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'webhook_0',
|
||||||
|
sourceHandle: 'webhook_0-output-0',
|
||||||
|
target: 'wait_0',
|
||||||
|
targetHandle: 'wait_0-input-0',
|
||||||
|
type: 'buttonedge',
|
||||||
|
id: 'webhook_0-webhook_0-output-0-wait_0-wait_0-input-0',
|
||||||
|
data: {
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: 'wait_0',
|
||||||
|
sourceHandle: 'wait_0-output-0',
|
||||||
|
target: 'nodeJS_0',
|
||||||
|
targetHandle: 'nodeJS_0-input-0',
|
||||||
|
type: 'buttonedge',
|
||||||
|
id: 'wait_0-wait_0-output-0-nodeJS_0-nodeJS_0-input-0',
|
||||||
|
data: {
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
import express, { Request, Response } from 'express'
|
||||||
|
import path from 'path'
|
||||||
|
import cors from 'cors'
|
||||||
|
import http from 'http'
|
||||||
|
|
||||||
|
import { IChatFlow, IComponentNodesPool, IncomingInput, IReactFlowNode, IReactFlowObject } from './Interface'
|
||||||
|
import { getNodeModulesPackagePath, getStartingNode, buildLangchain, getEndingNode, constructGraphs } from './utils'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { getDataSource } from './DataSource'
|
||||||
|
import { NodesPool } from './NodesPool'
|
||||||
|
import { ChatFlow } from './entity/ChatFlow'
|
||||||
|
import { ChatMessage } from './entity/ChatMessage'
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
app: express.Application
|
||||||
|
componentNodes: IComponentNodesPool = {}
|
||||||
|
AppDataSource = getDataSource()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.app = express()
|
||||||
|
}
|
||||||
|
|
||||||
|
async initDatabase() {
|
||||||
|
// Initialize database
|
||||||
|
this.AppDataSource.initialize()
|
||||||
|
.then(async () => {
|
||||||
|
console.info('📦[server]: Data Source has been initialized!')
|
||||||
|
|
||||||
|
// Initialize node instances
|
||||||
|
const nodesPool = new NodesPool()
|
||||||
|
await nodesPool.initialize()
|
||||||
|
this.componentNodes = nodesPool.componentNodes
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('❌[server]: Error during Data Source initialization:', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async config() {
|
||||||
|
// Limit is needed to allow sending/receiving base64 encoded string
|
||||||
|
this.app.use(express.json({ limit: '50mb' }))
|
||||||
|
this.app.use(express.urlencoded({ limit: '50mb', extended: true }))
|
||||||
|
|
||||||
|
// Allow access from ui when yarn run dev
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
this.app.use(cors({ credentials: true, origin: 'http://localhost:8080' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Nodes
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// Get all component nodes
|
||||||
|
this.app.get('/api/v1/nodes', (req: Request, res: Response) => {
|
||||||
|
const returnData = []
|
||||||
|
for (const nodeName in this.componentNodes) {
|
||||||
|
const clonedNode = cloneDeep(this.componentNodes[nodeName])
|
||||||
|
returnData.push(clonedNode)
|
||||||
|
}
|
||||||
|
return res.json(returnData)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get specific component node via name
|
||||||
|
this.app.get('/api/v1/nodes/:name', (req: Request, res: Response) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) {
|
||||||
|
return res.json(this.componentNodes[req.params.name])
|
||||||
|
} else {
|
||||||
|
throw new Error(`Node ${req.params.name} not found`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Returns specific component node icon via name
|
||||||
|
this.app.get('/api/v1/node-icon/:name', (req: Request, res: Response) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.componentNodes, req.params.name)) {
|
||||||
|
const nodeInstance = this.componentNodes[req.params.name]
|
||||||
|
if (nodeInstance.icon === undefined) {
|
||||||
|
throw new Error(`Node ${req.params.name} icon not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeInstance.icon.endsWith('.svg') || nodeInstance.icon.endsWith('.png') || nodeInstance.icon.endsWith('.jpg')) {
|
||||||
|
const filepath = nodeInstance.icon
|
||||||
|
res.sendFile(filepath)
|
||||||
|
} else {
|
||||||
|
throw new Error(`Node ${req.params.name} icon is missing icon`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Node ${req.params.name} not found`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Chatflows
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// Get all chatflows
|
||||||
|
this.app.get('/api/v1/chatflows', async (req: Request, res: Response) => {
|
||||||
|
const chatflows: IChatFlow[] = await this.AppDataSource.getRepository(ChatFlow).find()
|
||||||
|
return res.json(chatflows)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get specific chatflow via id
|
||||||
|
this.app.get('/api/v1/chatflows/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
if (chatflow) return res.json(chatflow)
|
||||||
|
return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save chatflow
|
||||||
|
this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => {
|
||||||
|
const body = req.body
|
||||||
|
const newChatFlow = new ChatFlow()
|
||||||
|
Object.assign(newChatFlow, body)
|
||||||
|
|
||||||
|
const chatflow = this.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
|
||||||
|
const results = await this.AppDataSource.getRepository(ChatFlow).save(chatflow)
|
||||||
|
|
||||||
|
return res.json(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update chatflow
|
||||||
|
this.app.put('/api/v1/chatflows/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!chatflow) {
|
||||||
|
res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = req.body
|
||||||
|
const updateChatFlow = new ChatFlow()
|
||||||
|
Object.assign(updateChatFlow, body)
|
||||||
|
|
||||||
|
this.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow)
|
||||||
|
const result = await this.AppDataSource.getRepository(ChatFlow).save(chatflow)
|
||||||
|
|
||||||
|
return res.json(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete chatflow via id
|
||||||
|
this.app.delete('/api/v1/chatflows/:id', async (req: Request, res: Response) => {
|
||||||
|
const results = await this.AppDataSource.getRepository(ChatFlow).delete({ id: req.params.id })
|
||||||
|
return res.json(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// ChatMessage
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// Get all chatmessages from chatflowid
|
||||||
|
this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatmessages = await this.AppDataSource.getRepository(ChatMessage).findBy({
|
||||||
|
chatflowid: req.params.id
|
||||||
|
})
|
||||||
|
return res.json(chatmessages)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add chatmessages for chatflowid
|
||||||
|
this.app.post('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
|
const body = req.body
|
||||||
|
const newChatMessage = new ChatMessage()
|
||||||
|
Object.assign(newChatMessage, body)
|
||||||
|
|
||||||
|
const chatmessage = this.AppDataSource.getRepository(ChatMessage).create(newChatMessage)
|
||||||
|
const results = await this.AppDataSource.getRepository(ChatMessage).save(chatmessage)
|
||||||
|
|
||||||
|
return res.json(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete all chatmessages from chatflowid
|
||||||
|
this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
|
const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id })
|
||||||
|
return res.json(results)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Prediction
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
// Send input message and get prediction result
|
||||||
|
this.app.post('/api/v1/prediction/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const incomingInput: IncomingInput = req.body
|
||||||
|
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
if (!chatflow) return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
|
|
||||||
|
const flowData = chatflow.flowData
|
||||||
|
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||||
|
const { graph, nodeDependencies } = constructGraphs(parsedFlowData.nodes, parsedFlowData.edges)
|
||||||
|
|
||||||
|
const startingNodeIds = getStartingNode(nodeDependencies)
|
||||||
|
const endingNodeId = getEndingNode(nodeDependencies, graph)
|
||||||
|
if (!endingNodeId) return res.status(500).send(`Ending node must be either Chain or Agent`)
|
||||||
|
|
||||||
|
const reactFlowNodes = await buildLangchain(startingNodeIds, parsedFlowData.nodes, graph, this.componentNodes)
|
||||||
|
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
|
||||||
|
if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`)
|
||||||
|
|
||||||
|
const nodeInstanceFilePath = this.componentNodes[nodeToExecute.data.name].filePath as string
|
||||||
|
const nodeModule = await import(nodeInstanceFilePath)
|
||||||
|
const nodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
|
const result = await nodeInstance.run(nodeToExecute.data, incomingInput.question)
|
||||||
|
|
||||||
|
return res.json(result)
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).send(e.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Serve UI static
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
const packagePath = getNodeModulesPackagePath('flowise-ui')
|
||||||
|
const uiBuildPath = path.join(packagePath, 'build')
|
||||||
|
const uiHtmlPath = path.join(packagePath, 'build', 'index.html')
|
||||||
|
|
||||||
|
this.app.use('/', express.static(uiBuildPath))
|
||||||
|
|
||||||
|
// All other requests not handled will return React app
|
||||||
|
this.app.use((req, res) => {
|
||||||
|
res.sendFile(uiHtmlPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopApp() {
|
||||||
|
try {
|
||||||
|
const removePromises: any[] = []
|
||||||
|
await Promise.all(removePromises)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`❌[server]: Flowise Server shut down error: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverApp: App | undefined
|
||||||
|
|
||||||
|
export async function start(): Promise<void> {
|
||||||
|
serverApp = new App()
|
||||||
|
|
||||||
|
const port = parseInt(process.env.PORT || '', 10) || 3000
|
||||||
|
const server = http.createServer(serverApp.app)
|
||||||
|
|
||||||
|
await serverApp.initDatabase()
|
||||||
|
await serverApp.config()
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.info(`⚡️[server]: Flowise Server is listening at ${port}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstance(): App | undefined {
|
||||||
|
return serverApp
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import {
|
||||||
|
IComponentNodesPool,
|
||||||
|
IExploredNode,
|
||||||
|
INodeDependencies,
|
||||||
|
INodeDirectedGraph,
|
||||||
|
INodeQueue,
|
||||||
|
IReactFlowEdge,
|
||||||
|
IReactFlowNode
|
||||||
|
} from '../Interface'
|
||||||
|
import { cloneDeep, get } from 'lodash'
|
||||||
|
import { ICommonObject, INodeData } from 'flowise-components'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the home folder path of the user if
|
||||||
|
* none can be found it falls back to the current
|
||||||
|
* working directory
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const getUserHome = (): string => {
|
||||||
|
let variableName = 'HOME'
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
variableName = 'USERPROFILE'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env[variableName] === undefined) {
|
||||||
|
// If for some reason the variable does not exist
|
||||||
|
// fall back to current folder
|
||||||
|
return process.cwd()
|
||||||
|
}
|
||||||
|
return process.env[variableName] as string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path of node modules package
|
||||||
|
* @param {string} packageName
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getNodeModulesPackagePath = (packageName: string): string => {
|
||||||
|
const checkPaths = [
|
||||||
|
path.join(__dirname, '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', '..', 'node_modules', packageName),
|
||||||
|
path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', packageName)
|
||||||
|
]
|
||||||
|
for (const checkPath of checkPaths) {
|
||||||
|
if (fs.existsSync(checkPath)) {
|
||||||
|
return checkPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct directed graph and node dependencies score
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @param {IReactFlowEdge[]} reactFlowEdges
|
||||||
|
*/
|
||||||
|
export const constructGraphs = (reactFlowNodes: IReactFlowNode[], reactFlowEdges: IReactFlowEdge[]) => {
|
||||||
|
const nodeDependencies = {} as INodeDependencies
|
||||||
|
const graph = {} as INodeDirectedGraph
|
||||||
|
|
||||||
|
for (let i = 0; i < reactFlowNodes.length; i += 1) {
|
||||||
|
const nodeId = reactFlowNodes[i].id
|
||||||
|
nodeDependencies[nodeId] = 0
|
||||||
|
graph[nodeId] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < reactFlowEdges.length; i += 1) {
|
||||||
|
const source = reactFlowEdges[i].source
|
||||||
|
const target = reactFlowEdges[i].target
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(graph, source)) {
|
||||||
|
graph[source].push(target)
|
||||||
|
} else {
|
||||||
|
graph[source] = [target]
|
||||||
|
}
|
||||||
|
nodeDependencies[target] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return { graph, nodeDependencies }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get starting node and check if flow is valid
|
||||||
|
* @param {INodeDependencies} nodeDependencies
|
||||||
|
*/
|
||||||
|
export const getStartingNode = (nodeDependencies: INodeDependencies) => {
|
||||||
|
// Find starting node
|
||||||
|
const startingNodeIds = [] as string[]
|
||||||
|
Object.keys(nodeDependencies).forEach((nodeId) => {
|
||||||
|
if (nodeDependencies[nodeId] === 0) {
|
||||||
|
startingNodeIds.push(nodeId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return startingNodeIds
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEndingNode = (nodeDependencies: INodeDependencies, graph: INodeDirectedGraph) => {
|
||||||
|
// Find starting node
|
||||||
|
let endingNodeId = ''
|
||||||
|
Object.keys(graph).forEach((nodeId) => {
|
||||||
|
if (!graph[nodeId].length && nodeDependencies[nodeId] > 0) {
|
||||||
|
endingNodeId = nodeId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return endingNodeId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build langchain from start to end
|
||||||
|
* @param {string} startingNodeId
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @param {IReactFlowEdge[]} reactFlowEdges
|
||||||
|
* @param {INodeDirectedGraph} graph
|
||||||
|
* @param {IComponentNodesPool} componentNodes
|
||||||
|
* @param {string} clientId
|
||||||
|
* @param {any} io
|
||||||
|
*/
|
||||||
|
export const buildLangchain = async (
|
||||||
|
startingNodeIds: string[],
|
||||||
|
reactFlowNodes: IReactFlowNode[],
|
||||||
|
graph: INodeDirectedGraph,
|
||||||
|
componentNodes: IComponentNodesPool
|
||||||
|
) => {
|
||||||
|
const flowNodes = cloneDeep(reactFlowNodes)
|
||||||
|
|
||||||
|
// Create a Queue and add our initial node in it
|
||||||
|
const nodeQueue = [] as INodeQueue[]
|
||||||
|
const exploredNode = {} as IExploredNode
|
||||||
|
|
||||||
|
// In the case of infinite loop, only max 3 loops will be executed
|
||||||
|
const maxLoop = 3
|
||||||
|
|
||||||
|
for (let i = 0; i < startingNodeIds.length; i += 1) {
|
||||||
|
nodeQueue.push({ nodeId: startingNodeIds[i], depth: 0 })
|
||||||
|
exploredNode[startingNodeIds[i]] = { remainingLoop: maxLoop, lastSeenDepth: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
while (nodeQueue.length) {
|
||||||
|
const { nodeId, depth } = nodeQueue.shift() as INodeQueue
|
||||||
|
|
||||||
|
const reactFlowNode = flowNodes.find((nd) => nd.id === nodeId)
|
||||||
|
const nodeIndex = flowNodes.findIndex((nd) => nd.id === nodeId)
|
||||||
|
if (!reactFlowNode || reactFlowNode === undefined || nodeIndex < 0) continue
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nodeInstanceFilePath = componentNodes[reactFlowNode.data.name].filePath as string
|
||||||
|
const nodeModule = await import(nodeInstanceFilePath)
|
||||||
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
|
const reactFlowNodeData: INodeData = resolveVariables(reactFlowNode.data, flowNodes)
|
||||||
|
|
||||||
|
flowNodes[nodeIndex].data.instance = await newNodeInstance.init(reactFlowNodeData)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e)
|
||||||
|
throw new Error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const neighbourNodeIds = graph[nodeId]
|
||||||
|
const nextDepth = depth + 1
|
||||||
|
|
||||||
|
for (let i = 0; i < neighbourNodeIds.length; i += 1) {
|
||||||
|
const neighNodeId = neighbourNodeIds[i]
|
||||||
|
|
||||||
|
// If nodeId has been seen, cycle detected
|
||||||
|
if (Object.prototype.hasOwnProperty.call(exploredNode, neighNodeId)) {
|
||||||
|
const { remainingLoop, lastSeenDepth } = exploredNode[neighNodeId]
|
||||||
|
|
||||||
|
if (lastSeenDepth === nextDepth) continue
|
||||||
|
|
||||||
|
if (remainingLoop === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const remainingLoopMinusOne = remainingLoop - 1
|
||||||
|
exploredNode[neighNodeId] = { remainingLoop: remainingLoopMinusOne, lastSeenDepth: nextDepth }
|
||||||
|
nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth })
|
||||||
|
} else {
|
||||||
|
exploredNode[neighNodeId] = { remainingLoop: maxLoop, lastSeenDepth: nextDepth }
|
||||||
|
nodeQueue.push({ nodeId: neighNodeId, depth: nextDepth })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flowNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get variable value from outputResponses.output
|
||||||
|
* @param {string} paramValue
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} loopIndex
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowNode[]) => {
|
||||||
|
let returnVal = paramValue
|
||||||
|
const variableStack = []
|
||||||
|
let startIdx = 0
|
||||||
|
const endIdx = returnVal.length - 1
|
||||||
|
|
||||||
|
while (startIdx < endIdx) {
|
||||||
|
const substr = returnVal.substring(startIdx, startIdx + 2)
|
||||||
|
|
||||||
|
// Store the opening double curly bracket
|
||||||
|
if (substr === '{{') {
|
||||||
|
variableStack.push({ substr, startIdx: startIdx + 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the complete variable
|
||||||
|
if (substr === '}}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{{') {
|
||||||
|
const variableStartIdx = variableStack[variableStack.length - 1].startIdx
|
||||||
|
const variableEndIdx = startIdx
|
||||||
|
const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx)
|
||||||
|
|
||||||
|
// Split by first occurence of '.' to get just nodeId
|
||||||
|
const [variableNodeId, _] = variableFullPath.split('.')
|
||||||
|
const executedNode = reactFlowNodes.find((nd) => nd.id === variableNodeId)
|
||||||
|
if (executedNode) {
|
||||||
|
const variableInstance = get(executedNode.data, 'instance')
|
||||||
|
returnVal = variableInstance
|
||||||
|
}
|
||||||
|
variableStack.pop()
|
||||||
|
}
|
||||||
|
startIdx += 1
|
||||||
|
}
|
||||||
|
return returnVal
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop through each inputs and resolve variable if neccessary
|
||||||
|
* @param {INodeData} reactFlowNodeData
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @returns {INodeData}
|
||||||
|
*/
|
||||||
|
export const resolveVariables = (reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[]): INodeData => {
|
||||||
|
const flowNodeData = cloneDeep(reactFlowNodeData)
|
||||||
|
const types = 'inputs'
|
||||||
|
|
||||||
|
const getParamValues = (paramsObj: ICommonObject) => {
|
||||||
|
for (const key in paramsObj) {
|
||||||
|
const paramValue: string = paramsObj[key]
|
||||||
|
|
||||||
|
if (Array.isArray(paramValue)) {
|
||||||
|
const resolvedInstances = []
|
||||||
|
for (const param of paramValue) {
|
||||||
|
const resolvedInstance = getVariableValue(param, reactFlowNodes)
|
||||||
|
resolvedInstances.push(resolvedInstance)
|
||||||
|
}
|
||||||
|
paramsObj[key] = resolvedInstances
|
||||||
|
} else {
|
||||||
|
const resolvedInstance = getVariableValue(paramValue, reactFlowNodes)
|
||||||
|
paramsObj[key] = resolvedInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsObj = (flowNodeData as any)[types]
|
||||||
|
|
||||||
|
getParamValues(paramsObj)
|
||||||
|
|
||||||
|
return flowNodeData
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2017"],
|
||||||
|
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
|
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
|
||||||
|
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
|
||||||
|
"module": "commonjs" /* Specify what module code is generated. */,
|
||||||
|
"outDir": "dist",
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/tests
|
||||||
|
/src
|
||||||
|
/public
|
||||||
|
!build
|
||||||
|
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
.eslintrc
|
||||||
|
.prettierignore
|
||||||
|
.prettierrc
|
||||||
|
jsconfig.json
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!-- markdownlint-disable MD030 -->
|
||||||
|
|
||||||
|
# Flowise UI
|
||||||
|
|
||||||
|
React frontend ui for Flowise.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i flowise-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Source code in this repository is made available under the [MIT License](https://github.com/FlowiseAI/Flowise/blob/master/LICENSE.md).
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "commonjs",
|
||||||
|
"baseUrl": "src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "flowise-ui",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
|
"homepage": "https://flowiseai.com",
|
||||||
|
"author": {
|
||||||
|
"name": "HenryHeng",
|
||||||
|
"email": "henryheng@flowiseai.com"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/cache": "^11.4.0",
|
||||||
|
"@emotion/react": "^11.10.6",
|
||||||
|
"@emotion/styled": "^11.10.6",
|
||||||
|
"@mui/icons-material": "^5.0.3",
|
||||||
|
"@mui/material": "^5.11.12",
|
||||||
|
"@tabler/icons": "^1.39.1",
|
||||||
|
"clsx": "^1.1.1",
|
||||||
|
"formik": "^2.2.6",
|
||||||
|
"framer-motion": "^4.1.13",
|
||||||
|
"history": "^5.0.0",
|
||||||
|
"html-react-parser": "^3.0.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"moment": "^2.29.3",
|
||||||
|
"notistack": "^2.0.4",
|
||||||
|
"prismjs": "^1.28.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-datepicker": "^4.8.0",
|
||||||
|
"react-device-detect": "^1.17.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-json-view": "^1.21.3",
|
||||||
|
"react-markdown": "^8.0.6",
|
||||||
|
"react-perfect-scrollbar": "^1.5.8",
|
||||||
|
"react-redux": "^8.0.5",
|
||||||
|
"react-router": "~6.3.0",
|
||||||
|
"react-router-dom": "~6.3.0",
|
||||||
|
"react-simple-code-editor": "^0.11.2",
|
||||||
|
"reactflow": "^11.5.6",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"yup": "^0.32.9"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"dev": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-react"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/eslint-parser": "^7.15.8",
|
||||||
|
"@testing-library/jest-dom": "^5.11.10",
|
||||||
|
"@testing-library/react": "^14.0.0",
|
||||||
|
"@testing-library/user-event": "^12.8.3",
|
||||||
|
"pretty-quick": "^3.1.3",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"sass": "^1.42.1",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 589 B |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Flowise - LangchainJS UI</title>
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<!-- Meta Tags-->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#2296f3" />
|
||||||
|
<meta name="title" content="Flowise - LangchainJS UI" />
|
||||||
|
<meta name="description" content="Flowise helps you to better integrate Web3 with existing Web2 applications" />
|
||||||
|
<meta name="keywords" content="react, material-ui, reactjs, reactjs, workflow automation, web3, web2, blockchain" />
|
||||||
|
<meta name="author" content="CodedThemes" />
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://flowiseai.com/" />
|
||||||
|
<meta property="og:site_name" content="flowiseai.com" />
|
||||||
|
<meta property="article:publisher" content="https://www.facebook.com/codedthemes" />
|
||||||
|
<meta property="og:title" content="Flowise - LangchainJS UI" />
|
||||||
|
<meta property="og:description" content="Flowise helps you to better build LLM flows using Langchain in simple GUI" />
|
||||||
|
<meta property="og:image" content="https://flowiseai.com/og-image/og-facebook.png" />
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:url" content="https://flowiseai.com" />
|
||||||
|
<meta property="twitter:title" content="Flowise - LangchainJS UI" />
|
||||||
|
<meta property="twitter:description" content="Flowise helps you to better build LLM flows using Langchain in simple GUI" />
|
||||||
|
<meta property="twitter:image" content="https://flowiseai.com/og-image/og-twitter.png" />
|
||||||
|
<meta name="twitter:creator" content="@codedthemes" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<div id="portal"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
import { ThemeProvider } from '@mui/material/styles'
|
||||||
|
import { CssBaseline, StyledEngineProvider } from '@mui/material'
|
||||||
|
|
||||||
|
// routing
|
||||||
|
import Routes from 'routes'
|
||||||
|
|
||||||
|
// defaultTheme
|
||||||
|
import themes from 'themes'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import NavigationScroll from 'layout/NavigationScroll'
|
||||||
|
|
||||||
|
// ==============================|| APP ||============================== //
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledEngineProvider injectFirst>
|
||||||
|
<ThemeProvider theme={themes(customization)}>
|
||||||
|
<CssBaseline />
|
||||||
|
<NavigationScroll>
|
||||||
|
<Routes />
|
||||||
|
</NavigationScroll>
|
||||||
|
</ThemeProvider>
|
||||||
|
</StyledEngineProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getAllChatflows = () => client.get('/chatflows')
|
||||||
|
|
||||||
|
const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`)
|
||||||
|
|
||||||
|
const createNewChatflow = (body) => client.post(`/chatflows`, body)
|
||||||
|
|
||||||
|
const updateChatflow = (id, body) => client.put(`/chatflows/${id}`, body)
|
||||||
|
|
||||||
|
const deleteChatflow = (id) => client.delete(`/chatflows/${id}`)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAllChatflows,
|
||||||
|
getSpecificChatflow,
|
||||||
|
createNewChatflow,
|
||||||
|
updateChatflow,
|
||||||
|
deleteChatflow
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getChatmessageFromChatflow = (id) => client.get(`/chatmessage/${id}`)
|
||||||
|
|
||||||
|
const createNewChatmessage = (id, body) => client.post(`/chatmessage/${id}`, body)
|
||||||
|
|
||||||
|
const deleteChatmessage = (id) => client.delete(`/chatmessage/${id}`)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getChatmessageFromChatflow,
|
||||||
|
createNewChatmessage,
|
||||||
|
deleteChatmessage
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import { baseURL } from 'store/constant'
|
||||||
|
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: `${baseURL}/api/v1`,
|
||||||
|
headers: {
|
||||||
|
'Content-type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default apiClient
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getAllNodes = () => client.get('/nodes')
|
||||||
|
|
||||||
|
const getSpecificNode = (name) => client.get(`/nodes/${name}`)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAllNodes,
|
||||||
|
getSpecificNode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const sendMessageAndGetPrediction = (id, input) => client.post(`/prediction/${id}`, input)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
sendMessageAndGetPrediction
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,157 @@
|
||||||
|
// paper & background
|
||||||
|
$paper: #ffffff;
|
||||||
|
|
||||||
|
// primary
|
||||||
|
$primaryLight: #e3f2fd;
|
||||||
|
$primaryMain: #2196f3;
|
||||||
|
$primaryDark: #1e88e5;
|
||||||
|
$primary200: #90caf9;
|
||||||
|
$primary800: #1565c0;
|
||||||
|
|
||||||
|
// secondary
|
||||||
|
$secondaryLight: #ede7f6;
|
||||||
|
$secondaryMain: #673ab7;
|
||||||
|
$secondaryDark: #5e35b1;
|
||||||
|
$secondary200: #b39ddb;
|
||||||
|
$secondary800: #4527a0;
|
||||||
|
|
||||||
|
// success Colors
|
||||||
|
$successLight: #cdf5d8;
|
||||||
|
$success200: #69f0ae;
|
||||||
|
$successMain: #00e676;
|
||||||
|
$successDark: #00c853;
|
||||||
|
|
||||||
|
// error
|
||||||
|
$errorLight: #f3d2d2;
|
||||||
|
$errorMain: #f44336;
|
||||||
|
$errorDark: #c62828;
|
||||||
|
|
||||||
|
// orange
|
||||||
|
$orangeLight: #fbe9e7;
|
||||||
|
$orangeMain: #ffab91;
|
||||||
|
$orangeDark: #d84315;
|
||||||
|
|
||||||
|
// warning
|
||||||
|
$warningLight: #fff8e1;
|
||||||
|
$warningMain: #ffe57f;
|
||||||
|
$warningDark: #ffc107;
|
||||||
|
|
||||||
|
// grey
|
||||||
|
$grey50: #fafafa;
|
||||||
|
$grey100: #f5f5f5;
|
||||||
|
$grey200: #eeeeee;
|
||||||
|
$grey300: #e0e0e0;
|
||||||
|
$grey500: #9e9e9e;
|
||||||
|
$grey600: #757575;
|
||||||
|
$grey700: #616161;
|
||||||
|
$grey900: #212121;
|
||||||
|
|
||||||
|
// ==============================|| DARK THEME VARIANTS ||============================== //
|
||||||
|
|
||||||
|
// paper & background
|
||||||
|
$darkBackground: #191b1f;
|
||||||
|
$darkPaper: #191b1f;
|
||||||
|
|
||||||
|
// dark 800 & 900
|
||||||
|
$darkLevel1: #252525; // level 1
|
||||||
|
$darkLevel2: #242424; // level 2
|
||||||
|
|
||||||
|
// primary dark
|
||||||
|
$darkPrimaryLight: #23262c;
|
||||||
|
$darkPrimaryMain: #23262c;
|
||||||
|
$darkPrimaryDark: #191b1f;
|
||||||
|
$darkPrimary200: #c9d4e9;
|
||||||
|
$darkPrimary800: #32353b;
|
||||||
|
|
||||||
|
// secondary dark
|
||||||
|
$darkSecondaryLight: #454c59;
|
||||||
|
$darkSecondaryMain: #7c4dff;
|
||||||
|
$darkSecondaryDark: #ffffff;
|
||||||
|
$darkSecondary200: #32353b;
|
||||||
|
$darkSecondary800: #6200ea;
|
||||||
|
|
||||||
|
// text variants
|
||||||
|
$darkTextTitle: #d7dcec;
|
||||||
|
$darkTextPrimary: #bdc8f0;
|
||||||
|
$darkTextSecondary: #8492c4;
|
||||||
|
|
||||||
|
// ==============================|| JAVASCRIPT ||============================== //
|
||||||
|
|
||||||
|
:export {
|
||||||
|
// paper & background
|
||||||
|
paper: $paper;
|
||||||
|
|
||||||
|
// primary
|
||||||
|
primaryLight: $primaryLight;
|
||||||
|
primary200: $primary200;
|
||||||
|
primaryMain: $primaryMain;
|
||||||
|
primaryDark: $primaryDark;
|
||||||
|
primary800: $primary800;
|
||||||
|
|
||||||
|
// secondary
|
||||||
|
secondaryLight: $secondaryLight;
|
||||||
|
secondary200: $secondary200;
|
||||||
|
secondaryMain: $secondaryMain;
|
||||||
|
secondaryDark: $secondaryDark;
|
||||||
|
secondary800: $secondary800;
|
||||||
|
|
||||||
|
// success
|
||||||
|
successLight: $successLight;
|
||||||
|
success200: $success200;
|
||||||
|
successMain: $successMain;
|
||||||
|
successDark: $successDark;
|
||||||
|
|
||||||
|
// error
|
||||||
|
errorLight: $errorLight;
|
||||||
|
errorMain: $errorMain;
|
||||||
|
errorDark: $errorDark;
|
||||||
|
|
||||||
|
// orange
|
||||||
|
orangeLight: $orangeLight;
|
||||||
|
orangeMain: $orangeMain;
|
||||||
|
orangeDark: $orangeDark;
|
||||||
|
|
||||||
|
// warning
|
||||||
|
warningLight: $warningLight;
|
||||||
|
warningMain: $warningMain;
|
||||||
|
warningDark: $warningDark;
|
||||||
|
|
||||||
|
// grey
|
||||||
|
grey50: $grey50;
|
||||||
|
grey100: $grey100;
|
||||||
|
grey200: $grey200;
|
||||||
|
grey300: $grey300;
|
||||||
|
grey500: $grey500;
|
||||||
|
grey600: $grey600;
|
||||||
|
grey700: $grey700;
|
||||||
|
grey900: $grey900;
|
||||||
|
|
||||||
|
// ==============================|| DARK THEME VARIANTS ||============================== //
|
||||||
|
|
||||||
|
// paper & background
|
||||||
|
darkPaper: $darkPaper;
|
||||||
|
darkBackground: $darkBackground;
|
||||||
|
|
||||||
|
// dark 800 & 900
|
||||||
|
darkLevel1: $darkLevel1;
|
||||||
|
darkLevel2: $darkLevel2;
|
||||||
|
|
||||||
|
// text variants
|
||||||
|
darkTextTitle: $darkTextTitle;
|
||||||
|
darkTextPrimary: $darkTextPrimary;
|
||||||
|
darkTextSecondary: $darkTextSecondary;
|
||||||
|
|
||||||
|
// primary dark
|
||||||
|
darkPrimaryLight: $darkPrimaryLight;
|
||||||
|
darkPrimaryMain: $darkPrimaryMain;
|
||||||
|
darkPrimaryDark: $darkPrimaryDark;
|
||||||
|
darkPrimary200: $darkPrimary200;
|
||||||
|
darkPrimary800: $darkPrimary800;
|
||||||
|
|
||||||
|
// secondary dark
|
||||||
|
darkSecondaryLight: $darkSecondaryLight;
|
||||||
|
darkSecondaryMain: $darkSecondaryMain;
|
||||||
|
darkSecondaryDark: $darkSecondaryDark;
|
||||||
|
darkSecondary200: $darkSecondary200;
|
||||||
|
darkSecondary800: $darkSecondary800;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
// color variants
|
||||||
|
@import 'themes-vars.module.scss';
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
@import '~react-perfect-scrollbar/dist/css/styles.css';
|
||||||
|
|
||||||
|
// ==============================|| LIGHT BOX ||============================== //
|
||||||
|
.fullscreen .react-images__blanket {
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================|| PERFECT SCROLLBAR ||============================== //
|
||||||
|
|
||||||
|
.scrollbar-container {
|
||||||
|
.ps__rail-y {
|
||||||
|
&:hover > .ps__thumb-y,
|
||||||
|
&:focus > .ps__thumb-y,
|
||||||
|
&.ps--clicking .ps__thumb-y {
|
||||||
|
background-color: $grey500;
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ps__thumb-y {
|
||||||
|
background-color: $grey500;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 5px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-container.ps,
|
||||||
|
.scrollbar-container > .ps {
|
||||||
|
&.ps--active-y > .ps__rail-y {
|
||||||
|
width: 5px;
|
||||||
|
background-color: transparent !important;
|
||||||
|
z-index: 999;
|
||||||
|
&:hover,
|
||||||
|
&.ps--clicking {
|
||||||
|
width: 5px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.ps--scrolling-y > .ps__rail-y,
|
||||||
|
&.ps--scrolling-x > .ps__rail-x {
|
||||||
|
opacity: 0.4;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==============================|| ANIMATION KEYFRAMES ||============================== //
|
||||||
|
|
||||||
|
@keyframes wings {
|
||||||
|
50% {
|
||||||
|
transform: translateY(-40px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%,
|
||||||
|
20%,
|
||||||
|
53%,
|
||||||
|
to {
|
||||||
|
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
40%,
|
||||||
|
43% {
|
||||||
|
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||||
|
transform: translate3d(0, -5px, 0);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||||
|
transform: translate3d(0, -7px, 0);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
transform: translate3d(0, -2px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideY {
|
||||||
|
0%,
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideX {
|
||||||
|
0%,
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
const config = {
|
||||||
|
// basename: only at build time to set, and Don't add '/' at end off BASENAME for breadcrumbs, also Don't put only '/' use blank('') instead,
|
||||||
|
basename: '',
|
||||||
|
defaultPath: '/chatflows',
|
||||||
|
fontFamily: `'Roboto', sans-serif`,
|
||||||
|
borderRadius: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default (apiFunc) => {
|
||||||
|
const [data, setData] = useState(null)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const request = async (...args) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await apiFunc(...args)
|
||||||
|
setData(result.data)
|
||||||
|
} catch (err) {
|
||||||
|
setError(err || 'Unexpected Error!')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import ConfirmContext from 'store/context/ConfirmContext'
|
||||||
|
import { HIDE_CONFIRM, SHOW_CONFIRM } from 'store/actions'
|
||||||
|
|
||||||
|
let resolveCallback
|
||||||
|
const useConfirm = () => {
|
||||||
|
const [confirmState, dispatch] = useContext(ConfirmContext)
|
||||||
|
|
||||||
|
const closeConfirm = () => {
|
||||||
|
dispatch({
|
||||||
|
type: HIDE_CONFIRM
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
closeConfirm()
|
||||||
|
resolveCallback(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
closeConfirm()
|
||||||
|
resolveCallback(false)
|
||||||
|
}
|
||||||
|
const confirm = (confirmPayload) => {
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_CONFIRM,
|
||||||
|
payload: confirmPayload
|
||||||
|
})
|
||||||
|
return new Promise((res) => {
|
||||||
|
resolveCallback = res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { confirm, onConfirm, onCancel, confirmState }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useConfirm
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
// ==============================|| ELEMENT REFERENCE HOOKS ||============================== //
|
||||||
|
|
||||||
|
const useScriptRef = () => {
|
||||||
|
const scripted = useRef(true)
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
scripted.current = false
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return scripted
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useScriptRef
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react'
|
||||||
|
import App from './App'
|
||||||
|
import { store } from 'store'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
|
// style + assets
|
||||||
|
import 'assets/scss/style.scss'
|
||||||
|
|
||||||
|
// third party
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
import ConfirmContextProvider from 'store/context/ConfirmContextProvider'
|
||||||
|
import { ReactFlowContext } from 'store/context/ReactFlowContext'
|
||||||
|
|
||||||
|
const container = document.getElementById('root')
|
||||||
|
const root = createRoot(container)
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Provider store={store}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<SnackbarProvider>
|
||||||
|
<ConfirmContextProvider>
|
||||||
|
<ReactFlowContext>
|
||||||
|
<App />
|
||||||
|
</ReactFlowContext>
|
||||||
|
</ConfirmContextProvider>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { Avatar, Box, ButtonBase, Switch } from '@mui/material'
|
||||||
|
import { styled } from '@mui/material/styles'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import LogoSection from '../LogoSection'
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import { IconMenu2 } from '@tabler/icons'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import { SET_DARKMODE } from 'store/actions'
|
||||||
|
|
||||||
|
// ==============================|| MAIN NAVBAR / HEADER ||============================== //
|
||||||
|
|
||||||
|
const MaterialUISwitch = styled(Switch)(({ theme }) => ({
|
||||||
|
width: 62,
|
||||||
|
height: 34,
|
||||||
|
padding: 7,
|
||||||
|
'& .MuiSwitch-switchBase': {
|
||||||
|
margin: 1,
|
||||||
|
padding: 0,
|
||||||
|
transform: 'translateX(6px)',
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: '#fff',
|
||||||
|
transform: 'translateX(22px)',
|
||||||
|
'& .MuiSwitch-thumb:before': {
|
||||||
|
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||||
|
'#fff'
|
||||||
|
)}" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>')`
|
||||||
|
},
|
||||||
|
'& + .MuiSwitch-track': {
|
||||||
|
opacity: 1,
|
||||||
|
backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-thumb': {
|
||||||
|
backgroundColor: theme.palette.mode === 'dark' ? '#003892' : '#001e3c',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
'&:before': {
|
||||||
|
content: "''",
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(
|
||||||
|
'#fff'
|
||||||
|
)}" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>')`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiSwitch-track': {
|
||||||
|
opacity: 1,
|
||||||
|
backgroundColor: theme.palette.mode === 'dark' ? '#8796A5' : '#aab4be',
|
||||||
|
borderRadius: 20 / 2
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const Header = ({ handleLeftDrawerToggle }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
|
const [isDark, setIsDark] = useState(customization.isDarkMode)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const changeDarkMode = () => {
|
||||||
|
dispatch({ type: SET_DARKMODE, isDarkMode: !isDark })
|
||||||
|
setIsDark((isDark) => !isDark)
|
||||||
|
localStorage.setItem('isDarkMode', !isDark)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* logo & toggler button */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 228,
|
||||||
|
display: 'flex',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
width: 'auto'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box component='span' sx={{ display: { xs: 'none', md: 'block' }, flexGrow: 1 }}>
|
||||||
|
<LogoSection />
|
||||||
|
</Box>
|
||||||
|
<ButtonBase sx={{ borderRadius: '12px', overflow: 'hidden' }}>
|
||||||
|
<Avatar
|
||||||
|
variant='rounded'
|
||||||
|
sx={{
|
||||||
|
...theme.typography.commonAvatar,
|
||||||
|
...theme.typography.mediumAvatar,
|
||||||
|
transition: 'all .2s ease-in-out',
|
||||||
|
background: theme.palette.secondary.light,
|
||||||
|
color: theme.palette.secondary.dark,
|
||||||
|
'&:hover': {
|
||||||
|
background: theme.palette.secondary.dark,
|
||||||
|
color: theme.palette.secondary.light
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={handleLeftDrawerToggle}
|
||||||
|
color='inherit'
|
||||||
|
>
|
||||||
|
<IconMenu2 stroke={1.5} size='1.3rem' />
|
||||||
|
</Avatar>
|
||||||
|
</ButtonBase>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flexGrow: 1 }} />
|
||||||
|
<MaterialUISwitch checked={isDark} onChange={changeDarkMode} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
handleLeftDrawerToggle: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { ButtonBase } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import config from 'config'
|
||||||
|
import Logo from 'ui-component/extended/Logo'
|
||||||
|
|
||||||
|
// ==============================|| MAIN LOGO ||============================== //
|
||||||
|
|
||||||
|
const LogoSection = () => (
|
||||||
|
<ButtonBase disableRipple component={Link} to={config.defaultPath}>
|
||||||
|
<Logo />
|
||||||
|
</ButtonBase>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default LogoSection
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import NavItem from '../NavItem'
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'
|
||||||
|
import { IconChevronDown, IconChevronUp } from '@tabler/icons'
|
||||||
|
|
||||||
|
// ==============================|| SIDEBAR MENU LIST COLLAPSE ITEMS ||============================== //
|
||||||
|
|
||||||
|
const NavCollapse = ({ menu, level }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [selected, setSelected] = useState(null)
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
setOpen(!open)
|
||||||
|
setSelected(!selected ? menu.id : null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// menu collapse & item
|
||||||
|
const menus = menu.children?.map((item) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'collapse':
|
||||||
|
return <NavCollapse key={item.id} menu={item} level={level + 1} />
|
||||||
|
case 'item':
|
||||||
|
return <NavItem key={item.id} item={item} level={level + 1} />
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Typography key={item.id} variant='h6' color='error' align='center'>
|
||||||
|
Menu Items Error
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const Icon = menu.icon
|
||||||
|
const menuIcon = menu.icon ? (
|
||||||
|
<Icon strokeWidth={1.5} size='1.3rem' style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||||
|
) : (
|
||||||
|
<FiberManualRecordIcon
|
||||||
|
sx={{
|
||||||
|
width: selected === menu.id ? 8 : 6,
|
||||||
|
height: selected === menu.id ? 8 : 6
|
||||||
|
}}
|
||||||
|
fontSize={level > 0 ? 'inherit' : 'medium'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListItemButton
|
||||||
|
sx={{
|
||||||
|
borderRadius: `${customization.borderRadius}px`,
|
||||||
|
mb: 0.5,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
|
||||||
|
py: level > 1 ? 1 : 1.25,
|
||||||
|
pl: `${level * 24}px`
|
||||||
|
}}
|
||||||
|
selected={selected === menu.id}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{ my: 'auto', minWidth: !menu.icon ? 18 : 36 }}>{menuIcon}</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant={selected === menu.id ? 'h5' : 'body1'} color='inherit' sx={{ my: 'auto' }}>
|
||||||
|
{menu.title}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
menu.caption && (
|
||||||
|
<Typography variant='caption' sx={{ ...theme.typography.subMenuCaption }} display='block' gutterBottom>
|
||||||
|
{menu.caption}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{open ? (
|
||||||
|
<IconChevronUp stroke={1.5} size='1rem' style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||||
|
) : (
|
||||||
|
<IconChevronDown stroke={1.5} size='1rem' style={{ marginTop: 'auto', marginBottom: 'auto' }} />
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
<Collapse in={open} timeout='auto' unmountOnExit>
|
||||||
|
<List
|
||||||
|
component='div'
|
||||||
|
disablePadding
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
'&:after': {
|
||||||
|
content: "''",
|
||||||
|
position: 'absolute',
|
||||||
|
left: '32px',
|
||||||
|
top: 0,
|
||||||
|
height: '100%',
|
||||||
|
width: '1px',
|
||||||
|
opacity: 1,
|
||||||
|
background: theme.palette.primary.light
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{menus}
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavCollapse.propTypes = {
|
||||||
|
menu: PropTypes.object,
|
||||||
|
level: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavCollapse
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { Divider, List, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import NavItem from '../NavItem'
|
||||||
|
import NavCollapse from '../NavCollapse'
|
||||||
|
|
||||||
|
// ==============================|| SIDEBAR MENU LIST GROUP ||============================== //
|
||||||
|
|
||||||
|
const NavGroup = ({ item }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
// menu list collapse & items
|
||||||
|
const items = item.children?.map((menu) => {
|
||||||
|
switch (menu.type) {
|
||||||
|
case 'collapse':
|
||||||
|
return <NavCollapse key={menu.id} menu={menu} level={1} />
|
||||||
|
case 'item':
|
||||||
|
return <NavItem key={menu.id} item={menu} level={1} navType='MENU' />
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Typography key={menu.id} variant='h6' color='error' align='center'>
|
||||||
|
Menu Items Error
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<List
|
||||||
|
subheader={
|
||||||
|
item.title && (
|
||||||
|
<Typography variant='caption' sx={{ ...theme.typography.menuCaption }} display='block' gutterBottom>
|
||||||
|
{item.title}
|
||||||
|
{item.caption && (
|
||||||
|
<Typography variant='caption' sx={{ ...theme.typography.subMenuCaption }} display='block' gutterBottom>
|
||||||
|
{item.caption}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{items}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
{/* group divider */}
|
||||||
|
<Divider sx={{ mt: 0.25, mb: 1.25 }} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavGroup.propTypes = {
|
||||||
|
item: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavGroup
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { forwardRef, useEffect } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import { MENU_OPEN, SET_MENU } from 'store/actions'
|
||||||
|
import config from 'config'
|
||||||
|
|
||||||
|
// assets
|
||||||
|
import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'
|
||||||
|
|
||||||
|
// ==============================|| SIDEBAR MENU LIST ITEMS ||============================== //
|
||||||
|
|
||||||
|
const NavItem = ({ item, level, navType, onClick, onUploadFile }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
const matchesSM = useMediaQuery(theme.breakpoints.down('lg'))
|
||||||
|
|
||||||
|
const Icon = item.icon
|
||||||
|
const itemIcon = item?.icon ? (
|
||||||
|
<Icon stroke={1.5} size='1.3rem' />
|
||||||
|
) : (
|
||||||
|
<FiberManualRecordIcon
|
||||||
|
sx={{
|
||||||
|
width: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6,
|
||||||
|
height: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6
|
||||||
|
}}
|
||||||
|
fontSize={level > 0 ? 'inherit' : 'medium'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
let itemTarget = '_self'
|
||||||
|
if (item.target) {
|
||||||
|
itemTarget = '_blank'
|
||||||
|
}
|
||||||
|
|
||||||
|
let listItemProps = {
|
||||||
|
component: forwardRef(function ListItemPropsComponent(props, ref) {
|
||||||
|
return <Link ref={ref} {...props} to={`${config.basename}${item.url}`} target={itemTarget} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item?.external) {
|
||||||
|
listItemProps = { component: 'a', href: item.url, target: itemTarget }
|
||||||
|
}
|
||||||
|
if (item?.id === 'loadChatflow') {
|
||||||
|
listItemProps.component = 'label'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileUpload = (e) => {
|
||||||
|
if (!e.target.files) return
|
||||||
|
|
||||||
|
const file = e.target.files[0]
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (evt) => {
|
||||||
|
if (!evt?.target?.result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { result } = evt.target
|
||||||
|
onUploadFile(result)
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemHandler = (id) => {
|
||||||
|
if (navType === 'SETTINGS' && id !== 'loadChatflow') {
|
||||||
|
onClick(id)
|
||||||
|
} else {
|
||||||
|
dispatch({ type: MENU_OPEN, id })
|
||||||
|
if (matchesSM) dispatch({ type: SET_MENU, opened: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// active menu item on page load
|
||||||
|
useEffect(() => {
|
||||||
|
if (navType === 'MENU') {
|
||||||
|
const currentIndex = document.location.pathname
|
||||||
|
.toString()
|
||||||
|
.split('/')
|
||||||
|
.findIndex((id) => id === item.id)
|
||||||
|
if (currentIndex > -1) {
|
||||||
|
dispatch({ type: MENU_OPEN, id: item.id })
|
||||||
|
}
|
||||||
|
if (!document.location.pathname.toString().split('/')[1]) {
|
||||||
|
itemHandler('chatflows')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [navType])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItemButton
|
||||||
|
{...listItemProps}
|
||||||
|
disabled={item.disabled}
|
||||||
|
sx={{
|
||||||
|
borderRadius: `${customization.borderRadius}px`,
|
||||||
|
mb: 0.5,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
|
||||||
|
py: level > 1 ? 1 : 1.25,
|
||||||
|
pl: `${level * 24}px`
|
||||||
|
}}
|
||||||
|
selected={customization.isOpen.findIndex((id) => id === item.id) > -1}
|
||||||
|
onClick={() => itemHandler(item.id)}
|
||||||
|
>
|
||||||
|
{item.id === 'loadChatflow' && <input type='file' hidden accept='.json' onChange={(e) => handleFileUpload(e)} />}
|
||||||
|
<ListItemIcon sx={{ my: 'auto', minWidth: !item?.icon ? 18 : 36 }}>{itemIcon}</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant={customization.isOpen.findIndex((id) => id === item.id) > -1 ? 'h5' : 'body1'} color='inherit'>
|
||||||
|
{item.title}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
item.caption && (
|
||||||
|
<Typography variant='caption' sx={{ ...theme.typography.subMenuCaption }} display='block' gutterBottom>
|
||||||
|
{item.caption}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{item.chip && (
|
||||||
|
<Chip
|
||||||
|
color={item.chip.color}
|
||||||
|
variant={item.chip.variant}
|
||||||
|
size={item.chip.size}
|
||||||
|
label={item.chip.label}
|
||||||
|
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavItem.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
level: PropTypes.number,
|
||||||
|
navType: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onUploadFile: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavItem
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// material-ui
|
||||||
|
import { Typography } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import NavGroup from './NavGroup'
|
||||||
|
import menuItem from 'menu-items'
|
||||||
|
|
||||||
|
// ==============================|| SIDEBAR MENU LIST ||============================== //
|
||||||
|
|
||||||
|
const MenuList = () => {
|
||||||
|
const navItems = menuItem.items.map((item) => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'group':
|
||||||
|
return <NavGroup key={item.id} item={item} />
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<Typography key={item.id} variant='h6' color='error' align='center'>
|
||||||
|
Menu Items Error
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <>{navItems}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuList
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { Box, Drawer, useMediaQuery } from '@mui/material'
|
||||||
|
|
||||||
|
// third-party
|
||||||
|
import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||||
|
import { BrowserView, MobileView } from 'react-device-detect'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import MenuList from './MenuList'
|
||||||
|
import LogoSection from '../LogoSection'
|
||||||
|
import { drawerWidth } from 'store/constant'
|
||||||
|
|
||||||
|
// ==============================|| SIDEBAR DRAWER ||============================== //
|
||||||
|
|
||||||
|
const Sidebar = ({ drawerOpen, drawerToggle, window }) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const matchUpMd = useMediaQuery(theme.breakpoints.up('md'))
|
||||||
|
|
||||||
|
const drawer = (
|
||||||
|
<>
|
||||||
|
<Box sx={{ display: { xs: 'block', md: 'none' } }}>
|
||||||
|
<Box sx={{ display: 'flex', p: 2, mx: 'auto' }}>
|
||||||
|
<LogoSection />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<BrowserView>
|
||||||
|
<PerfectScrollbar
|
||||||
|
component='div'
|
||||||
|
style={{
|
||||||
|
height: !matchUpMd ? 'calc(100vh - 56px)' : 'calc(100vh - 88px)',
|
||||||
|
paddingLeft: '16px',
|
||||||
|
paddingRight: '16px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuList />
|
||||||
|
</PerfectScrollbar>
|
||||||
|
</BrowserView>
|
||||||
|
<MobileView>
|
||||||
|
<Box sx={{ px: 2 }}>
|
||||||
|
<MenuList />
|
||||||
|
</Box>
|
||||||
|
</MobileView>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const container = window !== undefined ? () => window.document.body : undefined
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box component='nav' sx={{ flexShrink: { md: 0 }, width: matchUpMd ? drawerWidth : 'auto' }} aria-label='mailbox folders'>
|
||||||
|
<Drawer
|
||||||
|
container={container}
|
||||||
|
variant={matchUpMd ? 'persistent' : 'temporary'}
|
||||||
|
anchor='left'
|
||||||
|
open={drawerOpen}
|
||||||
|
onClose={drawerToggle}
|
||||||
|
sx={{
|
||||||
|
'& .MuiDrawer-paper': {
|
||||||
|
width: drawerWidth,
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
borderRight: 'none',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
top: '66px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ModalProps={{ keepMounted: true }}
|
||||||
|
color='inherit'
|
||||||
|
>
|
||||||
|
{drawer}
|
||||||
|
</Drawer>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Sidebar.propTypes = {
|
||||||
|
drawerOpen: PropTypes.bool,
|
||||||
|
drawerToggle: PropTypes.func,
|
||||||
|
window: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sidebar
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { Outlet } from 'react-router-dom'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { styled, useTheme } from '@mui/material/styles'
|
||||||
|
import { AppBar, Box, CssBaseline, Toolbar, useMediaQuery } from '@mui/material'
|
||||||
|
|
||||||
|
// project imports
|
||||||
|
import Header from './Header'
|
||||||
|
import Sidebar from './Sidebar'
|
||||||
|
import { drawerWidth } from 'store/constant'
|
||||||
|
import { SET_MENU } from 'store/actions'
|
||||||
|
|
||||||
|
// styles
|
||||||
|
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({
|
||||||
|
...theme.typography.mainContent,
|
||||||
|
...(!open && {
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
transition: theme.transitions.create('margin', {
|
||||||
|
easing: theme.transitions.easing.sharp,
|
||||||
|
duration: theme.transitions.duration.leavingScreen
|
||||||
|
}),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
marginLeft: -(drawerWidth - 20),
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
marginLeft: '20px',
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
padding: '16px'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
marginLeft: '10px',
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
padding: '16px',
|
||||||
|
marginRight: '10px'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...(open && {
|
||||||
|
transition: theme.transitions.create('margin', {
|
||||||
|
easing: theme.transitions.easing.easeOut,
|
||||||
|
duration: theme.transitions.duration.enteringScreen
|
||||||
|
}),
|
||||||
|
marginLeft: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
marginLeft: '20px'
|
||||||
|
},
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
marginLeft: '10px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
// ==============================|| MAIN LAYOUT ||============================== //
|
||||||
|
|
||||||
|
const MainLayout = () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const matchDownMd = useMediaQuery(theme.breakpoints.down('lg'))
|
||||||
|
|
||||||
|
// Handle left drawer
|
||||||
|
const leftDrawerOpened = useSelector((state) => state.customization.opened)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const handleLeftDrawerToggle = () => {
|
||||||
|
dispatch({ type: SET_MENU, opened: !leftDrawerOpened })
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({ type: SET_MENU, opened: !matchDownMd })
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [matchDownMd])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex' }}>
|
||||||
|
<CssBaseline />
|
||||||
|
{/* header */}
|
||||||
|
<AppBar
|
||||||
|
enableColorOnDark
|
||||||
|
position='fixed'
|
||||||
|
color='inherit'
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
bgcolor: theme.palette.background.default,
|
||||||
|
transition: leftDrawerOpened ? theme.transitions.create('width') : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar>
|
||||||
|
<Header handleLeftDrawerToggle={handleLeftDrawerToggle} />
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
|
||||||
|
{/* drawer */}
|
||||||
|
<Sidebar drawerOpen={leftDrawerOpened} drawerToggle={handleLeftDrawerToggle} />
|
||||||
|
|
||||||
|
{/* main content */}
|
||||||
|
<Main theme={theme} open={leftDrawerOpened}>
|
||||||
|
<Outlet />
|
||||||
|
</Main>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainLayout
|
||||||