Feature: Collect contact information from users inside the chatbot (#1948)
* Add leads settings to chatflow configuration * Add leads tab to chatflow configuration with options for lead capture * Add database entity and migrations for leads * Add endpoint for adding and fetching leads * Show lead capture form in UI chat window when enabled * Add view leads dialog * Make export leads functional * Add input for configuring message on successful lead capture * Add migrations for adding lead email in chat message if available * show lead email in view messages * ui touch up * Remove unused code and update how lead email is shown in view messages dialog * Fix lead not getting saved * Disable input when lead form is shown and save lead info to localstorage * Fix lead capture form not working * disabled lead save button until at least one form field is turned on, get rid of local storage _LEAD * add leads API to as whitelist public endpoint * Send leadEmail in internal chat inputs * Fix condition for disabling input field and related buttons when lead is enabled/disabled and when lead is saved * update leads ui * update error message and alter table add column sqlite migration --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
parent
adea2f0830
commit
db452cd74d
|
|
@ -42,6 +42,7 @@ export interface IChatMessage {
|
||||||
memoryType?: string
|
memoryType?: string
|
||||||
sessionId?: string
|
sessionId?: string
|
||||||
createdDate: Date
|
createdDate: Date
|
||||||
|
leadEmail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChatMessageFeedback {
|
export interface IChatMessageFeedback {
|
||||||
|
|
@ -93,6 +94,16 @@ export interface IVariable {
|
||||||
createdDate: Date
|
createdDate: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ILead {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
chatflowid: string
|
||||||
|
chatId: string
|
||||||
|
createdDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
export interface IUpsertHistory {
|
export interface IUpsertHistory {
|
||||||
id: string
|
id: string
|
||||||
chatflowid: string
|
chatflowid: string
|
||||||
|
|
@ -200,6 +211,7 @@ export interface IncomingInput {
|
||||||
chatId?: string
|
chatId?: string
|
||||||
stopNodeId?: string
|
stopNodeId?: string
|
||||||
uploads?: IFileUpload[]
|
uploads?: IFileUpload[]
|
||||||
|
leadEmail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IActiveChatflows {
|
export interface IActiveChatflows {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Request, Response, NextFunction } from 'express'
|
||||||
|
import leadsService from '../../services/leads'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
|
|
||||||
|
const getAllLeadsForChatflow = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.params.id === 'undefined' || req.params.id === '') {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: leadsController.getAllLeadsForChatflow - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const chatflowid = req.params.id
|
||||||
|
const apiResponse = await leadsService.getAllLeads(chatflowid)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLeadInChatflow = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.body === 'undefined' || req.body === '') {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: leadsController.createLeadInChatflow - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await leadsService.createLead(req.body)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createLeadInChatflow,
|
||||||
|
getAllLeadsForChatflow
|
||||||
|
}
|
||||||
|
|
@ -44,4 +44,7 @@ export class ChatMessage implements IChatMessage {
|
||||||
@Column({ type: 'timestamp' })
|
@Column({ type: 'timestamp' })
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdDate: Date
|
createdDate: Date
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'text' })
|
||||||
|
leadEmail?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'
|
||||||
|
import { ILead } from '../../Interface'
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Lead implements ILead {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
email?: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
phone?: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
chatflowid: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
chatId: string
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdDate: Date
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { Credential } from './Credential'
|
||||||
import { Tool } from './Tool'
|
import { Tool } from './Tool'
|
||||||
import { Assistant } from './Assistant'
|
import { Assistant } from './Assistant'
|
||||||
import { Variable } from './Variable'
|
import { Variable } from './Variable'
|
||||||
|
import { Lead } from './Lead'
|
||||||
import { UpsertHistory } from './UpsertHistory'
|
import { UpsertHistory } from './UpsertHistory'
|
||||||
|
|
||||||
export const entities = {
|
export const entities = {
|
||||||
|
|
@ -15,5 +16,6 @@ export const entities = {
|
||||||
Tool,
|
Tool,
|
||||||
Assistant,
|
Assistant,
|
||||||
Variable,
|
Variable,
|
||||||
|
Lead,
|
||||||
UpsertHistory
|
UpsertHistory
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLead1710832127079 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS \`lead\` (
|
||||||
|
\`id\` varchar(36) NOT NULL,
|
||||||
|
\`chatflowid\` varchar(255) NOT NULL,
|
||||||
|
\`chatId\` varchar(255) NOT NULL,
|
||||||
|
\`name\` text,
|
||||||
|
\`email\` text,
|
||||||
|
\`phone\` text,
|
||||||
|
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE lead`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLeadToChatMessage1711538023578 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
const columnExists = await queryRunner.hasColumn('chat_message', 'leadEmail')
|
||||||
|
if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_message\` ADD COLUMN \`leadEmail\` TEXT;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE \`chat_message\` DROP COLUMN \`leadEmail\`;`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntit
|
||||||
import { AddSpeechToText1706364937060 } from './1706364937060-AddSpeechToText'
|
import { AddSpeechToText1706364937060 } from './1706364937060-AddSpeechToText'
|
||||||
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
||||||
import { AddFeedback1707213626553 } from './1707213626553-AddFeedback'
|
import { AddFeedback1707213626553 } from './1707213626553-AddFeedback'
|
||||||
|
import { AddLead1710832127079 } from './1710832127079-AddLead'
|
||||||
|
import { AddLeadToChatMessage1711538023578 } from './1711538023578-AddLeadToChatMessage'
|
||||||
|
|
||||||
export const mysqlMigrations = [
|
export const mysqlMigrations = [
|
||||||
Init1693840429259,
|
Init1693840429259,
|
||||||
|
|
@ -33,5 +35,7 @@ export const mysqlMigrations = [
|
||||||
AddVariableEntity1699325775451,
|
AddVariableEntity1699325775451,
|
||||||
AddSpeechToText1706364937060,
|
AddSpeechToText1706364937060,
|
||||||
AddUpsertHistoryEntity1709814301358,
|
AddUpsertHistoryEntity1709814301358,
|
||||||
AddFeedback1707213626553
|
AddFeedback1707213626553,
|
||||||
|
AddLead1710832127079,
|
||||||
|
AddLeadToChatMessage1711538023578
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLead1710832137905 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS lead (
|
||||||
|
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
"chatflowid" varchar NOT NULL,
|
||||||
|
"chatId" varchar NOT NULL,
|
||||||
|
"name" text,
|
||||||
|
"email" text,
|
||||||
|
"phone" text,
|
||||||
|
"createdDate" timestamp NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_98419043dd704f54-9830ab78f0" PRIMARY KEY (id)
|
||||||
|
);`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE lead`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLeadToChatMessage1711538016098 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN IF NOT EXISTS "leadEmail" TEXT;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "leadEmail";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@ import { AddSpeechToText1706364937060 } from './1706364937060-AddSpeechToText'
|
||||||
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
||||||
import { AddFeedback1707213601923 } from './1707213601923-AddFeedback'
|
import { AddFeedback1707213601923 } from './1707213601923-AddFeedback'
|
||||||
import { FieldTypes1710497452584 } from './1710497452584-FieldTypes'
|
import { FieldTypes1710497452584 } from './1710497452584-FieldTypes'
|
||||||
|
import { AddLead1710832137905 } from './1710832137905-AddLead'
|
||||||
|
import { AddLeadToChatMessage1711538016098 } from './1711538016098-AddLeadToChatMessage'
|
||||||
|
|
||||||
export const postgresMigrations = [
|
export const postgresMigrations = [
|
||||||
Init1693891895163,
|
Init1693891895163,
|
||||||
|
|
@ -35,5 +37,7 @@ export const postgresMigrations = [
|
||||||
AddSpeechToText1706364937060,
|
AddSpeechToText1706364937060,
|
||||||
AddUpsertHistoryEntity1709814301358,
|
AddUpsertHistoryEntity1709814301358,
|
||||||
AddFeedback1707213601923,
|
AddFeedback1707213601923,
|
||||||
FieldTypes1710497452584
|
FieldTypes1710497452584,
|
||||||
|
AddLead1710832137905,
|
||||||
|
AddLeadToChatMessage1711538016098
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLead1710832117612 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE IF NOT EXISTS "lead" ("id" varchar PRIMARY KEY NOT NULL, "chatflowid" varchar NOT NULL, "chatId" varchar NOT NULL, "name" text, "email" text, "phone" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')));`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE IF EXISTS "lead";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddLeadToChatMessage1711537986113 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" ADD COLUMN "leadEmail" TEXT;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "chat_message" DROP COLUMN "leadEmail";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntit
|
||||||
import { AddSpeechToText1706364937060 } from './1706364937060-AddSpeechToText'
|
import { AddSpeechToText1706364937060 } from './1706364937060-AddSpeechToText'
|
||||||
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
import { AddUpsertHistoryEntity1709814301358 } from './1709814301358-AddUpsertHistoryEntity'
|
||||||
import { AddFeedback1707213619308 } from './1707213619308-AddFeedback'
|
import { AddFeedback1707213619308 } from './1707213619308-AddFeedback'
|
||||||
|
import { AddLead1710832117612 } from './1710832117612-AddLead'
|
||||||
|
import { AddLeadToChatMessage1711537986113 } from './1711537986113-AddLeadToChatMessage'
|
||||||
|
|
||||||
export const sqliteMigrations = [
|
export const sqliteMigrations = [
|
||||||
Init1693835579790,
|
Init1693835579790,
|
||||||
|
|
@ -33,5 +35,7 @@ export const sqliteMigrations = [
|
||||||
AddVariableEntity1699325775451,
|
AddVariableEntity1699325775451,
|
||||||
AddSpeechToText1706364937060,
|
AddSpeechToText1706364937060,
|
||||||
AddUpsertHistoryEntity1709814301358,
|
AddUpsertHistoryEntity1709814301358,
|
||||||
AddFeedback1707213619308
|
AddFeedback1707213619308,
|
||||||
|
AddLead1710832117612,
|
||||||
|
AddLeadToChatMessage1711537986113
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ export class App {
|
||||||
'/api/v1/chatflows-uploads',
|
'/api/v1/chatflows-uploads',
|
||||||
'/api/v1/openai-assistants-file/download',
|
'/api/v1/openai-assistants-file/download',
|
||||||
'/api/v1/feedback',
|
'/api/v1/feedback',
|
||||||
|
'/api/v1/leads',
|
||||||
'/api/v1/get-upload-file',
|
'/api/v1/get-upload-file',
|
||||||
'/api/v1/ip'
|
'/api/v1/ip'
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import internalChatmessagesRouter from './internal-chat-messages'
|
||||||
import internalPredictionRouter from './internal-predictions'
|
import internalPredictionRouter from './internal-predictions'
|
||||||
import getUploadFileRouter from './get-upload-file'
|
import getUploadFileRouter from './get-upload-file'
|
||||||
import getUploadPathRouter from './get-upload-path'
|
import getUploadPathRouter from './get-upload-path'
|
||||||
|
import leadsRouter from './leads'
|
||||||
import loadPromptRouter from './load-prompts'
|
import loadPromptRouter from './load-prompts'
|
||||||
import marketplacesRouter from './marketplaces'
|
import marketplacesRouter from './marketplaces'
|
||||||
import nodeConfigRouter from './node-configs'
|
import nodeConfigRouter from './node-configs'
|
||||||
|
|
@ -55,6 +56,7 @@ router.use('/internal-chatmessage', internalChatmessagesRouter)
|
||||||
router.use('/internal-prediction', internalPredictionRouter)
|
router.use('/internal-prediction', internalPredictionRouter)
|
||||||
router.use('/get-upload-file', getUploadFileRouter)
|
router.use('/get-upload-file', getUploadFileRouter)
|
||||||
router.use('/get-upload-path', getUploadPathRouter)
|
router.use('/get-upload-path', getUploadPathRouter)
|
||||||
|
router.use('/leads', leadsRouter)
|
||||||
router.use('/load-prompt', loadPromptRouter)
|
router.use('/load-prompt', loadPromptRouter)
|
||||||
router.use('/marketplaces', marketplacesRouter)
|
router.use('/marketplaces', marketplacesRouter)
|
||||||
router.use('/node-config', nodeConfigRouter)
|
router.use('/node-config', nodeConfigRouter)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import express from 'express'
|
||||||
|
import leadsController from '../../controllers/leads'
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
router.post('/', leadsController.createLeadInChatflow)
|
||||||
|
|
||||||
|
// READ
|
||||||
|
router.get(['/', '/:id'], leadsController.getAllLeadsForChatflow)
|
||||||
|
|
||||||
|
export default router
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
|
import { Lead } from '../../database/entities/Lead'
|
||||||
|
import { ILead } from '../../Interface'
|
||||||
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
|
|
||||||
|
const getAllLeads = async (chatflowid: string) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const dbResponse = await appServer.AppDataSource.getRepository(Lead).find({
|
||||||
|
where: {
|
||||||
|
chatflowid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: leadsService.getAllLeads - ${getErrorMessage(error)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLead = async (body: Partial<ILead>) => {
|
||||||
|
try {
|
||||||
|
const chatId = body.chatId ?? uuidv4()
|
||||||
|
|
||||||
|
const newLead = new Lead()
|
||||||
|
Object.assign(newLead, body)
|
||||||
|
Object.assign(newLead, { chatId })
|
||||||
|
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const lead = appServer.AppDataSource.getRepository(Lead).create(newLead)
|
||||||
|
const dbResponse = await appServer.AppDataSource.getRepository(Lead).save(lead)
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: leadsService.createLead - ${getErrorMessage(error)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createLead,
|
||||||
|
getAllLeads
|
||||||
|
}
|
||||||
|
|
@ -325,7 +325,8 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
|
||||||
memoryType,
|
memoryType,
|
||||||
sessionId,
|
sessionId,
|
||||||
createdDate: userMessageDateTime,
|
createdDate: userMessageDateTime,
|
||||||
fileUploads: incomingInput.uploads ? JSON.stringify(fileUploads) : undefined
|
fileUploads: incomingInput.uploads ? JSON.stringify(fileUploads) : undefined,
|
||||||
|
leadEmail: incomingInput.leadEmail
|
||||||
}
|
}
|
||||||
await utilAddChatMessage(userMessage)
|
await utilAddChatMessage(userMessage)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getLeads = (id) => client.get(`/leads/${id}`)
|
||||||
|
const addLead = (body) => client.post(`/leads/`, body)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getLeads,
|
||||||
|
addLead
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -6,7 +6,8 @@ import {
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal
|
IconAdjustmentsHorizontal,
|
||||||
|
IconUsers
|
||||||
} from '@tabler/icons'
|
} from '@tabler/icons'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
|
|
@ -17,7 +18,8 @@ const icons = {
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconDatabaseExport,
|
IconDatabaseExport,
|
||||||
IconAdjustmentsHorizontal
|
IconAdjustmentsHorizontal,
|
||||||
|
IconUsers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
|
|
@ -34,6 +36,13 @@ const settings = {
|
||||||
url: '',
|
url: '',
|
||||||
icon: icons.IconMessage
|
icon: icons.IconMessage
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'viewLeads',
|
||||||
|
title: 'View Leads',
|
||||||
|
type: 'item',
|
||||||
|
url: '',
|
||||||
|
icon: icons.IconUsers
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'viewUpsertHistory',
|
id: 'viewUpsertHistory',
|
||||||
title: 'Upsert History',
|
title: 'Upsert History',
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ const StatsCard = ({ title, stat }) => {
|
||||||
return (
|
return (
|
||||||
<Card sx={{ border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
<Card sx={{ border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography sx={{ fontSize: 14 }} color='text.primary' gutterBottom>
|
<Typography sx={{ fontSize: '0.875rem' }} color='text.primary' gutterBottom>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ fontSize: 30, fontWeight: 500 }} color='text.primary'>
|
<Typography sx={{ fontSize: '1.5rem', fontWeight: 500 }} color='text.primary'>
|
||||||
{stat}
|
{stat}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ import PropTypes from 'prop-types'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
||||||
|
import { tabsClasses } from '@mui/material/Tabs'
|
||||||
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||||
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||||
|
import Leads from '@/ui-component/extended/Leads'
|
||||||
|
|
||||||
const CHATFLOW_CONFIGURATION_TABS = [
|
const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
|
|
@ -33,6 +35,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
||||||
{
|
{
|
||||||
label: 'Analyse Chatflow',
|
label: 'Analyse Chatflow',
|
||||||
id: 'analyseChatflow'
|
id: 'analyseChatflow'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Leads',
|
||||||
|
id: 'leads'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -83,10 +89,19 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Tabs
|
<Tabs
|
||||||
sx={{ position: 'relative', minHeight: '40px', height: '40px' }}
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
minHeight: '40px',
|
||||||
|
height: '40px',
|
||||||
|
[`& .${tabsClasses.scrollButtons}`]: {
|
||||||
|
'&.Mui-disabled': { opacity: 0.3 }
|
||||||
|
}
|
||||||
|
}}
|
||||||
value={tabValue}
|
value={tabValue}
|
||||||
onChange={(event, value) => setTabValue(value)}
|
onChange={(event, value) => setTabValue(value)}
|
||||||
aria-label='tabs'
|
aria-label='tabs'
|
||||||
|
variant='scrollable'
|
||||||
|
scrollButtons='auto'
|
||||||
>
|
>
|
||||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
|
|
@ -105,6 +120,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||||
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||||
|
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useState, useEffect, forwardRef } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ListItemButton,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Stack,
|
||||||
|
Box,
|
||||||
|
OutlinedInput
|
||||||
|
} from '@mui/material'
|
||||||
|
import { useTheme } from '@mui/material/styles'
|
||||||
|
import { IconFileExport, IconSearch } from '@tabler/icons'
|
||||||
|
import leadsEmptySVG from '@/assets/images/leads_empty.svg'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import useApi from '@/hooks/useApi'
|
||||||
|
import leadsApi from '@/api/lead'
|
||||||
|
|
||||||
|
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 ViewLeadsDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
|
const [leads, setLeads] = useState([])
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const getLeadsApi = useApi(leadsApi.getLeads)
|
||||||
|
|
||||||
|
const onSearchChange = (event) => {
|
||||||
|
setSearch(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterLeads(data) {
|
||||||
|
return (
|
||||||
|
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
|
||||||
|
(data.email && data.email.toLowerCase().indexOf(search.toLowerCase()) > -1) ||
|
||||||
|
(data.phone && data.phone.toLowerCase().indexOf(search.toLowerCase()) > -1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportMessages = async () => {
|
||||||
|
const exportData = {
|
||||||
|
leads
|
||||||
|
}
|
||||||
|
const dataStr = JSON.stringify(exportData, null, 2)
|
||||||
|
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||||
|
|
||||||
|
const exportFileDefaultName = `${dialogProps.chatflow.id}-leads.json`
|
||||||
|
|
||||||
|
let linkElement = document.createElement('a')
|
||||||
|
linkElement.setAttribute('href', dataUri)
|
||||||
|
linkElement.setAttribute('download', exportFileDefaultName)
|
||||||
|
linkElement.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getLeadsApi.data) {
|
||||||
|
setLeads(getLeadsApi.data)
|
||||||
|
}
|
||||||
|
}, [getLeadsApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow) {
|
||||||
|
getLeadsApi.request(dialogProps.chatflow.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setLeads([])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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={leads && leads.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', alignItems: 'center' }}>
|
||||||
|
{dialogProps.title}
|
||||||
|
<OutlinedInput
|
||||||
|
size='small'
|
||||||
|
sx={{
|
||||||
|
ml: 3,
|
||||||
|
width: '280px',
|
||||||
|
height: '100%',
|
||||||
|
display: { xs: 'none', sm: 'flex' },
|
||||||
|
borderRadius: 2,
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderRadius: 2
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
placeholder='Search Name or Email or Phone'
|
||||||
|
onChange={onSearchChange}
|
||||||
|
startAdornment={
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.grey[400],
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
mr: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconSearch style={{ color: 'inherit', width: 16, height: 16 }} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
type='search'
|
||||||
|
/>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
{leads && leads.length > 0 && (
|
||||||
|
<Button variant='outlined' onClick={() => exportMessages()} startIcon={<IconFileExport />}>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{leads && leads.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={leadsEmptySVG} alt='msgEmptySVG' />
|
||||||
|
</Box>
|
||||||
|
<div>No Leads</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{leads && leads.length > 0 && (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Name</TableCell>
|
||||||
|
<TableCell>Email Address</TableCell>
|
||||||
|
<TableCell>Phone</TableCell>
|
||||||
|
<TableCell>Created Date</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{leads.filter(filterLeads).map((lead, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>{lead.name}</TableCell>
|
||||||
|
<TableCell>{lead.email}</TableCell>
|
||||||
|
<TableCell>{lead.phone}</TableCell>
|
||||||
|
<TableCell>{moment(lead.createdDate).format('MMMM Do, YYYY')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewLeadsDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewLeadsDialog
|
||||||
|
|
@ -102,6 +102,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||||
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
const [endDate, setEndDate] = useState(new Date())
|
const [endDate, setEndDate] = useState(new Date())
|
||||||
|
const [leadEmail, setLeadEmail] = useState('')
|
||||||
|
|
||||||
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||||
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||||
|
|
@ -191,6 +192,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed',
|
source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed',
|
||||||
sessionId: chatmsg.sessionId ?? null,
|
sessionId: chatmsg.sessionId ?? null,
|
||||||
memoryType: chatmsg.memoryType ?? null,
|
memoryType: chatmsg.memoryType ?? null,
|
||||||
|
email: leadEmail ?? null,
|
||||||
messages: [msg]
|
messages: [msg]
|
||||||
}
|
}
|
||||||
} else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
} else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||||
|
|
@ -407,6 +409,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
setSourceDialogOpen(true)
|
setSourceDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const leadEmailFromChatMessages = chatMessages.filter((message) => message.type === 'userMessage' && message.leadEmail)
|
||||||
|
if (leadEmailFromChatMessages.length) {
|
||||||
|
setLeadEmail(leadEmailFromChatMessages[0].leadEmail)
|
||||||
|
}
|
||||||
|
}, [chatMessages, selectedMessageIndex])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getChatmessageFromPKApi.data) {
|
if (getChatmessageFromPKApi.data) {
|
||||||
getChatMessages(getChatmessageFromPKApi.data)
|
getChatMessages(getChatmessageFromPKApi.data)
|
||||||
|
|
@ -450,6 +459,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
setEndDate(new Date())
|
setEndDate(new Date())
|
||||||
setStats([])
|
setStats([])
|
||||||
|
setLeadEmail('')
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
@ -639,6 +649,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
Memory: <b>{chatMessages[1].memoryType}</b>
|
Memory: <b>{chatMessages[1].memoryType}</b>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{leadEmail && (
|
||||||
|
<div>
|
||||||
|
Email: <b>{leadEmail}</b>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -675,6 +690,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
marginLeft: '20px',
|
marginLeft: '20px',
|
||||||
border: '1px solid #e0e0e0',
|
border: '1px solid #e0e0e0',
|
||||||
borderRadius: `${customization.borderRadius}px`
|
borderRadius: `${customization.borderRadius}px`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import { Button, Box, OutlinedInput, Typography } from '@mui/material'
|
||||||
|
import { IconX } from '@tabler/icons'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatflowsApi from '@/api/chatflows'
|
||||||
|
|
||||||
|
const formTitle = `Hey 👋 thanks for your interest!
|
||||||
|
Let us know where we can reach you`
|
||||||
|
|
||||||
|
const endTitle = `Thank you!
|
||||||
|
What can I do for you?`
|
||||||
|
|
||||||
|
const Leads = ({ dialogProps }) => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [leadsConfig, setLeadsConfig] = useState({})
|
||||||
|
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
|
setLeadsConfig({
|
||||||
|
...leadsConfig,
|
||||||
|
[key]: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
let value = {
|
||||||
|
leads: leadsConfig
|
||||||
|
}
|
||||||
|
chatbotConfig.leads = value.leads
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||||
|
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Leads configuration Saved',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Leads configuration: ${errorData}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) {
|
||||||
|
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||||
|
setChatbotConfig(chatbotConfig || {})
|
||||||
|
if (chatbotConfig.leads) {
|
||||||
|
setLeadsConfig(chatbotConfig.leads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'start',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 3,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SwitchInput label='Enable Lead Capture' onChange={(value) => handleChange('status', value)} value={leadsConfig.status} />
|
||||||
|
{leadsConfig && leadsConfig['status'] && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
|
||||||
|
<Typography>Form Title</Typography>
|
||||||
|
<OutlinedInput
|
||||||
|
id='form-title'
|
||||||
|
type='text'
|
||||||
|
fullWidth
|
||||||
|
multiline={true}
|
||||||
|
minRows={4}
|
||||||
|
value={leadsConfig.title}
|
||||||
|
placeholder={formTitle}
|
||||||
|
name='form-title'
|
||||||
|
size='small'
|
||||||
|
onChange={(e) => {
|
||||||
|
handleChange('title', e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
|
||||||
|
<Typography>Message after lead captured</Typography>
|
||||||
|
<OutlinedInput
|
||||||
|
id='success-message'
|
||||||
|
type='text'
|
||||||
|
fullWidth
|
||||||
|
multiline={true}
|
||||||
|
minRows={4}
|
||||||
|
value={leadsConfig.successMessage}
|
||||||
|
placeholder={endTitle}
|
||||||
|
name='form-title'
|
||||||
|
size='small'
|
||||||
|
onChange={(e) => {
|
||||||
|
handleChange('successMessage', e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Typography variant='h4'>Form fields</Typography>
|
||||||
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1 }}>
|
||||||
|
<SwitchInput label='Name' onChange={(value) => handleChange('name', value)} value={leadsConfig.name} />
|
||||||
|
<SwitchInput
|
||||||
|
label='Email Address'
|
||||||
|
onChange={(value) => handleChange('email', value)}
|
||||||
|
value={leadsConfig.email}
|
||||||
|
/>
|
||||||
|
<SwitchInput label='Phone' onChange={(value) => handleChange('phone', value)} value={leadsConfig.phone} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<StyledButton
|
||||||
|
disabled={!leadsConfig['name'] && !leadsConfig['phone'] && !leadsConfig['email'] && leadsConfig['status']}
|
||||||
|
style={{ marginBottom: 10, marginTop: 10 }}
|
||||||
|
variant='contained'
|
||||||
|
onClick={onSave}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Leads.propTypes = {
|
||||||
|
dialogProps: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Leads
|
||||||
|
|
@ -519,9 +519,9 @@ export const formatDataGridRows = (rows) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setLocalStorageChatflow = (chatflowid, chatId) => {
|
export const setLocalStorageChatflow = (chatflowid, chatId, saveObj = {}) => {
|
||||||
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||||
const obj = {}
|
const obj = { ...saveObj }
|
||||||
if (chatId) obj.chatId = chatId
|
if (chatId) obj.chatId = chatId
|
||||||
|
|
||||||
if (!chatDetails) {
|
if (!chatDetails) {
|
||||||
|
|
@ -538,6 +538,34 @@ export const setLocalStorageChatflow = (chatflowid, chatId) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getLocalStorageChatflow = (chatflowid) => {
|
||||||
|
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||||
|
if (!chatDetails) return {}
|
||||||
|
try {
|
||||||
|
return JSON.parse(chatDetails)
|
||||||
|
} catch (e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeLocalStorageChatHistory = (chatflowid) => {
|
||||||
|
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||||
|
if (!chatDetails) return
|
||||||
|
try {
|
||||||
|
const parsedChatDetails = JSON.parse(chatDetails)
|
||||||
|
if (parsedChatDetails.lead) {
|
||||||
|
// Dont remove lead when chat is cleared
|
||||||
|
const obj = { lead: parsedChatDetails.lead }
|
||||||
|
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||||
|
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj))
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const unshiftFiles = (configData) => {
|
export const unshiftFiles = (configData) => {
|
||||||
const filesConfig = configData.find((config) => config.name === 'files')
|
const filesConfig = configData.find((config) => config.name === 'files')
|
||||||
if (filesConfig) {
|
if (filesConfig) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import useApi from '@/hooks/useApi'
|
||||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||||
import { uiBaseURL } from '@/store/constant'
|
import { uiBaseURL } from '@/store/constant'
|
||||||
import { SET_CHATFLOW } from '@/store/actions'
|
import { SET_CHATFLOW } from '@/store/actions'
|
||||||
|
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||||
|
|
||||||
// ==============================|| CANVAS HEADER ||============================== //
|
// ==============================|| CANVAS HEADER ||============================== //
|
||||||
|
|
||||||
|
|
@ -46,6 +47,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||||
|
const [viewLeadsDialogOpen, setViewLeadsDialogOpen] = useState(false)
|
||||||
|
const [viewLeadsDialogProps, setViewLeadsDialogProps] = useState({})
|
||||||
const [upsertHistoryDialogOpen, setUpsertHistoryDialogOpen] = useState(false)
|
const [upsertHistoryDialogOpen, setUpsertHistoryDialogOpen] = useState(false)
|
||||||
const [upsertHistoryDialogProps, setUpsertHistoryDialogProps] = useState({})
|
const [upsertHistoryDialogProps, setUpsertHistoryDialogProps] = useState({})
|
||||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||||
|
|
@ -65,6 +68,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
chatflow: chatflow
|
chatflow: chatflow
|
||||||
})
|
})
|
||||||
setViewMessagesDialogOpen(true)
|
setViewMessagesDialogOpen(true)
|
||||||
|
} else if (setting === 'viewLeads') {
|
||||||
|
setViewLeadsDialogProps({
|
||||||
|
title: 'View Leads',
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setViewLeadsDialogOpen(true)
|
||||||
} else if (setting === 'viewUpsertHistory') {
|
} else if (setting === 'viewUpsertHistory') {
|
||||||
setUpsertHistoryDialogProps({
|
setUpsertHistoryDialogProps({
|
||||||
title: 'View Upsert History',
|
title: 'View Upsert History',
|
||||||
|
|
@ -402,6 +411,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||||
dialogProps={viewMessagesDialogProps}
|
dialogProps={viewMessagesDialogProps}
|
||||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
<ViewLeadsDialog show={viewLeadsDialogOpen} dialogProps={viewLeadsDialogProps} onCancel={() => setViewLeadsDialogOpen(false)} />
|
||||||
<UpsertHistoryDialog
|
<UpsertHistoryDialog
|
||||||
show={upsertHistoryDialogOpen}
|
show={upsertHistoryDialogOpen}
|
||||||
dialogProps={upsertHistoryDialogProps}
|
dialogProps={upsertHistoryDialogProps}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import chatmessageApi from '@/api/chatmessage'
|
||||||
import chatflowsApi from '@/api/chatflows'
|
import chatflowsApi from '@/api/chatflows'
|
||||||
import predictionApi from '@/api/prediction'
|
import predictionApi from '@/api/prediction'
|
||||||
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
||||||
|
import leadsApi from '@/api/lead'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import useApi from '@/hooks/useApi'
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
@ -55,7 +56,7 @@ import useApi from '@/hooks/useApi'
|
||||||
import { baseURL, maxScroll } from '@/store/constant'
|
import { baseURL, maxScroll } from '@/store/constant'
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from '@/utils/genericHelper'
|
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow, getLocalStorageChatflow } from '@/utils/genericHelper'
|
||||||
|
|
||||||
const messageImageStyle = {
|
const messageImageStyle = {
|
||||||
width: '128px',
|
width: '128px',
|
||||||
|
|
@ -91,10 +92,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
||||||
|
|
||||||
const [starterPrompts, setStarterPrompts] = useState([])
|
const [starterPrompts, setStarterPrompts] = useState([])
|
||||||
|
|
||||||
|
// feedback
|
||||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||||
const [feedbackId, setFeedbackId] = useState('')
|
const [feedbackId, setFeedbackId] = useState('')
|
||||||
const [showFeedbackContentDialog, setShowFeedbackContentDialog] = useState(false)
|
const [showFeedbackContentDialog, setShowFeedbackContentDialog] = useState(false)
|
||||||
|
|
||||||
|
// leads
|
||||||
|
const [leadsConfig, setLeadsConfig] = useState(null)
|
||||||
|
const [leadName, setLeadName] = useState('')
|
||||||
|
const [leadEmail, setLeadEmail] = useState('')
|
||||||
|
const [leadPhone, setLeadPhone] = useState('')
|
||||||
|
const [isLeadSaving, setIsLeadSaving] = useState(false)
|
||||||
|
const [isLeadSaved, setIsLeadSaved] = useState(false)
|
||||||
|
|
||||||
// drag & drop and file input
|
// drag & drop and file input
|
||||||
const fileUploadRef = useRef(null)
|
const fileUploadRef = useRef(null)
|
||||||
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
||||||
|
|
@ -414,6 +425,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
chatId
|
chatId
|
||||||
}
|
}
|
||||||
if (urls && urls.length > 0) params.uploads = urls
|
if (urls && urls.length > 0) params.uploads = urls
|
||||||
|
if (leadEmail) params.leadEmail = leadEmail
|
||||||
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
||||||
|
|
||||||
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
|
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
|
||||||
|
|
@ -573,6 +585,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
if (config.chatFeedback) {
|
if (config.chatFeedback) {
|
||||||
setChatFeedbackStatus(config.chatFeedback.status)
|
setChatFeedbackStatus(config.chatFeedback.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.leads) {
|
||||||
|
setLeadsConfig(config.leads)
|
||||||
|
if (config.leads.status && !getLocalStorageChatflow(chatflowid).lead) {
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
const leadCaptureMessage = {
|
||||||
|
message: '',
|
||||||
|
type: 'leadCaptureMessage'
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...prevMessages, leadCaptureMessage]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
@ -605,6 +631,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
|
|
||||||
setIsRecording(false)
|
setIsRecording(false)
|
||||||
|
|
||||||
|
// leads
|
||||||
|
const savedLead = getLocalStorageChatflow(chatflowid)?.lead
|
||||||
|
if (savedLead) {
|
||||||
|
setIsLeadSaved(!!savedLead)
|
||||||
|
setLeadEmail(savedLead.email)
|
||||||
|
}
|
||||||
|
|
||||||
// SocketIO
|
// SocketIO
|
||||||
socket = socketIOClient(baseURL)
|
socket = socketIOClient(baseURL)
|
||||||
|
|
||||||
|
|
@ -731,6 +764,36 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLeadCaptureSubmit = async (event) => {
|
||||||
|
if (event) event.preventDefault()
|
||||||
|
setIsLeadSaving(true)
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
chatflowid,
|
||||||
|
chatId,
|
||||||
|
name: leadName,
|
||||||
|
email: leadEmail,
|
||||||
|
phone: leadPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await leadsApi.addLead(body)
|
||||||
|
if (result.data) {
|
||||||
|
const data = result.data
|
||||||
|
if (!chatId) setChatId(data.chatId)
|
||||||
|
setLocalStorageChatflow(chatflowid, data.chatId, { lead: { name: leadName, email: leadEmail, phone: leadPhone } })
|
||||||
|
setIsLeadSaved(true)
|
||||||
|
setLeadEmail(leadEmail)
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
let allMessages = [...cloneDeep(prevMessages)]
|
||||||
|
if (allMessages[allMessages.length - 1].type !== 'leadCaptureMessage') return allMessages
|
||||||
|
allMessages[allMessages.length - 1].message =
|
||||||
|
leadsConfig.successMessage || 'Thank you for submitting your contact information.'
|
||||||
|
return allMessages
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setIsLeadSaving(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onDragEnter={handleDrag}>
|
<div onDragEnter={handleDrag}>
|
||||||
{isDragActive && (
|
{isDragActive && (
|
||||||
|
|
@ -763,7 +826,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
// The latest message sent by the user will be animated while waiting for a response
|
// The latest message sent by the user will be animated while waiting for a response
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
background: message.type === 'apiMessage' ? theme.palette.asyncSelect.main : ''
|
background:
|
||||||
|
message.type === 'apiMessage' || message.type === 'leadCaptureMessage'
|
||||||
|
? theme.palette.asyncSelect.main
|
||||||
|
: ''
|
||||||
}}
|
}}
|
||||||
key={index}
|
key={index}
|
||||||
style={{ display: 'flex' }}
|
style={{ display: 'flex' }}
|
||||||
|
|
@ -778,14 +844,26 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* Display the correct icon depending on the message type */}
|
{/* Display the correct icon depending on the message type */}
|
||||||
{message.type === 'apiMessage' ? (
|
{message.type === 'apiMessage' || message.type === 'leadCaptureMessage' ? (
|
||||||
<img src={robotPNG} alt='AI' width='30' height='30' className='boticon' />
|
<img src={robotPNG} alt='AI' width='30' height='30' className='boticon' />
|
||||||
) : (
|
) : (
|
||||||
<img src={userPNG} alt='Me' width='30' height='30' className='usericon' />
|
<img src={userPNG} alt='Me' width='30' height='30' className='usericon' />
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{message.usedTools && (
|
{message.usedTools && (
|
||||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{message.usedTools.map((tool, index) => {
|
{message.usedTools.map((tool, index) => {
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
|
|
@ -816,7 +894,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
{message.fileUploads.map((item, index) => {
|
{message.fileUploads.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{item.mime.startsWith('image/') ? (
|
{item?.mime?.startsWith('image/') ? (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={index}
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -848,36 +926,122 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='markdownanswer'>
|
<div className='markdownanswer'>
|
||||||
{/* Messages are being rendered in Markdown format */}
|
{message.type === 'leadCaptureMessage' &&
|
||||||
<MemoizedReactMarkdown
|
!getLocalStorageChatflow(chatflowid)?.lead &&
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
leadsConfig.status ? (
|
||||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
<Box
|
||||||
components={{
|
sx={{
|
||||||
code({ inline, className, children, ...props }) {
|
display: 'flex',
|
||||||
const match = /language-(\w+)/.exec(className || '')
|
flexDirection: 'column',
|
||||||
return !inline ? (
|
gap: 2,
|
||||||
<CodeBlock
|
marginTop: 2
|
||||||
key={Math.random()}
|
}}
|
||||||
chatflowid={chatflowid}
|
>
|
||||||
isDialog={isDialog}
|
<Typography sx={{ lineHeight: '1.5rem', whiteSpace: 'pre-line' }}>
|
||||||
language={(match && match[1]) || ''}
|
{leadsConfig.title || 'Let us know where we can reach you:'}
|
||||||
value={String(children).replace(/\n$/, '')}
|
</Typography>
|
||||||
{...props}
|
<form
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
width: isDialog ? '50%' : '100%'
|
||||||
|
}}
|
||||||
|
onSubmit={handleLeadCaptureSubmit}
|
||||||
|
>
|
||||||
|
{leadsConfig.name && (
|
||||||
|
<OutlinedInput
|
||||||
|
id='leadName'
|
||||||
|
type='text'
|
||||||
|
fullWidth
|
||||||
|
placeholder='Name'
|
||||||
|
name='leadName'
|
||||||
|
value={leadName}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
autoFocus={true}
|
||||||
|
onChange={(e) => setLeadName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
<code className={className} {...props}>
|
{leadsConfig.email && (
|
||||||
{children}
|
<OutlinedInput
|
||||||
</code>
|
id='leadEmail'
|
||||||
)
|
type='email'
|
||||||
}
|
fullWidth
|
||||||
}}
|
placeholder='Email Address'
|
||||||
>
|
name='leadEmail'
|
||||||
{message.message}
|
value={leadEmail}
|
||||||
</MemoizedReactMarkdown>
|
onChange={(e) => setLeadEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{leadsConfig.phone && (
|
||||||
|
<OutlinedInput
|
||||||
|
id='leadPhone'
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
placeholder='Phone Number'
|
||||||
|
name='leadPhone'
|
||||||
|
value={leadPhone}
|
||||||
|
onChange={(e) => setLeadPhone(e.target.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
fullWidth
|
||||||
|
type='submit'
|
||||||
|
sx={{ borderRadius: '20px' }}
|
||||||
|
>
|
||||||
|
{isLeadSaving ? 'Saving...' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Messages are being rendered in Markdown format */}
|
||||||
|
<MemoizedReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||||
|
components={{
|
||||||
|
code({ inline, className, children, ...props }) {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
return !inline ? (
|
||||||
|
<CodeBlock
|
||||||
|
key={Math.random()}
|
||||||
|
chatflowid={chatflowid}
|
||||||
|
isDialog={isDialog}
|
||||||
|
language={(match && match[1]) || ''}
|
||||||
|
value={String(children).replace(/\n$/, '')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{message.message}
|
||||||
|
</MemoizedReactMarkdown>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{message.type === 'apiMessage' && message.id && chatFeedbackStatus ? (
|
{message.type === 'apiMessage' && message.id && chatFeedbackStatus ? (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'start', gap: 1 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CopyToClipboardButton onClick={() => copyMessageToClipboard(message.message)} />
|
<CopyToClipboardButton onClick={() => copyMessageToClipboard(message.message)} />
|
||||||
{!message.feedback ||
|
{!message.feedback ||
|
||||||
message.feedback.rating === '' ||
|
message.feedback.rating === '' ||
|
||||||
|
|
@ -901,11 +1065,21 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{message.fileAnnotations && (
|
{message.fileAnnotations && (
|
||||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{message.fileAnnotations.map((fileAnnotation, index) => {
|
{message.fileAnnotations.map((fileAnnotation, index) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
sx={{ fontSize: '0.85rem', textTransform: 'none', mb: 1 }}
|
sx={{
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
textTransform: 'none',
|
||||||
|
mb: 1
|
||||||
|
}}
|
||||||
key={index}
|
key={index}
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
onClick={() => downloadFile(fileAnnotation)}
|
onClick={() => downloadFile(fileAnnotation)}
|
||||||
|
|
@ -918,7 +1092,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{message.sourceDocuments && (
|
{message.sourceDocuments && (
|
||||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{removeDuplicateURL(message).map((source, index) => {
|
{removeDuplicateURL(message).map((source, index) => {
|
||||||
const URL =
|
const URL =
|
||||||
source.metadata && source.metadata.source
|
source.metadata && source.metadata.source
|
||||||
|
|
@ -1076,7 +1256,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
autoFocus
|
autoFocus
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
disabled={loading || !chatflowid}
|
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||||
onKeyDown={handleEnter}
|
onKeyDown={handleEnter}
|
||||||
id='userInput'
|
id='userInput'
|
||||||
name='userInput'
|
name='userInput'
|
||||||
|
|
@ -1091,11 +1271,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleUploadClick}
|
onClick={handleUploadClick}
|
||||||
type='button'
|
type='button'
|
||||||
disabled={loading || !chatflowid}
|
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||||
edge='start'
|
edge='start'
|
||||||
>
|
>
|
||||||
<IconPhotoPlus
|
<IconPhotoPlus
|
||||||
color={loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
color={
|
||||||
|
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||||
|
? '#9e9e9e'
|
||||||
|
: customization.isDarkMode
|
||||||
|
? 'white'
|
||||||
|
: '#1e88e5'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
|
|
@ -1108,20 +1294,28 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => onMicrophonePressed()}
|
onClick={() => onMicrophonePressed()}
|
||||||
type='button'
|
type='button'
|
||||||
disabled={loading || !chatflowid}
|
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||||
edge='end'
|
edge='end'
|
||||||
>
|
>
|
||||||
<IconMicrophone
|
<IconMicrophone
|
||||||
className={'start-recording-button'}
|
className={'start-recording-button'}
|
||||||
color={
|
color={
|
||||||
loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'
|
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||||
|
? '#9e9e9e'
|
||||||
|
: customization.isDarkMode
|
||||||
|
? 'white'
|
||||||
|
: '#1e88e5'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
)}
|
)}
|
||||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
||||||
<IconButton type='submit' disabled={loading || !chatflowid} edge='end'>
|
<IconButton
|
||||||
|
type='submit'
|
||||||
|
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||||
|
edge='end'
|
||||||
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div>
|
<div>
|
||||||
<CircularProgress color='inherit' size={20} />
|
<CircularProgress color='inherit' size={20} />
|
||||||
|
|
@ -1130,7 +1324,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||||
// Send icon SVG in input field
|
// Send icon SVG in input field
|
||||||
<IconSend
|
<IconSend
|
||||||
color={
|
color={
|
||||||
loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'
|
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||||
|
? '#9e9e9e'
|
||||||
|
: customization.isDarkMode
|
||||||
|
? 'white'
|
||||||
|
: '#1e88e5'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ import useNotifier from '@/utils/useNotifier'
|
||||||
// Const
|
// Const
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { getLocalStorageChatflow, removeLocalStorageChatHistory } from '@/utils/genericHelper'
|
||||||
|
|
||||||
export const ChatPopUp = ({ chatflowid }) => {
|
export const ChatPopUp = ({ chatflowid }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { confirm } = useConfirm()
|
const { confirm } = useConfirm()
|
||||||
|
|
@ -86,11 +89,10 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||||
|
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
const objChatDetails = getLocalStorageChatflow(chatflowid)
|
||||||
if (!chatDetails) return
|
if (!objChatDetails.chatId) return
|
||||||
const objChatDetails = JSON.parse(chatDetails)
|
|
||||||
await chatmessageApi.deleteChatmessage(chatflowid, { chatId: objChatDetails.chatId, chatType: 'INTERNAL' })
|
await chatmessageApi.deleteChatmessage(chatflowid, { chatId: objChatDetails.chatId, chatType: 'INTERNAL' })
|
||||||
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
removeLocalStorageChatHistory(chatflowid)
|
||||||
resetChatDialog()
|
resetChatDialog()
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: 'Succesfully cleared all chat history',
|
message: 'Succesfully cleared all chat history',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue