mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
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:
@@ -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,
|
||||
IconMessage,
|
||||
IconDatabaseExport,
|
||||
IconAdjustmentsHorizontal
|
||||
IconAdjustmentsHorizontal,
|
||||
IconUsers
|
||||
} from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
@@ -17,7 +18,8 @@ const icons = {
|
||||
IconCopy,
|
||||
IconMessage,
|
||||
IconDatabaseExport,
|
||||
IconAdjustmentsHorizontal
|
||||
IconAdjustmentsHorizontal,
|
||||
IconUsers
|
||||
}
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
@@ -34,6 +36,13 @@ const settings = {
|
||||
url: '',
|
||||
icon: icons.IconMessage
|
||||
},
|
||||
{
|
||||
id: 'viewLeads',
|
||||
title: 'View Leads',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconUsers
|
||||
},
|
||||
{
|
||||
id: 'viewUpsertHistory',
|
||||
title: 'Upsert History',
|
||||
|
||||
@@ -10,10 +10,10 @@ const StatsCard = ({ title, stat }) => {
|
||||
return (
|
||||
<Card sx={{ border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||
<CardContent>
|
||||
<Typography sx={{ fontSize: 14 }} color='text.primary' gutterBottom>
|
||||
<Typography sx={{ fontSize: '0.875rem' }} color='text.primary' gutterBottom>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: 30, fontWeight: 500 }} color='text.primary'>
|
||||
<Typography sx={{ fontSize: '1.5rem', fontWeight: 500 }} color='text.primary'>
|
||||
{stat}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
||||
@@ -2,12 +2,14 @@ import PropTypes from 'prop-types'
|
||||
import { useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { Box, Dialog, DialogContent, DialogTitle, Tabs, Tab } from '@mui/material'
|
||||
import { tabsClasses } from '@mui/material/Tabs'
|
||||
import SpeechToText from '@/ui-component/extended/SpeechToText'
|
||||
import RateLimit from '@/ui-component/extended/RateLimit'
|
||||
import AllowedDomains from '@/ui-component/extended/AllowedDomains'
|
||||
import ChatFeedback from '@/ui-component/extended/ChatFeedback'
|
||||
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
|
||||
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
|
||||
import Leads from '@/ui-component/extended/Leads'
|
||||
|
||||
const CHATFLOW_CONFIGURATION_TABS = [
|
||||
{
|
||||
@@ -33,6 +35,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
|
||||
{
|
||||
label: 'Analyse Chatflow',
|
||||
id: 'analyseChatflow'
|
||||
},
|
||||
{
|
||||
label: 'Leads',
|
||||
id: 'leads'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -83,10 +89,19 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Tabs
|
||||
sx={{ position: 'relative', minHeight: '40px', height: '40px' }}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
minHeight: '40px',
|
||||
height: '40px',
|
||||
[`& .${tabsClasses.scrollButtons}`]: {
|
||||
'&.Mui-disabled': { opacity: 0.3 }
|
||||
}
|
||||
}}
|
||||
value={tabValue}
|
||||
onChange={(event, value) => setTabValue(value)}
|
||||
aria-label='tabs'
|
||||
variant='scrollable'
|
||||
scrollButtons='auto'
|
||||
>
|
||||
{CHATFLOW_CONFIGURATION_TABS.map((item, index) => (
|
||||
<Tab
|
||||
@@ -105,6 +120,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
|
||||
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'analyseChatflow' ? <AnalyseFlow dialogProps={dialogProps} /> : null}
|
||||
{item.id === 'leads' ? <Leads dialogProps={dialogProps} /> : null}
|
||||
</TabPanel>
|
||||
))}
|
||||
</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 [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
const [leadEmail, setLeadEmail] = useState('')
|
||||
|
||||
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||
@@ -191,6 +192,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed',
|
||||
sessionId: chatmsg.sessionId ?? null,
|
||||
memoryType: chatmsg.memoryType ?? null,
|
||||
email: leadEmail ?? null,
|
||||
messages: [msg]
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||
@@ -407,6 +409,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
setSourceDialogOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const leadEmailFromChatMessages = chatMessages.filter((message) => message.type === 'userMessage' && message.leadEmail)
|
||||
if (leadEmailFromChatMessages.length) {
|
||||
setLeadEmail(leadEmailFromChatMessages[0].leadEmail)
|
||||
}
|
||||
}, [chatMessages, selectedMessageIndex])
|
||||
|
||||
useEffect(() => {
|
||||
if (getChatmessageFromPKApi.data) {
|
||||
getChatMessages(getChatmessageFromPKApi.data)
|
||||
@@ -450,6 +459,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||
setEndDate(new Date())
|
||||
setStats([])
|
||||
setLeadEmail('')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -639,6 +649,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
Memory: <b>{chatMessages[1].memoryType}</b>
|
||||
</div>
|
||||
)}
|
||||
{leadEmail && (
|
||||
<div>
|
||||
Email: <b>{leadEmail}</b>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -675,6 +690,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginLeft: '20px',
|
||||
border: '1px solid #e0e0e0',
|
||||
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 obj = {}
|
||||
const obj = { ...saveObj }
|
||||
if (chatId) obj.chatId = chatId
|
||||
|
||||
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) => {
|
||||
const filesConfig = configData.find((config) => config.name === 'files')
|
||||
if (filesConfig) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import useApi from '@/hooks/useApi'
|
||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||
import { uiBaseURL } from '@/store/constant'
|
||||
import { SET_CHATFLOW } from '@/store/actions'
|
||||
import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||
|
||||
// ==============================|| CANVAS HEADER ||============================== //
|
||||
|
||||
@@ -46,6 +47,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||
const [viewLeadsDialogOpen, setViewLeadsDialogOpen] = useState(false)
|
||||
const [viewLeadsDialogProps, setViewLeadsDialogProps] = useState({})
|
||||
const [upsertHistoryDialogOpen, setUpsertHistoryDialogOpen] = useState(false)
|
||||
const [upsertHistoryDialogProps, setUpsertHistoryDialogProps] = useState({})
|
||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||
@@ -65,6 +68,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
chatflow: chatflow
|
||||
})
|
||||
setViewMessagesDialogOpen(true)
|
||||
} else if (setting === 'viewLeads') {
|
||||
setViewLeadsDialogProps({
|
||||
title: 'View Leads',
|
||||
chatflow: chatflow
|
||||
})
|
||||
setViewLeadsDialogOpen(true)
|
||||
} else if (setting === 'viewUpsertHistory') {
|
||||
setUpsertHistoryDialogProps({
|
||||
title: 'View Upsert History',
|
||||
@@ -402,6 +411,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
dialogProps={viewMessagesDialogProps}
|
||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||
/>
|
||||
<ViewLeadsDialog show={viewLeadsDialogOpen} dialogProps={viewLeadsDialogProps} onCancel={() => setViewLeadsDialogOpen(false)} />
|
||||
<UpsertHistoryDialog
|
||||
show={upsertHistoryDialogOpen}
|
||||
dialogProps={upsertHistoryDialogProps}
|
||||
|
||||
@@ -47,6 +47,7 @@ import chatmessageApi from '@/api/chatmessage'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import predictionApi from '@/api/prediction'
|
||||
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
||||
import leadsApi from '@/api/lead'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
@@ -55,7 +56,7 @@ import useApi from '@/hooks/useApi'
|
||||
import { baseURL, maxScroll } from '@/store/constant'
|
||||
|
||||
// Utils
|
||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from '@/utils/genericHelper'
|
||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow, getLocalStorageChatflow } from '@/utils/genericHelper'
|
||||
|
||||
const messageImageStyle = {
|
||||
width: '128px',
|
||||
@@ -91,10 +92,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
||||
|
||||
const [starterPrompts, setStarterPrompts] = useState([])
|
||||
|
||||
// feedback
|
||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||
const [feedbackId, setFeedbackId] = useState('')
|
||||
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
|
||||
const fileUploadRef = useRef(null)
|
||||
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
||||
@@ -414,6 +425,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
chatId
|
||||
}
|
||||
if (urls && urls.length > 0) params.uploads = urls
|
||||
if (leadEmail) params.leadEmail = leadEmail
|
||||
if (isChatFlowAvailableToStream) params.socketIOClientId = socketIOClientId
|
||||
|
||||
const response = await predictionApi.sendMessageAndGetPrediction(chatflowid, params)
|
||||
@@ -573,6 +585,20 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
if (config.chatFeedback) {
|
||||
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
|
||||
@@ -605,6 +631,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
|
||||
setIsRecording(false)
|
||||
|
||||
// leads
|
||||
const savedLead = getLocalStorageChatflow(chatflowid)?.lead
|
||||
if (savedLead) {
|
||||
setIsLeadSaved(!!savedLead)
|
||||
setLeadEmail(savedLead.email)
|
||||
}
|
||||
|
||||
// SocketIO
|
||||
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 (
|
||||
<div onDragEnter={handleDrag}>
|
||||
{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
|
||||
<Box
|
||||
sx={{
|
||||
background: message.type === 'apiMessage' ? theme.palette.asyncSelect.main : ''
|
||||
background:
|
||||
message.type === 'apiMessage' || message.type === 'leadCaptureMessage'
|
||||
? theme.palette.asyncSelect.main
|
||||
: ''
|
||||
}}
|
||||
key={index}
|
||||
style={{ display: 'flex' }}
|
||||
@@ -778,14 +844,26 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
}
|
||||
>
|
||||
{/* 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={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 && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{message.usedTools.map((tool, index) => {
|
||||
return (
|
||||
<Chip
|
||||
@@ -816,7 +894,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
{message.fileUploads.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
{item.mime.startsWith('image/') ? (
|
||||
{item?.mime?.startsWith('image/') ? (
|
||||
<Card
|
||||
key={index}
|
||||
sx={{
|
||||
@@ -848,36 +926,122 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
{/* 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}
|
||||
{message.type === 'leadCaptureMessage' &&
|
||||
!getLocalStorageChatflow(chatflowid)?.lead &&
|
||||
leadsConfig.status ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
marginTop: 2
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ lineHeight: '1.5rem', whiteSpace: 'pre-line' }}>
|
||||
{leadsConfig.title || 'Let us know where we can reach you:'}
|
||||
</Typography>
|
||||
<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}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message.message}
|
||||
</MemoizedReactMarkdown>
|
||||
)}
|
||||
{leadsConfig.email && (
|
||||
<OutlinedInput
|
||||
id='leadEmail'
|
||||
type='email'
|
||||
fullWidth
|
||||
placeholder='Email Address'
|
||||
name='leadEmail'
|
||||
value={leadEmail}
|
||||
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>
|
||||
{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)} />
|
||||
{!message.feedback ||
|
||||
message.feedback.rating === '' ||
|
||||
@@ -901,11 +1065,21 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
</>
|
||||
) : null}
|
||||
{message.fileAnnotations && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{message.fileAnnotations.map((fileAnnotation, index) => {
|
||||
return (
|
||||
<Button
|
||||
sx={{ fontSize: '0.85rem', textTransform: 'none', mb: 1 }}
|
||||
sx={{
|
||||
fontSize: '0.85rem',
|
||||
textTransform: 'none',
|
||||
mb: 1
|
||||
}}
|
||||
key={index}
|
||||
variant='outlined'
|
||||
onClick={() => downloadFile(fileAnnotation)}
|
||||
@@ -918,7 +1092,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
</div>
|
||||
)}
|
||||
{message.sourceDocuments && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{removeDuplicateURL(message).map((source, index) => {
|
||||
const URL =
|
||||
source.metadata && source.metadata.source
|
||||
@@ -1076,7 +1256,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
// eslint-disable-next-line
|
||||
autoFocus
|
||||
sx={{ width: '100%' }}
|
||||
disabled={loading || !chatflowid}
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
onKeyDown={handleEnter}
|
||||
id='userInput'
|
||||
name='userInput'
|
||||
@@ -1091,11 +1271,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
<IconButton
|
||||
onClick={handleUploadClick}
|
||||
type='button'
|
||||
disabled={loading || !chatflowid}
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
edge='start'
|
||||
>
|
||||
<IconPhotoPlus
|
||||
color={loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||
color={
|
||||
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||
? '#9e9e9e'
|
||||
: customization.isDarkMode
|
||||
? 'white'
|
||||
: '#1e88e5'
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
@@ -1108,20 +1294,28 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
<IconButton
|
||||
onClick={() => onMicrophonePressed()}
|
||||
type='button'
|
||||
disabled={loading || !chatflowid}
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
edge='end'
|
||||
>
|
||||
<IconMicrophone
|
||||
className={'start-recording-button'}
|
||||
color={
|
||||
loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'
|
||||
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||
? '#9e9e9e'
|
||||
: customization.isDarkMode
|
||||
? 'white'
|
||||
: '#1e88e5'
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)}
|
||||
<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 ? (
|
||||
<div>
|
||||
<CircularProgress color='inherit' size={20} />
|
||||
@@ -1130,7 +1324,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
// Send icon SVG in input field
|
||||
<IconSend
|
||||
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
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||
|
||||
// Utils
|
||||
import { getLocalStorageChatflow, removeLocalStorageChatHistory } from '@/utils/genericHelper'
|
||||
|
||||
export const ChatPopUp = ({ chatflowid }) => {
|
||||
const theme = useTheme()
|
||||
const { confirm } = useConfirm()
|
||||
@@ -86,11 +89,10 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||
if (!chatDetails) return
|
||||
const objChatDetails = JSON.parse(chatDetails)
|
||||
const objChatDetails = getLocalStorageChatflow(chatflowid)
|
||||
if (!objChatDetails.chatId) return
|
||||
await chatmessageApi.deleteChatmessage(chatflowid, { chatId: objChatDetails.chatId, chatType: 'INTERNAL' })
|
||||
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||
removeLocalStorageChatHistory(chatflowid)
|
||||
resetChatDialog()
|
||||
enqueueSnackbar({
|
||||
message: 'Succesfully cleared all chat history',
|
||||
|
||||
Reference in New Issue
Block a user