Feature: interactive swagger-ui auto-generated API docs from express (#1812)
* Add interactive swagger-ui auto-generated API docs from express * Update README.md * Update index.ts //@ts-ignore * Fix eslint no-console error * Add swagger paths * Add all end points * Update swagger.yml * update swagger yml file * update swagger config --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
parent
0f58d31493
commit
e8f5f07735
|
|
@ -72,6 +72,7 @@ Flowise has 3 different modules in a single mono repository.
|
||||||
- `server`: Node backend to serve API logics
|
- `server`: Node backend to serve API logics
|
||||||
- `ui`: React frontend
|
- `ui`: React frontend
|
||||||
- `components`: Third-party nodes integrations
|
- `components`: Third-party nodes integrations
|
||||||
|
- `api-documentation`: Auto-generated swagger-ui API docs from express
|
||||||
|
|
||||||
### Prerequisite
|
### Prerequisite
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
"packages/*",
|
"packages/*",
|
||||||
"flowise",
|
"flowise",
|
||||||
"ui",
|
"ui",
|
||||||
"components"
|
"components",
|
||||||
|
"api-documentation"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"ignore": ["**/*.spec.ts", ".git", "node_modules"],
|
||||||
|
"watch": ["src"],
|
||||||
|
"exec": "ts-node ./src/index.ts",
|
||||||
|
"ext": "ts, yml"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "flowise-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Flowise API documentation server",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"dev": "concurrently \"tsc-watch --noClear -p ./tsconfig.json\" \"nodemon\"",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
|
},
|
||||||
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
|
"dependencies": {
|
||||||
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"swagger-ui-express": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/swagger-jsdoc": "^6.0.1",
|
||||||
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
|
"tsc-watch": "^6.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import swaggerJSDoc from 'swagger-jsdoc'
|
||||||
|
|
||||||
|
const swaggerUiOptions = {
|
||||||
|
failOnErrors: true, // Throw when parsing errors
|
||||||
|
baseDir: __dirname, // Base directory which we use to locate your JSDOC files
|
||||||
|
exposeApiDocs: true,
|
||||||
|
definition: {
|
||||||
|
openapi: '3.0.3',
|
||||||
|
info: {
|
||||||
|
title: 'Flowise APIs',
|
||||||
|
summary: 'Interactive swagger-ui auto-generated API docs from express, based on a swagger.yml file',
|
||||||
|
version: '1.0.0',
|
||||||
|
description:
|
||||||
|
'This module serves auto-generated swagger-ui generated API docs from Flowise express backend, based on a swagger.yml file. Swagger is available on: http://localhost:6655/api-docs',
|
||||||
|
license: {
|
||||||
|
name: 'Apache 2.0',
|
||||||
|
url: 'https://github.com/FlowiseAI/Flowise/blob/main/LICENSE.md'
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'FlowiseAI',
|
||||||
|
email: 'support@flowiseai.com'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: 'http://localhost:3000/api/v1',
|
||||||
|
description: 'Flowise Server'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
apis: [`${process.cwd()}/dist/routes/**/*.js`, `${process.cwd()}/src/yml/swagger.yml`]
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
|
||||||
|
const swaggerExplorerOptions = {
|
||||||
|
swaggerOptions: {
|
||||||
|
validatorUrl: '127.0.0.1'
|
||||||
|
},
|
||||||
|
explorer: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const swaggerDocs = swaggerJSDoc(swaggerUiOptions)
|
||||||
|
|
||||||
|
export { swaggerDocs, swaggerExplorerOptions }
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import express, { Request, Response } from 'express'
|
||||||
|
import swaggerUi from 'swagger-ui-express'
|
||||||
|
import { swaggerDocs, swaggerExplorerOptions } from './configs/swagger.config'
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
const port = 6655
|
||||||
|
|
||||||
|
app.get('/', (req: Request, res: Response) => {
|
||||||
|
res.redirect('/api-docs')
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs, swaggerExplorerOptions))
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Flowise API documentation server listening on port ${port}`)
|
||||||
|
})
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2017"],
|
||||||
|
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
|
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
|
||||||
|
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
|
||||||
|
"module": "commonjs" /* Specify what module code is generated. */,
|
||||||
|
"outDir": "dist",
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ const createChatMessage = async (req: Request, res: Response, next: NextFunction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const apiResponse = await chatMessagesService.createChatMessage(req.body)
|
const apiResponse = await chatMessagesService.createChatMessage(req.body)
|
||||||
return res.json(apiResponse)
|
return res.json(parseAPIResponse(apiResponse))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,8 @@ const getAllChatMessages = async (req: Request, res: Response, next: NextFunctio
|
||||||
feedback,
|
feedback,
|
||||||
feedbackTypeFilters
|
feedbackTypeFilters
|
||||||
)
|
)
|
||||||
return res.json(apiResponse)
|
|
||||||
|
return res.json(parseAPIResponse(apiResponse))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +117,7 @@ const getAllInternalChatMessages = async (req: Request, res: Response, next: Nex
|
||||||
messageId,
|
messageId,
|
||||||
feedback
|
feedback
|
||||||
)
|
)
|
||||||
return res.json(apiResponse)
|
return res.json(parseAPIResponse(apiResponse))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +187,39 @@ const abortChatMessage = async (req: Request, res: Response, next: NextFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseAPIResponse = (apiResponse: ChatMessage | ChatMessage[]): ChatMessage | ChatMessage[] => {
|
||||||
|
const parseResponse = (response: ChatMessage): ChatMessage => {
|
||||||
|
const parsedResponse = { ...response }
|
||||||
|
|
||||||
|
if (parsedResponse.sourceDocuments) {
|
||||||
|
parsedResponse.sourceDocuments = JSON.parse(parsedResponse.sourceDocuments)
|
||||||
|
}
|
||||||
|
if (parsedResponse.usedTools) {
|
||||||
|
parsedResponse.usedTools = JSON.parse(parsedResponse.usedTools)
|
||||||
|
}
|
||||||
|
if (parsedResponse.fileAnnotations) {
|
||||||
|
parsedResponse.fileAnnotations = JSON.parse(parsedResponse.fileAnnotations)
|
||||||
|
}
|
||||||
|
if (parsedResponse.agentReasoning) {
|
||||||
|
parsedResponse.agentReasoning = JSON.parse(parsedResponse.agentReasoning)
|
||||||
|
}
|
||||||
|
if (parsedResponse.fileUploads) {
|
||||||
|
parsedResponse.fileUploads = JSON.parse(parsedResponse.fileUploads)
|
||||||
|
}
|
||||||
|
if (parsedResponse.action) {
|
||||||
|
parsedResponse.action = JSON.parse(parsedResponse.action)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(apiResponse)) {
|
||||||
|
return apiResponse.map(parseResponse)
|
||||||
|
} else {
|
||||||
|
return parseResponse(apiResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createChatMessage,
|
createChatMessage,
|
||||||
getAllChatMessages,
|
getAllChatMessages,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { sanitizeMiddleware, getCorsOptions, getAllowedIframeOrigins } from './u
|
||||||
import { Telemetry } from './utils/telemetry'
|
import { Telemetry } from './utils/telemetry'
|
||||||
import flowiseApiV1Router from './routes'
|
import flowiseApiV1Router from './routes'
|
||||||
import errorHandlerMiddleware from './middlewares/errors'
|
import errorHandlerMiddleware from './middlewares/errors'
|
||||||
|
import { validateAPIKey } from './utils/validateKey'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
|
|
@ -140,10 +141,38 @@ export class App {
|
||||||
'/api/v1/ip',
|
'/api/v1/ip',
|
||||||
'/api/v1/ping'
|
'/api/v1/ping'
|
||||||
]
|
]
|
||||||
this.app.use((req, res, next) => {
|
this.app.use(async (req, res, next) => {
|
||||||
if (/\/api\/v1\//i.test(req.url)) {
|
if (/\/api\/v1\//i.test(req.url)) {
|
||||||
whitelistURLs.some((url) => new RegExp(url, 'i').test(req.url)) ? next() : basicAuthMiddleware(req, res, next)
|
if (whitelistURLs.some((url) => new RegExp(url, 'i').test(req.url))) {
|
||||||
} else next()
|
next()
|
||||||
|
} else if (req.headers['x-request-from'] === 'internal') {
|
||||||
|
basicAuthMiddleware(req, res, next)
|
||||||
|
} else {
|
||||||
|
const isKeyValidated = await validateAPIKey(req)
|
||||||
|
if (!isKeyValidated) {
|
||||||
|
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.app.use(async (req, res, next) => {
|
||||||
|
if (/\/api\/v1\//i.test(req.url)) {
|
||||||
|
if (req.headers['x-request-from'] === 'internal') {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
const isKeyValidated = await validateAPIKey(req)
|
||||||
|
if (!isKeyValidated) {
|
||||||
|
return res.status(401).json({ error: 'Unauthorized Access' })
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const router = express.Router()
|
||||||
// Create document store
|
// Create document store
|
||||||
router.post('/store', documentStoreController.createDocumentStore)
|
router.post('/store', documentStoreController.createDocumentStore)
|
||||||
// List all stores
|
// List all stores
|
||||||
router.get('/stores', documentStoreController.getAllDocumentStores)
|
router.get('/store', documentStoreController.getAllDocumentStores)
|
||||||
// Get specific store
|
// Get specific store
|
||||||
router.get('/store/:id', documentStoreController.getDocumentStoreById)
|
router.get('/store/:id', documentStoreController.getDocumentStoreById)
|
||||||
// Update documentStore
|
// Update documentStore
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,14 @@ const getAllApiKeys = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApiKey = async (keyName: string) => {
|
const getApiKey = async (apiKey: string) => {
|
||||||
try {
|
try {
|
||||||
if (_apikeysStoredInJson()) {
|
if (_apikeysStoredInJson()) {
|
||||||
return getApiKey_json(keyName)
|
return getApiKey_json(apiKey)
|
||||||
} else if (_apikeysStoredInDb()) {
|
} else if (_apikeysStoredInDb()) {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
|
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
|
||||||
keyName: keyName
|
apiKey: apiKey
|
||||||
})
|
})
|
||||||
if (!currentKey) {
|
if (!currentKey) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import { Credential } from '../../database/entities/Credential'
|
||||||
import { decryptCredentialData, getAppVersion } from '../../utils'
|
import { decryptCredentialData, getAppVersion } from '../../utils'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { getErrorMessage } from '../../errors/utils'
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
|
import { DeleteResult } from 'typeorm'
|
||||||
|
|
||||||
const createAssistant = async (requestBody: any): Promise<any> => {
|
const createAssistant = async (requestBody: any): Promise<Assistant> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
if (!requestBody.details) {
|
if (!requestBody.details) {
|
||||||
|
|
@ -119,7 +120,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<any> => {
|
const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<DeleteResult> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||||
|
|
@ -150,12 +151,8 @@ const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<
|
||||||
if (isDeleteBoth) await openai.beta.assistants.del(assistantDetails.id)
|
if (isDeleteBoth) await openai.beta.assistants.del(assistantDetails.id)
|
||||||
return dbResponse
|
return dbResponse
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.status === 404 && error.type === 'invalid_request_error') {
|
|
||||||
return 'OK'
|
|
||||||
} else {
|
|
||||||
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error deleting assistant - ${getErrorMessage(error)}`)
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error deleting assistant - ${getErrorMessage(error)}`)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new InternalFlowiseError(
|
throw new InternalFlowiseError(
|
||||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
|
@ -164,7 +161,7 @@ const deleteAssistant = async (assistantId: string, isDeleteBoth: any): Promise<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllAssistants = async (): Promise<any> => {
|
const getAllAssistants = async (): Promise<Assistant[]> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).find()
|
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).find()
|
||||||
|
|
@ -177,7 +174,7 @@ const getAllAssistants = async (): Promise<any> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAssistantById = async (assistantId: string): Promise<any> => {
|
const getAssistantById = async (assistantId: string): Promise<Assistant> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||||
|
|
@ -195,7 +192,7 @@ const getAssistantById = async (assistantId: string): Promise<any> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateAssistant = async (assistantId: string, requestBody: any): Promise<any> => {
|
const updateAssistant = async (assistantId: string, requestBody: any): Promise<Assistant> => {
|
||||||
try {
|
try {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
const assistant = await appServer.AppDataSource.getRepository(Assistant).findOneBy({
|
||||||
|
|
|
||||||
|
|
@ -580,9 +580,8 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
|
||||||
`Error: documentStoreServices.processAndSaveChunks - Document store ${data.storeId} not found`
|
`Error: documentStoreServices.processAndSaveChunks - Document store ${data.storeId} not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newLoaderId = data.id ?? uuidv4()
|
|
||||||
const existingLoaders = JSON.parse(entity.loaders)
|
const existingLoaders = JSON.parse(entity.loaders)
|
||||||
|
const newLoaderId = data.id ?? uuidv4()
|
||||||
const found = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId)
|
const found = existingLoaders.find((ldr: IDocumentStoreLoader) => ldr.id === newLoaderId)
|
||||||
if (found) {
|
if (found) {
|
||||||
// clean up the current status and mark the loader as pending_sync
|
// clean up the current status and mark the loader as pending_sync
|
||||||
|
|
@ -590,6 +589,15 @@ const processAndSaveChunks = async (data: IDocumentStoreLoaderForPreview) => {
|
||||||
found.totalChars = 0
|
found.totalChars = 0
|
||||||
found.status = DocumentStoreStatus.SYNCING
|
found.status = DocumentStoreStatus.SYNCING
|
||||||
entity.loaders = JSON.stringify(existingLoaders)
|
entity.loaders = JSON.stringify(existingLoaders)
|
||||||
|
data.loaderId = found.loaderId
|
||||||
|
data.loaderName = found.loaderName
|
||||||
|
data.loaderConfig = found.loaderConfig
|
||||||
|
data.splitterId = found.splitterId
|
||||||
|
data.splitterName = found.splitterName
|
||||||
|
data.splitterConfig = found.splitterConfig
|
||||||
|
if (found.credential) {
|
||||||
|
data.credential = found.credential
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let loader: IDocumentStoreLoader = {
|
let loader: IDocumentStoreLoader = {
|
||||||
id: newLoaderId,
|
id: newLoaderId,
|
||||||
|
|
@ -833,6 +841,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||||
config: data.embeddingConfig,
|
config: data.embeddingConfig,
|
||||||
name: data.embeddingName
|
name: data.embeddingName
|
||||||
})
|
})
|
||||||
|
} else if (entity.embeddingConfig && !data.embeddingName && !data.embeddingConfig) {
|
||||||
|
data.embeddingConfig = JSON.parse(entity.embeddingConfig)?.config
|
||||||
|
data.embeddingName = JSON.parse(entity.embeddingConfig)?.name
|
||||||
} else if (!data.embeddingName && !data.embeddingConfig) {
|
} else if (!data.embeddingName && !data.embeddingConfig) {
|
||||||
entity.embeddingConfig = null
|
entity.embeddingConfig = null
|
||||||
}
|
}
|
||||||
|
|
@ -842,6 +853,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||||
config: data.vectorStoreConfig,
|
config: data.vectorStoreConfig,
|
||||||
name: data.vectorStoreName
|
name: data.vectorStoreName
|
||||||
})
|
})
|
||||||
|
} else if (entity.vectorStoreConfig && !data.vectorStoreName && !data.vectorStoreConfig) {
|
||||||
|
data.vectorStoreConfig = JSON.parse(entity.vectorStoreConfig)?.config
|
||||||
|
data.vectorStoreName = JSON.parse(entity.vectorStoreConfig)?.name
|
||||||
} else if (!data.vectorStoreName && !data.vectorStoreConfig) {
|
} else if (!data.vectorStoreName && !data.vectorStoreConfig) {
|
||||||
entity.vectorStoreConfig = null
|
entity.vectorStoreConfig = null
|
||||||
}
|
}
|
||||||
|
|
@ -851,6 +865,9 @@ const saveVectorStoreConfig = async (data: ICommonObject) => {
|
||||||
config: data.recordManagerConfig,
|
config: data.recordManagerConfig,
|
||||||
name: data.recordManagerName
|
name: data.recordManagerName
|
||||||
})
|
})
|
||||||
|
} else if (entity.recordManagerConfig && !data.recordManagerName && !data.recordManagerConfig) {
|
||||||
|
data.recordManagerConfig = JSON.parse(entity.recordManagerConfig)?.config
|
||||||
|
data.recordManagerName = JSON.parse(entity.recordManagerConfig)?.name
|
||||||
} else if (!data.recordManagerName && !data.recordManagerConfig) {
|
} else if (!data.recordManagerName && !data.recordManagerConfig) {
|
||||||
entity.recordManagerConfig = null
|
entity.recordManagerConfig = null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const getAllChatMessageFeedback = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add chatmessage feedback for chatflowid
|
// Add chatmessage feedback
|
||||||
const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const dbResponse = await utilAddChatMessageFeedback(requestBody)
|
const dbResponse = await utilAddChatMessageFeedback(requestBody)
|
||||||
|
|
@ -38,10 +38,10 @@ const createChatMessageFeedbackForChatflow = async (requestBody: Partial<IChatMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add chatmessage feedback for chatflowid
|
// Add chatmessage feedback
|
||||||
const updateChatMessageFeedbackForChatflow = async (chatflowId: string, requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
const updateChatMessageFeedbackForChatflow = async (feedbackId: string, requestBody: Partial<IChatMessageFeedback>): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const dbResponse = await utilUpdateChatMessageFeedback(chatflowId, requestBody)
|
const dbResponse = await utilUpdateChatMessageFeedback(feedbackId, requestBody)
|
||||||
return dbResponse
|
return dbResponse
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new InternalFlowiseError(
|
throw new InternalFlowiseError(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import {
|
||||||
getEndingNodes,
|
getEndingNodes,
|
||||||
constructGraphs
|
constructGraphs
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { utilValidateKey } from './validateKey'
|
import { validateChatflowAPIKey } from './validateKey'
|
||||||
import { databaseEntities } from '.'
|
import { databaseEntities } from '.'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { omit } from 'lodash'
|
import { omit } from 'lodash'
|
||||||
|
|
@ -73,7 +73,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||||
const userMessageDateTime = new Date()
|
const userMessageDateTime = new Date()
|
||||||
|
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const isKeyValidated = await utilValidateKey(req, chatflow)
|
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||||
if (!isKeyValidated) {
|
if (!isKeyValidated) {
|
||||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
getTelemetryFlowObj,
|
getTelemetryFlowObj,
|
||||||
getStartingNodes
|
getStartingNodes
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { utilValidateKey } from './validateKey'
|
import { validateChatflowAPIKey } from './validateKey'
|
||||||
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, chatType } from '../Interface'
|
import { IncomingInput, INodeDirectedGraph, IReactFlowObject, chatType } from '../Interface'
|
||||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||||
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../utils/getRunningExpressApp'
|
||||||
|
|
@ -43,7 +43,7 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const isKeyValidated = await utilValidateKey(req, chatflow)
|
const isKeyValidated = await validateChatflowAPIKey(req, chatflow)
|
||||||
if (!isKeyValidated) {
|
if (!isKeyValidated) {
|
||||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import { Request } from 'express'
|
||||||
import { ChatFlow } from '../database/entities/ChatFlow'
|
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||||
import { compareKeys } from './apiKey'
|
import { compareKeys } from './apiKey'
|
||||||
import apikeyService from '../services/apikey'
|
import apikeyService from '../services/apikey'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate API Key
|
* Validate Chatflow API Key
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
|
||||||
* @param {ChatFlow} chatflow
|
* @param {ChatFlow} chatflow
|
||||||
*/
|
*/
|
||||||
export const utilValidateKey = async (req: Request, chatflow: ChatFlow) => {
|
export const validateChatflowAPIKey = async (req: Request, chatflow: ChatFlow) => {
|
||||||
const chatFlowApiKeyId = chatflow.apikeyid
|
const chatFlowApiKeyId = chatflow?.apikeyid
|
||||||
if (!chatFlowApiKeyId) return true
|
if (!chatFlowApiKeyId) return true
|
||||||
|
|
||||||
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||||
|
|
@ -24,3 +24,22 @@ export const utilValidateKey = async (req: Request, chatflow: ChatFlow) => {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate API Key
|
||||||
|
* @param {Request} req
|
||||||
|
*/
|
||||||
|
export const validateAPIKey = async (req: Request) => {
|
||||||
|
const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
|
||||||
|
if (!authorizationHeader) return false
|
||||||
|
|
||||||
|
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
|
||||||
|
if (suppliedKey) {
|
||||||
|
const keys = await apikeyService.getAllApiKeys()
|
||||||
|
const apiSecret = keys.find((key: any) => key.apiKey === suppliedKey)?.apiSecret
|
||||||
|
if (!apiSecret) return false
|
||||||
|
if (!compareKeys(apiSecret, suppliedKey)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
import { ChatFlow } from '../../src/database/entities/ChatFlow'
|
import { ChatFlow } from '../../src/database/entities/ChatFlow'
|
||||||
import { utilValidateKey } from '../../src/utils/validateKey'
|
import { validateChatflowAPIKey } from '../../src/utils/validateKey'
|
||||||
import { compareKeys, getAPIKeys } from '../../src/utils/apiKey'
|
import { compareKeys, getAPIKeys } from '../../src/utils/apiKey'
|
||||||
|
|
||||||
jest.mock('../../src/utils/apiKey')
|
jest.mock('../../src/utils/apiKey')
|
||||||
|
|
||||||
describe('utilValidateKey', () => {
|
describe('validateChatflowAPIKey', () => {
|
||||||
let req: Partial<Request>
|
let req: Partial<Request>
|
||||||
let chatflow: ChatFlow
|
let chatflow: ChatFlow
|
||||||
|
|
||||||
|
|
@ -19,13 +19,13 @@ describe('utilValidateKey', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return true if chatflow.apikeyid is not set', async () => {
|
it('should return true if chatflow.apikeyid is not set', async () => {
|
||||||
const result = await utilValidateKey(req as Request, chatflow)
|
const result = await validateChatflowAPIKey(req as Request, chatflow)
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false if chatflow.apikeyid is set but authorization header is missing', async () => {
|
it('should return false if chatflow.apikeyid is set but authorization header is missing', async () => {
|
||||||
chatflow.apikeyid = 'some-api-key-id'
|
chatflow.apikeyid = 'some-api-key-id'
|
||||||
const result = await utilValidateKey(req as Request, chatflow)
|
const result = await validateChatflowAPIKey(req as Request, chatflow)
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ describe('utilValidateKey', () => {
|
||||||
;(getAPIKeys as jest.Mock).mockResolvedValue([{ id: 'some-api-key-id', apiSecret: 'expected-secret-key' }])
|
;(getAPIKeys as jest.Mock).mockResolvedValue([{ id: 'some-api-key-id', apiSecret: 'expected-secret-key' }])
|
||||||
;(compareKeys as jest.Mock).mockImplementation((expected, supplied) => expected === supplied)
|
;(compareKeys as jest.Mock).mockImplementation((expected, supplied) => expected === supplied)
|
||||||
|
|
||||||
const result = await utilValidateKey(req as Request, chatflow)
|
const result = await validateChatflowAPIKey(req as Request, chatflow)
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import { baseURL } from '@/store/constant'
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: `${baseURL}/api/v1`,
|
baseURL: `${baseURL}/api/v1`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-type': 'application/json'
|
'Content-type': 'application/json',
|
||||||
|
'x-request-from': 'internal'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import client from './client'
|
import client from './client'
|
||||||
|
|
||||||
const getAllDocumentStores = () => client.get('/document-store/stores')
|
const getAllDocumentStores = () => client.get('/document-store/store')
|
||||||
const getDocumentLoaders = () => client.get('/document-store/components/loaders')
|
const getDocumentLoaders = () => client.get('/document-store/components/loaders')
|
||||||
const getSpecificDocumentStore = (id) => client.get(`/document-store/store/${id}`)
|
const getSpecificDocumentStore = (id) => client.get(`/document-store/store/${id}`)
|
||||||
const createDocumentStore = (body) => client.post(`/document-store/store`, body)
|
const createDocumentStore = (body) => client.post(`/document-store/store`, body)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ const AboutDialog = ({ show, onCancel }) => {
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
|
config.headers = {
|
||||||
|
'Content-type': 'application/json',
|
||||||
|
'x-request-from': 'internal'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const latestReleaseReq = axios.get('https://api.github.com/repos/FlowiseAI/Flowise/releases/latest')
|
const latestReleaseReq = axios.get('https://api.github.com/repos/FlowiseAI/Flowise/releases/latest')
|
||||||
const currentVersionReq = axios.get(`${baseURL}/api/v1/version`, { ...config })
|
const currentVersionReq = axios.get(`${baseURL}/api/v1/version`, { ...config })
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const chatmsg = allChatlogs[i]
|
const chatmsg = allChatlogs[i]
|
||||||
const chatPK = getChatPK(chatmsg)
|
const chatPK = getChatPK(chatmsg)
|
||||||
let filePaths = []
|
let filePaths = []
|
||||||
if (chatmsg.fileUploads) {
|
if (chatmsg.fileUploads && Array.isArray(chatmsg.fileUploads)) {
|
||||||
chatmsg.fileUploads = JSON.parse(chatmsg.fileUploads)
|
|
||||||
chatmsg.fileUploads.forEach((file) => {
|
chatmsg.fileUploads.forEach((file) => {
|
||||||
if (file.type === 'stored-file') {
|
if (file.type === 'stored-file') {
|
||||||
filePaths.push(
|
filePaths.push(
|
||||||
|
|
@ -203,11 +202,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
time: chatmsg.createdDate
|
time: chatmsg.createdDate
|
||||||
}
|
}
|
||||||
if (filePaths.length) msg.filePaths = filePaths
|
if (filePaths.length) msg.filePaths = filePaths
|
||||||
if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
if (chatmsg.sourceDocuments) msg.sourceDocuments = chatmsg.sourceDocuments
|
||||||
if (chatmsg.usedTools) msg.usedTools = JSON.parse(chatmsg.usedTools)
|
if (chatmsg.usedTools) msg.usedTools = Jchatmsg.usedTools
|
||||||
if (chatmsg.fileAnnotations) msg.fileAnnotations = JSON.parse(chatmsg.fileAnnotations)
|
if (chatmsg.fileAnnotations) msg.fileAnnotations = chatmsg.fileAnnotations
|
||||||
if (chatmsg.feedback) msg.feedback = chatmsg.feedback?.content
|
if (chatmsg.feedback) msg.feedback = chatmsg.feedback?.content
|
||||||
if (chatmsg.agentReasoning) msg.agentReasoning = JSON.parse(chatmsg.agentReasoning)
|
if (chatmsg.agentReasoning) msg.agentReasoning = chatmsg.agentReasoning
|
||||||
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||||
obj[chatPK] = {
|
obj[chatPK] = {
|
||||||
|
|
@ -326,8 +325,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chatmsg.fileUploads) {
|
if (chatmsg.fileUploads && Array.isArray(chatmsg.fileUploads)) {
|
||||||
chatmsg.fileUploads = JSON.parse(chatmsg.fileUploads)
|
|
||||||
chatmsg.fileUploads.forEach((file) => {
|
chatmsg.fileUploads.forEach((file) => {
|
||||||
if (file.type === 'stored-file') {
|
if (file.type === 'stored-file') {
|
||||||
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatmsg.chatflowid}&chatId=${chatmsg.chatId}&fileName=${file.name}`
|
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatmsg.chatflowid}&chatId=${chatmsg.chatId}&fileName=${file.name}`
|
||||||
|
|
@ -339,10 +337,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
message: chatmsg.content,
|
message: chatmsg.content,
|
||||||
type: chatmsg.role
|
type: chatmsg.role
|
||||||
}
|
}
|
||||||
if (chatmsg.sourceDocuments) obj.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
if (chatmsg.sourceDocuments) obj.sourceDocuments = chatmsg.sourceDocuments
|
||||||
if (chatmsg.usedTools) obj.usedTools = JSON.parse(chatmsg.usedTools)
|
if (chatmsg.usedTools) obj.usedTools = chatmsg.usedTools
|
||||||
if (chatmsg.fileAnnotations) obj.fileAnnotations = JSON.parse(chatmsg.fileAnnotations)
|
if (chatmsg.fileAnnotations) obj.fileAnnotations = chatmsg.fileAnnotations
|
||||||
if (chatmsg.agentReasoning) obj.agentReasoning = JSON.parse(chatmsg.agentReasoning)
|
if (chatmsg.agentReasoning) obj.agentReasoning = chatmsg.agentReasoning
|
||||||
|
|
||||||
loadedMessages.push(obj)
|
loadedMessages.push(obj)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,10 @@ const fetchList = async ({ name, nodeData }) => {
|
||||||
.post(
|
.post(
|
||||||
`${baseURL}/api/v1/node-load-method/${nodeData.name}`,
|
`${baseURL}/api/v1/node-load-method/${nodeData.name}`,
|
||||||
{ ...nodeData, loadMethod },
|
{ ...nodeData, loadMethod },
|
||||||
{ auth: username && password ? { username, password } : undefined }
|
{
|
||||||
|
auth: username && password ? { username, password } : undefined,
|
||||||
|
headers: { 'Content-type': 'application/json', 'x-request-from': 'internal' }
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.then(async function (response) {
|
.then(async function (response) {
|
||||||
return response.data
|
return response.data
|
||||||
|
|
|
||||||
|
|
@ -684,13 +684,13 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
feedback: message.feedback,
|
feedback: message.feedback,
|
||||||
type: message.role
|
type: message.role
|
||||||
}
|
}
|
||||||
if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments)
|
if (message.sourceDocuments) obj.sourceDocuments = message.sourceDocuments
|
||||||
if (message.usedTools) obj.usedTools = JSON.parse(message.usedTools)
|
if (message.usedTools) obj.usedTools = message.usedTools
|
||||||
if (message.fileAnnotations) obj.fileAnnotations = JSON.parse(message.fileAnnotations)
|
if (message.fileAnnotations) obj.fileAnnotations = message.fileAnnotations
|
||||||
if (message.agentReasoning) obj.agentReasoning = JSON.parse(message.agentReasoning)
|
if (message.agentReasoning) obj.agentReasoning = message.agentReasoning
|
||||||
if (message.action) obj.action = JSON.parse(message.action)
|
if (message.action) obj.action = message.action
|
||||||
if (message.fileUploads) {
|
if (message.fileUploads) {
|
||||||
obj.fileUploads = JSON.parse(message.fileUploads)
|
obj.fileUploads = message.fileUploads
|
||||||
obj.fileUploads.forEach((file) => {
|
obj.fileUploads.forEach((file) => {
|
||||||
if (file.type === 'stored-file') {
|
if (file.type === 'stored-file') {
|
||||||
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatflowid}&chatId=${chatId}&fileName=${file.name}`
|
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatflowid}&chatId=${chatId}&fileName=${file.name}`
|
||||||
|
|
@ -1390,44 +1390,13 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{message.type === 'apiMessage' && message.id && chatFeedbackStatus ? (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'start',
|
|
||||||
gap: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CopyToClipboardButton onClick={() => copyMessageToClipboard(message.message)} />
|
|
||||||
{!message.feedback ||
|
|
||||||
message.feedback.rating === '' ||
|
|
||||||
message.feedback.rating === 'THUMBS_UP' ? (
|
|
||||||
<ThumbsUpButton
|
|
||||||
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_UP'}
|
|
||||||
rating={message.feedback ? message.feedback.rating : ''}
|
|
||||||
onClick={() => onThumbsUpClick(message.id)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{!message.feedback ||
|
|
||||||
message.feedback.rating === '' ||
|
|
||||||
message.feedback.rating === 'THUMBS_DOWN' ? (
|
|
||||||
<ThumbsDownButton
|
|
||||||
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_DOWN'}
|
|
||||||
rating={message.feedback ? message.feedback.rating : ''}
|
|
||||||
onClick={() => onThumbsDownClick(message.id)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{message.fileAnnotations && (
|
{message.fileAnnotations && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'block',
|
display: 'block',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%'
|
width: '100%',
|
||||||
|
marginBottom: '8px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message.fileAnnotations.map((fileAnnotation, index) => {
|
{message.fileAnnotations.map((fileAnnotation, index) => {
|
||||||
|
|
@ -1454,7 +1423,8 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
style={{
|
style={{
|
||||||
display: 'block',
|
display: 'block',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%'
|
width: '100%',
|
||||||
|
marginBottom: '8px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{removeDuplicateURL(message).map((source, index) => {
|
{removeDuplicateURL(message).map((source, index) => {
|
||||||
|
|
@ -1486,7 +1456,8 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
gap: '8px'
|
gap: '8px',
|
||||||
|
marginBottom: '8px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(message.action.elements || []).map((elem, index) => {
|
{(message.action.elements || []).map((elem, index) => {
|
||||||
|
|
@ -1537,6 +1508,38 @@ export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, preview
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{message.type === 'apiMessage' && message.id && chatFeedbackStatus ? (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyToClipboardButton onClick={() => copyMessageToClipboard(message.message)} />
|
||||||
|
{!message.feedback ||
|
||||||
|
message.feedback.rating === '' ||
|
||||||
|
message.feedback.rating === 'THUMBS_UP' ? (
|
||||||
|
<ThumbsUpButton
|
||||||
|
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_UP'}
|
||||||
|
rating={message.feedback ? message.feedback.rating : ''}
|
||||||
|
onClick={() => onThumbsUpClick(message.id)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{!message.feedback ||
|
||||||
|
message.feedback.rating === '' ||
|
||||||
|
message.feedback.rating === 'THUMBS_DOWN' ? (
|
||||||
|
<ThumbsDownButton
|
||||||
|
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_DOWN'}
|
||||||
|
rating={message.feedback ? message.feedback.rating : ''}
|
||||||
|
onClick={() => onThumbsDownClick(message.id)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
521
pnpm-lock.yaml
521
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue