feature: modularized express routes for reusability, testability, composability and performance (#2030)

* transition GET /api/v1/apikey

* transition POST /api/v1/apikey

* transition PUT /api/v1/apikey/:id

* transition DELETE /api/v1/apikey/:id

* Enable e2e tests for api/v1/apikey routes

* remove unused addChatflowsCount

* Enable e2e tests for api/v1/variables routes

* Enable Cypress in GitHub Action

* Update main.yml

* Update main.yml

* Transition GET /api/v1/variables

* Enable cypress on github workflow

* Transition POST /api/v1/variables

* Transition PUT /api/v1/variables

* Transition DELETE /api/v1/variables

* Transition GET /api/v1/variables

* Transition GET /api/v1/chatflows

* Transition GET /api/v1/chatflows/:id

* Transition POST /api/v1/chatflows

* Transition DELETE /api/v1/chatflows/:id

* Transition PUT /api/v1/chatflows/:id

* Transition GET /api/v1/chatflows/apikey/:apiKey

* Transition GET /api/v1/credentials

* Transition POST /api/v1/credentials

* Transition GET /api/v1/credentials/:id

* Transition PUT /api/v1/credentials/:id

* Transition DELETE /api/v1/credentials/:id

* Transition GET /api/v1/tools

* Transition GET /api/v1/tools/:id

* Transition POST /api/v1/tools

* Transition PUT & DELETE /api/v1/tools/:id

* Transition /api/v1/assistants routes

* Transition /api/v1/nodes routes

* Transition GET /api/v1/chatflows-streaming/:id & GET /api/v1/chatflows-uploads/:id

* wip-all-routes

* Transition GET /api/v1/public-chatflows/:id & /api/v1/public-chatbotConfig/:id

* Remove ts-ignore annotations

* Transition GET /api/v1/chatmessage/:id

* Transition POST /api/v1/chatmessage/:id

* delete /api/v1/chatmessage/:id

* transition /api/v1/feedback/:id routes

* transition /api/v1/stats/:id

* Transition GET /api/v1/openai-assistants/:id

* Transition GET /api/v1/openai-assistants

* Transition POST /api/v1/openai-assistants-file

* transition GET /api/v1/get-upload-path

* transition GET /api/v1/get-upload-file

* transition GET /api/v1/flow-config/:id

* transition POST /api/v1/node-config

* transition GET /api/v1/version

* transition GET /api/v1/fetch-links

* transition POST /api/v1/vector/upsert/:id

* transition POST /api/v1/vector/internal-upsert/:id

* transition POST /api/v1/load-prompt

* Update index.ts

* transition POST /api/v1/prompts-list

* transition predictions

* Update index.ts

* transition GET /api/v1/marketplaces/templates

* Router update modularity cleanup

* extend request interface - express namespace

* Update index.ts

* add errorMiddleware

* Add custom application error handler

* Fix pnpm lock file

* prediction return and vector upsert

* Move the getUploadsConfig into its own file

* Remove lint warnings

* fix undefined variable value

* Fix node-load-method api call

* standardize the error message display

* Apply review comment bugfixes

* Update index.ts

* standardize error message display  in snack notifications

* Error message standard in the UI

* Rename flowXpressApp to appServer

* Upload middleware fix and axios update

* fix async await

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Octavian FlowiseAI 2024-04-02 17:44:04 +02:00 committed by GitHub
parent ea255db15d
commit 957694a912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
136 changed files with 5347 additions and 2380 deletions

View File

@ -1,17 +1,13 @@
name: Node CI
on:
push:
branches:
- main
pull_request:
branches:
- '*'
permissions:
contents: read
jobs:
build:
strategy:
@ -31,12 +27,22 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
check-latest: false
cache: 'pnpm'
- run: npm i -g pnpm
- run: pnpm install
- run: ./node_modules/.bin/cypress install
- run: pnpm lint
- run: pnpm build
- name: Install dependencies
uses: cypress-io/github-action@v6
with:
working-directory: ./
runTests: false
- name: Cypress test
uses: cypress-io/github-action@v6
with:
install: false
working-directory: packages/server
start: pnpm start
wait-on: 'http://localhost:3000'
wait-on-timeout: 120
browser: chrome

View File

@ -41,6 +41,19 @@ You can also specify the env variables when using `npx`. For example:
npx flowise start --PORT=3000 --DEBUG=true
```
## 📖 Tests
We use [Cypress](https://github.com/cypress-io) for our e2e testing. If you want to run the test suite in dev mode please follow this guide:
```sh
cd Flowise/packages/server
pnpm install
./node_modules/.bin/cypress install
pnpm build
#Only for writting new tests on local dev -> pnpm run cypress:open
pnpm run e2e
```
## 📖 Documentation
[Flowise Docs](https://docs.flowiseai.com/)

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
setupNodeEvents() {
// implement node event listeners here
}
}
})

View File

@ -0,0 +1,45 @@
describe('E2E suite for api/v1/apikey API endpoint', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/apikey')
})
// DEFAULT TEST ON PAGE LOAD
it('displays 1 api key by default', () => {
cy.get('table.MuiTable-root tbody tr').should('have.length', 1)
cy.get('table.MuiTable-root tbody tr td').first().should('have.text', 'DefaultKey')
})
// CREATE
it('can add new api key', () => {
const newApiKeyItem = 'MafiKey'
cy.get('#btn_createApiKey').click()
cy.get('#keyName').type(`${newApiKeyItem}`)
cy.get('#btn_confirmAddingApiKey').click()
cy.get('table.MuiTable-root tbody tr').should('have.length', 2)
cy.get('table.MuiTable-root tbody tr').last().find('td').first().should('have.text', newApiKeyItem)
})
// READ
it('can retrieve all api keys', () => {
cy.get('table.MuiTable-root tbody tr').should('have.length', 2)
cy.get('table.MuiTable-root tbody tr').first().find('td').first().should('have.text', 'DefaultKey')
cy.get('table.MuiTable-root tbody tr').last().find('td').first().should('have.text', 'MafiKey')
})
// UPDATE
it('can update new api key', () => {
const UpdatedApiKeyItem = 'UpsertCloudKey'
cy.get('table.MuiTable-root tbody tr').last().find('td').eq(4).find('button').click()
cy.get('#keyName').clear().type(`${UpdatedApiKeyItem}`)
cy.get('#btn_confirmEditingApiKey').click()
cy.get('table.MuiTable-root tbody tr').should('have.length', 2)
cy.get('table.MuiTable-root tbody tr').last().find('td').first().should('have.text', UpdatedApiKeyItem)
})
// DELETE
it('can delete new api key', () => {
cy.get('table.MuiTable-root tbody tr').last().find('td').eq(5).find('button').click()
cy.get('.MuiDialog-scrollPaper .MuiDialogActions-spacing button').last().click()
cy.get('table.MuiTable-root tbody tr').should('have.length', 1)
})
})

View File

@ -0,0 +1,49 @@
describe('E2E suite for api/v1/variables API endpoint', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/variables')
})
// DEFAULT TEST ON PAGE LOAD
it('displays no variables by default', () => {
cy.get('.MuiCardContent-root .MuiStack-root').last().find('div').last().should('have.text', 'No Variables Yet')
})
// CREATE
it('can add new variable', () => {
const newVariableName = 'MafiVariable'
const newVariableValue = 'shh!!! secret value'
cy.get('#btn_createVariable').click()
cy.get('#txtInput_variableName').type(`${newVariableName}`)
cy.get('#txtInput_variableValue').type(`${newVariableValue}`)
cy.get('.MuiDialogActions-spacing button').click()
cy.get('.MuiTable-root tbody tr').should('have.length', 1)
cy.get('.MuiTable-root tbody tr').last().find('th').first().find('div').first().should('have.text', newVariableName)
})
// READ
it('can retrieve all api keys', () => {
const newVariableName = 'MafiVariable'
cy.get('.MuiTable-root tbody tr').should('have.length', 1)
cy.get('.MuiTable-root tbody tr').last().find('th').first().find('div').first().should('have.text', newVariableName)
})
// UPDATE
it('can update new api key', () => {
const updatedVariableName = 'PichiVariable'
const updatedVariableValue = 'silence shh! value'
cy.get('.MuiTable-root tbody tr').last().find('td').eq(4).find('button').click()
cy.get('#txtInput_variableName').clear().type(`${updatedVariableName}`)
cy.get('#txtInput_variableValue').clear().type(`${updatedVariableValue}`)
cy.get('.MuiDialogActions-spacing button').click()
cy.get('.MuiTable-root tbody tr').should('have.length', 1)
cy.get('.MuiTable-root tbody tr').last().find('th').first().find('div').first().should('have.text', updatedVariableName)
})
// DELETE
it('can delete new api key', () => {
cy.get('.MuiTable-root tbody tr').last().find('td').eq(5).find('button').click()
cy.get('.MuiDialog-scrollPaper .MuiDialogActions-spacing button').last().click()
cy.get('.MuiTable-root tbody tr').should('have.length', 0)
cy.get('.MuiCardContent-root .MuiStack-root').last().find('div').last().should('have.text', 'No Variables Yet')
})
})

View File

View File

@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

View File

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -36,7 +36,11 @@
"typeorm:migration-generate": "pnpm typeorm migration:generate -d ./src/utils/typeormDataSource.ts",
"typeorm:migration-run": "pnpm typeorm migration:run -d ./src/utils/typeormDataSource.ts",
"watch": "tsc --watch",
"version": "oclif readme && git add README.md"
"version": "oclif readme && git add README.md",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"e2e": "start-server-and-test dev http://localhost:3000 cypress:run",
"cypress:ci": "START_SERVER_AND_TEST_INSECURE=1 start-server-and-test start https-get://localhost:3000 cypress:run"
},
"keywords": [],
"homepage": "https://flowiseai.com",
@ -63,6 +67,8 @@
"express-rate-limit": "^6.9.0",
"flowise-components": "workspace:^",
"flowise-ui": "workspace:^",
"http-errors": "^2.0.0",
"http-status-codes": "^2.3.0",
"lodash": "^4.17.21",
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
@ -86,11 +92,13 @@
"@types/multer": "^1.4.7",
"@types/sanitize-html": "^2.9.5",
"concurrently": "^7.1.0",
"cypress": "^13.7.1",
"nodemon": "^2.0.22",
"oclif": "^3",
"rimraf": "^5.0.5",
"run-script-os": "^1.1.6",
"shx": "^0.3.3",
"start-server-and-test": "^2.0.3",
"ts-node": "^10.7.0",
"tsc-watch": "^6.0.4",
"typescript": "^4.8.4"

View File

@ -0,0 +1,79 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { ApiError } from '../../errors/apiError'
import apikeyService from '../../services/apikey'
// Get api keys
const getAllApiKeys = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await apikeyService.getAllApiKeys()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const createApiKey = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body.keyName === 'undefined' || req.body.keyName === '') {
throw new ApiError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.createApiKey - keyName not provided!`)
}
const apiResponse = await apikeyService.createApiKey(req.body.keyName)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Update api key
const updateApiKey = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
new ApiError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.updateApiKey - id not provided!`)
}
if (typeof req.body.keyName === 'undefined' || req.body.keyName === '') {
new ApiError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.updateApiKey - keyName not provided!`)
}
const apiResponse = await apikeyService.updateApiKey(req.params.id, req.body.keyName)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Delete api key
const deleteApiKey = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
new ApiError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.deleteApiKey - id not provided!`)
}
const apiResponse = await apikeyService.deleteApiKey(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Verify api key
const verifyApiKey = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.apiKey === 'undefined' || req.params.apiKey === '') {
new ApiError(StatusCodes.PRECONDITION_FAILED, `Error: apikeyController.verifyApiKey - apiKey not provided!`)
}
const apiResponse = await apikeyService.verifyApiKey(req.params.apiKey)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createApiKey,
deleteApiKey,
getAllApiKeys,
updateApiKey,
verifyApiKey
}

View File

@ -0,0 +1,85 @@
import { Request, Response, NextFunction } from 'express'
import assistantsService from '../../services/assistants'
const creatAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: assistantsController.creatAssistant - body not provided!`)
}
const apiResponse = await assistantsService.creatAssistant(req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: assistantsController.deleteAssistant - id not provided!`)
}
const apiResponse = await assistantsService.deleteAssistant(req.params.id, req.query.isDeleteBoth)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllAssistants = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await assistantsService.getAllAssistants()
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAssistantById = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: assistantsController.getAssistantById - id not provided!`)
}
const apiResponse = await assistantsService.getAssistantById(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: assistantsController.updateAssistant - id not provided!`)
}
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: assistantsController.updateAssistant - body not provided!`)
}
const apiResponse = await assistantsService.updateAssistant(req.params.id, req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
creatAssistant,
deleteAssistant,
getAllAssistants,
getAssistantById,
updateAssistant
}

View File

@ -0,0 +1,156 @@
import { Request, Response, NextFunction } from 'express'
import { chatType, IReactFlowObject } from '../../Interface'
import chatflowsService from '../../services/chatflows'
import chatMessagesService from '../../services/chat-messages'
import { clearSessionMemory } from '../../utils'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { FindOptionsWhere } from 'typeorm'
import { ChatMessage } from '../../database/entities/ChatMessage'
const createChatMessage = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error('Error: chatMessagesController.createChatMessage - request body not provided!')
}
const apiResponse = await chatMessagesService.createChatMessage(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
let chatTypeFilter = req.query?.chatType as chatType | undefined
if (chatTypeFilter) {
try {
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = undefined
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
chatTypeFilter = chatType.EXTERNAL
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = chatType.INTERNAL
}
} catch (e) {
return res.status(500).send(e)
}
}
const sortOrder = req.query?.order as string | undefined
const chatId = req.query?.chatId as string | undefined
const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined
const messageId = req.query?.messageId as string | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
const feedback = req.query?.feedback as boolean | undefined
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatMessageController.getAllChatMessages - id not provided!`)
}
const apiResponse = await chatMessagesService.getAllChatMessages(
req.params.id,
chatTypeFilter,
sortOrder,
chatId,
memoryType,
sessionId,
startDate,
endDate,
messageId,
feedback
)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllInternalChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
const sortOrder = req.query?.order as string | undefined
const chatId = req.query?.chatId as string | undefined
const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined
const messageId = req.query?.messageId as string | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
const feedback = req.query?.feedback as boolean | undefined
const apiResponse = await chatMessagesService.getAllInternalChatMessages(
req.params.id,
chatType.INTERNAL,
sortOrder,
chatId,
memoryType,
sessionId,
startDate,
endDate,
messageId,
feedback
)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
//Delete all chatmessages from chatId
const removeAllChatMessages = async (req: Request, res: Response, next: NextFunction) => {
try {
const appServer = getRunningExpressApp()
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error('Error: chatMessagesController.removeAllChatMessages - id not provided!')
}
const chatflowid = req.params.id
const chatflow = await chatflowsService.getChatflowById(req.params.id)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
const chatId = req.query?.chatId as string
const memoryType = req.query?.memoryType as string | undefined
const sessionId = req.query?.sessionId as string | undefined
const chatType = req.query?.chatType as string | undefined
const isClearFromViewMessageDialog = req.query?.isClearFromViewMessageDialog as string | undefined
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
try {
await clearSessionMemory(
nodes,
appServer.nodesPool.componentNodes,
chatId,
appServer.AppDataSource,
sessionId,
memoryType,
isClearFromViewMessageDialog
)
} catch (e) {
return res.status(500).send('Error clearing chat messages')
}
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid }
if (chatId) deleteOptions.chatId = chatId
if (memoryType) deleteOptions.memoryType = memoryType
if (sessionId) deleteOptions.sessionId = sessionId
if (chatType) deleteOptions.chatType = chatType
const apiResponse = await chatMessagesService.removeAllChatMessages(chatId, chatflowid, deleteOptions)
if (apiResponse.executionError) {
res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createChatMessage,
getAllChatMessages,
getAllInternalChatMessages,
removeAllChatMessages
}

View File

@ -0,0 +1,170 @@
import { Request, Response, NextFunction } from 'express'
import chatflowsService from '../../services/chatflows'
import { ChatFlow } from '../../database/entities/ChatFlow'
import { createRateLimiter } from '../../utils/rateLimit'
import { getApiKey } from '../../utils/apiKey'
const checkIfChatflowIsValidForStreaming = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.checkIfChatflowIsValidForStreaming - id not provided!`)
}
const apiResponse = await chatflowsService.checkIfChatflowIsValidForStreaming(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const checkIfChatflowIsValidForUploads = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.checkIfChatflowIsValidForUploads - id not provided!`)
}
const apiResponse = await chatflowsService.checkIfChatflowIsValidForUploads(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.deleteChatflow - id not provided!`)
}
const apiResponse = await chatflowsService.deleteChatflow(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllChatflows = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await chatflowsService.getAllChatflows()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Get specific chatflow via api key
const getChatflowByApiKey = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.apiKey === 'undefined' || req.params.apiKey === '') {
throw new Error(`Error: chatflowsRouter.getChatflowById - apiKey not provided!`)
}
const apiKey = await getApiKey(req.params.apiKey)
if (!apiKey) {
return res.status(401).send('Unauthorized')
}
const apiResponse = await chatflowsService.getChatflowByApiKey(apiKey.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getChatflowById = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.getChatflowById - id not provided!`)
}
const apiResponse = await chatflowsService.getChatflowById(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const saveChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: chatflowsRouter.saveChatflow - body not provided!`)
}
const body = req.body
const newChatFlow = new ChatFlow()
Object.assign(newChatFlow, body)
const apiResponse = await chatflowsService.saveChatflow(newChatFlow)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.updateChatflow - id not provided!`)
}
const chatflow = await chatflowsService.getChatflowById(req.params.id)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
const body = req.body
const updateChatFlow = new ChatFlow()
Object.assign(updateChatFlow, body)
updateChatFlow.id = chatflow.id
createRateLimiter(updateChatFlow)
const apiResponse = await chatflowsService.updateChatflow(chatflow, updateChatFlow)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getSinglePublicChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.updateChatflow - id not provided!`)
}
const apiResponse = await chatflowsService.getSinglePublicChatflow(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getSinglePublicChatbotConfig = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: chatflowsRouter.getSinglePublicChatbotConfig - id not provided!`)
}
const apiResponse = await chatflowsService.getSinglePublicChatbotConfig(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
checkIfChatflowIsValidForStreaming,
checkIfChatflowIsValidForUploads,
deleteChatflow,
getAllChatflows,
getChatflowByApiKey,
getChatflowById,
saveChatflow,
updateChatflow,
getSinglePublicChatflow,
getSinglePublicChatbotConfig
}

View File

@ -0,0 +1,44 @@
import { Request, Response, NextFunction } from 'express'
import componentsCredentialsService from '../../services/components-credentials'
// Get all component credentials
const getAllComponentsCredentials = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await componentsCredentialsService.getAllComponentsCredentials()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Get component credential via name
const getComponentByName = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.name === 'undefined' || req.params.name === '') {
throw new Error(`Error: componentsCredentialsController.getComponentByName - name not provided!`)
}
const apiResponse = await componentsCredentialsService.getComponentByName(req.params.name)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Returns specific component credential icon via name
const getSingleComponentsCredentialIcon = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.name === 'undefined' || req.params.name === '') {
throw new Error(`Error: componentsCredentialsController.getSingleComponentsCredentialIcon - name not provided!`)
}
const apiResponse = await componentsCredentialsService.getSingleComponentsCredentialIcon(req.params.name)
return res.sendFile(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllComponentsCredentials,
getComponentByName,
getSingleComponentsCredentialIcon
}

View File

@ -0,0 +1,79 @@
import { Request, Response, NextFunction } from 'express'
import credentialsService from '../../services/credentials'
const createCredential = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: credentialsController.createCredential - body not provided!`)
}
const apiResponse = await credentialsService.createCredential(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteCredentials = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: credentialsController.deleteCredentials - id not provided!`)
}
const apiResponse = await credentialsService.deleteCredentials(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllCredentials = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await credentialsService.getAllCredentials(req.query.credentialName)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getCredentialById = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: credentialsController.getCredentialById - id not provided!`)
}
const apiResponse = await credentialsService.getCredentialById(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateCredential = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: credentialsController.updateCredential - id not provided!`)
}
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: credentialsController.updateCredential - body not provided!`)
}
const apiResponse = await credentialsService.updateCredential(req.params.id, req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createCredential,
deleteCredentials,
getAllCredentials,
getCredentialById,
updateCredential
}

View File

@ -0,0 +1,52 @@
import { Request, Response, NextFunction } from 'express'
import feedbackService from '../../services/feedback'
const getAllChatMessageFeedback = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: feedbackController.getAllChatMessageFeedback - id not provided!`)
}
const chatflowid = req.params.id
const chatId = req.query?.chatId as string | undefined
const sortOrder = req.query?.order as string | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
const apiResponse = await feedbackService.getAllChatMessageFeedback(chatflowid, chatId, sortOrder, startDate, endDate)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const createChatMessageFeedbackForChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: feedbackController.createChatMessageFeedbackForChatflow - body not provided!`)
}
const apiResponse = await feedbackService.createChatMessageFeedbackForChatflow(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateChatMessageFeedbackForChatflow = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: feedbackController.updateChatMessageFeedbackForChatflow - body not provided!`)
}
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: feedbackController.updateChatMessageFeedbackForChatflow - id not provided!`)
}
const apiResponse = await feedbackService.updateChatMessageFeedbackForChatflow(req.params.id, req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllChatMessageFeedback,
createChatMessageFeedbackForChatflow,
updateChatMessageFeedbackForChatflow
}

View File

@ -0,0 +1,31 @@
import { Request, Response, NextFunction } from 'express'
import fetchLinksService from '../../services/fetch-links'
const getAllLinks = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.query.url === 'undefined' || req.query.url === '') {
throw new Error(`Error: fetchLinksController.getAllLinks - url not provided!`)
}
if (typeof req.query.relativeLinksMethod === 'undefined' || req.query.relativeLinksMethod === '') {
throw new Error(`Error: fetchLinksController.getAllLinks - relativeLinksMethod not provided!`)
}
if (typeof req.query.limit === 'undefined' || req.query.limit === '') {
throw new Error(`Error: fetchLinksController.getAllLinks - limit not provided!`)
}
const apiResponse = await fetchLinksService.getAllLinks(
req.query.url as string,
req.query.relativeLinksMethod as string,
req.query.limit as string
)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllLinks
}

View File

@ -0,0 +1,21 @@
import { Request, Response, NextFunction } from 'express'
import flowConfigsService from '../../services/flow-configs'
const getSingleFlowConfig = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: flowConfigsController.getSingleFlowConfig - id not provided!`)
}
const apiResponse = await flowConfigsService.getSingleFlowConfig(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getSingleFlowConfig
}

View File

@ -0,0 +1,37 @@
import { Request, Response, NextFunction } from 'express'
import path from 'path'
import contentDisposition from 'content-disposition'
import { getStoragePath } from 'flowise-components'
import * as fs from 'fs'
const streamUploadedImage = async (req: Request, res: Response, next: NextFunction) => {
try {
if (!req.query.chatflowId || !req.query.chatId || !req.query.fileName) {
return res.status(500).send(`Invalid file path`)
}
const chatflowId = req.query.chatflowId as string
const chatId = req.query.chatId as string
const fileName = req.query.fileName as string
const filePath = path.join(getStoragePath(), chatflowId, chatId, fileName)
//raise error if file path is not absolute
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
//raise error if file path contains '..'
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
//only return from the storage folder
if (!filePath.startsWith(getStoragePath())) return res.status(500).send(`Invalid file path`)
if (fs.existsSync(filePath)) {
res.setHeader('Content-Disposition', contentDisposition(path.basename(filePath)))
const fileStream = fs.createReadStream(filePath)
fileStream.pipe(res)
} else {
return res.status(404).send(`File ${fileName} not found`)
}
} catch (error) {
next(error)
}
}
export default {
streamUploadedImage
}

View File

@ -0,0 +1,17 @@
import { Request, Response, NextFunction } from 'express'
import { getStoragePath } from 'flowise-components'
const getPathForUploads = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = {
storagePath: getStoragePath()
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getPathForUploads
}

View File

@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express'
import { utilBuildChatflow } from '../../utils/buildChatflow'
// Send input message and get prediction result (Internal)
const createInternalPrediction = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await utilBuildChatflow(req, req.io, true)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createInternalPrediction
}

View File

@ -0,0 +1,21 @@
import { Request, Response, NextFunction } from 'express'
// Configure number of proxies in Host Environment
const configureProxyNrInHostEnv = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.ip === 'undefined' || req.ip) {
throw new Error(`Error: ipController.configureProxyNrInHostEnv - ip not provided!`)
}
const apiResponse = {
ip: req.ip,
msg: `Check returned IP address in the response. If it matches your current IP address ( which you can get by going to http://ip.nfriedly.com/ or https://api.ipify.org/ ), then the number of proxies is correct and the rate limiter should now work correctly. If not, increase the number of proxies by 1 and restart Cloud-Hosted Flowise until the IP address matches your own. Visit https://docs.flowiseai.com/configuration/rate-limit#cloud-hosted-rate-limit-setup-guide for more information.`
}
return res.send(apiResponse)
} catch (error) {
next(error)
}
}
export default {
configureProxyNrInHostEnv
}

View File

@ -0,0 +1,21 @@
import { Request, Response, NextFunction } from 'express'
import loadPromptsService from '../../services/load-prompts'
const createPrompt = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || typeof req.body.promptName === 'undefined' || req.body.promptName === '') {
throw new Error(`Error: loadPromptsController.createPrompt - promptName not provided!`)
}
const apiResponse = await loadPromptsService.createPrompt(req.body.promptName as string)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createPrompt
}

View File

@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express'
import marketplacesService from '../../services/marketplaces'
// Get all templates for marketplaces
const getAllTemplates = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await marketplacesService.getAllTemplates()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllTemplates
}

View File

@ -0,0 +1,18 @@
import { Request, Response, NextFunction } from 'express'
import nodeConfigsService from '../../services/node-configs'
const getAllNodeConfigs = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: nodeConfigsController.getAllNodeConfigs - body not provided!`)
}
const apiResponse = await nodeConfigsService.getAllNodeConfigs(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllNodeConfigs
}

View File

@ -0,0 +1,30 @@
import { Request, Response, NextFunction } from 'express'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
// Returns specific component node icon via name
const getSingleNodeIcon = async (req: Request, res: Response, next: NextFunction) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, req.params.name)) {
const nodeInstance = appServer.nodesPool.componentNodes[req.params.name]
if (nodeInstance.icon === undefined) {
throw new Error(`Error: nodeIconController.getSingleNodeIcon - 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(`Error: nodeIconController.getSingleNodeIcon - Node ${req.params.name} icon is missing icon`)
}
} else {
throw new Error(`Error: nodeIconController.getSingleNodeIcon - Node ${req.params.name} not found`)
}
} catch (error) {
next(error)
}
}
export default {
getSingleNodeIcon
}

View File

@ -0,0 +1,76 @@
import { Request, Response, NextFunction } from 'express'
import nodesService from '../../services/nodes'
const getAllNodes = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await nodesService.getAllNodes()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getNodeByName = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.name === 'undefined' || req.params.name === '') {
throw new Error(`Error: nodesController.getNodeByName - name not provided!`)
}
const apiResponse = await nodesService.getNodeByName(req.params.name)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getSingleNodeIcon = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.name === 'undefined' || req.params.name === '') {
throw new Error(`Error: nodesController.getSingleNodeIcon - name not provided!`)
}
const apiResponse = await nodesService.getSingleNodeIcon(req.params.name)
return res.sendFile(apiResponse)
} catch (error) {
next(error)
}
}
const getSingleNodeAsyncOptions = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: nodesController.getSingleNodeAsyncOptions - body not provided!`)
}
if (typeof req.params.name === 'undefined' || req.params.name === '') {
throw new Error(`Error: nodesController.getSingleNodeAsyncOptions - name not provided!`)
}
const apiResponse = await nodesService.getSingleNodeAsyncOptions(req.params.name, req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const executeCustomFunction = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: nodesController.executeCustomFunction - body not provided!`)
}
const apiResponse = await nodesService.executeCustomFunction(req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getAllNodes,
getNodeByName,
getSingleNodeIcon,
getSingleNodeAsyncOptions,
executeCustomFunction
}

View File

@ -0,0 +1,69 @@
import { Request, Response, NextFunction } from 'express'
import path from 'path'
import * as fs from 'fs'
import openaiAssistantsService from '../../services/openai-assistants'
import { getUserHome } from '../../utils'
import contentDisposition from 'content-disposition'
// List available assistants
const getAllOpenaiAssistants = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.query.credential === 'undefined' || req.query.credential === '') {
throw new Error(`Error: openaiAssistantsController.getAllOpenaiAssistants - credential not provided!`)
}
const apiResponse = await openaiAssistantsService.getAllOpenaiAssistants(req.query.credential as string)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Get assistant object
const getSingleOpenaiAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: openaiAssistantsController.getSingleOpenaiAssistant - id not provided!`)
}
if (typeof req.query.credential === 'undefined' || req.query.credential === '') {
throw new Error(`Error: openaiAssistantsController.getSingleOpenaiAssistant - credential not provided!`)
}
const apiResponse = await openaiAssistantsService.getSingleOpenaiAssistant(req.query.credential as string, req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
// Download file from assistant
const getFileFromAssistant = async (req: Request, res: Response, next: NextFunction) => {
try {
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', req.body.fileName)
//raise error if file path is not absolute
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
//raise error if file path contains '..'
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
//only return from the .flowise openai-assistant folder
if (!(filePath.includes('.flowise') && filePath.includes('openai-assistant'))) return res.status(500).send(`Invalid file path`)
if (fs.existsSync(filePath)) {
res.setHeader('Content-Disposition', contentDisposition(path.basename(filePath)))
const fileStream = fs.createReadStream(filePath)
fileStream.pipe(res)
} else {
return res.status(404).send(`File ${req.body.fileName} not found`)
}
} catch (error) {
next(error)
}
}
export default {
getAllOpenaiAssistants,
getSingleOpenaiAssistant,
getFileFromAssistant
}

View File

@ -0,0 +1,66 @@
import { Request, Response, NextFunction } from 'express'
import { getRateLimiter } from '../../utils/rateLimit'
import chatflowsService from '../../services/chatflows'
import logger from '../../utils/logger'
import { utilBuildChatflow } from '../../utils/buildChatflow'
// Send input message and get prediction result (External)
const createPrediction = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: predictionsController.createPrediction - id not provided!`)
}
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: predictionsController.createPrediction - body not provided!`)
}
const chatflow = await chatflowsService.getChatflowById(req.params.id)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
let isDomainAllowed = true
logger.info(`[server]: Request originated from ${req.headers.origin}`)
if (chatflow.chatbotConfig) {
const parsedConfig = JSON.parse(chatflow.chatbotConfig)
// check whether the first one is not empty. if it is empty that means the user set a value and then removed it.
const isValidAllowedOrigins = parsedConfig.allowedOrigins?.length && parsedConfig.allowedOrigins[0] !== ''
if (isValidAllowedOrigins) {
const originHeader = req.headers.origin as string
const origin = new URL(originHeader).host
isDomainAllowed =
parsedConfig.allowedOrigins.filter((domain: string) => {
try {
const allowedOrigin = new URL(domain).host
return origin === allowedOrigin
} catch (e) {
return false
}
}).length > 0
}
}
if (isDomainAllowed) {
const apiResponse = await utilBuildChatflow(req, req.io)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} else {
return res.status(401).send(`This site is not allowed to access this chatbot`)
}
} catch (error) {
next(error)
}
}
const getRateLimiterMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
return getRateLimiter(req, res, next)
} catch (error) {
next(error)
}
}
export default {
createPrediction,
getRateLimiterMiddleware
}

View File

@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from 'express'
import promptsListsService from '../../services/prompts-lists'
// Prompt from Hub
const createPromptsList = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await promptsListsService.createPromptsList(req.body)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createPromptsList
}

View File

@ -0,0 +1,40 @@
import { Request, Response, NextFunction } from 'express'
import statsService from '../../services/stats'
import { chatType } from '../../Interface'
const getChatflowStats = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: statsController.getChatflowStats - id not provided!`)
}
const chatflowid = req.params.id
let chatTypeFilter = req.query?.chatType as chatType | undefined
const startDate = req.query?.startDate as string | undefined
const endDate = req.query?.endDate as string | undefined
if (chatTypeFilter) {
try {
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = undefined
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
chatTypeFilter = chatType.EXTERNAL
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
chatTypeFilter = chatType.INTERNAL
}
} catch (e) {
return res.status(500).send(e)
}
}
const apiResponse = await statsService.getChatflowStats(chatflowid, chatTypeFilter, startDate, endDate, '', true)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getChatflowStats
}

View File

