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

* transition GET /api/v1/apikey

* transition POST /api/v1/apikey

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

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

* Enable e2e tests for api/v1/apikey routes

* remove unused addChatflowsCount

* Enable e2e tests for api/v1/variables routes

* Enable Cypress in GitHub Action

* Update main.yml

* Update main.yml

* Transition GET /api/v1/variables

* Enable cypress on github workflow

* Transition POST /api/v1/variables

* Transition PUT /api/v1/variables

* Transition DELETE /api/v1/variables

* Transition GET /api/v1/variables

* Transition GET /api/v1/chatflows

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

* Transition POST /api/v1/chatflows

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

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

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

* Transition GET /api/v1/credentials

* Transition POST /api/v1/credentials

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

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

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

* Transition GET /api/v1/tools

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

* Transition POST /api/v1/tools

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

* Transition /api/v1/assistants routes

* Transition /api/v1/nodes routes

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

* wip-all-routes

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

* Remove ts-ignore annotations

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

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

* delete /api/v1/chatmessage/:id

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

* transition /api/v1/stats/:id

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

* Transition GET /api/v1/openai-assistants

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

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

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

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

* transition POST /api/v1/node-config

* transition GET /api/v1/version

* transition GET /api/v1/fetch-links

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

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

* transition POST /api/v1/load-prompt

* Update index.ts

* transition POST /api/v1/prompts-list

* transition predictions

* Update index.ts

* transition GET /api/v1/marketplaces/templates

* Router update modularity cleanup

* extend request interface - express namespace

* Update index.ts

* add errorMiddleware

* Add custom application error handler

* Fix pnpm lock file

* prediction return and vector upsert

* Move the getUploadsConfig into its own file

* Remove lint warnings

* fix undefined variable value

* Fix node-load-method api call

* standardize the error message display

* Apply review comment bugfixes

* Update index.ts

* standardize error message display  in snack notifications

* Error message standard in the UI

* Rename flowXpressApp to appServer

* Upload middleware fix and axios update

* fix async await

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Octavian FlowiseAI
2024-04-02 17:44:04 +02:00
committed by GitHub
parent ea255db15d
commit 957694a912
136 changed files with 5347 additions and 2380 deletions
@@ -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('&amp;')) {
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, credentialName)) {
return appServer.nodesPool.componentCredentials[credentialName]
} else {
throw new Error(
`Error: componentsCredentialsService.getSingleComponentsCredential - Credential ${credentialName} not found`
)
}
} else {
const dbResponse = []
for (const name of credentialName.split('&amp;')) {
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, name)) {
dbResponse.push(appServer.nodesPool.componentCredentials[name])
} else {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredential - Credential ${name} not found`)
}
}
return dbResponse
}
} catch (error) {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredential - ${error}`)
}
}
// Returns specific component credential icon via name
const getSingleComponentsCredentialIcon = async (credentialName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentCredentials, credentialName)) {
const credInstance = appServer.nodesPool.componentCredentials[credentialName]
if (credInstance.icon === undefined) {
throw new Error(`Credential ${credentialName} icon not found`)
}
if (credInstance.icon.endsWith('.svg') || credInstance.icon.endsWith('.png') || credInstance.icon.endsWith('.jpg')) {
const filepath = credInstance.icon
return filepath
} else {
throw new Error(`Credential ${credentialName} icon is missing icon`)
}
} else {
throw new Error(`Credential ${credentialName} not found`)
}
} catch (error) {
throw new Error(`Error: componentsCredentialsService.getSingleComponentsCredentialIcon - ${error}`)
}
}
export default {
getAllComponentsCredentials,
getComponentByName,
getSingleComponentsCredentialIcon
}
@@ -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
}
+142
View File
@@ -0,0 +1,142 @@
import { cloneDeep } from 'lodash'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { INodeData } from '../../Interface'
import { INodeOptionsValue, ICommonObject, handleEscapeCharacters } from 'flowise-components'
import { databaseEntities } from '../../utils'
import logger from '../../utils/logger'
// Get all component nodes
const getAllNodes = async () => {
try {
const appServer = getRunningExpressApp()
const dbResponse = []
for (const nodeName in appServer.nodesPool.componentNodes) {
const clonedNode = cloneDeep(appServer.nodesPool.componentNodes[nodeName])
dbResponse.push(clonedNode)
}
return dbResponse
} catch (error) {
throw new Error(`Error: nodesService.getAllNodes - ${error}`)
}
}
// Get specific component node via name
const getNodeByName = async (nodeName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
const dbResponse = appServer.nodesPool.componentNodes[nodeName]
return dbResponse
} else {
throw new Error(`Node ${nodeName} not found`)
}
} catch (error) {
throw new Error(`Error: nodesService.getAllNodes - ${error}`)
}
}
// Returns specific component node icon via name
const getSingleNodeIcon = async (nodeName: string) => {
try {
const appServer = getRunningExpressApp()
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
const nodeInstance = appServer.nodesPool.componentNodes[nodeName]
if (nodeInstance.icon === undefined) {
throw new Error(`Node ${nodeName} icon not found`)
}
if (nodeInstance.icon.endsWith('.svg') || nodeInstance.icon.endsWith('.png') || nodeInstance.icon.endsWith('.jpg')) {
const filepath = nodeInstance.icon
return filepath
} else {
throw new Error(`Node ${nodeName} icon is missing icon`)
}
} else {
throw new Error(`Node ${nodeName} not found`)
}
} catch (error) {
throw new Error(`Error: nodesService.getSingleNodeIcon - ${error}`)
}
}
const getSingleNodeAsyncOptions = async (nodeName: string, requestBody: any): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const nodeData: INodeData = requestBody
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, nodeName)) {
try {
const nodeInstance = appServer.nodesPool.componentNodes[nodeName]
const methodName = nodeData.loadMethod || ''
const dbResponse: INodeOptionsValue[] = await nodeInstance.loadMethods![methodName]!.call(nodeInstance, nodeData, {
appDataSource: appServer.AppDataSource,
databaseEntities: databaseEntities
})
return dbResponse
} catch (error) {
return []
}
} else {
return {
executionError: true,
status: 404,
msg: `Node ${nodeName} not found`
}
}
} catch (error) {
throw new Error(`Error: nodesService.getSingleNodeAsyncOptions - ${error}`)
}
}
// execute custom function node
const executeCustomFunction = async (requestBody: any) => {
try {
const appServer = getRunningExpressApp()
const body = requestBody
const functionInputVariables = Object.fromEntries(
[...(body?.javascriptFunction ?? '').matchAll(/\$([a-zA-Z0-9_]+)/g)].map((g) => [g[1], undefined])
)
const nodeData = { inputs: { functionInputVariables, ...body } }
if (Object.prototype.hasOwnProperty.call(appServer.nodesPool.componentNodes, 'customFunction')) {
try {
const nodeInstanceFilePath = appServer.nodesPool.componentNodes['customFunction'].filePath as string
const nodeModule = await import(nodeInstanceFilePath)
const newNodeInstance = new nodeModule.nodeClass()
const options: ICommonObject = {
appDataSource: appServer.AppDataSource,
databaseEntities,
logger
}
const returnData = await newNodeInstance.init(nodeData, '', options)
const dbResponse = typeof returnData === 'string' ? handleEscapeCharacters(returnData, true) : returnData
return dbResponse
} catch (error) {
return {
executionError: true,
status: 500,
msg: `Error running custom function: ${error}`
}
}
} else {
return {
executionError: true,
status: 404,
msg: `Node customFunction not found`
}
}
} catch (error) {
throw new Error(`Error: nodesService.executeCustomFunction - ${error}`)
}
}
export default {
getAllNodes,
getNodeByName,
getSingleNodeIcon,
getSingleNodeAsyncOptions,
executeCustomFunction
}
@@ -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
}