mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 11:00:55 +03:00
code cleanup
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import client from './client'
|
||||
|
||||
const getChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`)
|
||||
|
||||
const deleteChatmessage = (id) => client.delete(`/chatmessage/${id}`)
|
||||
const getInternalChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`)
|
||||
const getAllChatmessageFromChatflow = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'DESC', ...params } })
|
||||
const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', ...params } })
|
||||
const deleteChatmessage = (id, params = {}) => client.delete(`/chatmessage/${id}`, { params: { ...params } })
|
||||
|
||||
export default {
|
||||
getChatmessageFromChatflow,
|
||||
getInternalChatmessageFromChatflow,
|
||||
getAllChatmessageFromChatflow,
|
||||
getChatmessageFromPK,
|
||||
deleteChatmessage
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
@@ -71,7 +71,7 @@ const ProfileSection = ({ username, handleLogout }) => {
|
||||
try {
|
||||
const response = await databaseApi.getExportDatabase()
|
||||
const exportItems = response.data
|
||||
let dataStr = JSON.stringify(exportItems)
|
||||
let dataStr = JSON.stringify(exportItems, null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `DB.json`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// assets
|
||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch } from '@tabler/icons'
|
||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch }
|
||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage }
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
|
||||
@@ -11,6 +11,13 @@ const settings = {
|
||||
title: '',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'viewMessages',
|
||||
title: 'View Messages',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconMessage
|
||||
},
|
||||
{
|
||||
id: 'duplicateChatflow',
|
||||
title: 'Duplicate Chatflow',
|
||||
|
||||
@@ -80,6 +80,9 @@ export default function themePalette(theme) {
|
||||
asyncSelect: {
|
||||
main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50
|
||||
},
|
||||
timeMessage: {
|
||||
main: theme.customization.isDarkMode ? theme.colors?.darkLevel2 : theme.colors?.grey200
|
||||
},
|
||||
canvasHeader: {
|
||||
deployLight: theme.colors?.primaryLight,
|
||||
deployDark: theme.colors?.primaryDark,
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useState, useEffect, forwardRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Button,
|
||||
Tooltip,
|
||||
ListItemButton,
|
||||
Box,
|
||||
Stack,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Chip
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import DatePicker from 'react-datepicker'
|
||||
|
||||
import robotPNG from 'assets/images/robot.png'
|
||||
import userPNG from 'assets/images/account.png'
|
||||
import msgEmptySVG from 'assets/images/message_empty.svg'
|
||||
import { IconFileExport, IconEraser, IconX } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||
import SourceDocDialog from 'ui-component/dialog/SourceDocDialog'
|
||||
import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
|
||||
// API
|
||||
import chatmessageApi from 'api/chatmessage'
|
||||
import useApi from 'hooks/useApi'
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
|
||||
// Utils
|
||||
import { isValidURL, removeDuplicateURL } from 'utils/genericHelper'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
|
||||
import 'views/chatmessage/ChatMessage.css'
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) {
|
||||
return (
|
||||
<ListItemButton style={{ borderRadius: 15, border: '1px solid #e0e0e0' }} onClick={onClick} ref={ref}>
|
||||
{value}
|
||||
</ListItemButton>
|
||||
)
|
||||
})
|
||||
|
||||
DatePickerCustomInput.propTypes = {
|
||||
value: PropTypes.string,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [chatlogs, setChatLogs] = useState([])
|
||||
const [allChatlogs, setAllChatLogs] = useState([])
|
||||
const [chatMessages, setChatMessages] = useState([])
|
||||
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||
const [startDate, setStartDate] = useState(new Date().setMonth(new Date().getMonth() - 1))
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
|
||||
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||
|
||||
const onStartDateSelected = (date) => {
|
||||
setStartDate(date)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
startDate: date,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const onEndDateSelected = (date) => {
|
||||
setEndDate(date)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
endDate: date,
|
||||
startDate: startDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const onChatTypeSelected = (chatTypes) => {
|
||||
setChatTypeFilter(chatTypes)
|
||||
getChatmessageApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
}
|
||||
|
||||
const exportMessages = () => {
|
||||
const obj = {}
|
||||
for (let i = 0; i < allChatlogs.length; i += 1) {
|
||||
const chatmsg = allChatlogs[i]
|
||||
const chatPK = getChatPK(chatmsg)
|
||||
const msg = {
|
||||
content: chatmsg.content,
|
||||
role: chatmsg.role === 'apiMessage' ? 'bot' : 'user',
|
||||
time: chatmsg.createdDate
|
||||
}
|
||||
if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||
obj[chatPK] = {
|
||||
id: chatmsg.chatId,
|
||||
source: chatmsg.chatType === 'INTERNAL' ? 'UI' : 'API/Embed',
|
||||
sessionId: chatmsg.sessionId ?? null,
|
||||
memoryType: chatmsg.memoryType ?? null,
|
||||
messages: [msg]
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||
obj[chatPK].messages = [...obj[chatPK].messages, msg]
|
||||
}
|
||||
}
|
||||
|
||||
const exportMessages = []
|
||||
for (const key in obj) {
|
||||
exportMessages.push({
|
||||
...obj[key]
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < exportMessages.length; i += 1) {
|
||||
exportMessages[i].messages = exportMessages[i].messages.reverse()
|
||||
}
|
||||
|
||||
const dataStr = JSON.stringify(exportMessages, null, 2)
|
||||
const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
const exportFileDefaultName = `${dialogProps.chatflow.id}-Message.json`
|
||||
|
||||
let linkElement = document.createElement('a')
|
||||
linkElement.setAttribute('href', dataUri)
|
||||
linkElement.setAttribute('download', exportFileDefaultName)
|
||||
linkElement.click()
|
||||
}
|
||||
|
||||
const clearChat = async (chatmsg) => {
|
||||
const description =
|
||||
chatmsg.sessionId && chatmsg.memoryType
|
||||
? `Are you sure you want to clear session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}?`
|
||||
: `Are you sure you want to clear messages?`
|
||||
const confirmPayload = {
|
||||
title: `Clear Session`,
|
||||
description,
|
||||
confirmButtonName: 'Clear',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
const chatflowid = dialogProps.chatflow.id
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const obj = { chatflowid, isClearFromViewMessageDialog: true }
|
||||
if (chatmsg.chatId) obj.chatId = chatmsg.chatId
|
||||
if (chatmsg.chatType) obj.chatType = chatmsg.chatType
|
||||
if (chatmsg.memoryType) obj.memoryType = chatmsg.memoryType
|
||||
if (chatmsg.sessionId) obj.sessionId = chatmsg.sessionId
|
||||
|
||||
await chatmessageApi.deleteChatmessage(chatflowid, obj)
|
||||
const description =
|
||||
chatmsg.sessionId && chatmsg.memoryType
|
||||
? `Succesfully cleared session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}`
|
||||
: `Succesfully cleared messages`
|
||||
enqueueSnackbar({
|
||||
message: description,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
getChatmessageApi.request(chatflowid)
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: errorData,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getChatMessages = (chatmessages) => {
|
||||
let prevDate = ''
|
||||
const loadedMessages = []
|
||||
for (let i = 0; i < chatmessages.length; i += 1) {
|
||||
const chatmsg = chatmessages[i]
|
||||
if (!prevDate) {
|
||||
prevDate = chatmsg.createdDate.split('T')[0]
|
||||
loadedMessages.push({
|
||||
message: chatmsg.createdDate,
|
||||
type: 'timeMessage'
|
||||
})
|
||||
} else {
|
||||
const currentDate = chatmsg.createdDate.split('T')[0]
|
||||
if (currentDate !== prevDate) {
|
||||
prevDate = currentDate
|
||||
loadedMessages.push({
|
||||
message: chatmsg.createdDate,
|
||||
type: 'timeMessage'
|
||||
})
|
||||
}
|
||||
}
|
||||
const obj = {
|
||||
...chatmsg,
|
||||
message: chatmsg.content,
|
||||
type: chatmsg.role
|
||||
}
|
||||
if (chatmsg.sourceDocuments) obj.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||
loadedMessages.push(obj)
|
||||
}
|
||||
setChatMessages(loadedMessages)
|
||||
}
|
||||
|
||||
const getChatPK = (chatmsg) => {
|
||||
const chatId = chatmsg.chatId
|
||||
const memoryType = chatmsg.memoryType ?? 'null'
|
||||
const sessionId = chatmsg.sessionId ?? 'null'
|
||||
return `${chatId}_${memoryType}_${sessionId}`
|
||||
}
|
||||
|
||||
const transformChatPKToParams = (chatPK) => {
|
||||
const chatId = chatPK.split('_')[0]
|
||||
const memoryType = chatPK.split('_')[1]
|
||||
const sessionId = chatPK.split('_')[2]
|
||||
|
||||
const params = { chatId }
|
||||
if (memoryType !== 'null') params.memoryType = memoryType
|
||||
if (sessionId !== 'null') params.sessionId = sessionId
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const processChatLogs = (allChatMessages) => {
|
||||
const seen = {}
|
||||
const filteredChatLogs = []
|
||||
for (let i = 0; i < allChatMessages.length; i += 1) {
|
||||
const PK = getChatPK(allChatMessages[i])
|
||||
|
||||
const item = allChatMessages[i]
|
||||
if (!Object.prototype.hasOwnProperty.call(seen, PK)) {
|
||||
seen[PK] = {
|
||||
counter: 1,
|
||||
item: allChatMessages[i]
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(seen, PK) && seen[PK].counter === 1) {
|
||||
seen[PK] = {
|
||||
counter: 2,
|
||||
item: {
|
||||
...seen[PK].item,
|
||||
apiContent:
|
||||
seen[PK].item.role === 'apiMessage' ? `Bot: ${seen[PK].item.content}` : `User: ${seen[PK].item.content}`,
|
||||
userContent: item.role === 'apiMessage' ? `Bot: ${item.content}` : `User: ${item.content}`
|
||||
}
|
||||
}
|
||||
filteredChatLogs.push(seen[PK].item)
|
||||
}
|
||||
}
|
||||
setChatLogs(filteredChatLogs)
|
||||
if (filteredChatLogs.length) return getChatPK(filteredChatLogs[0])
|
||||
return undefined
|
||||
}
|
||||
|
||||
const handleItemClick = (idx, chatmsg) => {
|
||||
setSelectedMessageIndex(idx)
|
||||
getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(getChatPK(chatmsg)))
|
||||
}
|
||||
|
||||
const onURLClick = (data) => {
|
||||
window.open(data, '_blank')
|
||||
}
|
||||
|
||||
const onSourceDialogClick = (data) => {
|
||||
setSourceDialogProps({ data })
|
||||
setSourceDialogOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getChatmessageFromPKApi.data) {
|
||||
getChatMessages(getChatmessageFromPKApi.data)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getChatmessageFromPKApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getChatmessageApi.data) {
|
||||
setAllChatLogs(getChatmessageApi.data)
|
||||
const chatPK = processChatLogs(getChatmessageApi.data)
|
||||
setSelectedMessageIndex(0)
|
||||
if (chatPK) getChatmessageFromPKApi.request(dialogProps.chatflow.id, transformChatPKToParams(chatPK))
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getChatmessageApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow) {
|
||||
getChatmessageApi.request(dialogProps.chatflow.id)
|
||||
}
|
||||
|
||||
return () => {
|
||||
setChatLogs([])
|
||||
setAllChatLogs([])
|
||||
setChatMessages([])
|
||||
setChatTypeFilter([])
|
||||
setSelectedMessageIndex(0)
|
||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||
setEndDate(new Date())
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth={chatlogs && chatlogs.length == 0 ? 'md' : 'lg'}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{dialogProps.title}
|
||||
<div style={{ flex: 1 }} />
|
||||
<Button variant='outlined' onClick={() => exportMessages()} startIcon={<IconFileExport />}>
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%', marginBottom: 10 }}>
|
||||
<div style={{ marginRight: 10 }}>
|
||||
<b style={{ marginRight: 10 }}>From Date</b>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={(date) => onStartDateSelected(date)}
|
||||
selectsStart
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
customInput={<DatePickerCustomInput />}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginRight: 10 }}>
|
||||
<b style={{ marginRight: 10 }}>To Date</b>
|
||||
<DatePicker
|
||||
selected={endDate}
|
||||
onChange={(date) => onEndDateSelected(date)}
|
||||
selectsEnd
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
minDate={startDate}
|
||||
maxDate={new Date()}
|
||||
customInput={<DatePickerCustomInput />}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', minWidth: '200px', marginRight: 10 }}>
|
||||
<b style={{ marginRight: 10 }}>Source</b>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(chatTypeFilter)}
|
||||
name='chatType'
|
||||
options={[
|
||||
{
|
||||
label: 'UI',
|
||||
name: 'INTERNAL'
|
||||
},
|
||||
{
|
||||
label: 'API/Embed',
|
||||
name: 'EXTERNAL'
|
||||
}
|
||||
]}
|
||||
onSelect={(newValue) => onChatTypeSelected(newValue)}
|
||||
value={chatTypeFilter}
|
||||
formControlSx={{ mt: 0 }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}></div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{chatlogs && chatlogs.length == 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||
<Box sx={{ p: 5, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={msgEmptySVG}
|
||||
alt='msgEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Messages</div>
|
||||
</Stack>
|
||||
)}
|
||||
{chatlogs && chatlogs.length > 0 && (
|
||||
<div style={{ flexBasis: '40%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'column',
|
||||
maxHeight: 'calc(100vh - 260px)'
|
||||
}}
|
||||
>
|
||||
{chatlogs.map((chatmsg, index) => (
|
||||
<ListItemButton
|
||||
key={index}
|
||||
sx={{
|
||||
p: 0,
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
|
||||
mt: 1,
|
||||
ml: 1,
|
||||
mr: 1,
|
||||
mb: index === chatlogs.length - 1 ? 1 : 0
|
||||
}}
|
||||
selected={selectedMessageIndex === index}
|
||||
onClick={() => handleItemClick(index, chatmsg)}
|
||||
>
|
||||
<ListItem alignItems='center'>
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginBottom: 10 }}>
|
||||
<span>{chatmsg?.userContent}</span>
|
||||
<div
|
||||
style={{
|
||||
maxHeight: '100px',
|
||||
maxWidth: '400px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
{chatmsg?.apiContent}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
secondary={moment(chatmsg.createdDate).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
{chatlogs && chatlogs.length > 0 && (
|
||||
<div style={{ flexBasis: '60%', paddingRight: '30px' }}>
|
||||
{chatMessages && chatMessages.length > 1 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<div style={{ flex: 1, marginLeft: '20px', marginBottom: '15px', marginTop: '10px' }}>
|
||||
{chatMessages[1].sessionId && (
|
||||
<div>
|
||||
Session Id: <b>{chatMessages[1].sessionId}</b>
|
||||
</div>
|
||||
)}
|
||||
{chatMessages[1].chatType && (
|
||||
<div>
|
||||
Source: <b>{chatMessages[1].chatType === 'INTERNAL' ? 'UI' : 'API/Embed'}</b>
|
||||
</div>
|
||||
)}
|
||||
{chatMessages[1].memoryType && (
|
||||
<div>
|
||||
Memory: <b>{chatMessages[1].memoryType}</b>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignContent: 'center',
|
||||
alignItems: 'end'
|
||||
}}
|
||||
>
|
||||
<StyledButton
|
||||
sx={{ height: 'max-content', width: 'max-content' }}
|
||||
variant='outlined'
|
||||
color='error'
|
||||
title='Clear Message'
|
||||
onClick={() => clearChat(chatMessages[1])}
|
||||
startIcon={<IconEraser />}
|
||||
>
|
||||
Clear
|
||||
</StyledButton>
|
||||
{chatMessages[1].sessionId && (
|
||||
<Tooltip
|
||||
title={
|
||||
'At your left 👈 you will see the Memory node that was used in this conversation. You need to have the matching Memory node with same parameters in the canvas, in order to delete the session conversations stored on the Memory node'
|
||||
}
|
||||
placement='bottom'
|
||||
>
|
||||
<h5 style={{ cursor: 'pointer', color: theme.palette.primary.main }}>
|
||||
Why my session is not deleted?
|
||||
</h5>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginLeft: '20px',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: `${customization.borderRadius}px`
|
||||
}}
|
||||
className='cloud-message'
|
||||
>
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
{chatMessages &&
|
||||
chatMessages.map((message, index) => {
|
||||
if (message.type === 'apiMessage' || message.type === 'userMessage') {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
background:
|
||||
message.type === 'apiMessage' ? theme.palette.asyncSelect.main : '',
|
||||
pl: 1,
|
||||
pr: 1
|
||||
}}
|
||||
key={index}
|
||||
style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }}
|
||||
>
|
||||
{/* Display the correct icon depending on the message type */}
|
||||
{message.type === 'apiMessage' ? (
|
||||
<img
|
||||
style={{ marginLeft: '10px' }}
|
||||
src={robotPNG}
|
||||
alt='AI'
|
||||
width='25'
|
||||
height='25'
|
||||
className='boticon'
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
style={{ marginLeft: '10px' }}
|
||||
src={userPNG}
|
||||
alt='Me'
|
||||
width='25'
|
||||
height='25'
|
||||
className='usericon'
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<div className='markdownanswer'>
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
return !inline ? (
|
||||
<CodeBlock
|
||||
key={Math.random()}
|
||||
chatflowid={dialogProps.chatflow.id}
|
||||
isDialog={true}
|
||||
language={(match && match[1]) || ''}
|
||||
value={String(children).replace(/\n$/, '')}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message.message}
|
||||
</MemoizedReactMarkdown>
|
||||
</div>
|
||||
{message.sourceDocuments && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
{removeDuplicateURL(message).map((source, index) => {
|
||||
const URL = isValidURL(source.metadata.source)
|
||||
return (
|
||||
<Chip
|
||||
size='small'
|
||||
key={index}
|
||||
label={
|
||||
URL
|
||||
? URL.pathname.substring(0, 15) === '/'
|
||||
? URL.host
|
||||
: `${URL.pathname.substring(0, 15)}...`
|
||||
: `${source.pageContent.substring(0, 15)}...`
|
||||
}
|
||||
component='a'
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
variant='outlined'
|
||||
clickable
|
||||
onClick={() =>
|
||||
URL
|
||||
? onURLClick(source.metadata.source)
|
||||
: onSourceDialogClick(source)
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
background: theme.palette.timeMessage.main,
|
||||
p: 2
|
||||
}}
|
||||
key={index}
|
||||
style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }}
|
||||
>
|
||||
{moment(message.message).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||
</>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
ViewMessagesDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func
|
||||
}
|
||||
|
||||
export default ViewMessagesDialog
|
||||
@@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({
|
||||
}
|
||||
})
|
||||
|
||||
export const MultiDropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => {
|
||||
export const MultiDropdown = ({ name, value, options, onSelect, formControlSx = {}, disabled = false, disableClearable = false }) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const findMatchingOptions = (options = [], internalValue) => {
|
||||
let values = []
|
||||
@@ -30,7 +30,7 @@ export const MultiDropdown = ({ name, value, options, onSelect, disabled = false
|
||||
let [internalValue, setInternalValue] = useState(value ?? [])
|
||||
|
||||
return (
|
||||
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
||||
<FormControl sx={{ mt: 1, width: '100%', ...formControlSx }} size='small'>
|
||||
<Autocomplete
|
||||
id={name}
|
||||
disabled={disabled}
|
||||
@@ -75,5 +75,6 @@ MultiDropdown.propTypes = {
|
||||
options: PropTypes.array,
|
||||
onSelect: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
formControlSx: PropTypes.object,
|
||||
disableClearable: PropTypes.bool
|
||||
}
|
||||
|
||||
@@ -415,6 +415,23 @@ export const getInputVariables = (paramValue) => {
|
||||
return inputVariables
|
||||
}
|
||||
|
||||
export const removeDuplicateURL = (message) => {
|
||||
const visitedURLs = []
|
||||
const newSourceDocuments = []
|
||||
|
||||
if (!message.sourceDocuments) return newSourceDocuments
|
||||
|
||||
message.sourceDocuments.forEach((source) => {
|
||||
if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) {
|
||||
visitedURLs.push(source.metadata.source)
|
||||
newSourceDocuments.push(source)
|
||||
} else if (!isValidURL(source.metadata.source)) {
|
||||
newSourceDocuments.push(source)
|
||||
}
|
||||
})
|
||||
return newSourceDocuments
|
||||
}
|
||||
|
||||
export const isValidURL = (url) => {
|
||||
try {
|
||||
return new URL(url)
|
||||
|
||||
@@ -15,6 +15,7 @@ import Settings from 'views/settings'
|
||||
import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
||||
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
||||
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
||||
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
||||
|
||||
// API
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
@@ -44,6 +45,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const canvas = useSelector((state) => state.canvas)
|
||||
@@ -59,6 +62,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
chatflow: chatflow
|
||||
})
|
||||
setAnalyseDialogOpen(true)
|
||||
} else if (setting === 'viewMessages') {
|
||||
setViewMessagesDialogProps({
|
||||
title: 'View Messages',
|
||||
chatflow: chatflow
|
||||
})
|
||||
setViewMessagesDialogOpen(true)
|
||||
} else if (setting === 'duplicateChatflow') {
|
||||
try {
|
||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||
@@ -69,7 +78,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
} else if (setting === 'exportChatflow') {
|
||||
try {
|
||||
const flowData = JSON.parse(chatflow.flowData)
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData))
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `${chatflow.name} Chatflow.json`
|
||||
@@ -367,6 +376,11 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
/>
|
||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
||||
<ViewMessagesDialog
|
||||
show={viewMessagesDialogOpen}
|
||||
dialogProps={viewMessagesDialogProps}
|
||||
onCancel={() => setViewMessagesDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -134,3 +134,13 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cloud-message {
|
||||
width: 100%;
|
||||
height: calc(100vh - 260px);
|
||||
overflow-y: scroll;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { baseURL, maxScroll } from 'store/constant'
|
||||
|
||||
import robotPNG from 'assets/images/robot.png'
|
||||
import userPNG from 'assets/images/account.png'
|
||||
import { isValidURL } from 'utils/genericHelper'
|
||||
import { isValidURL, removeDuplicateURL } from 'utils/genericHelper'
|
||||
|
||||
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const theme = useTheme()
|
||||
@@ -53,7 +53,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const [chatId, setChatId] = useState(undefined)
|
||||
|
||||
const inputRef = useRef(null)
|
||||
const getChatmessageApi = useApi(chatmessageApi.getChatmessageFromChatflow)
|
||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||
|
||||
const onSourceDialogClick = (data) => {
|
||||
@@ -65,21 +65,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
window.open(data, '_blank')
|
||||
}
|
||||
|
||||
const removeDuplicateURL = (message) => {
|
||||
const visitedURLs = []
|
||||
const newSourceDocuments = []
|
||||
|
||||
message.sourceDocuments.forEach((source) => {
|
||||
if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) {
|
||||
visitedURLs.push(source.metadata.source)
|
||||
newSourceDocuments.push(source)
|
||||
} else if (!isValidURL(source.metadata.source)) {
|
||||
newSourceDocuments.push(source)
|
||||
}
|
||||
})
|
||||
return newSourceDocuments
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (ps.current) {
|
||||
ps.current.scrollTo({ top: maxScroll })
|
||||
|
||||
@@ -86,7 +86,7 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const chatId = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||
await chatmessageApi.deleteChatmessage(chatId)
|
||||
await chatmessageApi.deleteChatmessage(chatflowid, { chatId, chatType: 'INTERNAL' })
|
||||
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||
resetChatDialog()
|
||||
enqueueSnackbar({
|
||||
|
||||
@@ -227,7 +227,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
|
||||
delete toolData.id
|
||||
delete toolData.createdDate
|
||||
delete toolData.updatedDate
|
||||
let dataStr = JSON.stringify(toolData)
|
||||
let dataStr = JSON.stringify(toolData, null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `${toolName}-CustomTool.json`
|
||||
|
||||
Reference in New Issue
Block a user