@ -0,0 +1,85 @@
import { Request, Response, NextFunction } from 'express'
import toolsService from '../../services/tools'
const creatTool = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: toolsController.creatTool - body not provided!`)
}
const apiResponse = await toolsService.creatTool(req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteTool = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: toolsController.updateTool - id not provided!`)
}
const apiResponse = await toolsService.deleteTool(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllTools = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await toolsService.getAllTools()
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getToolById = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: toolsController.getToolById - id not provided!`)
}
const apiResponse = await toolsService.getToolById(req.params.id)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateTool = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error(`Error: toolsController.updateTool - id not provided!`)
}
if (typeof req.body === 'undefined' || req.body === '') {
throw new Error(`Error: toolsController.deleteTool - body not provided!`)
}
const apiResponse = await toolsService.updateTool(req.params.id, req.body)
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
creatTool,
deleteTool,
getAllTools,
getToolById,
updateTool
}

View File

@ -0,0 +1,68 @@
import { Request, Response, NextFunction } from 'express'
import variablesService from '../../services/variables'
import { Variable } from '../../database/entities/Variable'
const createVariable = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.body === 'undefined') {
throw new Error(`Error: variablesController.createVariable - body not provided!`)
}
const body = req.body
const newVariable = new Variable()
Object.assign(newVariable, body)
const apiResponse = await variablesService.createVariable(newVariable)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const deleteVariable = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error('Error: variablesController.deleteVariable - id not provided!')
}
const apiResponse = await variablesService.deleteVariable(req.params.id)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const getAllVariables = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await variablesService.getAllVariables()
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
const updateVariable = async (req: Request, res: Response, next: NextFunction) => {
try {
if (typeof req.params.id === 'undefined' || req.params.id === '') {
throw new Error('Error: variablesController.updateVariable - id not provided!')
}
if (typeof req.body === 'undefined') {
throw new Error('Error: variablesController.updateVariable - body not provided!')
}
const variable = await variablesService.getVariableById(req.params.id)
if (!variable) {
return res.status(404).send(`Variable ${req.params.id} not found in the database`)
}
const body = req.body
const updatedVariable = new Variable()
Object.assign(updatedVariable, body)
const apiResponse = await variablesService.updateVariable(variable, updatedVariable)
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
createVariable,
deleteVariable,
getAllVariables,
updateVariable
}

View File

@ -0,0 +1,33 @@
import { Request, Response, NextFunction } from 'express'
import vectorsService from '../../services/vectors'
import { getRateLimiter } from '../../utils/rateLimit'
const getRateLimiterMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
return getRateLimiter(req, res, next)
} catch (error) {
next(error)
}
}
const upsertVectorMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
return await vectorsService.upsertVectorMiddleware(req, res)
} catch (error) {
next(error)
}
}
const createInternalUpsert = async (req: Request, res: Response, next: NextFunction) => {
try {
return await vectorsService.upsertVectorMiddleware(req, res, true)
} catch (error) {
next(error)
}
}
export default {
upsertVectorMiddleware,
createInternalUpsert,
getRateLimiterMiddleware
}

View File

@ -0,0 +1,18 @@
import { Request, Response, NextFunction } from 'express'
import versionsService from '../../services/versions'
const getVersion = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await versionsService.getVersion()
if (apiResponse.executionError) {
return res.status(apiResponse.status).send(apiResponse.msg)
}
return res.json(apiResponse)
} catch (error) {
next(error)
}
}
export default {
getVersion
}

View File

@ -0,0 +1,9 @@
export class ApiError extends Error {
statusCode: number
constructor(statusCode: number, message: string) {
super(message)
this.statusCode = statusCode
// capture the stack trace of the error from anywhere in the application
Error.captureStackTrace(this, this.constructor)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
import { NextFunction, Request, Response } from 'express'
import { StatusCodes } from 'http-status-codes'
import { ApiError } from '../../errors/apiError'
// we need eslint because we have to pass next arg for the error middleware
// eslint-disable-next-line
async function errorHandlerMiddleware(err: ApiError, req: Request, res: Response, next: NextFunction) {
let displayedError = {
statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
success: false,
message: err.message,
// Provide error stack trace only in development
stack: process.env.NODE_ENV === 'development' ? err.stack : {}
}
res.setHeader('Content-Type', 'application/json')
res.status(displayedError.statusCode).json(displayedError)
}
export default errorHandlerMiddleware

View File

@ -0,0 +1,17 @@
import express from 'express'
import apikeyController from '../../controllers/apikey'
const router = express.Router()
// CREATE
router.post('/', apikeyController.createApiKey)
// READ
router.get('/', apikeyController.getAllApiKeys)
// UPDATE
router.put('/:id', apikeyController.updateApiKey)
// DELETE
router.delete('/:id', apikeyController.deleteApiKey)
export default router

View File

@ -0,0 +1,19 @@
import express from 'express'
import assistantsController from '../../controllers/assistants'
const router = express.Router()
// CREATE
router.post('/', assistantsController.creatAssistant)
// READ
router.get('/', assistantsController.getAllAssistants)
router.get('/:id', assistantsController.getAssistantById)
// UPDATE
router.put('/:id', assistantsController.updateAssistant)
// DELETE
router.delete('/:id', assistantsController.deleteAssistant)
export default router

View File

@ -0,0 +1,16 @@
import express from 'express'
import chatMessageController from '../../controllers/chat-messages'
const router = express.Router()
// CREATE
router.post('/:id', chatMessageController.createChatMessage)
// READ
router.get('/:id', chatMessageController.getAllChatMessages)
// UPDATE
// DELETE
router.delete('/:id', chatMessageController.removeAllChatMessages)
export default router

View File

@ -0,0 +1,9 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
const router = express.Router()
// READ
router.get('/:id', chatflowsController.checkIfChatflowIsValidForStreaming)
export default router

View File

@ -0,0 +1,9 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
const router = express.Router()
// READ
router.get('/:id', chatflowsController.checkIfChatflowIsValidForUploads)
export default router

View File

@ -0,0 +1,19 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
const router = express.Router()
// CREATE
router.post('/', chatflowsController.saveChatflow)
// READ
router.get('/', chatflowsController.getAllChatflows)
router.get('/:id', chatflowsController.getChatflowById)
router.get('/apikey/:apikey', chatflowsController.getChatflowByApiKey)
// UPDATE
router.put('/:id', chatflowsController.updateChatflow)
// DELETE
router.delete('/:id', chatflowsController.deleteChatflow)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import componentsCredentialsController from '../../controllers/components-credentials'
const router = express.Router()
// CREATE
// READ
router.get('/:name', componentsCredentialsController.getSingleComponentsCredentialIcon)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,9 @@
import express from 'express'
import componentsCredentialsController from '../../controllers/components-credentials'
const router = express.Router()
// READ
router.get('/', componentsCredentialsController.getAllComponentsCredentials)
router.get('/:name', componentsCredentialsController.getComponentByName)
export default router

View File

@ -0,0 +1,18 @@
import express from 'express'
import credentialsController from '../../controllers/credentials'
const router = express.Router()
// CREATE
router.post('/', credentialsController.createCredential)
// READ
router.get('/', credentialsController.getAllCredentials)
router.get('/:id', credentialsController.getCredentialById)
// UPDATE
router.put('/:id', credentialsController.updateCredential)
// DELETE
router.delete('/:id', credentialsController.deleteCredentials)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import feedbackController from '../../controllers/feedback'
const router = express.Router()
// CREATE
router.post('/:id', feedbackController.createChatMessageFeedbackForChatflow)
// READ
router.get('/:id', feedbackController.getAllChatMessageFeedback)
// UPDATE
router.put('/:id', feedbackController.updateChatMessageFeedbackForChatflow)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import fetchLinksController from '../../controllers/fetch-links'
const router = express.Router()
// READ
router.get('/', fetchLinksController.getAllLinks)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import flowConfigsController from '../../controllers/flow-configs'
const router = express.Router()
// CREATE
// READ
router.get('/:id', flowConfigsController.getSingleFlowConfig)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import getUploadFileController from '../../controllers/get-upload-file'
const router = express.Router()
// READ
router.get('/', getUploadFileController.streamUploadedImage)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import getUploadPathController from '../../controllers/get-upload-path'
const router = express.Router()
// READ
router.get('/', getUploadPathController.getPathForUploads)
export default router

View File

@ -0,0 +1,78 @@
import express from 'express'
import apikeyRouter from './apikey'
import assistantsRouter from './assistants'
import chatflowsRouter from './chatflows'
import chatflowsStreamingRouter from './chatflows-streaming'
import chatflowsUploadsRouter from './chatflows-uploads'
import chatMessageRouter from './chat-messages'
import componentsCredentialsRouter from './components-credentials'
import componentsCredentialsIconRouter from './components-credentials-icon'
import credentialsRouter from './credentials'
import feedbackRouter from './feedback'
import fetchLinksRouter from './fetch-links'
import flowConfigRouter from './flow-config'
import internalChatmessagesRouter from './internal-chat-messages'
import internalPredictionRouter from './internal-predictions'
import ipRouter from './ip'
import getUploadFileRouter from './get-upload-file'
import getUploadPathRouter from './get-upload-path'
import loadPromptRouter from './load-prompts'
import marketplacesRouter from './marketplaces'
import nodeConfigRouter from './node-configs'
import nodeCustomFunctionRouter from './node-custom-functions'
import nodeIconRouter from './node-icons'
import nodeLoadMethodRouter from './node-load-methods'
import nodesRouter from './nodes'
import openaiAssistantsRouter from './openai-assistants'
import openaiAssistantsFileRouter from './openai-assistants-files'
import predictionRouter from './predictions'
import promptListsRouter from './prompts-lists'
import publicChatbotRouter from './public-chatbots'
import publicChatflowsRouter from './public-chatflows'
import statsRouter from './stats'
import toolsRouter from './tools'
import variablesRouter from './variables'
import vectorRouter from './vectors'
import verifyRouter from './verify'
import versionRouter from './versions'
const router = express.Router()
router.use('/apikey', apikeyRouter)
router.use('/assistants', assistantsRouter)
router.use('/chatflows', chatflowsRouter)
router.use('/chatflows-streaming', chatflowsStreamingRouter)
router.use('/chatmessage', chatMessageRouter)
router.use('/components-credentials', componentsCredentialsRouter)
router.use('/components-credentials-icon', componentsCredentialsIconRouter)
router.use('/chatflows-uploads', chatflowsUploadsRouter)
router.use('/credentials', credentialsRouter)
router.use('/feedback', feedbackRouter)
router.use('/fetch-links', fetchLinksRouter)
router.use('/flow-config', flowConfigRouter)
router.use('/internal-chatmessage', internalChatmessagesRouter)
router.use('/internal-prediction', internalPredictionRouter)
router.use('/ip', ipRouter)
router.use('/get-upload-file', getUploadFileRouter)
router.use('/get-upload-path', getUploadPathRouter)
router.use('/load-prompt', loadPromptRouter)
router.use('/marketplaces', marketplacesRouter)
router.use('/node-config', nodeConfigRouter)
router.use('/node-custom-function', nodeCustomFunctionRouter)
router.use('/node-icon', nodeIconRouter)
router.use('/node-load-method', nodeLoadMethodRouter)
router.use('/nodes', nodesRouter)
router.use('/openai-assistants', openaiAssistantsRouter)
router.use('/openai-assistants-file', openaiAssistantsFileRouter)
router.use('/prediction', predictionRouter)
router.use('/prompts-list', promptListsRouter)
router.use('/public-chatbotConfig', publicChatbotRouter)
router.use('/public-chatflows', publicChatflowsRouter)
router.use('/stats', statsRouter)
router.use('/tools', toolsRouter)
router.use('/variables', variablesRouter)
router.use('/vector', vectorRouter)
router.use('/verify', verifyRouter)
router.use('/version', versionRouter)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import chatMessagesController from '../../controllers/chat-messages'
const router = express.Router()
// CREATE
// READ
router.get('/:id', chatMessagesController.getAllInternalChatMessages)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import internalPredictionsController from '../../controllers/internal-predictions'
const router = express.Router()
// CREATE
router.post('/:id', internalPredictionsController.createInternalPrediction)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import ipController from '../../controllers/ip'
const router = express.Router()
// CREATE
// READ
router.get('/', ipController.configureProxyNrInHostEnv)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import loadPromptsController from '../../controllers/load-prompts'
const router = express.Router()
// CREATE
router.post('/', loadPromptsController.createPrompt)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import marketplacesController from '../../controllers/marketplaces'
const router = express.Router()
// READ
router.get('/templates', marketplacesController.getAllTemplates)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import nodeConfigsController from '../../controllers/node-configs'
const router = express.Router()
// CREATE
router.post('/', nodeConfigsController.getAllNodeConfigs)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import nodesRouter from '../../controllers/nodes'
const router = express.Router()
// CREATE
// READ
router.post('/', nodesRouter.executeCustomFunction)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import nodesController from '../../controllers/nodes'
const router = express.Router()
// CREATE
// READ
router.get('/:name', nodesController.getSingleNodeIcon)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,7 @@
import express from 'express'
import nodesRouter from '../../controllers/nodes'
const router = express.Router()
router.post('/:name', nodesRouter.getSingleNodeAsyncOptions)
export default router

View File

@ -0,0 +1,9 @@
import express from 'express'
import nodesController from '../../controllers/nodes'
const router = express.Router()
// READ
router.get('/', nodesController.getAllNodes)
router.get('/:name', nodesController.getNodeByName)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import openaiAssistantsController from '../../controllers/openai-assistants'
const router = express.Router()
// CREATE
router.post('/', openaiAssistantsController.getFileFromAssistant)
export default router

View File

@ -0,0 +1,15 @@
import express from 'express'
import openaiAssistantsController from '../../controllers/openai-assistants'
const router = express.Router()
// CREATE
// READ
router.get('/', openaiAssistantsController.getAllOpenaiAssistants)
router.get('/:id', openaiAssistantsController.getSingleOpenaiAssistant)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,13 @@
import express from 'express'
import multer from 'multer'
import path from 'path'
import predictionsController from '../../controllers/predictions'
const router = express.Router()
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
// CREATE
router.post('/:id', upload.array('files'), predictionsController.getRateLimiterMiddleware, predictionsController.createPrediction)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import promptsListController from '../../controllers/prompts-lists'
const router = express.Router()
// CREATE
router.post('/', promptsListController.createPromptsList)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
const router = express.Router()
// CREATE
// READ
router.get('/:id', chatflowsController.getSinglePublicChatbotConfig)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
const router = express.Router()
// CREATE
// READ
router.get('/:id', chatflowsController.getSinglePublicChatflow)
// UPDATE
// DELETE
export default router

View File

@ -0,0 +1,9 @@
import express from 'express'
import statsController from '../../controllers/stats'
const router = express.Router()
// READ
router.get('/:id', statsController.getChatflowStats)
export default router

View File

@ -0,0 +1,19 @@
import express from 'express'
import toolsController from '../../controllers/tools'
const router = express.Router()
// CREATE
router.post('/', toolsController.creatTool)
// READ
router.get('/', toolsController.getAllTools)
router.get('/:id', toolsController.getToolById)
// UPDATE
router.put('/:id', toolsController.updateTool)
// DELETE
router.delete('/:id', toolsController.deleteTool)
export default router

View File

@ -0,0 +1,18 @@
import express from 'express'
import variablesController from '../../controllers/variables'
const router = express.Router()
// CREATE
router.post('/', variablesController.createVariable)
// READ
router.get('/', variablesController.getAllVariables)
// UPDATE
router.put('/:id', variablesController.updateVariable)
// DELETE
router.delete('/:id', variablesController.deleteVariable)
export default router

View File

@ -0,0 +1,14 @@
import express from 'express'
import multer from 'multer'
import path from 'path'
import vectorsController from '../../controllers/vectors'
const router = express.Router()
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
// CREATE
router.post('/upsert/:id', upload.array('files'), vectorsController.getRateLimiterMiddleware, vectorsController.upsertVectorMiddleware)
router.post('/internal-upsert/:id', vectorsController.createInternalUpsert)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import apikeyController from '../../controllers/apikey'
const router = express.Router()
// READ
router.get('/apikey/:apikey', apikeyController.verifyApiKey)
export default router

View File

@ -0,0 +1,8 @@
import express from 'express'
import versionsController from '../../controllers/versions'
const router = express.Router()
// READ
router.get('/', versionsController.getVersion)
export default router

View File

@ -0,0 +1,69 @@
import { addAPIKey, deleteAPIKey, getAPIKeys, updateAPIKey } from '../../utils/apiKey'
import { addChatflowsCount } from '../../utils/addChatflowsCount'
import { getApiKey } from '../../utils/apiKey'
const getAllApiKeys = async () => {
try {
const keys = await getAPIKeys()
const dbResponse = await addChatflowsCount(keys)
return dbResponse
} catch (error) {
throw new Error(`Error: apikeyService.getAllApiKeys - ${error}`)
}
}
const createApiKey = async (keyName: string) => {
try {
const keys = await addAPIKey(keyName)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
} catch (error) {
throw new Error(`Error: apikeyService.createApiKey - ${error}`)
}
}
// Update api key
const updateApiKey = async (id: string, keyName: string) => {
try {
const keys = await updateAPIKey(id, keyName)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
} catch (error) {
throw new Error(`Error: apikeyService.updateApiKey - ${error}`)
}
}
const deleteApiKey = async (id: string) => {
try {
const keys = await deleteAPIKey(id)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
} catch (error) {
throw new Error(`Error: apikeyService.deleteApiKey - ${error}`)
}
}
const verifyApiKey = async (paramApiKey: string): Promise<any> => {
try {
const apiKey = await getApiKey(paramApiKey)
if (!apiKey) {
return {
executionError: true,
status: 401,
msg: `Unauthorized`
}
}
const dbResponse = 'OK'
return dbResponse
} catch (error) {
throw new Error(`Error: apikeyService.verifyApiKey - ${error}`)
}
}
export default {
createApiKey,
deleteApiKey,
getAllApiKeys,
updateApiKey,
verifyApiKey
}

View File

@ -0,0 +1,366 @@
import OpenAI from 'openai'
import path from 'path'
import * as fs from 'fs'
import { uniqWith, isEqual } from 'lodash'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Assistant } from '../../database/entities/Assistant'
import { Credential } from '../../database/entities/Credential'
import { getUserHome, decryptCredentialData, getAppVersion } from '../../utils'
const creatAssistant = async (requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
if (!requestBody.details) {
return {
executionError: true,
status: 500,
msg: `Invalid request body`
}
}
const assistantDetails = JSON.parse(requestBody.details)
try {
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: requestBody.credential
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${requestBody.credential} not found`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) {
return {
executionError: true,
status: 404,
msg: `OpenAI ApiKey not found`
}
}
const openai = new OpenAI({ apiKey: openAIApiKey })
let tools = []
if (assistantDetails.tools) {
for (const tool of assistantDetails.tools ?? []) {
tools.push({
type: tool
})
}
}
if (assistantDetails.uploadFiles) {
// Base64 strings
let files: string[] = []
const fileBase64 = assistantDetails.uploadFiles
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
files = JSON.parse(fileBase64)
} else {
files = [fileBase64]
}
const uploadedFiles = []
for (const file of files) {
const splitDataURI = file.split(',')
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
}
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, bf)
}
const createdFile = await openai.files.create({
file: fs.createReadStream(filePath),
purpose: 'assistants'
})
uploadedFiles.push(createdFile)
fs.unlinkSync(filePath)
}
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
}
if (!assistantDetails.id) {
const newAssistant = await openai.beta.assistants.create({
name: assistantDetails.name,
description: assistantDetails.description,
instructions: assistantDetails.instructions,
model: assistantDetails.model,
tools,
file_ids: (assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)
})
assistantDetails.id = newAssistant.id
} else {
const retrievedAssistant = await openai.beta.assistants.retrieve(assistantDetails.id)
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
await openai.beta.assistants.update(assistantDetails.id, {
name: assistantDetails.name,
description: assistantDetails.description ?? '',
instructions: assistantDetails.instructions ?? '',
model: assistantDetails.model,
tools: filteredTools,
file_ids: uniqWith(
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
isEqual
)
})
}
const newAssistantDetails = {
...assistantDetails
}
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
requestBody.details = JSON.stringify(newAssistantDetails)
} catch (error) {
return {
executionError: true,
status: 500,
msg: `Error creating new assistant: ${error}`
}
}
const newAssistant = new Assistant()
Object.assign(newAssistant, requestBody)
const assistant = await appServer.AppDataSource.getRepository(Assistant).create(newAssistant)
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
await appServer.telemetry.sendTelemetry('assistant_created', {
version: await getAppVersion(),
assistantId: dbResponse.id
})
return dbResponse
} catch (error) {
throw new Error(`Error: assistantsService.creatTool - ${error}`)
}
}
const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
id: assistantId
})
if (!assistant) {
return {
executionError: true,
status: 404,
msg: `Assistant ${assistantId} not found`
}
}
try {
const assistantDetails = JSON.parse(assistant.details)
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: assistant.credential
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${assistant.credential} not found`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) {
return {
executionError: true,
status: 404,
msg: `OpenAI ApiKey not found`
}
}
const openai = new OpenAI({ apiKey: openAIApiKey })
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).delete({ id: assistantId })
if (isDeleteBoth) await openai.beta.assistants.del(assistantDetails.id)
return dbResponse
} catch (error: any) {
if (error.status === 404 && error.type === 'invalid_request_error') {
return 'OK'
} else {
return {
executionError: true,
status: 500,
msg: `Error deleting assistant: ${error}`
}
}
}
} catch (error) {
throw new Error(`Error: assistantsService.deleteTool - ${error}`)
}
}
const getAllAssistants = async (): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).find()
return dbResponse
} catch (error) {
throw new Error(`Error: assistantsService.getAllAssistants - ${error}`)
}
}
const getAssistantById = async (assistantId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
id: assistantId
})
if (!dbResponse) {
return {
executionError: true,
status: 404,
msg: `Assistant ${assistantId} not found`
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: assistantsService.getAssistantById - ${error}`)
}
}
const updateAssistant = async (assistantId: string, requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
id: assistantId
})
if (!assistant) {
return {
executionError: true,
status: 404,
msg: `Assistant ${assistantId} not found`
}
}
try {
const openAIAssistantId = JSON.parse(assistant.details)?.id
const body = requestBody
const assistantDetails = JSON.parse(body.details)
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: body.credential
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${body.credential} not found`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) {
return {
executionError: true,
status: 404,
msg: `OpenAI ApiKey not found`
}
}
const openai = new OpenAI({ apiKey: openAIApiKey })
let tools = []
if (assistantDetails.tools) {
for (const tool of assistantDetails.tools ?? []) {
tools.push({
type: tool
})
}
}
if (assistantDetails.uploadFiles) {
// Base64 strings
let files: string[] = []
const fileBase64 = assistantDetails.uploadFiles
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
files = JSON.parse(fileBase64)
} else {
files = [fileBase64]
}
const uploadedFiles = []
for (const file of files) {
const splitDataURI = file.split(',')
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
}
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, bf)
}
const createdFile = await openai.files.create({
file: fs.createReadStream(filePath),
purpose: 'assistants'
})
uploadedFiles.push(createdFile)
fs.unlinkSync(filePath)
}
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
}
const retrievedAssistant = await openai.beta.assistants.retrieve(openAIAssistantId)
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
await openai.beta.assistants.update(openAIAssistantId, {
name: assistantDetails.name,
description: assistantDetails.description,
instructions: assistantDetails.instructions,
model: assistantDetails.model,
tools: filteredTools,
file_ids: uniqWith(
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
isEqual
)
})
const newAssistantDetails = {
...assistantDetails,
id: openAIAssistantId
}
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
const updateAssistant = new Assistant()
body.details = JSON.stringify(newAssistantDetails)
Object.assign(updateAssistant, body)
await appServer.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
return dbResponse
} catch (error) {
return {
executionError: true,
status: 500,
msg: `Error updating assistant: ${error}`
}
}
} catch (error) {
throw new Error(`Error: assistantsService.updateAssistant - ${error}`)
}
}
export default {
creatAssistant,
deleteAssistant,
getAllAssistants,
getAssistantById,
updateAssistant
}

View File

@ -0,0 +1,116 @@
import { FindOptionsWhere } from 'typeorm'
import path from 'path'
import { chatType, IChatMessage } from '../../Interface'
import { utilGetChatMessage } from '../../utils/getChatMessage'
import { utilAddChatMessage } from '../../utils/addChatMesage'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
import { getStoragePath } from 'flowise-components'
import { deleteFolderRecursive } from '../../utils'
import logger from '../../utils/logger'
import { ChatMessage } from '../../database/entities/ChatMessage'
// Add chatmessages for chatflowid
const createChatMessage = async (chatMessage: Partial<IChatMessage>) => {
try {
const dbResponse = await utilAddChatMessage(chatMessage)
return dbResponse
} catch (error) {
throw new Error(`Error: chatMessagesService.createChatMessage - ${error}`)
}
}
// Get all chatmessages from chatflowid
const getAllChatMessages = async (
chatflowId: string,
chatTypeFilter: chatType | undefined,
sortOrder: string = 'ASC',
chatId?: string,
memoryType?: string,
sessionId?: string,
startDate?: string,
endDate?: string,
messageId?: string,
feedback?: boolean
): Promise<any> => {
try {
const dbResponse = await utilGetChatMessage(
chatflowId,
chatTypeFilter,
sortOrder,
chatId,
memoryType,
sessionId,
startDate,
endDate,
messageId,
feedback
)
return dbResponse
} catch (error) {
throw new Error(`Error: chatMessagesService.getAllChatMessages - ${error}`)
}
}
// Get internal chatmessages from chatflowid
const getAllInternalChatMessages = async (
chatflowId: string,
chatTypeFilter: chatType | undefined,
sortOrder: string = 'ASC',
chatId?: string,
memoryType?: string,
sessionId?: string,
startDate?: string,
endDate?: string,
messageId?: string,
feedback?: boolean
): Promise<any> => {
try {
const dbResponse = await utilGetChatMessage(
chatflowId,
chatTypeFilter,
sortOrder,
chatId,
memoryType,
sessionId,
startDate,
endDate,
messageId,
feedback
)
return dbResponse
} catch (error) {
throw new Error(`Error: chatMessagesService.getAllInternalChatMessages - ${error}`)
}
}
const removeAllChatMessages = async (chatId: string, chatflowid: string, deleteOptions: FindOptionsWhere<ChatMessage>): Promise<any> => {
try {
const appServer = getRunningExpressApp()
// remove all related feedback records
const feedbackDeleteOptions: FindOptionsWhere<ChatMessageFeedback> = { chatId }
await appServer.AppDataSource.getRepository(ChatMessageFeedback).delete(feedbackDeleteOptions)
// Delete all uploads corresponding to this chatflow/chatId
if (chatId) {
try {
const directory = path.join(getStoragePath(), chatflowid, chatId)
deleteFolderRecursive(directory)
} catch (e) {
logger.error(`[server]: Error deleting file storage for chatflow ${chatflowid}, chatId ${chatId}: ${e}`)
}
}
const dbResponse = await appServer.AppDataSource.getRepository(ChatMessage).delete(deleteOptions)
return dbResponse
} catch (error) {
throw new Error(`Error: chatMessagesService.removeAllChatMessages - ${error}`)
}
}
export default {
createChatMessage,
getAllChatMessages,
getAllInternalChatMessages,
removeAllChatMessages
}

View File

@ -0,0 +1,278 @@
import path from 'path'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { IChatFlow } from '../../Interface'
import { ChatFlow } from '../../database/entities/ChatFlow'
import {
getAppVersion,
getTelemetryFlowObj,
deleteFolderRecursive,
isFlowValidForStream,
constructGraphs,
getEndingNodes
} from '../../utils'
import logger from '../../utils/logger'
import { getStoragePath } from 'flowise-components'
import { IReactFlowObject } from '../../Interface'
import { utilGetUploadsConfig } from '../../utils/getUploadsConfig'
// Check if chatflow valid for streaming
const checkIfChatflowIsValidForStreaming = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
//**
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (!chatflow) {
return {
executionError: true,
status: 404,
msg: `Chatflow ${chatflowId} not found`
}
}
/*** Get Ending Node with Directed Graph ***/
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
const edges = parsedFlowData.edges
const { graph, nodeDependencies } = constructGraphs(nodes, edges)
const endingNodeIds = getEndingNodes(nodeDependencies, graph)
if (!endingNodeIds.length) {
return {
executionError: true,
status: 500,
msg: `Ending nodes not found`
}
}
const endingNodes = nodes.filter((nd) => endingNodeIds.includes(nd.id))
let isStreaming = false
let isEndingNodeExists = endingNodes.find((node) => node.data?.outputs?.output === 'EndingNode')
for (const endingNode of endingNodes) {
const endingNodeData = endingNode.data
if (!endingNodeData) {
return {
executionError: true,
status: 500,
msg: `Ending node ${endingNode.id} data not found`
}
}
const isEndingNode = endingNodeData?.outputs?.output === 'EndingNode'
if (!isEndingNode) {
if (
endingNodeData &&
endingNodeData.category !== 'Chains' &&
endingNodeData.category !== 'Agents' &&
endingNodeData.category !== 'Engine'
) {
return {
executionError: true,
status: 500,
msg: `Ending node must be either a Chain or Agent`
}
}
}
isStreaming = isEndingNode ? false : isFlowValidForStream(nodes, endingNodeData)
}
// Once custom function ending node exists, flow is always unavailable to stream
const dbResponse = { isStreaming: isEndingNodeExists ? false : isStreaming }
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.checkIfChatflowIsValidForStreaming - ${error}`)
}
}
// Check if chatflow valid for uploads
const checkIfChatflowIsValidForUploads = async (chatflowId: string): Promise<any> => {
try {
const dbResponse = await utilGetUploadsConfig(chatflowId)
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.checkIfChatflowIsValidForUploads - ${error}`)
}
}
const deleteChatflow = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).delete({ id: chatflowId })
try {
// Delete all uploads corresponding to this chatflow
const directory = path.join(getStoragePath(), chatflowId)
deleteFolderRecursive(directory)
} catch (e) {
logger.error(`[server]: Error deleting file storage for chatflow ${chatflowId}: ${e}`)
}
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.getAllChatflows - ${error}`)
}
}
const getAllChatflows = async (): Promise<IChatFlow[]> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).find()
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.getAllChatflows - ${error}`)
}
}
const getChatflowByApiKey = async (apiKeyId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow)
.createQueryBuilder('cf')
.where('cf.apikeyid = :apikeyid', { apikeyid: apiKeyId })
.orWhere('cf.apikeyid IS NULL')
.orWhere('cf.apikeyid = ""')
.orderBy('cf.name', 'ASC')
.getMany()
if (dbResponse.length < 1) {
return {
executionError: true,
status: 404,
msg: `Chatflow not found in the database!`
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.getChatflowByApiKey - ${error}`)
}
}
const getChatflowById = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (!dbResponse) {
return {
executionError: true,
status: 404,
msg: `Chatflow ${chatflowId} not found in the database!`
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.getAllChatflows - ${error}`)
}
}
const saveChatflow = async (newChatFlow: ChatFlow): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const newDbChatflow = await appServer.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(newDbChatflow)
await appServer.telemetry.sendTelemetry('chatflow_created', {
version: await getAppVersion(),
chatflowId: dbResponse.id,
flowGraph: getTelemetryFlowObj(JSON.parse(dbResponse.flowData)?.nodes, JSON.parse(dbResponse.flowData)?.edges)
})
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.saveChatflow - ${error}`)
}
}
const updateChatflow = async (chatflow: ChatFlow, updateChatFlow: ChatFlow): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const newDbChatflow = await appServer.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow)
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(newDbChatflow)
// chatFlowPool is initialized only when a flow is opened
// if the user attempts to rename/update category without opening any flow, chatFlowPool will be undefined
if (appServer.chatflowPool) {
// Update chatflowpool inSync to false, to build flow from scratch again because data has been changed
appServer.chatflowPool.updateInSync(chatflow.id, false)
}
return dbResponse
} catch (error) {
throw new Error(`Error: chatflowsService.updateChatflow - ${error}`)
}
}
// Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link)
const getSinglePublicChatflow = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (dbResponse && dbResponse.isPublic) {
return dbResponse
} else if (dbResponse && !dbResponse.isPublic) {
return {
executionError: true,
status: 401,
msg: `Unauthorized`
}
}
return {
executionError: true,
status: 404,
msg: `Chatflow ${chatflowId} not found`
}
} catch (error) {
throw new Error(`Error: chatflowsService.getSinglePublicChatflow - ${error}`)
}
}
// Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat)
// Safe as public endpoint as chatbotConfig doesn't contain sensitive credential
const getSinglePublicChatbotConfig = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowId
})
if (!dbResponse) {
return {
executionError: true,
status: 404,
msg: `Chatflow ${chatflowId} not found`
}
}
const uploadsConfig = await utilGetUploadsConfig(chatflowId)
// even if chatbotConfig is not set but uploads are enabled
// send uploadsConfig to the chatbot
if (dbResponse.chatbotConfig || uploadsConfig) {
try {
const parsedConfig = dbResponse.chatbotConfig ? JSON.parse(dbResponse.chatbotConfig) : {}
return { ...parsedConfig, uploads: uploadsConfig }
} catch (e) {
return {
executionError: true,
status: 500,
msg: `Error parsing Chatbot Config for Chatflow ${chatflowId}`
}
}
}
return 'OK'
} catch (error) {
throw new Error(`Error: chatflowsService.getSinglePublicChatbotConfig - ${error}`)
}
}
export default {
checkIfChatflowIsValidForStreaming,
checkIfChatflowIsValidForUploads,
deleteChatflow,
getAllChatflows,
getChatflowByApiKey,
getChatflowById,
saveChatflow,
updateChatflow,
getSinglePublicChatflow,
getSinglePublicChatbotConfig
}

View File

@ -0,0 +1,74 @@
import { cloneDeep } from 'lodash'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
// Get all component credentials
const getAllComponentsCredentials = async (): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = []
for (const credName in appServer.nodesPool.componentCredentials) {
const clonedCred = cloneDeep(appServer.nodesPool.componentCredentials[credName])
dbResponse.push(clonedCred)
}
return dbResponse
} catch (error) {
throw new Error(`Error: componentsCredentialsService.getAllComponentsCredentials - ${error}`)
}
}
const getComponentByName = async (credentialName: string) => {
try {
const appServer = getRunningExpressApp()
if (!credentialName.includes('&amp;')) {
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, credentialName)) {
return appServer.nodesPool.componentCredentials[credentialName]
} else {
throw new Error(
`Error: componentsCredentialsService.getSingleComponentsCredential - Credential ${credentialName} not found`
)
}
} else {
const dbResponse = []
for (const name of credentialName.split('&amp;')) {
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, name)) {
dbResponse.push(appServer.nodesPool.componentCredentials[name])
} else {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredential - Credential ${name} not found`)
}
}
return dbResponse
}
} catch (error) {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredential - ${error}`)
}
}
// Returns specific component credential icon via name
const getSingleComponentsCredentialIcon = async (credentialName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, credentialName)) {
const credInstance = appServer.nodesPool.componentCredentials[credentialName]
if (credInstance.icon === undefined) {
throw new Error(`Credential ${credentialName} icon not found`)
}
if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) {
const filepath = credInstance.icon
return filepath
} else {
throw new Error(`Credential ${credentialName} icon is missing icon`)
}
} else {
throw new Error(`Credential ${credentialName} not found`)
}
} catch (error) {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredentialIcon - ${error}`)
}
}
export default {
getAllComponentsCredentials,
getComponentByName,
getSingleComponentsCredentialIcon
}

View File

@ -0,0 +1,126 @@
import { omit } from 'lodash'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Credential } from '../../database/entities/Credential'
import { transformToCredentialEntity, decryptCredentialData } from '../../utils'
import { ICredentialReturnResponse } from '../../Interface'
const createCredential = async (requestBody: any) => {
try {
const appServer = getRunningExpressApp()
const newCredential = await transformToCredentialEntity(requestBody)
const credential = await appServer.AppDataSource.getRepository(Credential).create(newCredential)
const dbResponse = await appServer.AppDataSource.getRepository(Credential).save(credential)
return dbResponse
} catch (error) {
throw new Error(`Error: credentialsService.createCredential - ${error}`)
}
}
// Delete all credentials from chatflowid
const deleteCredentials = async (credentialId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Credential).delete({ id: credentialId })
if (!dbResponse) {
return {
executionError: true,
status: 404,
msg: `Credential ${credentialId} not found`
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: credentialsService.deleteCredential - ${error}`)
}
}
const getAllCredentials = async (paramCredentialName: any) => {
try {
const appServer = getRunningExpressApp()
let dbResponse = []
if (paramCredentialName) {
if (Array.isArray(paramCredentialName)) {
for (let i = 0; i < paramCredentialName.length; i += 1) {
const name = paramCredentialName[i] as string
const credentials = await appServer.AppDataSource.getRepository(Credential).findBy({
credentialName: name
})
dbResponse.push(...credentials)
}
} else {
const credentials = await appServer.AppDataSource.getRepository(Credential).findBy({
credentialName: paramCredentialName as string
})
dbResponse = [...credentials]
}
} else {
const credentials = await appServer.AppDataSource.getRepository(Credential).find()
for (const credential of credentials) {
dbResponse.push(omit(credential, ['encryptedData']))
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: credentialsService.getAllCredentials - ${error}`)
}
}
const getCredentialById = async (credentialId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${credentialId} not found`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(
credential.encryptedData,
credential.credentialName,
appServer.nodesPool.componentCredentials
)
const returnCredential: ICredentialReturnResponse = {
...credential,
plainDataObj: decryptedCredentialData
}
const dbResponse = omit(returnCredential, ['encryptedData'])
return dbResponse
} catch (error) {
throw new Error(`Error: credentialsService.createCredential - ${error}`)
}
}
const updateCredential = async (credentialId: string, requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${credentialId} not found`
}
}
const updateCredential = await transformToCredentialEntity(requestBody)
await appServer.AppDataSource.getRepository(Credential).merge(credential, updateCredential)
const dbResponse = await appServer.AppDataSource.getRepository(Credential).save(credential)
return dbResponse
} catch (error) {
throw new Error(`Error: credentialsService.updateCredential - ${error}`)
}
}
export default {
createCredential,
deleteCredentials,
getAllCredentials,
getCredentialById,
updateCredential
}

View File

@ -0,0 +1,46 @@
import { utilGetChatMessageFeedback } from '../../utils/getChatMessageFeedback'
import { utilAddChatMessageFeedback } from '../../utils/addChatMessageFeedback'
import { utilUpdateChatMessageFeedback } from '../../utils/updateChatMessageFeedback'
import { IChatMessageFeedback } from '../../Interface'
// Get all chatmessage feedback from chatflowid
const getAllChatMessageFeedback = async (
chatflowid: string,
chatId: string | undefined,
sortOrder: string | undefined,
startDate: string | undefined,
endDate: string | undefined
) => {
try {
const dbResponse = await utilGetChatMessageFeedback(chatflowid, chatId, sortOrder, startDate, endDate)
return dbResponse
} catch (error) {
throw new Error(`Error: feedbackService.getAllChatMessageFeedback - ${error}`)
}
}
// Add chatmessage feedback for chatflowid
const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
try {
const dbResponse = await utilAddChatMessageFeedback(requestBody)
return dbResponse
} catch (error) {
throw new Error(`Error: feedbackService.createChatMessageFeedbackForChatflow - ${error}`)
}
}
// Add chatmessage feedback for chatflowid
const updateChatMessageFeedbackForChatflow = async (chatflowId: string, requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
try {
const dbResponse = await utilUpdateChatMessageFeedback(chatflowId, requestBody)
return dbResponse
} catch (error) {
throw new Error(`Error: feedbackService.updateChatMessageFeedbackForChatflow - ${error}`)
}
}
export default {
getAllChatMessageFeedback,
createChatMessageFeedbackForChatflow,
updateChatMessageFeedbackForChatflow
}

View File

@ -0,0 +1,29 @@
import { webCrawl, xmlScrape } from 'flowise-components'
const getAllLinks = async (requestUrl: string, relativeLinksMethod: string, queryLimit: string): Promise<any> => {
try {
const url = decodeURIComponent(requestUrl)
if (!relativeLinksMethod) {
return {
executionError: true,
status: 500,
msg: `Please choose a Relative Links Method in Additional Parameters!`
}
}
const limit = parseInt(queryLimit)
if (process.env.DEBUG === 'true') console.info(`Start ${relativeLinksMethod}`)
const links: string[] = relativeLinksMethod === 'webCrawl' ? await webCrawl(url, limit) : await xmlScrape(url, limit)
if (process.env.DEBUG === 'true') console.info(`Finish ${relativeLinksMethod}`)
const dbResponse = {
status: 'OK',
links
}
return dbResponse
} catch (error) {
throw new Error(`Error: fetchLinksService.getAllLinks - ${error}`)
}
}
export default {
getAllLinks
}

View File

@ -0,0 +1,29 @@
import { findAvailableConfigs } from '../../utils'
import { IReactFlowObject } from '../../Interface'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import chatflowsService from '../chatflows'
const getSingleFlowConfig = async (chatflowId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const chatflow = await chatflowsService.getChatflowById(chatflowId)
if (!chatflow) {
return {
executionError: true,
status: 404,
msg: `Chatflow ${chatflowId} not found in the database!`
}
}
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
const dbResponse = findAvailableConfigs(nodes, appServer.nodesPool.componentCredentials)
return dbResponse
} catch (error) {
throw new Error(`Error: flowConfigService.getSingleFlowConfig - ${error}`)
}
}
export default {
getSingleFlowConfig
}

View File

@ -0,0 +1,22 @@
import { Client } from 'langchainhub'
import { parsePrompt } from '../../utils/hub'
const createPrompt = async (promptName: string): Promise<any> => {
try {
let hub = new Client()
const prompt = await hub.pull(promptName)
const templates = parsePrompt(prompt)
const dbResponse = {
status: 'OK',
prompt: promptName,
templates: templates
}
return dbResponse
} catch (error) {
throw new Error(`Error: loadPromptsService.createPrompt - ${error}`)
}
}
export default {
createPrompt
}

View File

@ -0,0 +1,58 @@
import path from 'path'
import * as fs from 'fs'
// Get all templates for marketplaces
const getAllTemplates = async () => {
try {
let marketplaceDir = path.join(__dirname, '..', '..', '..', 'marketplaces', 'chatflows')
let jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
let templates: any[] = []
jsonsInDir.forEach((file, index) => {
const filePath = path.join(__dirname, '..', '..', '..', 'marketplaces', 'chatflows', file)
const fileData = fs.readFileSync(filePath)
const fileDataObj = JSON.parse(fileData.toString())
const template = {
id: index,
templateName: file.split('.json')[0],
flowData: fileData.toString(),
badge: fileDataObj?.badge,
framework: fileDataObj?.framework,
categories: fileDataObj?.categories,
type: 'Chatflow',
description: fileDataObj?.description || ''
}
templates.push(template)
})
marketplaceDir = path.join(__dirname, '..', '..', '..', 'marketplaces', 'tools')
jsonsInDir = fs.readdirSync(marketplaceDir).filter((file) => path.extname(file) === '.json')
jsonsInDir.forEach((file, index) => {
const filePath = path.join(__dirname, '..', '..', '..', 'marketplaces', 'tools', file)
const fileData = fs.readFileSync(filePath)
const fileDataObj = JSON.parse(fileData.toString())
const template = {
...fileDataObj,
id: index,
type: 'Tool',
framework: fileDataObj?.framework,
badge: fileDataObj?.badge,
categories: '',
templateName: file.split('.json')[0]
}
templates.push(template)
})
const sortedTemplates = templates.sort((a, b) => a.templateName.localeCompare(b.templateName))
const FlowiseDocsQnAIndex = sortedTemplates.findIndex((tmp) => tmp.templateName === 'Flowise Docs QnA')
if (FlowiseDocsQnAIndex > 0) {
sortedTemplates.unshift(sortedTemplates.splice(FlowiseDocsQnAIndex, 1)[0])
}
const dbResponse = sortedTemplates
return dbResponse
} catch (error) {
throw new Error(`Error: marketplacesService.getAllTemplates - ${error}`)
}
}
export default {
getAllTemplates
}

View File

@ -0,0 +1,18 @@
import { findAvailableConfigs } from '../../utils'
import { IReactFlowNode } from '../../Interface'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
const getAllNodeConfigs = async (requestBody: any) => {
try {
const appServer = getRunningExpressApp()
const nodes = [{ data: requestBody }] as IReactFlowNode[]
const dbResponse = findAvailableConfigs(nodes, appServer.nodesPool.componentCredentials)
return dbResponse
} catch (error) {
throw new Error(`Error: nodeConfigsService.getAllNodeConfigs - ${error}`)
}
}
export default {
getAllNodeConfigs
}

View File

@ -0,0 +1,142 @@
import { cloneDeep } from 'lodash'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { INodeData } from '../../Interface'
import { INodeOptionsValue, ICommonObject, handleEscapeCharacters } from 'flowise-components'
import { databaseEntities } from '../../utils'
import logger from '../../utils/logger'
// Get all component nodes
const getAllNodes = async () => {
try {
const appServer = getRunningExpressApp()
const dbResponse = []
for (const nodeName in appServer.nodesPool.componentNodes) {
const clonedNode = cloneDeep(appServer.nodesPool.componentNodes[nodeName])
dbResponse.push(clonedNode)
}
return dbResponse
} catch (error) {
throw new Error(`Error: nodesService.getAllNodes - ${error}`)
}
}
// Get specific component node via name
const getNodeByName = async (nodeName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
const dbResponse = appServer.nodesPool.componentNodes[nodeName]
return dbResponse
} else {
throw new Error(`Node ${nodeName} not found`)
}
} catch (error) {
throw new Error(`Error: nodesService.getAllNodes - ${error}`)
}
}
// Returns specific component node icon via name
const getSingleNodeIcon = async (nodeName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
const nodeInstance = appServer.nodesPool.componentNodes[nodeName]
if (nodeInstance.icon === undefined) {
throw new Error(`Node ${nodeName} icon not found`)
}
if (nodeInstance.icon.endsWith('.svg') || nodeInstance.icon.endsWith('.png') || nodeInstance.icon.endsWith('.jpg')) {
const filepath = nodeInstance.icon
return filepath
} else {
throw new Error(`Node ${nodeName} icon is missing icon`)
}
} else {
throw new Error(`Node ${nodeName} not found`)
}
} catch (error) {
throw new Error(`Error: nodesService.getSingleNodeIcon - ${error}`)
}
}
const getSingleNodeAsyncOptions = async (nodeName: string, requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const nodeData: INodeData = requestBody
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
try {
const nodeInstance = appServer.nodesPool.componentNodes[nodeName]
const methodName = nodeData.loadMethod || ''
const dbResponse: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call(nodeInstance, nodeData, {
appDataSource: appServer.AppDataSource,
databaseEntities: databaseEntities
})
return dbResponse
} catch (error) {
return []
}
} else {
return {
executionError: true,
status: 404,
msg: `Node ${nodeName} not found`
}
}
} catch (error) {
throw new Error(`Error: nodesService.getSingleNodeAsyncOptions - ${error}`)
}
}
// execute custom function node
const executeCustomFunction = async (requestBody: any) => {
try {
const appServer = getRunningExpressApp()
const body = requestBody
const functionInputVariables = Object.fromEntries(
[...(body?.javascriptFunction ?? '').matchAll(/\$([a-zA-Z0-9_]+)/g)].map((g) => [g[1], undefined])
)
const nodeData = { inputs: { functionInputVariables, ...body } }
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, 'customFunction')) {
try {
const nodeInstanceFilePath = appServer.nodesPool.componentNodes['customFunction'].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass()
const options: ICommonObject = {
appDataSource: appServer.AppDataSource,
databaseEntities,
logger
}
const returnData = await newNodeInstance.init(nodeData, '', options)
const dbResponse = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData
return dbResponse
} catch (error) {
return {
executionError: true,
status: 500,
msg: `Error running custom function: ${error}`
}
}
} else {
return {
executionError: true,
status: 404,
msg: `Node customFunction not found`
}
}
} catch (error) {
throw new Error(`Error: nodesService.executeCustomFunction - ${error}`)
}
}
export default {
getAllNodes,
getNodeByName,
getSingleNodeIcon,
getSingleNodeAsyncOptions,
executeCustomFunction
}

View File

@ -0,0 +1,84 @@
import OpenAI from 'openai'
import { decryptCredentialData } from '../../utils'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Credential } from '../../database/entities/Credential'
// ----------------------------------------
// Assistants
// ----------------------------------------
// List available assistants
const getAllOpenaiAssistants = async (credentialId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${credentialId} not found in the database!`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) {
return {
executionError: true,
status: 404,
msg: `OpenAI ApiKey not found`
}
}
const openai = new OpenAI({ apiKey: openAIApiKey })
const retrievedAssistants = await openai.beta.assistants.list()
const dbResponse = retrievedAssistants.data
return dbResponse
} catch (error) {
throw new Error(`Error: openaiAssistantsService.getAllOpenaiAssistants - ${error}`)
}
}
// Get assistant object
const getSingleOpenaiAssistant = async (credentialId: string, assistantId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
id: credentialId
})
if (!credential) {
return {
executionError: true,
status: 404,
msg: `Credential ${credentialId} not found in the database!`
}
}
// Decrpyt credentialData
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
const openAIApiKey = decryptedCredentialData['openAIApiKey']
if (!openAIApiKey) {
return {
executionError: true,
status: 404,
msg: `OpenAI ApiKey not found`
}
}
const openai = new OpenAI({ apiKey: openAIApiKey })
const dbResponse = await openai.beta.assistants.retrieve(assistantId)
const resp = await openai.files.list()
const existingFiles = resp.data ?? []
if (dbResponse.file_ids && dbResponse.file_ids.length) {
;(dbResponse as any).files = existingFiles.filter((file) => dbResponse.file_ids.includes(file.id))
}
return dbResponse
} catch (error) {
throw new Error(`Error: openaiAssistantsService.getSingleOpenaiAssistant - ${error}`)
}
}
export default {
getAllOpenaiAssistants,
getSingleOpenaiAssistant
}

View File

@ -0,0 +1,22 @@
import axios from 'axios'
const createPromptsList = async (requestBody: any) => {
try {
const tags = requestBody.tags ? `tags=${requestBody.tags}` : ''
// Default to 100, TODO: add pagination and use offset & limit
const url = `https://api.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false`
const resp = await axios.get(url)
if (resp.data.repos) {
return {
status: 'OK',
repos: resp.data.repos
}
}
} catch (error) {
return { status: 'ERROR', repos: [] }
}
}
export default {
createPromptsList
}

View File

@ -0,0 +1,45 @@
import { chatType } from '../../Interface'
import { ChatMessage } from '../../database/entities/ChatMessage'
import { utilGetChatMessage } from '../../utils/getChatMessage'
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
// get stats for showing in chatflow
const getChatflowStats = async (
chatflowid: string,
chatTypeFilter: chatType | undefined,
startDate?: string,
endDate?: string,
messageId?: string,
feedback?: boolean
): Promise<any> => {
try {
const chatmessages = (await utilGetChatMessage(
chatflowid,
chatTypeFilter,
undefined,
undefined,
undefined,
undefined,
startDate,
endDate,
messageId,
feedback
)) as Array<ChatMessage & { feedback?: ChatMessageFeedback }>
const totalMessages = chatmessages.length
const totalFeedback = chatmessages.filter((message) => message?.feedback).length
const positiveFeedback = chatmessages.filter((message) => message?.feedback?.rating === 'THUMBS_UP').length
const dbResponse = {
totalMessages,
totalFeedback,
positiveFeedback
}
return dbResponse
} catch (error) {
throw new Error(`Error: statsService.getChatflowStats - ${error}`)
}
}
export default {
getChatflowStats
}

View File

@ -0,0 +1,10 @@
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
const createEvent = async (eventInfo: any) => {
const appServer = getRunningExpressApp()
await appServer.telemetry.sendTelemetry(eventInfo.name, eventInfo.data)
}
export default {
createEvent
}

View File

@ -0,0 +1,93 @@
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Tool } from '../../database/entities/Tool'
import { getAppVersion } from '../../utils'
const creatTool = async (requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const newTool = new Tool()
Object.assign(newTool, requestBody)
const tool = await appServer.AppDataSource.getRepository(Tool).create(newTool)
const dbResponse = await appServer.AppDataSource.getRepository(Tool).save(tool)
await appServer.telemetry.sendTelemetry('tool_created', {
version: await getAppVersion(),
toolId: dbResponse.id,
toolName: dbResponse.name
})
return dbResponse
} catch (error) {
throw new Error(`Error: toolsService.creatTool - ${error}`)
}
}
const deleteTool = async (toolId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Tool).delete({
id: toolId
})
return dbResponse
} catch (error) {
throw new Error(`Error: toolsService.deleteTool - ${error}`)
}
}
const getAllTools = async (): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Tool).find()
return dbResponse
} catch (error) {
throw new Error(`Error: toolsService.getAllTools - ${error}`)
}
}
const getToolById = async (toolId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Tool).findOneBy({
id: toolId
})
if (!dbResponse) {
return {
executionError: true,
status: 404,
msg: `Tool ${toolId} not found`
}
}
return dbResponse
} catch (error) {
throw new Error(`Error: toolsService.getToolById - ${error}`)
}
}
const updateTool = async (toolId: string, toolBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const tool = await appServer.AppDataSource.getRepository(Tool).findOneBy({
id: toolId
})
if (!tool) {
return {
executionError: true,
status: 404,
msg: `Tool ${toolId} not found`
}
}
const updateTool = new Tool()
Object.assign(updateTool, toolBody)
await appServer.AppDataSource.getRepository(Tool).merge(tool, updateTool)
const dbResponse = await appServer.AppDataSource.getRepository(Tool).save(tool)
return dbResponse
} catch (error) {
throw new Error(`Error: toolsService.getToolById - ${error}`)
}
}
export default {
creatTool,
deleteTool,
getAllTools,
getToolById,
updateTool
}

View File

@ -0,0 +1,64 @@
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Variable } from '../../database/entities/Variable'
const createVariable = async (newVariable: Variable) => {
try {
const appServer = getRunningExpressApp()
const variable = await appServer.AppDataSource.getRepository(Variable).create(newVariable)
const dbResponse = await appServer.AppDataSource.getRepository(Variable).save(variable)
return dbResponse
} catch (error) {
throw new Error(`Error: variablesServices.createVariable - ${error}`)
}
}
const deleteVariable = async (variableId: string): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Variable).delete({ id: variableId })
return dbResponse
} catch (error) {
throw new Error(`Error: variablesServices.createVariable - ${error}`)
}
}
const getAllVariables = async () => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Variable).find()
return dbResponse
} catch (error) {
throw new Error(`Error: variablesServices.getAllVariables - ${error}`)
}
}
const getVariableById = async (variableId: string) => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Variable).findOneBy({
id: variableId
})
return dbResponse
} catch (error) {
throw new Error(`Error: variablesServices.getVariableById - ${error}`)
}
}
const updateVariable = async (variable: Variable, updatedVariable: Variable) => {
try {
const appServer = getRunningExpressApp()
const tmpUpdatedVariable = await appServer.AppDataSource.getRepository(Variable).merge(variable, updatedVariable)
const dbResponse = await appServer.AppDataSource.getRepository(Variable).save(tmpUpdatedVariable)
return dbResponse
} catch (error) {
throw new Error(`Error: variablesServices.updateVariable - ${error}`)
}
}
export default {
createVariable,
deleteVariable,
getAllVariables,
getVariableById,
updateVariable
}

View File

@ -0,0 +1,14 @@
import { Request, Response } from 'express'
import { upsertVector } from '../../utils/upsertVector'
const upsertVectorMiddleware = async (req: Request, res: Response, isInternal: boolean = false) => {
try {
await upsertVector(req, res, isInternal)
} catch (error) {
throw new Error(`Error: vectorsService.getRateLimiter - ${error}`)
}
}
export default {
upsertVectorMiddleware
}

View File

@ -0,0 +1,49 @@
import path from 'path'
import * as fs from 'fs'
const getVersion = async () => {
try {
const getPackageJsonPath = (): string => {
const checkPaths = [
path.join(__dirname, '..', 'package.json'),
path.join(__dirname, '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', '..', 'package.json'),
path.join(__dirname, '..', '..', '..', '..', '..', 'package.json')
]
for (const checkPath of checkPaths) {
if (fs.existsSync(checkPath)) {
return checkPath
}
}
return ''
}
const packagejsonPath = getPackageJsonPath()
if (!packagejsonPath) {
return {
executionError: true,
status: 404,
msg: 'Version not found'
}
}
try {
const content = await fs.promises.readFile(packagejsonPath, 'utf8')
const parsedContent = JSON.parse(content)
return {
version: parsedContent.version
}
} catch (error) {
return {
executionError: true,
status: 500,
msg: `Version not found: ${error}`
}
}
} catch (error) {
throw new Error(`Error: versionService.getVersion - ${error}`)
}
}
export default {
getVersion
}

View File

@ -0,0 +1,19 @@
import { ChatMessage } from '../database/entities/ChatMessage'
import { IChatMessage } from '../Interface'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
/**
* Method that add chat messages.
* @param {Partial<IChatMessage>} chatMessage
*/
export const utilAddChatMessage = async (chatMessage: Partial<IChatMessage>): Promise<ChatMessage> => {
const appServer = getRunningExpressApp()
const newChatMessage = new ChatMessage()
Object.assign(newChatMessage, chatMessage)
if (!newChatMessage.createdDate) {
newChatMessage.createdDate = new Date()
}
const chatmessage = await appServer.AppDataSource.getRepository(ChatMessage).create(newChatMessage)
const dbResponse = await appServer.AppDataSource.getRepository(ChatMessage).save(chatmessage)
return dbResponse
}

View File

@ -0,0 +1,16 @@
import { ChatMessageFeedback } from '../database/entities/ChatMessageFeedback'
import { IChatMessageFeedback } from '../Interface'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
/**
* Method that add chat message feedback.
* @param {Partial<IChatMessageFeedback>} chatMessageFeedback
*/
export const utilAddChatMessageFeedback = async (chatMessageFeedback: Partial<IChatMessageFeedback>): Promise<ChatMessageFeedback> => {
const appServer = getRunningExpressApp()
const newChatMessageFeedback = new ChatMessageFeedback()
Object.assign(newChatMessageFeedback, chatMessageFeedback)
const feedback = await appServer.AppDataSource.getRepository(ChatMessageFeedback).create(newChatMessageFeedback)
return await appServer.AppDataSource.getRepository(ChatMessageFeedback).save(feedback)
}

View File

@ -0,0 +1,33 @@
import { ChatFlow } from '../database/entities/ChatFlow'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
export const addChatflowsCount = async (keys: any) => {
try {
const appServer = getRunningExpressApp()
let tmpResult = keys
if (typeof keys !== 'undefined' && keys.length > 0) {
const updatedKeys: any[] = []
//iterate through keys and get chatflows
for (const key of keys) {
const chatflows = await appServer.AppDataSource.getRepository(ChatFlow)
.createQueryBuilder('cf')
.where('cf.apikeyid = :apikeyid', { apikeyid: key.id })
.getMany()
const linkedChatFlows: any[] = []
chatflows.map((cf) => {
linkedChatFlows.push({
flowName: cf.name,
category: cf.category,
updatedDate: cf.updatedDate
})
})
key.chatFlows = linkedChatFlows
updatedKeys.push(key)
}
tmpResult = updatedKeys
}
return tmpResult
} catch (error) {
throw new Error(`Error: addChatflowsCount - ${error}`)
}
}

View File

@ -0,0 +1,427 @@
import { Request } from 'express'
import { IFileUpload, getStoragePath, convertSpeechToText, ICommonObject } from 'flowise-components'
import { StatusCodes } from 'http-status-codes'
import { IncomingInput, IMessage, INodeData, IReactFlowObject, IReactFlowNode, IDepthQueue, chatType, IChatMessage } from '../Interface'
import path from 'path'
import { ChatFlow } from '../database/entities/ChatFlow'
import { Server } from 'socket.io'
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
import {
mapMimeTypeToInputField,
isFlowValidForStream,
buildFlow,
getTelemetryFlowObj,
getAppVersion,
resolveVariables,
getSessionChatHistory,
findMemoryNode,
replaceInputsWithConfig,
getStartingNodes,
isStartNodeDependOnInput,
getMemorySessionId,
isSameOverrideConfig,
getEndingNodes,
constructGraphs
} from '../utils'
import { utilValidateKey } from './validateKey'
import { databaseEntities } from '.'
import { v4 as uuidv4 } from 'uuid'
import { omit } from 'lodash'
import * as fs from 'fs'
import logger from './logger'
import { utilAddChatMessage } from './addChatMesage'
/**
* Build Chatflow
* @param {Request} req
* @param {Server} socketIO
* @param {boolean} isInternal
* @param {boolean} isUpsert
*/
export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInternal: boolean = false): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const chatflowid = req.params.id
let incomingInput: IncomingInput = req.body
let nodeToExecuteData: INodeData
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowid
})
if (!chatflow) {
return {
executionError: true,
status: StatusCodes.NOT_FOUND,
msg: `Chatflow ${chatflowid} not found`
}
}
const chatId = incomingInput.chatId ?? incomingInput.overrideConfig?.sessionId ?? uuidv4()
const userMessageDateTime = new Date()
if (!isInternal) {
const isKeyValidated = await utilValidateKey(req, chatflow)
if (!isKeyValidated) {
return {
executionError: true,
status: StatusCodes.UNAUTHORIZED,
msg: `Unauthorized`
}
}
}
let fileUploads: IFileUpload[] = []
if (incomingInput.uploads) {
fileUploads = incomingInput.uploads
for (let i = 0; i < fileUploads.length; i += 1) {
const upload = fileUploads[i]
if ((upload.type === 'file' || upload.type === 'audio') && upload.data) {
const filename = upload.name
const dir = path.join(getStoragePath(), chatflowid, chatId)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
const filePath = path.join(dir, filename)
const splitDataURI = upload.data.split(',')
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
fs.writeFileSync(filePath, bf)
// Omit upload.data since we don't store the content in database
upload.type = 'stored-file'
fileUploads[i] = omit(upload, ['data'])
}
// Run Speech to Text conversion
if (upload.mime === 'audio/webm') {
let speechToTextConfig: ICommonObject = {}
if (chatflow.speechToText) {
const speechToTextProviders = JSON.parse(chatflow.speechToText)
for (const provider in speechToTextProviders) {
const providerObj = speechToTextProviders[provider]
if (providerObj.status) {
speechToTextConfig = providerObj
speechToTextConfig['name'] = provider
break
}
}
}
if (speechToTextConfig) {
const options: ICommonObject = {
chatId,
chatflowid,
appDataSource: appServer.AppDataSource,
databaseEntities: databaseEntities
}
const speechToTextResult = await convertSpeechToText(upload, speechToTextConfig, options)
if (speechToTextResult) {
incomingInput.question = speechToTextResult
}
}
}
}
}
let isStreamValid = false
const files = (req.files as any[]) || []
if (files.length) {
const overrideConfig: ICommonObject = { ...req.body }
for (const file of files) {
const fileData = fs.readFileSync(file.path, { encoding: 'base64' })
const dataBase64String = `data:${file.mimetype};base64,${fileData},filename:${file.filename}`
const fileInputField = mapMimeTypeToInputField(file.mimetype)
if (overrideConfig[fileInputField]) {
overrideConfig[fileInputField] = JSON.stringify([...JSON.parse(overrideConfig[fileInputField]), dataBase64String])
} else {
overrideConfig[fileInputField] = JSON.stringify([dataBase64String])
}
}
incomingInput = {
question: req.body.question ?? 'hello',
overrideConfig,
history: [],
socketIOClientId: req.body.socketIOClientId
}
}
/*** Get chatflows and prepare data ***/
const flowData = chatflow.flowData
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
const nodes = parsedFlowData.nodes
const edges = parsedFlowData.edges
// Get session ID
const memoryNode = findMemoryNode(nodes, edges)
const memoryType = memoryNode?.data.label
let sessionId = undefined
if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal)
/* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met:
* - Node Data already exists in pool
* - Still in sync (i.e the flow has not been modified since)
* - Existing overrideConfig and new overrideConfig are the same
* - Flow doesn't start with/contain nodes that depend on incomingInput.question
* TODO: convert overrideConfig to hash when we no longer store base64 string but filepath
***/
const isFlowReusable = () => {
return (
Object.prototype.hasOwnProperty.call(appServer.chatflowPool.activeChatflows, chatflowid) &&
appServer.chatflowPool.activeChatflows[chatflowid].inSync &&
appServer.chatflowPool.activeChatflows[chatflowid].endingNodeData &&
isSameOverrideConfig(
isInternal,
appServer.chatflowPool.activeChatflows[chatflowid].overrideConfig,
incomingInput.overrideConfig
) &&
!isStartNodeDependOnInput(appServer.chatflowPool.activeChatflows[chatflowid].startingNodes, nodes)
)
}
if (isFlowReusable()) {
nodeToExecuteData = appServer.chatflowPool.activeChatflows[chatflowid].endingNodeData as INodeData
isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData)
logger.debug(
`[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})`
)
} else {
/*** Get Ending Node with Directed Graph ***/
const { graph, nodeDependencies } = constructGraphs(nodes, edges)
const directedGraph = graph
const endingNodeIds = getEndingNodes(nodeDependencies, directedGraph)
if (!endingNodeIds.length) {
return {
executionError: true,
status: 500,
msg: `Ending nodes not found`
}
}
const endingNodes = nodes.filter((nd) => endingNodeIds.includes(nd.id))
let isEndingNodeExists = endingNodes.find((node) => node.data?.outputs?.output === 'EndingNode')
for (const endingNode of endingNodes) {
const endingNodeData = endingNode.data
if (!endingNodeData) {
return {
executionError: true,
status: 500,
msg: `Ending node ${endingNode.id} data not found`
}
}
const isEndingNode = endingNodeData?.outputs?.output === 'EndingNode'
if (!isEndingNode) {
if (
endingNodeData &&
endingNodeData.category !== 'Chains' &&
endingNodeData.category !== 'Agents' &&
endingNodeData.category !== 'Engine'
) {
return {
executionError: true,
status: 500,
msg: `Ending node must be either a Chain or Agent`
}
}
if (
endingNodeData.outputs &&
Object.keys(endingNodeData.outputs).length &&
!Object.values(endingNodeData.outputs ?? {}).includes(endingNodeData.name)
) {
return {
executionError: true,
status: 500,
msg: `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction`
}
}
}
isStreamValid = isFlowValidForStream(nodes, endingNodeData)
}
// Once custom function ending node exists, flow is always unavailable to stream
isStreamValid = isEndingNodeExists ? false : isStreamValid
let chatHistory: IMessage[] = incomingInput.history ?? []
// When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory node
for (const endingNode of endingNodes) {
const endingNodeData = endingNode.data
if (!endingNodeData.inputs?.memory) continue
const memoryNodeId = endingNodeData.inputs?.memory.split('.')[0].replace('{{', '')
const memoryNode = nodes.find((node) => node.data.id === memoryNodeId)
if (!memoryNode) continue
if (!chatHistory.length && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) {
chatHistory = await getSessionChatHistory(
memoryNode,
appServer.nodesPool.componentNodes,
incomingInput,
appServer.AppDataSource,
databaseEntities,
logger
)
}
}
/*** Get Starting Nodes with Reversed Graph ***/
const constructedObj = constructGraphs(nodes, edges, { isReversed: true })
const nonDirectedGraph = constructedObj.graph
let startingNodeIds: string[] = []
let depthQueue: IDepthQueue = {}
for (const endingNodeId of endingNodeIds) {
const resx = getStartingNodes(nonDirectedGraph, endingNodeId)
startingNodeIds.push(...resx.startingNodeIds)
depthQueue = Object.assign(depthQueue, resx.depthQueue)
}
startingNodeIds = [...new Set(startingNodeIds)]
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
/*** BFS to traverse from Starting Nodes to Ending Node ***/
const reactFlowNodes = await buildFlow(
startingNodeIds,
nodes,
edges,
graph,
depthQueue,
appServer.nodesPool.componentNodes,
incomingInput.question,
chatHistory,
chatId,
sessionId ?? '',
chatflowid,
appServer.AppDataSource,
incomingInput?.overrideConfig,
appServer.cachePool,
false,
undefined,
incomingInput.uploads
)
const nodeToExecute =
endingNodeIds.length === 1
? reactFlowNodes.find((node: IReactFlowNode) => endingNodeIds[0] === node.id)
: reactFlowNodes[reactFlowNodes.length - 1]
if (!nodeToExecute) {
return {
executionError: true,
status: 404,
msg: `Node not found`
}
}
if (incomingInput.overrideConfig) {
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig)
}
const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question, chatHistory)
nodeToExecuteData = reactFlowNodeData
appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
}
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
const nodeInstanceFilePath = appServer.nodesPool.componentNodes[nodeToExecuteData.name].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const nodeInstance = new nodeModule.nodeClass({ sessionId })
let result = isStreamValid
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatId,
chatflowid,
chatHistory: incomingInput.history,
logger,
appDataSource: appServer.AppDataSource,
databaseEntities,
analytic: chatflow.analytic,
uploads: incomingInput.uploads,
socketIO,
socketIOClientId: incomingInput.socketIOClientId
})
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
chatId,
chatflowid,
chatHistory: incomingInput.history,
logger,
appDataSource: appServer.AppDataSource,
databaseEntities,
analytic: chatflow.analytic,
uploads: incomingInput.uploads
})
result = typeof result === 'string' ? { text: result } : result
// Retrieve threadId from assistant if exists
if (typeof result === 'object' && result.assistant) {
sessionId = result.assistant.threadId
}
const userMessage: Omit<IChatMessage, 'id'> = {
role: 'userMessage',
content: incomingInput.question,
chatflowid,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatId,
memoryType,
sessionId,
createdDate: userMessageDateTime,
fileUploads: incomingInput.uploads ? JSON.stringify(fileUploads) : undefined
}
await utilAddChatMessage(userMessage)
let resultText = ''
if (result.text) resultText = result.text
else if (result.json) resultText = '```json\n' + JSON.stringify(result.json, null, 2)
else resultText = JSON.stringify(result, null, 2)
const apiMessage: Omit<IChatMessage, 'id' | 'createdDate'> = {
role: 'apiMessage',
content: resultText,
chatflowid,
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
chatId,
memoryType,
sessionId
}
if (result?.sourceDocuments) apiMessage.sourceDocuments = JSON.stringify(result.sourceDocuments)
if (result?.usedTools) apiMessage.usedTools = JSON.stringify(result.usedTools)
if (result?.fileAnnotations) apiMessage.fileAnnotations = JSON.stringify(result.fileAnnotations)
const chatMessage = await utilAddChatMessage(apiMessage)
logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
await appServer.telemetry.sendTelemetry('prediction_sent', {
version: await getAppVersion(),
chatflowId: chatflowid,
chatId,
type: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
flowGraph: getTelemetryFlowObj(nodes, edges)
})
// Prepare response
// return the question in the response
// this is used when input text is empty but question is in audio format
result.question = incomingInput.question
result.chatId = chatId
result.chatMessageId = chatMessage.id
if (sessionId) result.sessionId = sessionId
if (memoryType) result.memoryType = memoryType
return result
} catch (e: any) {
logger.error('[server]: Error:', e)
return {
executionError: true,
status: 500,
msg: e.message
}
}
}

Some files were not shown because too many files have changed in this diff Show More