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:
parent
ea255db15d
commit
957694a912
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
setupNodeEvents() {
|
||||
// implement node event listeners here
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
@ -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>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -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')
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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('&')) {
|
||||
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('&')) {
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue