mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Merge pull request #1009 from FlowiseAI/feature/ChatHistory2
Feature/chat history2
This commit is contained in:
@@ -64,6 +64,7 @@
|
|||||||
"socket.io": "^4.6.1",
|
"socket.io": "^4.6.1",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"typeorm": "^0.3.6",
|
"typeorm": "^0.3.6",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
"winston": "^3.9.0"
|
"winston": "^3.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { ICommonObject, INode, INodeData as INodeDataFromComponent, INodeParams
|
|||||||
|
|
||||||
export type MessageType = 'apiMessage' | 'userMessage'
|
export type MessageType = 'apiMessage' | 'userMessage'
|
||||||
|
|
||||||
|
export enum chatType {
|
||||||
|
INTERNAL = 'INTERNAL',
|
||||||
|
EXTERNAL = 'EXTERNAL'
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Databases
|
* Databases
|
||||||
*/
|
*/
|
||||||
@@ -24,8 +28,12 @@ export interface IChatMessage {
|
|||||||
role: MessageType
|
role: MessageType
|
||||||
content: string
|
content: string
|
||||||
chatflowid: string
|
chatflowid: string
|
||||||
createdDate: Date
|
|
||||||
sourceDocuments?: string
|
sourceDocuments?: string
|
||||||
|
chatType: string
|
||||||
|
chatId: string
|
||||||
|
memoryType?: string
|
||||||
|
sessionId?: string
|
||||||
|
createdDate: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITool {
|
export interface ITool {
|
||||||
@@ -146,6 +154,7 @@ export interface IncomingInput {
|
|||||||
history: IMessage[]
|
history: IMessage[]
|
||||||
overrideConfig?: ICommonObject
|
overrideConfig?: ICommonObject
|
||||||
socketIOClientId?: string
|
socketIOClientId?: string
|
||||||
|
chatId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IActiveChatflows {
|
export interface IActiveChatflows {
|
||||||
|
|||||||
@@ -20,6 +20,18 @@ export class ChatMessage implements IChatMessage {
|
|||||||
@Column({ nullable: true, type: 'text' })
|
@Column({ nullable: true, type: 'text' })
|
||||||
sourceDocuments?: string
|
sourceDocuments?: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
chatType: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
chatId: string
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
memoryType?: string
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
sessionId?: string
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdDate: Date
|
createdDate: Date
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
|||||||
|
|
||||||
export class AddApiConfig1694099200729 implements MigrationInterface {
|
export class AddApiConfig1694099200729 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`apiConfig\` TEXT;`)
|
const columnExists = await queryRunner.hasColumn('chat_flow', 'apiConfig')
|
||||||
|
if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`apiConfig\` TEXT;`)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
|||||||
|
|
||||||
export class AddAnalytic1694432361423 implements MigrationInterface {
|
export class AddAnalytic1694432361423 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`analytic\` TEXT;`)
|
const columnExists = await queryRunner.hasColumn('chat_flow', 'analytic')
|
||||||
|
if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`analytic\` TEXT;`)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddChatHistory1694658767766 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
const chatTypeColumnExists = await queryRunner.hasColumn('chat_message', 'chatType')
|
||||||
|
if (!chatTypeColumnExists)
|
||||||
|
await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`chatType\` VARCHAR(255) NOT NULL DEFAULT 'INTERNAL';`)
|
||||||
|
|
||||||
|
const chatIdColumnExists = await queryRunner.hasColumn('chat_message', 'chatId')
|
||||||
|
if (!chatIdColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`chatId\` VARCHAR(255);`)
|
||||||
|
const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS (
|
||||||
|
SELECT
|
||||||
|
\`chatflowid\`,
|
||||||
|
\`id\`,
|
||||||
|
\`createdDate\`,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY \`chatflowid\` ORDER BY \`createdDate\`) AS row_num
|
||||||
|
FROM \`chat_message\`
|
||||||
|
)
|
||||||
|
SELECT \`chatflowid\`, \`id\`
|
||||||
|
FROM RankedMessages
|
||||||
|
WHERE row_num = 1;`)
|
||||||
|
for (const chatMessage of results) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE \`chat_message\` SET \`chatId\` = '${chatMessage.id}' WHERE \`chatflowid\` = '${chatMessage.chatflowid}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await queryRunner.query(`ALTER TABLE \`chat_message\` MODIFY \`chatId\` VARCHAR(255) NOT NULL;`)
|
||||||
|
|
||||||
|
const memoryTypeColumnExists = await queryRunner.hasColumn('chat_message', 'memoryType')
|
||||||
|
if (!memoryTypeColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`memoryType\` VARCHAR(255);`)
|
||||||
|
|
||||||
|
const sessionIdColumnExists = await queryRunner.hasColumn('chat_message', 'sessionId')
|
||||||
|
if (!sessionIdColumnExists) await queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`sessionId\` VARCHAR(255);`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE \`chat_message\` DROP COLUMN \`chatType\`, DROP COLUMN \`chatId\`, DROP COLUMN \`memoryType\`, DROP COLUMN \`sessionId\`;`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { ModifyCredential1693999261583 } from './1693999261583-ModifyCredential'
|
|||||||
import { ModifyTool1694001465232 } from './1694001465232-ModifyTool'
|
import { ModifyTool1694001465232 } from './1694001465232-ModifyTool'
|
||||||
import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig'
|
import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig'
|
||||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||||
|
import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory'
|
||||||
|
|
||||||
export const mysqlMigrations = [
|
export const mysqlMigrations = [
|
||||||
Init1693840429259,
|
Init1693840429259,
|
||||||
@@ -13,5 +14,6 @@ export const mysqlMigrations = [
|
|||||||
ModifyCredential1693999261583,
|
ModifyCredential1693999261583,
|
||||||
ModifyTool1694001465232,
|
ModifyTool1694001465232,
|
||||||
AddApiConfig1694099200729,
|
AddApiConfig1694099200729,
|
||||||
AddAnalytic1694432361423
|
AddAnalytic1694432361423,
|
||||||
|
AddChatHistory1694658767766
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
|||||||
|
|
||||||
export class AddApiConfig1694099183389 implements MigrationInterface {
|
export class AddApiConfig1694099183389 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "apiConfig" TEXT;`)
|
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "apiConfig" TEXT;`)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
|||||||
|
|
||||||
export class AddAnalytic1694432361423 implements MigrationInterface {
|
export class AddAnalytic1694432361423 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "analytic" TEXT;`)
|
await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "analytic" TEXT;`)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddChatHistory1694658756136 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "chat_message" ADD COLUMN IF NOT EXISTS "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', ADD COLUMN IF NOT EXISTS "chatId" VARCHAR, ADD COLUMN IF NOT EXISTS "memoryType" VARCHAR, ADD COLUMN IF NOT EXISTS "sessionId" VARCHAR;`
|
||||||
|
)
|
||||||
|
const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS (
|
||||||
|
SELECT
|
||||||
|
"chatflowid",
|
||||||
|
"id",
|
||||||
|
"createdDate",
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY "chatflowid" ORDER BY "createdDate") AS row_num
|
||||||
|
FROM "chat_message"
|
||||||
|
)
|
||||||
|
SELECT "chatflowid", "id"
|
||||||
|
FROM RankedMessages
|
||||||
|
WHERE row_num = 1;`)
|
||||||
|
for (const chatMessage of results) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE "chat_message" SET "chatId" = '${chatMessage.id}' WHERE "chatflowid" = '${chatMessage.chatflowid}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" ALTER COLUMN "chatId" SET NOT NULL;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "chat_message" DROP COLUMN "chatType", DROP COLUMN "chatId", DROP COLUMN "memoryType", DROP COLUMN "sessionId";`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { ModifyCredential1693997070000 } from './1693997070000-ModifyCredential'
|
|||||||
import { ModifyTool1693997339912 } from './1693997339912-ModifyTool'
|
import { ModifyTool1693997339912 } from './1693997339912-ModifyTool'
|
||||||
import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig'
|
import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig'
|
||||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||||
|
import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory'
|
||||||
|
|
||||||
export const postgresMigrations = [
|
export const postgresMigrations = [
|
||||||
Init1693891895163,
|
Init1693891895163,
|
||||||
@@ -13,5 +14,6 @@ export const postgresMigrations = [
|
|||||||
ModifyCredential1693997070000,
|
ModifyCredential1693997070000,
|
||||||
ModifyTool1693997339912,
|
ModifyTool1693997339912,
|
||||||
AddApiConfig1694099183389,
|
AddApiConfig1694099183389,
|
||||||
AddAnalytic1694432361423
|
AddAnalytic1694432361423,
|
||||||
|
AddChatHistory1694658756136
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddChatHistory1694657778173 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN "chatId" VARCHAR;`)
|
||||||
|
const results: { id: string; chatflowid: string }[] = await queryRunner.query(`WITH RankedMessages AS (
|
||||||
|
SELECT
|
||||||
|
"chatflowid",
|
||||||
|
"id",
|
||||||
|
"createdDate",
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY "chatflowid" ORDER BY "createdDate") AS row_num
|
||||||
|
FROM "chat_message"
|
||||||
|
)
|
||||||
|
SELECT "chatflowid", "id"
|
||||||
|
FROM RankedMessages
|
||||||
|
WHERE row_num = 1;`)
|
||||||
|
for (const chatMessage of results) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE "chat_message" SET "chatId" = '${chatMessage.id}' WHERE "chatflowid" = '${chatMessage.chatflowid}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "temp_chat_message" ("id" varchar PRIMARY KEY NOT NULL, "role" varchar NOT NULL, "chatflowid" varchar NOT NULL, "content" text NOT NULL, "sourceDocuments" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "chatType" VARCHAR NOT NULL DEFAULT 'INTERNAL', "chatId" VARCHAR NOT NULL, "memoryType" VARCHAR, "sessionId" VARCHAR);`
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
`INSERT INTO "temp_chat_message" ("id", "role", "chatflowid", "content", "sourceDocuments", "createdDate", "chatId") SELECT "id", "role", "chatflowid", "content", "sourceDocuments", "createdDate", "chatId" FROM "chat_message";`
|
||||||
|
)
|
||||||
|
await queryRunner.query(`DROP TABLE "chat_message";`)
|
||||||
|
await queryRunner.query(`ALTER TABLE "temp_chat_message" RENAME TO "chat_message";`)
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_e574527322272fd838f4f0f3d3" ON "chat_message" ("chatflowid") ;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE IF EXISTS "temp_chat_message";`)
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "chatType";`)
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "chatId";`)
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "memoryType";`)
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "sessionId";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { ModifyCredential1693923551694 } from './1693923551694-ModifyCredential'
|
|||||||
import { ModifyTool1693924207475 } from './1693924207475-ModifyTool'
|
import { ModifyTool1693924207475 } from './1693924207475-ModifyTool'
|
||||||
import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig'
|
import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig'
|
||||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||||
|
import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory'
|
||||||
|
|
||||||
export const sqliteMigrations = [
|
export const sqliteMigrations = [
|
||||||
Init1693835579790,
|
Init1693835579790,
|
||||||
@@ -13,5 +14,6 @@ export const sqliteMigrations = [
|
|||||||
ModifyCredential1693923551694,
|
ModifyCredential1693923551694,
|
||||||
ModifyTool1693924207475,
|
ModifyTool1693924207475,
|
||||||
AddApiConfig1694090982460,
|
AddApiConfig1694090982460,
|
||||||
AddAnalytic1694432361423
|
AddAnalytic1694432361423,
|
||||||
|
AddChatHistory1694657778173
|
||||||
]
|
]
|
||||||
|
|||||||
+189
-30
@@ -8,7 +8,8 @@ import basicAuth from 'express-basic-auth'
|
|||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import logger from './utils/logger'
|
import logger from './utils/logger'
|
||||||
import { expressRequestLogger } from './utils/logger'
|
import { expressRequestLogger } from './utils/logger'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { Between, IsNull, FindOptionsWhere } from 'typeorm'
|
||||||
import {
|
import {
|
||||||
IChatFlow,
|
IChatFlow,
|
||||||
IncomingInput,
|
IncomingInput,
|
||||||
@@ -16,7 +17,10 @@ import {
|
|||||||
IReactFlowObject,
|
IReactFlowObject,
|
||||||
INodeData,
|
INodeData,
|
||||||
IDatabaseExport,
|
IDatabaseExport,
|
||||||
ICredentialReturnResponse
|
ICredentialReturnResponse,
|
||||||
|
chatType,
|
||||||
|
IChatMessage,
|
||||||
|
IReactFlowEdge
|
||||||
} from './Interface'
|
} from './Interface'
|
||||||
import {
|
import {
|
||||||
getNodeModulesPackagePath,
|
getNodeModulesPackagePath,
|
||||||
@@ -40,10 +44,11 @@ import {
|
|||||||
getApiKey,
|
getApiKey,
|
||||||
transformToCredentialEntity,
|
transformToCredentialEntity,
|
||||||
decryptCredentialData,
|
decryptCredentialData,
|
||||||
clearSessionMemory,
|
clearAllSessionMemory,
|
||||||
replaceInputsWithConfig,
|
replaceInputsWithConfig,
|
||||||
getEncryptionKey,
|
getEncryptionKey,
|
||||||
checkMemorySessionId
|
checkMemorySessionId,
|
||||||
|
clearSessionMemoryFromViewMessageDialog
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { cloneDeep, omit } from 'lodash'
|
import { cloneDeep, omit } from 'lodash'
|
||||||
import { getDataSource } from './DataSource'
|
import { getDataSource } from './DataSource'
|
||||||
@@ -395,45 +400,92 @@ export class App {
|
|||||||
|
|
||||||
// Get all chatmessages from chatflowid
|
// Get all chatmessages from chatflowid
|
||||||
this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
this.app.get('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
const chatmessages = await this.AppDataSource.getRepository(ChatMessage).find({
|
const sortOrder = req.query?.order as string | undefined
|
||||||
where: {
|
const chatId = req.query?.chatId as string | undefined
|
||||||
chatflowid: req.params.id
|
const memoryType = req.query?.memoryType as string | undefined
|
||||||
},
|
const sessionId = req.query?.sessionId as string | undefined
|
||||||
order: {
|
const startDate = req.query?.startDate as string | undefined
|
||||||
createdDate: 'ASC'
|
const endDate = req.query?.endDate as string | undefined
|
||||||
|
let chatTypeFilter = req.query?.chatType as chatType | undefined
|
||||||
|
|
||||||
|
if (chatTypeFilter) {
|
||||||
|
try {
|
||||||
|
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
|
||||||
|
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
||||||
|
chatTypeFilter = undefined
|
||||||
|
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
|
||||||
|
chatTypeFilter = chatType.EXTERNAL
|
||||||
|
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
||||||
|
chatTypeFilter = chatType.INTERNAL
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(500).send(e)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const chatmessages = await this.getChatMessage(
|
||||||
|
req.params.id,
|
||||||
|
chatTypeFilter,
|
||||||
|
sortOrder,
|
||||||
|
chatId,
|
||||||
|
memoryType,
|
||||||
|
sessionId,
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
|
)
|
||||||
|
return res.json(chatmessages)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get internal chatmessages from chatflowid
|
||||||
|
this.app.get('/api/v1/internal-chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatmessages = await this.getChatMessage(req.params.id, chatType.INTERNAL)
|
||||||
return res.json(chatmessages)
|
return res.json(chatmessages)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add chatmessages for chatflowid
|
// Add chatmessages for chatflowid
|
||||||
this.app.post('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
this.app.post('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
const body = req.body
|
const body = req.body
|
||||||
const newChatMessage = new ChatMessage()
|
const results = await this.addChatMessage(body)
|
||||||
Object.assign(newChatMessage, body)
|
|
||||||
|
|
||||||
const chatmessage = this.AppDataSource.getRepository(ChatMessage).create(newChatMessage)
|
|
||||||
const results = await this.AppDataSource.getRepository(ChatMessage).save(chatmessage)
|
|
||||||
|
|
||||||
return res.json(results)
|
return res.json(results)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Delete all chatmessages from chatflowid
|
// Delete all chatmessages from chatId
|
||||||
this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatflowid = req.params.id
|
||||||
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
id: req.params.id
|
id: chatflowid
|
||||||
})
|
})
|
||||||
if (!chatflow) {
|
if (!chatflow) {
|
||||||
res.status(404).send(`Chatflow ${req.params.id} not found`)
|
res.status(404).send(`Chatflow ${chatflowid} not found`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const chatId = (req.query?.chatId as string) ?? (await getChatId(chatflowid))
|
||||||
|
const memoryType = req.query?.memoryType as string | undefined
|
||||||
|
const sessionId = req.query?.sessionId as string | undefined
|
||||||
|
const chatType = req.query?.chatType as string | undefined
|
||||||
|
const isClearFromViewMessageDialog = req.query?.isClearFromViewMessageDialog as string | undefined
|
||||||
|
|
||||||
const flowData = chatflow.flowData
|
const flowData = chatflow.flowData
|
||||||
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||||
const nodes = parsedFlowData.nodes
|
const nodes = parsedFlowData.nodes
|
||||||
let chatId = await getChatId(chatflow.id)
|
|
||||||
if (!chatId) chatId = chatflow.id
|
if (isClearFromViewMessageDialog)
|
||||||
clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, req.query.sessionId as string)
|
clearSessionMemoryFromViewMessageDialog(
|
||||||
const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id })
|
nodes,
|
||||||
|
this.nodesPool.componentNodes,
|
||||||
|
chatId,
|
||||||
|
this.AppDataSource,
|
||||||
|
sessionId,
|
||||||
|
memoryType
|
||||||
|
)
|
||||||
|
else clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId)
|
||||||
|
|
||||||
|
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid, chatId }
|
||||||
|
if (memoryType) deleteOptions.memoryType = memoryType
|
||||||
|
if (sessionId) deleteOptions.sessionId = sessionId
|
||||||
|
if (chatType) deleteOptions.chatType = chatType
|
||||||
|
|
||||||
|
const results = await this.AppDataSource.getRepository(ChatMessage).delete(deleteOptions)
|
||||||
return res.json(results)
|
return res.json(results)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -826,6 +878,80 @@ export class App {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that get chat messages.
|
||||||
|
* @param {string} chatflowid
|
||||||
|
* @param {chatType} chatType
|
||||||
|
* @param {string} sortOrder
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {string} memoryType
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @param {string} startDate
|
||||||
|
* @param {string} endDate
|
||||||
|
*/
|
||||||
|
async getChatMessage(
|
||||||
|
chatflowid: string,
|
||||||
|
chatType: chatType | undefined,
|
||||||
|
sortOrder: string = 'ASC',
|
||||||
|
chatId?: string,
|
||||||
|
memoryType?: string,
|
||||||
|
sessionId?: string,
|
||||||
|
startDate?: string,
|
||||||
|
endDate?: string
|
||||||
|
): Promise<ChatMessage[]> {
|
||||||
|
let fromDate
|
||||||
|
if (startDate) fromDate = new Date(startDate)
|
||||||
|
|
||||||
|
let toDate
|
||||||
|
if (endDate) toDate = new Date(endDate)
|
||||||
|
|
||||||
|
return await this.AppDataSource.getRepository(ChatMessage).find({
|
||||||
|
where: {
|
||||||
|
chatflowid,
|
||||||
|
chatType,
|
||||||
|
chatId,
|
||||||
|
memoryType: memoryType ?? (chatId ? IsNull() : undefined),
|
||||||
|
sessionId: sessionId ?? (chatId ? IsNull() : undefined),
|
||||||
|
createdDate: toDate && fromDate ? Between(fromDate, toDate) : undefined
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that add chat messages.
|
||||||
|
* @param {Partial<IChatMessage>} chatMessage
|
||||||
|
*/
|
||||||
|
async addChatMessage(chatMessage: Partial<IChatMessage>): Promise<ChatMessage> {
|
||||||
|
const newChatMessage = new ChatMessage()
|
||||||
|
Object.assign(newChatMessage, chatMessage)
|
||||||
|
|
||||||
|
const chatmessage = this.AppDataSource.getRepository(ChatMessage).create(newChatMessage)
|
||||||
|
return await this.AppDataSource.getRepository(ChatMessage).save(chatmessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that find memory label that is connected within chatflow
|
||||||
|
* In a chatflow, there should only be 1 memory node
|
||||||
|
* @param {IReactFlowNode[]} nodes
|
||||||
|
* @param {IReactFlowEdge[]} edges
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
findMemoryLabel(nodes: IReactFlowNode[], edges: IReactFlowEdge[]): string | undefined {
|
||||||
|
const memoryNodes = nodes.filter((node) => node.data.category === 'Memory')
|
||||||
|
const memoryNodeIds = memoryNodes.map((mem) => mem.data.id)
|
||||||
|
|
||||||
|
for (const edge of edges) {
|
||||||
|
if (memoryNodeIds.includes(edge.source)) {
|
||||||
|
const memoryNode = nodes.find((node) => node.data.id === edge.source)
|
||||||
|
return memoryNode ? memoryNode.data.label : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process Prediction
|
* Process Prediction
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -833,7 +959,7 @@ export class App {
|
|||||||
* @param {Server} socketIO
|
* @param {Server} socketIO
|
||||||
* @param {boolean} isInternal
|
* @param {boolean} isInternal
|
||||||
*/
|
*/
|
||||||
async processPrediction(req: Request, res: Response, socketIO?: Server, isInternal = false) {
|
async processPrediction(req: Request, res: Response, socketIO?: Server, isInternal: boolean = false) {
|
||||||
try {
|
try {
|
||||||
const chatflowid = req.params.id
|
const chatflowid = req.params.id
|
||||||
let incomingInput: IncomingInput = req.body
|
let incomingInput: IncomingInput = req.body
|
||||||
@@ -845,8 +971,8 @@ export class App {
|
|||||||
})
|
})
|
||||||
if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`)
|
if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`)
|
||||||
|
|
||||||
let chatId = await getChatId(chatflow.id)
|
const chatId = incomingInput.chatId ?? incomingInput.overrideConfig?.sessionId ?? uuidv4()
|
||||||
if (!chatId) chatId = chatflowid
|
const userMessageDateTime = new Date()
|
||||||
|
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const isKeyValidated = await this.validateKey(req, chatflow)
|
const isKeyValidated = await this.validateKey(req, chatflow)
|
||||||
@@ -982,9 +1108,12 @@ export class App {
|
|||||||
|
|
||||||
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
logger.debug(`[server]: Running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
||||||
|
|
||||||
if (nodeToExecuteData.instance) checkMemorySessionId(nodeToExecuteData.instance, chatId)
|
let sessionId = undefined
|
||||||
|
if (nodeToExecuteData.instance) sessionId = checkMemorySessionId(nodeToExecuteData.instance, chatId)
|
||||||
|
|
||||||
const result = isStreamValid
|
const memoryType = this.findMemoryLabel(nodes, edges)
|
||||||
|
|
||||||
|
let result = isStreamValid
|
||||||
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
||||||
chatHistory: incomingInput.history,
|
chatHistory: incomingInput.history,
|
||||||
socketIO,
|
socketIO,
|
||||||
@@ -1002,7 +1131,37 @@ export class App {
|
|||||||
analytic: chatflow.analytic
|
analytic: chatflow.analytic
|
||||||
})
|
})
|
||||||
|
|
||||||
|
result = typeof result === 'string' ? { text: result } : result
|
||||||
|
|
||||||
|
const userMessage: Omit<IChatMessage, 'id'> = {
|
||||||
|
role: 'userMessage',
|
||||||
|
content: incomingInput.question,
|
||||||
|
chatflowid,
|
||||||
|
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||||
|
chatId,
|
||||||
|
memoryType,
|
||||||
|
sessionId,
|
||||||
|
createdDate: userMessageDateTime
|
||||||
|
}
|
||||||
|
await this.addChatMessage(userMessage)
|
||||||
|
|
||||||
|
const apiMessage: Omit<IChatMessage, 'id' | 'createdDate'> = {
|
||||||
|
role: 'apiMessage',
|
||||||
|
content: result.text,
|
||||||
|
chatflowid,
|
||||||
|
chatType: isInternal ? chatType.INTERNAL : chatType.EXTERNAL,
|
||||||
|
chatId,
|
||||||
|
memoryType,
|
||||||
|
sessionId
|
||||||
|
}
|
||||||
|
if (result?.sourceDocuments) apiMessage.sourceDocuments = JSON.stringify(result.sourceDocuments)
|
||||||
|
await this.addChatMessage(apiMessage)
|
||||||
|
|
||||||
logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
logger.debug(`[server]: Finished running ${nodeToExecuteData.label} (${nodeToExecuteData.id})`)
|
||||||
|
|
||||||
|
// Only return ChatId when its Internal OR incoming input has ChatId, to avoid confusion when calling API
|
||||||
|
if (incomingInput.chatId || isInternal) result.chatId = chatId
|
||||||
|
|
||||||
return res.json(result)
|
return res.json(result)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error('[server]: Error:', e)
|
logger.error('[server]: Error:', e)
|
||||||
@@ -1025,7 +1184,7 @@ export class App {
|
|||||||
* @param {string} chatflowid
|
* @param {string} chatflowid
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export async function getChatId(chatflowid: string) {
|
export async function getChatId(chatflowid: string): Promise<string> {
|
||||||
// first chatmessage id as the unique chat id
|
// first chatmessage id as the unique chat id
|
||||||
const firstChatMessage = await getDataSource()
|
const firstChatMessage = await getDataSource()
|
||||||
.getRepository(ChatMessage)
|
.getRepository(ChatMessage)
|
||||||
|
|||||||
@@ -298,14 +298,14 @@ export const buildLangchain = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear memory
|
* Clear all session memories on the canvas
|
||||||
* @param {IReactFlowNode[]} reactFlowNodes
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
* @param {IComponentNodes} componentNodes
|
* @param {IComponentNodes} componentNodes
|
||||||
* @param {string} chatId
|
* @param {string} chatId
|
||||||
* @param {DataSource} appDataSource
|
* @param {DataSource} appDataSource
|
||||||
* @param {string} sessionId
|
* @param {string} sessionId
|
||||||
*/
|
*/
|
||||||
export const clearSessionMemory = async (
|
export const clearAllSessionMemory = async (
|
||||||
reactFlowNodes: IReactFlowNode[],
|
reactFlowNodes: IReactFlowNode[],
|
||||||
componentNodes: IComponentNodes,
|
componentNodes: IComponentNodes,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
@@ -317,9 +317,46 @@ export const clearSessionMemory = async (
|
|||||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||||
const nodeModule = await import(nodeInstanceFilePath)
|
const nodeModule = await import(nodeInstanceFilePath)
|
||||||
const newNodeInstance = new nodeModule.nodeClass()
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
|
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
|
||||||
if (newNodeInstance.clearSessionMemory)
|
|
||||||
|
if (newNodeInstance.clearSessionMemory) {
|
||||||
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
|
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear specific session memory from View Message Dialog UI
|
||||||
|
* @param {IReactFlowNode[]} reactFlowNodes
|
||||||
|
* @param {IComponentNodes} componentNodes
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {DataSource} appDataSource
|
||||||
|
* @param {string} sessionId
|
||||||
|
* @param {string} memoryType
|
||||||
|
*/
|
||||||
|
export const clearSessionMemoryFromViewMessageDialog = async (
|
||||||
|
reactFlowNodes: IReactFlowNode[],
|
||||||
|
componentNodes: IComponentNodes,
|
||||||
|
chatId: string,
|
||||||
|
appDataSource: DataSource,
|
||||||
|
sessionId?: string,
|
||||||
|
memoryType?: string
|
||||||
|
) => {
|
||||||
|
if (!sessionId) return
|
||||||
|
for (const node of reactFlowNodes) {
|
||||||
|
if (node.data.category !== 'Memory') continue
|
||||||
|
if (node.data.label !== memoryType) continue
|
||||||
|
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||||
|
const nodeModule = await import(nodeInstanceFilePath)
|
||||||
|
const newNodeInstance = new nodeModule.nodeClass()
|
||||||
|
|
||||||
|
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
|
||||||
|
|
||||||
|
if (newNodeInstance.clearSessionMemory) {
|
||||||
|
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,8 +965,10 @@ export const redactCredentialWithPasswordType = (
|
|||||||
* @param {any} instance
|
* @param {any} instance
|
||||||
* @param {string} chatId
|
* @param {string} chatId
|
||||||
*/
|
*/
|
||||||
export const checkMemorySessionId = (instance: any, chatId: string) => {
|
export const checkMemorySessionId = (instance: any, chatId: string): string => {
|
||||||
if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) {
|
if (instance.memory && instance.memory.isSessionIdUsingChatMessageId && chatId) {
|
||||||
instance.memory.sessionId = chatId
|
instance.memory.sessionId = chatId
|
||||||
|
instance.memory.chatHistory.sessionId = chatId
|
||||||
}
|
}
|
||||||
|
return instance.memory ? instance.memory.sessionId ?? instance.memory.chatHistory.sessionId : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-code-blocks": "^0.0.9-0",
|
"react-code-blocks": "^0.0.9-0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-datepicker": "^4.8.0",
|
"react-datepicker": "^4.21.0",
|
||||||
"react-device-detect": "^1.17.0",
|
"react-device-detect": "^1.17.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.6",
|
"react-markdown": "^8.0.6",
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import client from './client'
|
import client from './client'
|
||||||
|
|
||||||
const getChatmessageFromChatflow = (id) => client.get(`/chatmessage/${id}`)
|
const getInternalChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`)
|
||||||
|
const getAllChatmessageFromChatflow = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'DESC', ...params } })
|
||||||
const createNewChatmessage = (id, body) => client.post(`/chatmessage/${id}`, body)
|
const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', ...params } })
|
||||||
|
const deleteChatmessage = (id, params = {}) => client.delete(`/chatmessage/${id}`, { params: { ...params } })
|
||||||
const deleteChatmessage = (id) => client.delete(`/chatmessage/${id}`)
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getChatmessageFromChatflow,
|
getInternalChatmessageFromChatflow,
|
||||||
createNewChatmessage,
|
getAllChatmessageFromChatflow,
|
||||||
|
getChatmessageFromPK,
|
||||||
deleteChatmessage
|
deleteChatmessage
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
@@ -71,7 +71,7 @@ const ProfileSection = ({ username, handleLogout }) => {
|
|||||||
try {
|
try {
|
||||||
const response = await databaseApi.getExportDatabase()
|
const response = await databaseApi.getExportDatabase()
|
||||||
const exportItems = response.data
|
const exportItems = response.data
|
||||||
let dataStr = JSON.stringify(exportItems)
|
let dataStr = JSON.stringify(exportItems, null, 2)
|
||||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||||
|
|
||||||
let exportFileDefaultName = `DB.json`
|
let exportFileDefaultName = `DB.json`
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// assets
|
// assets
|
||||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch } from '@tabler/icons'
|
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch }
|
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage }
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
|
|
||||||
@@ -11,6 +11,13 @@ const settings = {
|
|||||||
title: '',
|
title: '',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: 'viewMessages',
|
||||||
|
title: 'View Messages',
|
||||||
|
type: 'item',
|
||||||
|
url: '',
|
||||||
|
icon: icons.IconMessage
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'duplicateChatflow',
|
id: 'duplicateChatflow',
|
||||||
title: 'Duplicate Chatflow',
|
title: 'Duplicate Chatflow',
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ export default function themePalette(theme) {
|
|||||||
asyncSelect: {
|
asyncSelect: {
|
||||||
main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50
|
main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50
|
||||||
},
|
},
|
||||||
|
timeMessage: {
|
||||||
|
main: theme.customization.isDarkMode ? theme.colors?.darkLevel2 : theme.colors?.grey200
|
||||||
|
},
|
||||||
canvasHeader: {
|
canvasHeader: {
|
||||||
deployLight: theme.colors?.primaryLight,
|
deployLight: theme.colors?.primaryLight,
|
||||||
deployDark: theme.colors?.primaryDark,
|
deployDark: theme.colors?.primaryDark,
|
||||||
|
|||||||
@@ -0,0 +1,695 @@
|
|||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { useState, useEffect, forwardRef } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import moment from 'moment'
|
||||||
|
import rehypeMathjax from 'rehype-mathjax'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import remarkMath from 'remark-math'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
ListItemButton,
|
||||||
|
Box,
|
||||||
|
Stack,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
Chip
|
||||||
|
} from '@mui/material'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import DatePicker from 'react-datepicker'
|
||||||
|
|
||||||
|
import robotPNG from 'assets/images/robot.png'
|
||||||
|
import userPNG from 'assets/images/account.png'
|
||||||
|
import msgEmptySVG from 'assets/images/message_empty.svg'
|
||||||
|
import { IconFileExport, IconEraser, IconX } from '@tabler/icons'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||||
|
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||||
|
import SourceDocDialog from 'ui-component/dialog/SourceDocDialog'
|
||||||
|
import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown'
|
||||||
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatmessageApi from 'api/chatmessage'
|
||||||
|
import useApi from 'hooks/useApi'
|
||||||
|
import useConfirm from 'hooks/useConfirm'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { isValidURL, removeDuplicateURL } from 'utils/genericHelper'
|
||||||
|
import useNotifier from 'utils/useNotifier'
|
||||||
|
|
||||||
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||||
|
|
||||||
|
import 'views/chatmessage/ChatMessage.css'
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css'
|
||||||
|
|
||||||
|
const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) {
|
||||||
|
return (
|
||||||
|
<ListItemButton style={{ borderRadius: 15, border: '1px solid #e0e0e0' }} onClick={onClick} ref={ref}>
|
||||||
|
{value}
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
DatePickerCustomInput.propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
onClick: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const theme = useTheme()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
const { confirm } = useConfirm()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [chatlogs, setChatLogs] = useState([])
|
||||||
|
const [allChatlogs, setAllChatLogs] = useState([])
|
||||||
|
const [chatMessages, setChatMessages] = useState([])
|
||||||
|
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
||||||
|
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||||
|
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||||
|
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||||
|
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
|
const [endDate, setEndDate] = useState(new Date())
|
||||||
|
|
||||||
|
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||||
|
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||||
|
|
||||||
|
const onStartDateSelected = (date) => {
|
||||||
|
setStartDate(date)
|
||||||
|
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||||
|
startDate: date,
|
||||||
|
endDate: endDate,
|
||||||
|
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEndDateSelected = (date) => {
|
||||||
|
setEndDate(date)
|
||||||
|
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||||
|
endDate: date,
|
||||||
|
startDate: startDate,
|
||||||
|
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChatTypeSelected = (chatTypes) => {
|
||||||
|
setChatTypeFilter(chatTypes)
|
||||||
|
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||||
|
chatType: chatTypes.length ? chatTypes : undefined,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportMessages = () => {
|
||||||
|
const obj = {}
|
||||||
|
for (let i = 0; i < allChatlogs.length; i += 1) {
|
||||||
|
const chatmsg = allChatlogs[i]
|
||||||
|
const chatPK = getChatPK(chatmsg)
|
||||||
|
const msg = {
|
||||||
|
content: chatmsg.content,
|
||||||
|
role: chatmsg.role === 'apiMessage' ? 'bot' : 'user',
|
||||||
|
time: chatmsg.createdDate
|
||||||
|
}
|
||||||
|
if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||||
|
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||||
|
obj[chatPK] = {
|
||||||
|
id: chatmsg.chatId,
|
||||||
|
source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed',
|
||||||
|
sessionId: chatmsg.sessionId ?? null,
|
||||||
|
memoryType: chatmsg.memoryType ?? null,
|
||||||
|
messages: [msg]
|
||||||
|
}
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||||
|
obj[chatPK].messages = [...obj[chatPK].messages, msg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportMessages = []
|
||||||
|
for (const key in obj) {
|
||||||
|
exportMessages.push({
|
||||||
|
...obj[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < exportMessages.length; i += 1) {
|
||||||
|
exportMessages[i].messages = exportMessages[i].messages.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataStr = JSON.stringify(exportMessages, null, 2)
|
||||||
|
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||||
|
|
||||||
|
const exportFileDefaultName = `${dialogProps.chatflow.id}-Message.json`
|
||||||
|
|
||||||
|
let linkElement = document.createElement('a')
|
||||||
|
linkElement.setAttribute('href', dataUri)
|
||||||
|
linkElement.setAttribute('download', exportFileDefaultName)
|
||||||
|
linkElement.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearChat = async (chatmsg) => {
|
||||||
|
const description =
|
||||||
|
chatmsg.sessionId && chatmsg.memoryType
|
||||||
|
? `Are you sure you want to clear session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}?`
|
||||||
|
: `Are you sure you want to clear messages?`
|
||||||
|
const confirmPayload = {
|
||||||
|
title: `Clear Session`,
|
||||||
|
description,
|
||||||
|
confirmButtonName: 'Clear',
|
||||||
|
cancelButtonName: 'Cancel'
|
||||||
|
}
|
||||||
|
const isConfirmed = await confirm(confirmPayload)
|
||||||
|
|
||||||
|
const chatflowid = dialogProps.chatflow.id
|
||||||
|
if (isConfirmed) {
|
||||||
|
try {
|
||||||
|
const obj = { chatflowid, isClearFromViewMessageDialog: true }
|
||||||
|
if (chatmsg.chatId) obj.chatId = chatmsg.chatId
|
||||||
|
if (chatmsg.chatType) obj.chatType = chatmsg.chatType
|
||||||
|
if (chatmsg.memoryType) obj.memoryType = chatmsg.memoryType
|
||||||
|
if (chatmsg.sessionId) obj.sessionId = chatmsg.sessionId
|
||||||
|
|
||||||
|
await chatmessageApi.deleteChatmessage(chatflowid, obj)
|
||||||
|
const description =
|
||||||
|
chatmsg.sessionId && chatmsg.memoryType
|
||||||
|
? `Succesfully cleared session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}`
|
||||||
|
: `Succesfully cleared messages`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: description,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
getChatmessageApi.request(chatflowid)
|
||||||
|
} catch (error) {
|
||||||
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: errorData,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChatMessages = (chatmessages) => {
|
||||||
|
let prevDate = ''
|
||||||
|
const loadedMessages = []
|
||||||
|
for (let i = 0; i < chatmessages.length; i += 1) {
|
||||||
|
const chatmsg = chatmessages[i]
|
||||||
|
if (!prevDate) {
|
||||||
|
prevDate = chatmsg.createdDate.split('T')[0]
|
||||||
|
loadedMessages.push({
|
||||||
|
message: chatmsg.createdDate,
|
||||||
|
type: 'timeMessage'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const currentDate = chatmsg.createdDate.split('T')[0]
|
||||||
|
if (currentDate !== prevDate) {
|
||||||
|
prevDate = currentDate
|
||||||
|
loadedMessages.push({
|
||||||
|
message: chatmsg.createdDate,
|
||||||
|
type: 'timeMessage'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const obj = {
|
||||||
|
...chatmsg,
|
||||||
|
message: chatmsg.content,
|
||||||
|
type: chatmsg.role
|
||||||
|
}
|
||||||
|
if (chatmsg.sourceDocuments) obj.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||||
|
loadedMessages.push(obj)
|
||||||
|
}
|
||||||
|
setChatMessages(loadedMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChatPK = (chatmsg) => {
|
||||||
|
const chatId = chatmsg.chatId
|
||||||
|
const memoryType = chatmsg.memoryType ?? 'null'
|
||||||
|
const sessionId = chatmsg.sessionId ?? 'null'
|
||||||
|
return `${chatId}_${memoryType}_${sessionId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformChatPKToParams = (chatPK) => {
|
||||||
|
const chatId = chatPK.split('_')[0]
|
||||||
|
const memoryType = chatPK.split('_')[1]
|
||||||
|
const sessionId = chatPK.split('_')[2]
|
||||||
|
|
||||||
|
const params = { chatId }
|
||||||
|
if (memoryType !== 'null') params.memoryType = memoryType
|
||||||
|
if (sessionId !== 'null') params.sessionId = sessionId
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
const processChatLogs = (allChatMessages) => {
|
||||||
|
const seen = {}
|
||||||
|
const filteredChatLogs = []
|
||||||
|
for (let i = 0; i < allChatMessages.length; i += 1) {
|
||||||
|
const PK = getChatPK(allChatMessages[i])
|
||||||
|
|
||||||
|
const item = allChatMessages[i]
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(seen, PK)) {
|
||||||
|
seen[PK] = {
|
||||||
|
counter: 1,
|
||||||
|
item: allChatMessages[i]
|
||||||
|
}
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(seen, PK) && seen[PK].counter === 1) {
|
||||||
|
seen[PK] = {
|
||||||
|
counter: 2,
|
||||||
|
item: {
|
||||||
|
...seen[PK].item,
|
||||||
|
apiContent:
|
||||||
|
seen[PK].item.role === 'apiMessage' ? `Bot: ${seen[PK].item.content}` : `User: ${seen[PK].item.content}`,
|
||||||
|
userContent: item.role === 'apiMessage' ? `Bot: ${item.content}` : `User: ${item.content}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredChatLogs.push(seen[PK].item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setChatLogs(filteredChatLogs)
|
||||||
|
if (filteredChatLogs.length) return getChatPK(filteredChatLogs[0])
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemClick = (idx, chatmsg) => {
|
||||||
|
setSelectedMessageIndex(idx)
|
||||||
|
getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(getChatPK(chatmsg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onURLClick = (data) => {
|
||||||
|
window.open(data, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSourceDialogClick = (data) => {
|
||||||
|
setSourceDialogProps({ data })
|
||||||
|
setSourceDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getChatmessageFromPKApi.data) {
|
||||||
|
getChatMessages(getChatmessageFromPKApi.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getChatmessageFromPKApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getChatmessageApi.data) {
|
||||||
|
setAllChatLogs(getChatmessageApi.data)
|
||||||
|
const chatPK = processChatLogs(getChatmessageApi.data)
|
||||||
|
setSelectedMessageIndex(0)
|
||||||
|
if (chatPK) getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(chatPK))
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getChatmessageApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
getChatmessageApi.request(dialogProps.chatflow.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setChatLogs([])
|
||||||
|
setAllChatLogs([])
|
||||||
|
setChatMessages([])
|
||||||
|
setChatTypeFilter([])
|
||||||
|
setSelectedMessageIndex(0)
|
||||||
|
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
|
setEndDate(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
}, [show, dispatch])
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={show}
|
||||||
|
fullWidth
|
||||||
|
maxWidth={chatlogs && chatlogs.length == 0 ? 'md' : 'lg'}
|
||||||
|
aria-labelledby='alert-dialog-title'
|
||||||
|
aria-describedby='alert-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
{dialogProps.title}
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Button variant='outlined' onClick={() => exportMessages()} startIcon={<IconFileExport />}>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%', marginBottom: 10 }}>
|
||||||
|
<div style={{ marginRight: 10 }}>
|
||||||
|
<b style={{ marginRight: 10 }}>From Date</b>
|
||||||
|
<DatePicker
|
||||||
|
selected={startDate}
|
||||||
|
onChange={(date) => onStartDateSelected(date)}
|
||||||
|
selectsStart
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
customInput={<DatePickerCustomInput />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginRight: 10 }}>
|
||||||
|
<b style={{ marginRight: 10 }}>To Date</b>
|
||||||
|
<DatePicker
|
||||||
|
selected={endDate}
|
||||||
|
onChange={(date) => onEndDateSelected(date)}
|
||||||
|
selectsEnd
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
minDate={startDate}
|
||||||
|
maxDate={new Date()}
|
||||||
|
customInput={<DatePickerCustomInput />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', minWidth: '200px', marginRight: 10 }}>
|
||||||
|
<b style={{ marginRight: 10 }}>Source</b>
|
||||||
|
<MultiDropdown
|
||||||
|
key={JSON.stringify(chatTypeFilter)}
|
||||||
|
name='chatType'
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: 'UI',
|
||||||
|
name: 'INTERNAL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'API/Embed',
|
||||||
|
name: 'EXTERNAL'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onSelect={(newValue) => onChatTypeSelected(newValue)}
|
||||||
|
value={chatTypeFilter}
|
||||||
|
formControlSx={{ mt: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }}></div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
{chatlogs && chatlogs.length == 0 && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 5, height: 'auto' }}>
|
||||||
|
<img
|
||||||
|
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||||
|
src={msgEmptySVG}
|
||||||
|
alt='msgEmptySVG'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<div>No Messages</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{chatlogs && chatlogs.length > 0 && (
|
||||||
|
<div style={{ flexBasis: '40%' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
overflowY: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
maxHeight: 'calc(100vh - 260px)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chatlogs.map((chatmsg, index) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
borderRadius: `${customization.borderRadius}px`,
|
||||||
|
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
|
||||||
|
mt: 1,
|
||||||
|
ml: 1,
|
||||||
|
mr: 1,
|
||||||
|
mb: index === chatlogs.length - 1 ? 1 : 0
|
||||||
|
}}
|
||||||
|
selected={selectedMessageIndex === index}
|
||||||
|
onClick={() => handleItemClick(index, chatmsg)}
|
||||||
|
>
|
||||||
|
<ListItem alignItems='center'>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: 10 }}>
|
||||||
|
<span>{chatmsg?.userContent}</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxHeight: '100px',
|
||||||
|
maxWidth: '400px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chatmsg?.apiContent}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
secondary={moment(chatmsg.createdDate).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ListItemButton>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chatlogs && chatlogs.length > 0 && (
|
||||||
|
<div style={{ flexBasis: '60%', paddingRight: '30px' }}>
|
||||||
|
{chatMessages && chatMessages.length > 1 && (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
<div style={{ flex: 1, marginLeft: '20px', marginBottom: '15px', marginTop: '10px' }}>
|
||||||
|
{chatMessages[1].sessionId && (
|
||||||
|
<div>
|
||||||
|
Session Id: <b>{chatMessages[1].sessionId}</b>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chatMessages[1].chatType && (
|
||||||
|
<div>
|
||||||
|
Source: <b>{chatMessages[1].chatType === 'INTERNAL' ? 'UI' : 'API/Embed'}</b>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chatMessages[1].memoryType && (
|
||||||
|
<div>
|
||||||
|
Memory: <b>{chatMessages[1].memoryType}</b>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignContent: 'center',
|
||||||
|
alignItems: 'end'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledButton
|
||||||
|
sx={{ height: 'max-content', width: 'max-content' }}
|
||||||
|
variant='outlined'
|
||||||
|
color='error'
|
||||||
|
title='Clear Message'
|
||||||
|
onClick={() => clearChat(chatMessages[1])}
|
||||||
|
startIcon={<IconEraser />}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</StyledButton>
|
||||||
|
{chatMessages[1].sessionId && (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
'At your left 👈 you will see the Memory node that was used in this conversation. You need to have the matching Memory node with same parameters in the canvas, in order to delete the session conversations stored on the Memory node'
|
||||||
|
}
|
||||||
|
placement='bottom'
|
||||||
|
>
|
||||||
|
<h5 style={{ cursor: 'pointer', color: theme.palette.primary.main }}>
|
||||||
|
Why my session is not deleted?
|
||||||
|
</h5>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: '20px',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
borderRadius: `${customization.borderRadius}px`
|
||||||
|
}}
|
||||||
|
className='cloud-message'
|
||||||
|
>
|
||||||
|
<div style={{ width: '100%', height: '100%' }}>
|
||||||
|
{chatMessages &&
|
||||||
|
chatMessages.map((message, index) => {
|
||||||
|
if (message.type === 'apiMessage' || message.type === 'userMessage') {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
background:
|
||||||
|
message.type === 'apiMessage' ? theme.palette.asyncSelect.main : '',
|
||||||
|
pl: 1,
|
||||||
|
pr: 1
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }}
|
||||||
|
>
|
||||||
|
{/* Display the correct icon depending on the message type */}
|
||||||
|
{message.type === 'apiMessage' ? (
|
||||||
|
<img
|
||||||
|
style={{ marginLeft: '10px' }}
|
||||||
|
src={robotPNG}
|
||||||
|
alt='AI'
|
||||||
|
width='25'
|
||||||
|
height='25'
|
||||||
|
className='boticon'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
style={{ marginLeft: '10px' }}
|
||||||
|
src={userPNG}
|
||||||
|
alt='Me'
|
||||||
|
width='25'
|
||||||
|
height='25'
|
||||||
|
className='usericon'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='markdownanswer'>
|
||||||
|
{/* Messages are being rendered in Markdown format */}
|
||||||
|
<MemoizedReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeMathjax]}
|
||||||
|
components={{
|
||||||
|
code({ inline, className, children, ...props }) {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
return !inline ? (
|
||||||
|
<CodeBlock
|
||||||
|
key={Math.random()}
|
||||||
|
chatflowid={dialogProps.chatflow.id}
|
||||||
|
isDialog={true}
|
||||||
|
language={(match && match[1]) || ''}
|
||||||
|
value={String(children).replace(/\n$/, '')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{message.message}
|
||||||
|
</MemoizedReactMarkdown>
|
||||||
|
</div>
|
||||||
|
{message.sourceDocuments && (
|
||||||
|
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||||
|
{removeDuplicateURL(message).map((source, index) => {
|
||||||
|
const URL = isValidURL(source.metadata.source)
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
size='small'
|
||||||
|
key={index}
|
||||||
|
label={
|
||||||
|
URL
|
||||||
|
? URL.pathname.substring(0, 15) === '/'
|
||||||
|
? URL.host
|
||||||
|
: `${URL.pathname.substring(0, 15)}...`
|
||||||
|
: `${source.pageContent.substring(0, 15)}...`
|
||||||
|
}
|
||||||
|
component='a'
|
||||||
|
sx={{ mr: 1, mb: 1 }}
|
||||||
|
variant='outlined'
|
||||||
|
clickable
|
||||||
|
onClick={() =>
|
||||||
|
URL
|
||||||
|
? onURLClick(source.metadata.source)
|
||||||
|
: onSourceDialogClick(source)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.timeMessage.main,
|
||||||
|
p: 2
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }}
|
||||||
|
>
|
||||||
|
{moment(message.message).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||||
|
</>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewMessagesDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewMessagesDialog
|
||||||
@@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MultiDropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => {
|
export const MultiDropdown = ({ name, value, options, onSelect, formControlSx = {}, disabled = false, disableClearable = false }) => {
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
const findMatchingOptions = (options = [], internalValue) => {
|
const findMatchingOptions = (options = [], internalValue) => {
|
||||||
let values = []
|
let values = []
|
||||||
@@ -30,7 +30,7 @@ export const MultiDropdown = ({ name, value, options, onSelect, disabled = false
|
|||||||
let [internalValue, setInternalValue] = useState(value ?? [])
|
let [internalValue, setInternalValue] = useState(value ?? [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
<FormControl sx={{ mt: 1, width: '100%', ...formControlSx }} size='small'>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={name}
|
id={name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -75,5 +75,6 @@ MultiDropdown.propTypes = {
|
|||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
formControlSx: PropTypes.object,
|
||||||
disableClearable: PropTypes.bool
|
disableClearable: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -415,6 +415,23 @@ export const getInputVariables = (paramValue) => {
|
|||||||
return inputVariables
|
return inputVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const removeDuplicateURL = (message) => {
|
||||||
|
const visitedURLs = []
|
||||||
|
const newSourceDocuments = []
|
||||||
|
|
||||||
|
if (!message.sourceDocuments) return newSourceDocuments
|
||||||
|
|
||||||
|
message.sourceDocuments.forEach((source) => {
|
||||||
|
if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) {
|
||||||
|
visitedURLs.push(source.metadata.source)
|
||||||
|
newSourceDocuments.push(source)
|
||||||
|
} else if (!isValidURL(source.metadata.source)) {
|
||||||
|
newSourceDocuments.push(source)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newSourceDocuments
|
||||||
|
}
|
||||||
|
|
||||||
export const isValidURL = (url) => {
|
export const isValidURL = (url) => {
|
||||||
try {
|
try {
|
||||||
return new URL(url)
|
return new URL(url)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Settings from 'views/settings'
|
|||||||
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
||||||
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
||||||
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
||||||
|
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import chatflowsApi from 'api/chatflows'
|
import chatflowsApi from 'api/chatflows'
|
||||||
@@ -44,6 +45,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
||||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
||||||
|
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||||
|
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||||
|
|
||||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||||
const canvas = useSelector((state) => state.canvas)
|
const canvas = useSelector((state) => state.canvas)
|
||||||
@@ -59,6 +62,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
chatflow: chatflow
|
chatflow: chatflow
|
||||||
})
|
})
|
||||||
setAnalyseDialogOpen(true)
|
setAnalyseDialogOpen(true)
|
||||||
|
} else if (setting === 'viewMessages') {
|
||||||
|
setViewMessagesDialogProps({
|
||||||
|
title: 'View Messages',
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setViewMessagesDialogOpen(true)
|
||||||
} else if (setting === 'duplicateChatflow') {
|
} else if (setting === 'duplicateChatflow') {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||||
@@ -69,7 +78,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
} else if (setting === 'exportChatflow') {
|
} else if (setting === 'exportChatflow') {
|
||||||
try {
|
try {
|
||||||
const flowData = JSON.parse(chatflow.flowData)
|
const flowData = JSON.parse(chatflow.flowData)
|
||||||
let dataStr = JSON.stringify(generateExportFlowData(flowData))
|
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||||
|
|
||||||
let exportFileDefaultName = `${chatflow.name} Chatflow.json`
|
let exportFileDefaultName = `${chatflow.name} Chatflow.json`
|
||||||
@@ -367,6 +376,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
/>
|
/>
|
||||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
||||||
|
<ViewMessagesDialog
|
||||||
|
show={viewMessagesDialogOpen}
|
||||||
|
dialogProps={viewMessagesDialogProps}
|
||||||
|
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ const ShareChatbot = ({ isSessionMemory }) => {
|
|||||||
const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false)
|
const [isPublicChatflow, setChatflowIsPublic] = useState(chatflow.isPublic ?? false)
|
||||||
const [generateNewSession, setGenerateNewSession] = useState(chatbotConfig?.generateNewSession ?? false)
|
const [generateNewSession, setGenerateNewSession] = useState(chatbotConfig?.generateNewSession ?? false)
|
||||||
|
|
||||||
|
const [title, setTitle] = useState(chatbotConfig?.title ?? '')
|
||||||
|
const [titleAvatarSrc, setTitleAvatarSrc] = useState(chatbotConfig?.titleAvatarSrc ?? '')
|
||||||
|
|
||||||
const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '')
|
const [welcomeMessage, setWelcomeMessage] = useState(chatbotConfig?.welcomeMessage ?? '')
|
||||||
const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor)
|
const [backgroundColor, setBackgroundColor] = useState(chatbotConfig?.backgroundColor ?? defaultConfig.backgroundColor)
|
||||||
const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize)
|
const [fontSize, setFontSize] = useState(chatbotConfig?.fontSize ?? defaultConfig.fontSize)
|
||||||
@@ -108,6 +111,8 @@ const ShareChatbot = ({ isSessionMemory }) => {
|
|||||||
textInput: {},
|
textInput: {},
|
||||||
overrideConfig: {}
|
overrideConfig: {}
|
||||||
}
|
}
|
||||||
|
if (title) obj.title = title
|
||||||
|
if (titleAvatarSrc) obj.titleAvatarSrc = titleAvatarSrc
|
||||||
if (welcomeMessage) obj.welcomeMessage = welcomeMessage
|
if (welcomeMessage) obj.welcomeMessage = welcomeMessage
|
||||||
if (backgroundColor) obj.backgroundColor = backgroundColor
|
if (backgroundColor) obj.backgroundColor = backgroundColor
|
||||||
if (fontSize) obj.fontSize = fontSize
|
if (fontSize) obj.fontSize = fontSize
|
||||||
@@ -252,6 +257,12 @@ const ShareChatbot = ({ isSessionMemory }) => {
|
|||||||
|
|
||||||
const onTextChanged = (value, fieldName) => {
|
const onTextChanged = (value, fieldName) => {
|
||||||
switch (fieldName) {
|
switch (fieldName) {
|
||||||
|
case 'title':
|
||||||
|
setTitle(value)
|
||||||
|
break
|
||||||
|
case 'titleAvatarSrc':
|
||||||
|
setTitleAvatarSrc(value)
|
||||||
|
break
|
||||||
case 'welcomeMessage':
|
case 'welcomeMessage':
|
||||||
setWelcomeMessage(value)
|
setWelcomeMessage(value)
|
||||||
break
|
break
|
||||||
@@ -395,6 +406,14 @@ const ShareChatbot = ({ isSessionMemory }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{textField(title, 'title', 'Title', 'string', 'Flowise Assistant')}
|
||||||
|
{textField(
|
||||||
|
titleAvatarSrc,
|
||||||
|
'titleAvatarSrc',
|
||||||
|
'Title Avatar Link',
|
||||||
|
'string',
|
||||||
|
`https://raw.githubusercontent.com/FlowiseAI/Flowise/main/assets/FloWiseAI_dark.png`
|
||||||
|
)}
|
||||||
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')}
|
{textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')}
|
||||||
{colorField(backgroundColor, 'backgroundColor', 'Background Color')}
|
{colorField(backgroundColor, 'backgroundColor', 'Background Color')}
|
||||||
{textField(fontSize, 'fontSize', 'Font Size', 'number')}
|
{textField(fontSize, 'fontSize', 'Font Size', 'number')}
|
||||||
|
|||||||
@@ -134,3 +134,13 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cloud-message {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 260px);
|
||||||
|
overflow-y: scroll;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { baseURL, maxScroll } from 'store/constant'
|
|||||||
|
|
||||||
import robotPNG from 'assets/images/robot.png'
|
import robotPNG from 'assets/images/robot.png'
|
||||||
import userPNG from 'assets/images/account.png'
|
import userPNG from 'assets/images/account.png'
|
||||||
import { isValidURL } from 'utils/genericHelper'
|
import { isValidURL, removeDuplicateURL } from 'utils/genericHelper'
|
||||||
|
|
||||||
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@@ -50,9 +50,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
const [isChatFlowAvailableToStream, setIsChatFlowAvailableToStream] = useState(false)
|
const [isChatFlowAvailableToStream, setIsChatFlowAvailableToStream] = useState(false)
|
||||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||||
|
const [chatId, setChatId] = useState(undefined)
|
||||||
|
|
||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
const getChatmessageApi = useApi(chatmessageApi.getChatmessageFromChatflow)
|
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||||
|
|
||||||
const onSourceDialogClick = (data) => {
|
const onSourceDialogClick = (data) => {
|
||||||
@@ -64,21 +65,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
window.open(data, '_blank')
|
window.open(data, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDuplicateURL = (message) => {
|
|
||||||
const visitedURLs = []
|
|
||||||
const newSourceDocuments = []
|
|
||||||
|
|
||||||
message.sourceDocuments.forEach((source) => {
|
|
||||||
if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) {
|
|
||||||
visitedURLs.push(source.metadata.source)
|
|
||||||
newSourceDocuments.push(source)
|
|
||||||
} else if (!isValidURL(source.metadata.source)) {
|
|
||||||
newSourceDocuments.push(source)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return newSourceDocuments
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
if (ps.current) {
|
if (ps.current) {
|
||||||
ps.current.scrollTo({ top: maxScroll })
|
ps.current.scrollTo({ top: maxScroll })
|
||||||
@@ -87,20 +73,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
|
|
||||||
const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput])
|
const onChange = useCallback((e) => setUserInput(e.target.value), [setUserInput])
|
||||||
|
|
||||||
const addChatMessage = async (message, type, sourceDocuments) => {
|
|
||||||
try {
|
|
||||||
const newChatMessageBody = {
|
|
||||||
role: type,
|
|
||||||
content: message,
|
|
||||||
chatflowid: chatflowid
|
|
||||||
}
|
|
||||||
if (sourceDocuments) newChatMessageBody.sourceDocuments = JSON.stringify(sourceDocuments)
|
|
||||||
await chatmessageApi.createNewChatmessage(chatflowid, newChatMessageBody)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateLastMessage = (text) => {
|
const updateLastMessage = (text) => {
|
||||||
setMessages((prevMessages) => {
|
setMessages((prevMessages) => {
|
||||||
let allMessages = [...cloneDeep(prevMessages)]
|
let allMessages = [...cloneDeep(prevMessages)]
|
||||||
@@ -123,7 +95,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
||||||
message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '')
|
message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '')
|
||||||
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
|
setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }])
|
||||||
addChatMessage(message, 'apiMessage')
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -141,14 +112,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }])
|
setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage' }])
|
||||||
// waiting for first chatmessage saved, the first chatmessage will be used in sendMessageAndGetPrediction
|
|
||||||
await addChatMessage(userInput, 'userMessage')
|
|
||||||
|
|
||||||
// Send user question and history to API
|
// Send user question and history to API
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
question: userInput,
|
question: userInput,
|
||||||
history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?')
|
history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'),
|
||||||
|
chatId
|
||||||
}
|
}
|
||||||
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
||||||
|
|
||||||
@@ -157,20 +127,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
let data = response.data
|
let data = response.data
|
||||||
|
|
||||||
if (typeof data === 'object' && data.text && data.sourceDocuments) {
|
if (!chatId) {
|
||||||
if (!isChatFlowAvailableToStream) {
|
setChatId(data.chatId)
|
||||||
setMessages((prevMessages) => [
|
localStorage.setItem(`${chatflowid}_INTERNAL`, data.chatId)
|
||||||
...prevMessages,
|
|
||||||
{ message: data.text, sourceDocuments: data.sourceDocuments, type: 'apiMessage' }
|
|
||||||
])
|
|
||||||
}
|
|
||||||
addChatMessage(data.text, 'apiMessage', data.sourceDocuments)
|
|
||||||
} else {
|
|
||||||
if (!isChatFlowAvailableToStream) {
|
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: data, type: 'apiMessage' }])
|
|
||||||
}
|
|
||||||
addChatMessage(data, 'apiMessage')
|
|
||||||
}
|
}
|
||||||
|
if (!isChatFlowAvailableToStream) {
|
||||||
|
setMessages((prevMessages) => [
|
||||||
|
...prevMessages,
|
||||||
|
{ message: data.text, sourceDocuments: data?.sourceDocuments, type: 'apiMessage' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -200,16 +167,18 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
|
|
||||||
// Get chatmessages successful
|
// Get chatmessages successful
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getChatmessageApi.data) {
|
if (getChatmessageApi.data?.length) {
|
||||||
const loadedMessages = []
|
const chatId = getChatmessageApi.data[0]?.chatId
|
||||||
for (const message of getChatmessageApi.data) {
|
setChatId(chatId)
|
||||||
|
localStorage.setItem(`${chatflowid}_INTERNAL`, chatId)
|
||||||
|
const loadedMessages = getChatmessageApi.data.map((message) => {
|
||||||
const obj = {
|
const obj = {
|
||||||
message: message.content,
|
message: message.content,
|
||||||
type: message.role
|
type: message.role
|
||||||
}
|
}
|
||||||
if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments)
|
if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments)
|
||||||
loadedMessages.push(obj)
|
return obj
|
||||||
}
|
})
|
||||||
setMessages((prevMessages) => [...prevMessages, ...loadedMessages])
|
setMessages((prevMessages) => [...prevMessages, ...loadedMessages])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ export const ChatPopUp = ({ chatflowid }) => {
|
|||||||
|
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
await chatmessageApi.deleteChatmessage(chatflowid)
|
const chatId = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||||
|
await chatmessageApi.deleteChatmessage(chatflowid, { chatId, chatType: 'INTERNAL' })
|
||||||
|
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||||
resetChatDialog()
|
resetChatDialog()
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: 'Succesfully cleared all chat history',
|
message: 'Succesfully cleared all chat history',
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
|
|||||||
delete toolData.id
|
delete toolData.id
|
||||||
delete toolData.createdDate
|
delete toolData.createdDate
|
||||||
delete toolData.updatedDate
|
delete toolData.updatedDate
|
||||||
let dataStr = JSON.stringify(toolData)
|
let dataStr = JSON.stringify(toolData, null, 2)
|
||||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||||
|
|
||||||
let exportFileDefaultName = `${toolName}-CustomTool.json`
|
let exportFileDefaultName = `${toolName}-CustomTool.json`
|
||||||
|
|||||||
Reference in New Issue
Block a user