Fix merge conflicts
This commit is contained in:
commit
3096a0fa50
|
|
@ -75,7 +75,7 @@ Flowise has 3 different modules in a single mono repository.
|
|||
|
||||
### Prerequisite
|
||||
|
||||
- Install [Yarn v1](https://pnpm.io/installation)
|
||||
- Install [PNPM](https://pnpm.io/installation)
|
||||
```bash
|
||||
npm i -g pnpm
|
||||
```
|
||||
|
|
|
|||
|
|
@ -199,14 +199,15 @@ const prepareChatPrompt = (nodeData: INodeData, humanImageMessages: MessageConte
|
|||
|
||||
const messages: BaseMessagePromptTemplateLike[] = [
|
||||
SystemMessagePromptTemplate.fromTemplate(prompt ? prompt : systemMessage),
|
||||
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history')
|
||||
new MessagesPlaceholder(memory.memoryKey ?? 'chat_history'),
|
||||
HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`)
|
||||
]
|
||||
|
||||
// OpenAI works better when separate images into standalone human messages
|
||||
if (model instanceof ChatOpenAI && humanImageMessages.length) {
|
||||
messages.push(HumanMessagePromptTemplate.fromTemplate(`{${inputKey}}`))
|
||||
messages.push(new HumanMessage({ content: [...humanImageMessages] }))
|
||||
} else if (humanImageMessages.length) {
|
||||
messages.pop()
|
||||
messages.push(HumanMessagePromptTemplate.fromTemplate([`{${inputKey}}`, ...humanImageMessages]))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Inter
|
|||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
|
||||
/**
|
||||
* I had to run the following to build the component
|
||||
* and get the icon copied over to the dist directory
|
||||
* Flowise/packages/components > yarn build
|
||||
*
|
||||
* @author Michael Connor <mlconnor@yahoo.com>
|
||||
*/
|
||||
class AWSChatBedrock_ChatModels implements INode {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../
|
|||
import { BaseBedrockInput } from '@langchain/community/dist/utils/bedrock'
|
||||
|
||||
/**
|
||||
* I had to run the following to build the component
|
||||
* and get the icon copied over to the dist directory
|
||||
* Flowise/packages/components > yarn build
|
||||
*
|
||||
* @author Michael Connor <mlconnor@yahoo.com>
|
||||
*/
|
||||
class AWSBedrock_LLMs implements INode {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export class HuggingFaceInference extends LLM implements HFInput {
|
|||
const { HfInference } = await import('@huggingface/inference')
|
||||
return { HfInference }
|
||||
} catch (e) {
|
||||
throw new Error('Please install huggingface as a dependency with, e.g. `yarn add @huggingface/inference`')
|
||||
throw new Error('Please install huggingface as a dependency with, e.g. `pnpm add @huggingface/inference`')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,7 +595,27 @@ export class App {
|
|||
|
||||
// Get internal chatmessages from chatflowid
|
||||
this.app.get('/api/v1/internal-chatmessage/:id', async (req: Request, res: Response) => {
|
||||
const chatmessages = await this.getChatMessage(req.params.id, chatType.INTERNAL)
|
||||
const sortOrder = req.query?.order as string | undefined
|
||||
const chatId = req.query?.chatId as string | undefined
|
||||
const memoryType = req.query?.memoryType as string | undefined
|
||||
const sessionId = req.query?.sessionId as string | undefined
|
||||
const messageId = req.query?.messageId as string | undefined
|
||||
const startDate = req.query?.startDate as string | undefined
|
||||
const endDate = req.query?.endDate as string | undefined
|
||||
const feedback = req.query?.feedback as boolean | undefined
|
||||
|
||||
const chatmessages = await this.getChatMessage(
|
||||
req.params.id,
|
||||
chatType.INTERNAL,
|
||||
sortOrder,
|
||||
chatId,
|
||||
memoryType,
|
||||
sessionId,
|
||||
startDate,
|
||||
endDate,
|
||||
messageId,
|
||||
feedback
|
||||
)
|
||||
return res.json(chatmessages)
|
||||
})
|
||||
|
||||
|
|
@ -703,19 +723,41 @@ export class App {
|
|||
// get stats for showing in chatflow
|
||||
this.app.get('/api/v1/stats/:id', async (req: Request, res: Response) => {
|
||||
const chatflowid = req.params.id
|
||||
const chatTypeFilter = chatType.EXTERNAL
|
||||
let chatTypeFilter = req.query?.chatType as chatType | undefined
|
||||
const startDate = req.query?.startDate as string | undefined
|
||||
const endDate = req.query?.endDate as string | undefined
|
||||
|
||||
const totalMessages = await this.AppDataSource.getRepository(ChatMessage).count({
|
||||
where: {
|
||||
chatflowid,
|
||||
chatType: chatTypeFilter
|
||||
if (chatTypeFilter) {
|
||||
try {
|
||||
const chatTypeFilterArray = JSON.parse(chatTypeFilter)
|
||||
if (chatTypeFilterArray.includes(chatType.EXTERNAL) && chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
||||
chatTypeFilter = undefined
|
||||
} else if (chatTypeFilterArray.includes(chatType.EXTERNAL)) {
|
||||
chatTypeFilter = chatType.EXTERNAL
|
||||
} else if (chatTypeFilterArray.includes(chatType.INTERNAL)) {
|
||||
chatTypeFilter = chatType.INTERNAL
|
||||
}
|
||||
} catch (e) {
|
||||
return res.status(500).send(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const chatMessageFeedbackRepo = this.AppDataSource.getRepository(ChatMessageFeedback)
|
||||
const chatmessages = (await this.getChatMessage(
|
||||
chatflowid,
|
||||
chatTypeFilter,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
startDate,
|
||||
endDate,
|
||||
'',
|
||||
true
|
||||
)) as Array<ChatMessage & { feedback?: ChatMessageFeedback }>
|
||||
const totalMessages = chatmessages.length
|
||||
|
||||
const totalFeedback = await chatMessageFeedbackRepo.count({ where: { chatflowid } })
|
||||
const positiveFeedback = await chatMessageFeedbackRepo.countBy({ chatflowid, rating: ChatMessageRatingType.THUMBS_UP })
|
||||
const totalFeedback = chatmessages.filter((message) => message?.feedback).length
|
||||
const positiveFeedback = chatmessages.filter((message) => message?.feedback?.rating === 'THUMBS_UP').length
|
||||
|
||||
const results = {
|
||||
totalMessages,
|
||||
|
|
|
|||
|
|
@ -162,39 +162,22 @@ export const constructGraphs = (
|
|||
* @param {string} endNodeId
|
||||
*/
|
||||
export const getStartingNodes = (graph: INodeDirectedGraph, endNodeId: string) => {
|
||||
const visited = new Set<string>()
|
||||
const queue: Array<[string, number]> = [[endNodeId, 0]]
|
||||
const depthQueue: IDepthQueue = {
|
||||
[endNodeId]: 0
|
||||
}
|
||||
|
||||
let maxDepth = 0
|
||||
let startingNodeIds: string[] = []
|
||||
|
||||
while (queue.length > 0) {
|
||||
const [currentNode, depth] = queue.shift()!
|
||||
|
||||
if (visited.has(currentNode)) {
|
||||
continue
|
||||
}
|
||||
|
||||
visited.add(currentNode)
|
||||
|
||||
if (depth > maxDepth) {
|
||||
maxDepth = depth
|
||||
startingNodeIds = [currentNode]
|
||||
} else if (depth === maxDepth) {
|
||||
startingNodeIds.push(currentNode)
|
||||
}
|
||||
|
||||
for (const neighbor of graph[currentNode]) {
|
||||
if (!visited.has(neighbor)) {
|
||||
queue.push([neighbor, depth + 1])
|
||||
depthQueue[neighbor] = depth + 1
|
||||
}
|
||||
}
|
||||
// Assuming that this is a directed acyclic graph, there will be no infinite loop problem.
|
||||
const walkGraph = (nodeId: string) => {
|
||||
const depth = depthQueue[nodeId]
|
||||
graph[nodeId].flatMap((id) => {
|
||||
depthQueue[id] = Math.max(depthQueue[id] ?? 0, depth + 1)
|
||||
walkGraph(id)
|
||||
})
|
||||
}
|
||||
|
||||
walkGraph(endNodeId)
|
||||
|
||||
const maxDepth = Math.max(...Object.values(depthQueue))
|
||||
const depthQueueReversed: IDepthQueue = {}
|
||||
for (const nodeId in depthQueue) {
|
||||
if (Object.prototype.hasOwnProperty.call(depthQueue, nodeId)) {
|
||||
|
|
@ -202,6 +185,10 @@ export const getStartingNodes = (graph: INodeDirectedGraph, endNodeId: string) =
|
|||
}
|
||||
}
|
||||
|
||||
const startingNodeIds = Object.entries(depthQueueReversed)
|
||||
.filter(([_, depth]) => depth === 0)
|
||||
.map(([id, _]) => id)
|
||||
|
||||
return { startingNodeIds, depthQueue: depthQueueReversed }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import client from './client'
|
||||
|
||||
const getInternalChatmessageFromChatflow = (id) => client.get(`/internal-chatmessage/${id}`)
|
||||
const getInternalChatmessageFromChatflow = (id, params = {}) =>
|
||||
client.get(`/internal-chatmessage/${id}`, { params: { feedback: true, ...params } })
|
||||
const getAllChatmessageFromChatflow = (id, params = {}) =>
|
||||
client.get(`/chatmessage/${id}`, { params: { order: 'DESC', feedback: true, ...params } })
|
||||
const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', feedback: true, ...params } })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import client from './client'
|
||||
|
||||
const addFeedback = (id, body) => client.post(`/feedback/${id}`, body)
|
||||
const updateFeedback = (id, body) => client.put(`/feedback/${id}`, body)
|
||||
|
||||
export default {
|
||||
addFeedback,
|
||||
updateFeedback
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import client from './client'
|
||||
|
||||
const getStatsFromChatflow = (id) => client.get(`/stats/${id}`)
|
||||
const getStatsFromChatflow = (id, params) => client.get(`/stats/${id}`, { params: { ...params } })
|
||||
|
||||
export default {
|
||||
getStatsFromChatflow
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { IconButton } from '@mui/material'
|
||||
import { IconClipboard } from '@tabler/icons'
|
||||
|
||||
const CopyToClipboardButton = (props) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
disabled={props.isDisabled || props.isLoading}
|
||||
onClick={props.onClick}
|
||||
size='small'
|
||||
sx={{ background: 'transparent', border: 'none' }}
|
||||
title='Copy to clipboard'
|
||||
>
|
||||
<IconClipboard
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
color={props.isLoading ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||
/>
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
||||
CopyToClipboardButton.propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
export default CopyToClipboardButton
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { IconButton } from '@mui/material'
|
||||
import { IconThumbDown } from '@tabler/icons'
|
||||
|
||||
const ThumbsDownButton = (props) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
return (
|
||||
<IconButton
|
||||
disabled={props.isDisabled || props.isLoading}
|
||||
onClick={props.onClick}
|
||||
size='small'
|
||||
sx={{ background: 'transparent', border: 'none' }}
|
||||
title='Thumbs Down'
|
||||
>
|
||||
<IconThumbDown
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
color={props.rating === 'THUMBS_DOWN' ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||
/>
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
||||
ThumbsDownButton.propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
rating: PropTypes.string
|
||||
}
|
||||
|
||||
export default ThumbsDownButton
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { IconButton } from '@mui/material'
|
||||
import { IconThumbUp } from '@tabler/icons'
|
||||
|
||||
const ThumbsUpButton = (props) => {
|
||||
const customization = useSelector((state) => state.customization)
|
||||
return (
|
||||
<IconButton
|
||||
disabled={props.isDisabled || props.isLoading}
|
||||
onClick={props.onClick}
|
||||
size='small'
|
||||
sx={{ background: 'transparent', border: 'none' }}
|
||||
title='Thumbs Up'
|
||||
>
|
||||
<IconThumbUp
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
color={props.rating === 'THUMBS_UP' ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'}
|
||||
/>
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
||||
ThumbsUpButton.propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
rating: PropTypes.string
|
||||
}
|
||||
|
||||
export default ThumbsUpButton
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions'
|
||||
|
||||
// material-ui
|
||||
import { Button, Box } from '@mui/material'
|
||||
|
|
@ -12,6 +11,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import { useCallback, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Button, Dialog, DialogContent, DialogTitle, DialogActions, Box, OutlinedInput } from '@mui/material'
|
||||
import { useState } from 'react'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
|
||||
const ChatFeedbackContentDialog = ({ show, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [feedbackContent, setFeedbackContent] = useState('')
|
||||
|
||||
const onChange = useCallback((e) => setFeedbackContent(e.target.value), [setFeedbackContent])
|
||||
|
||||
const onSave = () => {
|
||||
onConfirm(feedbackContent)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => {
|
||||
dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
setFeedbackContent('')
|
||||
}
|
||||
}, [show, dispatch])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
Provide additional feedback
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<OutlinedInput
|
||||
// eslint-disable-next-line
|
||||
autoFocus
|
||||
id='feedbackContentInput'
|
||||
multiline={true}
|
||||
name='feedbackContentInput'
|
||||
onChange={onChange}
|
||||
placeholder='What do you think of the response?'
|
||||
rows={4}
|
||||
value={feedbackContent}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Submit Feedback
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
export default ChatFeedbackContentDialog
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// material-ui
|
||||
import { Button, Box } 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 ChatFeedback = ({ dialogProps }) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const handleChange = (value) => {
|
||||
setChatFeedbackStatus(value)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
chatFeedback: {
|
||||
status: chatFeedbackStatus
|
||||
}
|
||||
}
|
||||
chatbotConfig.chatFeedback = value.chatFeedback
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Chat Feedback Settings 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 Chat Feedback Settings: ${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.chatFeedback) {
|
||||
setChatFeedbackStatus(chatbotConfig.chatFeedback.status)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [dialogProps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<SwitchInput label='Enable chat feedback' onChange={handleChange} value={chatFeedbackStatus} />
|
||||
</Box>
|
||||
<StyledButton style={{ marginBottom: 10, marginTop: 10 }} variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ChatFeedback.propTypes = {
|
||||
dialogProps: PropTypes.object
|
||||
}
|
||||
|
||||
export default ChatFeedback
|
||||
|
|
@ -115,6 +115,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
startDate: date,
|
||||
endDate: endDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const onEndDateSelected = (date) => {
|
||||
|
|
@ -124,6 +129,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
startDate: startDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
endDate: date,
|
||||
startDate: startDate,
|
||||
chatType: chatTypeFilter.length ? chatTypeFilter : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const onChatTypeSelected = (chatTypes) => {
|
||||
|
|
@ -133,6 +143,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
getStatsApi.request(dialogProps.chatflow.id, {
|
||||
chatType: chatTypes.length ? chatTypes : undefined,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
})
|
||||
}
|
||||
|
||||
const exportMessages = async () => {
|
||||
|
|
@ -432,6 +447,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
setSelectedMessageIndex(0)
|
||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||
setEndDate(new Date())
|
||||
setStats([])
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -463,23 +479,6 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
marginBottom: 16,
|
||||
marginLeft: 8,
|
||||
marginRight: 8
|
||||
}}
|
||||
>
|
||||
<StatsCard title='Total Messages (API/Embed)' stat={`${stats.totalMessages}`} />
|
||||
<StatsCard title='Total Feedback Received' stat={`${stats.totalFeedback}`} />
|
||||
<StatsCard
|
||||
title='Positive Feedback'
|
||||
stat={`${((stats.positiveFeedback / stats.totalFeedback) * 100 || 0).toFixed(2)}%`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
|
@ -536,6 +535,23 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
</div>
|
||||
<div style={{ flex: 1 }}></div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
|
||||
gap: 10,
|
||||
marginBottom: 16,
|
||||
marginLeft: 8,
|
||||
marginRight: 8
|
||||
}}
|
||||
>
|
||||
<StatsCard title='Total Messages' stat={`${stats.totalMessages}`} />
|
||||
<StatsCard title='Total Feedback Received' stat={`${stats.totalFeedback}`} />
|
||||
<StatsCard
|
||||
title='Positive Feedback'
|
||||
stat={`${((stats.positiveFeedback / stats.totalFeedback) * 100 || 0).toFixed(2)}%`}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{chatlogs && chatlogs.length == 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||
|
|
|
|||
|
|
@ -32,9 +32,13 @@ import audioUploadSVG from '@/assets/images/wave-sound.jpg'
|
|||
import { CodeBlock } from '@/ui-component/markdown/CodeBlock'
|
||||
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
|
||||
import SourceDocDialog from '@/ui-component/dialog/SourceDocDialog'
|
||||
import ChatFeedbackContentDialog from '@/ui-component/dialog/ChatFeedbackContentDialog'
|
||||
import StarterPromptsCard from '@/ui-component/cards/StarterPromptsCard'
|
||||
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||
import { ImageButton, ImageSrc, ImageBackdrop, ImageMarked } from '@/ui-component/button/ImageButton'
|
||||
import CopyToClipboardButton from '@/ui-component/button/CopyToClipboardButton'
|
||||
import ThumbsUpButton from '@/ui-component/button/ThumbsUpButton'
|
||||
import ThumbsDownButton from '@/ui-component/button/ThumbsDownButton'
|
||||
import './ChatMessage.css'
|
||||
import './audio-recording.css'
|
||||
|
||||
|
|
@ -42,6 +46,7 @@ import './audio-recording.css'
|
|||
import chatmessageApi from '@/api/chatmessage'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import predictionApi from '@/api/prediction'
|
||||
import chatmessagefeedbackApi from '@/api/chatmessagefeedback'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
|
@ -86,6 +91,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
||||
|
||||
const [starterPrompts, setStarterPrompts] = useState([])
|
||||
const [chatFeedbackStatus, setChatFeedbackStatus] = useState(false)
|
||||
const [feedbackId, setFeedbackId] = useState('')
|
||||
const [showFeedbackContentDialog, setShowFeedbackContentDialog] = useState(false)
|
||||
|
||||
// drag & drop and file input
|
||||
const fileUploadRef = useRef(null)
|
||||
|
|
@ -318,6 +326,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||
allMessages[allMessages.length - 1].message += text
|
||||
allMessages[allMessages.length - 1].feedback = null
|
||||
return allMessages
|
||||
})
|
||||
}
|
||||
|
|
@ -389,6 +398,14 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
if (response.data) {
|
||||
const data = response.data
|
||||
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'apiMessage') {
|
||||
allMessages[allMessages.length - 1].id = data?.chatMessageId
|
||||
}
|
||||
return allMessages
|
||||
})
|
||||
|
||||
if (!chatId) setChatId(data.chatId)
|
||||
|
||||
if (input === '' && data.question) {
|
||||
|
|
@ -412,10 +429,12 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
...prevMessages,
|
||||
{
|
||||
message: text,
|
||||
id: data?.chatMessageId,
|
||||
sourceDocuments: data?.sourceDocuments,
|
||||
usedTools: data?.usedTools,
|
||||
fileAnnotations: data?.fileAnnotations,
|
||||
type: 'apiMessage'
|
||||
type: 'apiMessage',
|
||||
feedback: null
|
||||
}
|
||||
])
|
||||
}
|
||||
|
|
@ -474,7 +493,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
setChatId(chatId)
|
||||
const loadedMessages = getChatmessageApi.data.map((message) => {
|
||||
const obj = {
|
||||
id: message.id,
|
||||
message: message.content,
|
||||
feedback: message.feedback,
|
||||
type: message.role
|
||||
}
|
||||
if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments)
|
||||
|
|
@ -527,6 +548,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
})
|
||||
setStarterPrompts(inputFields)
|
||||
}
|
||||
if (config.chatFeedback) {
|
||||
setChatFeedbackStatus(config.chatFeedback.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -604,6 +628,83 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
// eslint-disable-next-line
|
||||
}, [previews])
|
||||
|
||||
const copyMessageToClipboard = async (text) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text || '')
|
||||
} catch (error) {
|
||||
console.error('Error copying to clipboard:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onThumbsUpClick = async (messageId) => {
|
||||
const body = {
|
||||
chatflowid,
|
||||
chatId,
|
||||
messageId,
|
||||
rating: 'THUMBS_UP',
|
||||
content: ''
|
||||
}
|
||||
const result = await chatmessagefeedbackApi.addFeedback(chatflowid, body)
|
||||
if (result.data) {
|
||||
const data = result.data
|
||||
let id = ''
|
||||
if (data && data.id) id = data.id
|
||||
setMessages((prevMessages) => {
|
||||
const allMessages = [...cloneDeep(prevMessages)]
|
||||
return allMessages.map((message) => {
|
||||
if (message.id === messageId) {
|
||||
message.feedback = {
|
||||
rating: 'THUMBS_UP'
|
||||
}
|
||||
}
|
||||
return message
|
||||
})
|
||||
})
|
||||
setFeedbackId(id)
|
||||
setShowFeedbackContentDialog(true)
|
||||
}
|
||||
}
|
||||
|
||||
const onThumbsDownClick = async (messageId) => {
|
||||
const body = {
|
||||
chatflowid,
|
||||
chatId,
|
||||
messageId,
|
||||
rating: 'THUMBS_DOWN',
|
||||
content: ''
|
||||
}
|
||||
const result = await chatmessagefeedbackApi.addFeedback(chatflowid, body)
|
||||
if (result.data) {
|
||||
const data = result.data
|
||||
let id = ''
|
||||
if (data && data.id) id = data.id
|
||||
setMessages((prevMessages) => {
|
||||
const allMessages = [...cloneDeep(prevMessages)]
|
||||
return allMessages.map((message) => {
|
||||
if (message.id === messageId) {
|
||||
message.feedback = {
|
||||
rating: 'THUMBS_DOWN'
|
||||
}
|
||||
}
|
||||
return message
|
||||
})
|
||||
})
|
||||
setFeedbackId(id)
|
||||
setShowFeedbackContentDialog(true)
|
||||
}
|
||||
}
|
||||
|
||||
const submitFeedbackContent = async (text) => {
|
||||
const body = {
|
||||
content: text
|
||||
}
|
||||
const result = await chatmessagefeedbackApi.updateFeedback(feedbackId, body)
|
||||
if (result.data) {
|
||||
setFeedbackId('')
|
||||
setShowFeedbackContentDialog(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div onDragEnter={handleDrag}>
|
||||
{isDragActive && (
|
||||
|
|
@ -747,6 +848,31 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
{message.message}
|
||||
</MemoizedReactMarkdown>
|
||||
</div>
|
||||
{message.type === 'apiMessage' && message.id && chatFeedbackStatus ? (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'start', gap: 1 }}>
|
||||
<CopyToClipboardButton onClick={() => copyMessageToClipboard(message.message)} />
|
||||
{!message.feedback ||
|
||||
message.feedback.rating === '' ||
|
||||
message.feedback.rating === 'THUMBS_UP' ? (
|
||||
<ThumbsUpButton
|
||||
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_UP'}
|
||||
rating={message.feedback ? message.feedback.rating : ''}
|
||||
onClick={() => onThumbsUpClick(message.id)}
|
||||
/>
|
||||
) : null}
|
||||
{!message.feedback ||
|
||||
message.feedback.rating === '' ||
|
||||
message.feedback.rating === 'THUMBS_DOWN' ? (
|
||||
<ThumbsDownButton
|
||||
isDisabled={message.feedback && message.feedback.rating === 'THUMBS_DOWN'}
|
||||
rating={message.feedback ? message.feedback.rating : ''}
|
||||
onClick={() => onThumbsDownClick(message.id)}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
{message.fileAnnotations && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
{message.fileAnnotations.map((fileAnnotation, index) => {
|
||||
|
|
@ -993,6 +1119,11 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||
)}
|
||||
</div>
|
||||
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||
<ChatFeedbackContentDialog
|
||||
show={showFeedbackContentDialog}
|
||||
onCancel={() => setShowFeedbackContentDialog(false)}
|
||||
onConfirm={submitFeedbackContent}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue