mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Feature/lang graph (#2319)
* add langgraph * datasource: initial commit * datasource: datasource details and chunks * datasource: Document Store Node * more changes * Document Store - Base functionality * Document Store Loader Component * Document Store Loader Component * before merging the modularity PR * after merging the modularity PR * preview mode * initial draft PR * fixes * minor updates and fixes * preview with loader and splitter * preview with credential * show stored chunks * preview update... * edit config * save, preview and other changes * save, preview and other changes * save, process and other changes * save, process and other changes * alpha1 - for internal testing * rerouting urls * bug fix on new leader create * pagination support for chunks * delete document store * Update pnpm-lock.yaml * doc store card view * Update store files to use updated storage functions, Document Store Table View and other changes * ui changes * add expanded chunk dialog, improve ui * change throw Error to InternalError * Bug Fixes and removal of subFolder, adding of view chunks for store * lint fixes * merge changes * DocumentStoreStatus component * ui changes for doc store * add remove metadata key field, add custom document loader * add chatflows used doc store chips * add types/interfaces to DocumentStore Services * document loader list dialog title bar color change * update interfaces * Whereused Chatflow Name and Added chunkNo to retain order of created chunks. * use typeorm order chunkNo, ui changes * update tabler icons react * cleanup agents * add pysandbox tool * add abort functionality, loading next agent * add empty view svg * update chatflow tool with chatId * rename to agentflows * update worker for prompt input values * update dashboard to agentflows, agentcanvas * fix marketplace use template * add agentflow templates * resolve merge conflict * update baseURL --------- Co-authored-by: vinodkiran <vinodkiran@usa.net> Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>
This commit is contained in:
@@ -2,6 +2,8 @@ import client from './client'
|
||||
|
||||
const getAllChatflows = () => client.get('/chatflows')
|
||||
|
||||
const getAllAgentflows = () => client.get('/chatflows?type=MULTIAGENT')
|
||||
|
||||
const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`)
|
||||
|
||||
const getSpecificChatflowFromPublicEndpoint = (id) => client.get(`/public-chatflows/${id}`)
|
||||
@@ -18,6 +20,7 @@ const getAllowChatflowUploads = (id) => client.get(`/chatflows-uploads/${id}`)
|
||||
|
||||
export default {
|
||||
getAllChatflows,
|
||||
getAllAgentflows,
|
||||
getSpecificChatflow,
|
||||
getSpecificChatflowFromPublicEndpoint,
|
||||
createNewChatflow,
|
||||
|
||||
@@ -7,11 +7,13 @@ const getAllChatmessageFromChatflow = (id, params = {}) =>
|
||||
const getChatmessageFromPK = (id, params = {}) => client.get(`/chatmessage/${id}`, { params: { order: 'ASC', feedback: true, ...params } })
|
||||
const deleteChatmessage = (id, params = {}) => client.delete(`/chatmessage/${id}`, { params: { ...params } })
|
||||
const getStoragePath = () => client.get(`/get-upload-path`)
|
||||
const abortMessage = (chatflowid, chatid) => client.put(`/chatmessage/abort/${chatflowid}/${chatid}`)
|
||||
|
||||
export default {
|
||||
getInternalChatmessageFromChatflow,
|
||||
getAllChatmessageFromChatflow,
|
||||
getChatmessageFromPK,
|
||||
deleteChatmessage,
|
||||
getStoragePath
|
||||
getStoragePath,
|
||||
abortMessage
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="649.67538" height="226.1298" viewBox="0 0 649.67538 226.1298" xmlns:xlink="http://www.w3.org/1999/xlink"><path id="bcc0edba-f0dd-423b-bdfc-f2fa10ec70b3-2273" data-name="Path 680" d="M363.08335,336.9351a3.78766,3.78766,0,0,0,0,7.57532H439.9178a3.78766,3.78766,0,1,0,0-7.57532Z" transform="translate(-275.16231 -336.9351)" fill="#3f3d56"/><path id="fa56b25f-6ad1-4bb0-b587-f288aecf2229-2274" data-name="Path 681" d="M363.08335,359.25074a3.78767,3.78767,0,0,0,0,7.57533H439.9178a3.78767,3.78767,0,0,0,0-7.57533Z" transform="translate(-275.16231 -336.9351)" fill="#3f3d56"/><path id="b413f49f-a7ee-4528-83fd-849b3d17d98f-2275" data-name="Path 682" d="M401.50117,430.02472a23.84091,23.84091,0,1,1,23.84091-23.842h0A23.86874,23.86874,0,0,1,401.50117,430.02472Z" transform="translate(-275.16231 -336.9351)" fill="#e4e4e4"/><path id="ba255611-006f-40c2-b981-3cbda4cfa311-2276" data-name="Path 680" d="M760.08335,336.9351a3.78766,3.78766,0,1,0,0,7.57532H836.9178a3.78766,3.78766,0,0,0,0-7.57532Z" transform="translate(-275.16231 -336.9351)" fill="#3f3d56"/><path id="f7616677-88a5-4e4e-be2c-5696cf4c3e42-2277" data-name="Path 681" d="M760.08335,359.25074a3.78767,3.78767,0,0,0,0,7.57533H836.9178a3.78767,3.78767,0,0,0,0-7.57533Z" transform="translate(-275.16231 -336.9351)" fill="#3f3d56"/><path id="b0ec98cc-856e-42d0-b3d1-ea9b3df6bb16-2278" data-name="Path 682" d="M798.50117,430.02472a23.84091,23.84091,0,1,1,23.84091-23.842h0A23.86874,23.86874,0,0,1,798.50117,430.02472Z" transform="translate(-275.16231 -336.9351)" fill="#e4e4e4"/><ellipse cx="484.90215" cy="475.49165" rx="7.19508" ry="22.91937" transform="translate(-427.67489 372.91709) rotate(-64.62574)" fill="#2f2e41"/><ellipse cx="585.28661" cy="504.7203" rx="7.19508" ry="22.91937" transform="translate(-462.64755 156.53075) rotate(-39.93837)" fill="#2f2e41"/><circle cx="261.3695" cy="159.8261" r="45.83867" fill="#2f2e41"/><rect x="266.84564" y="195.8006" width="13.92567" height="24.95017" fill="#2f2e41"/><rect x="238.9943" y="195.8006" width="13.92567" height="24.95017" fill="#2f2e41"/><ellipse cx="269.16657" cy="221.04089" rx="11.60475" ry="4.35179" fill="#2f2e41"/><ellipse cx="241.31523" cy="220.46068" rx="11.60475" ry="4.35179" fill="#2f2e41"/><path d="M522.13878,439.14059c4.093-16.48357,22.16034-26.18381,40.35452-21.66607s29.6254,21.54274,25.53236,38.02632-17.67239,16.53678-35.86657,12.019S518.04574,455.6242,522.13878,439.14059Z" transform="translate(-275.16231 -336.9351)" fill="#673ab7"/><circle cx="254.51656" cy="150.15373" r="15.28261" fill="#fff"/><circle cx="248.23545" cy="144.67575" r="5.0942" fill="#3f3d56"/><path d="M539.26587,518.92927a10.18842,10.18842,0,0,1-20.04736,3.64948h0l-.00358-.01969c-1.00237-5.5369,3.27861-7.49624,8.81551-8.49855S538.26356,513.39243,539.26587,518.92927Z" transform="translate(-275.16231 -336.9351)" fill="#fff"/><ellipse cx="715.09785" cy="475.49165" rx="22.91937" ry="7.19508" transform="translate(-409.9374 15.37692) rotate(-25.37426)" fill="#2f2e41"/><ellipse cx="614.71339" cy="504.7203" rx="22.91937" ry="7.19508" transform="translate(-442.05925 315.09586) rotate(-50.06163)" fill="#2f2e41"/><circle cx="388.30588" cy="159.8261" r="45.83867" fill="#2f2e41"/><rect x="368.90407" y="195.8006" width="13.92567" height="24.95017" fill="#2f2e41"/><rect x="396.75541" y="195.8006" width="13.92567" height="24.95017" fill="#2f2e41"/><ellipse cx="380.50882" cy="221.04089" rx="11.60475" ry="4.35179" fill="#2f2e41"/><ellipse cx="408.36016" cy="220.46068" rx="11.60475" ry="4.35179" fill="#2f2e41"/><path d="M677.86122,439.14059c-4.093-16.48357-22.16034-26.18381-40.35452-21.66607s-29.6254,21.54274-25.53236,38.02632,17.67239,16.53678,35.86657,12.019S681.95426,455.6242,677.86122,439.14059Z" transform="translate(-275.16231 -336.9351)" fill="#673ab7"/><circle cx="395.15882" cy="150.15373" r="15.28261" fill="#fff"/><circle cx="401.43994" cy="144.67575" r="5.0942" fill="#3f3d56"/><path d="M660.73413,518.92927a10.18842,10.18842,0,0,0,20.04736,3.64948h0l.00358-.01969c1.00237-5.5369-3.27861-7.49624-8.81551-8.49855S661.73644,513.39243,660.73413,518.92927Z" transform="translate(-275.16231 -336.9351)" fill="#fff"/><path d="M923.647,563.0649H276.353a1.19069,1.19069,0,0,1,0-2.38137H923.647a1.19068,1.19068,0,0,1,0,2.38137Z" transform="translate(-275.16231 -336.9351)" fill="#3f3d56"/></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -135,6 +135,18 @@ const NavItem = ({ item, level, navType, onClick, onUploadFile }) => {
|
||||
avatar={item.chip.avatar && <Avatar>{item.chip.avatar}</Avatar>}
|
||||
/>
|
||||
)}
|
||||
{item.isBeta && (
|
||||
<Chip
|
||||
sx={{
|
||||
width: 'max-content',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.65rem',
|
||||
background: theme.palette.teal.main,
|
||||
color: 'white'
|
||||
}}
|
||||
label={'BETA'}
|
||||
/>
|
||||
)}
|
||||
</ListItemButton>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// assets
|
||||
import {
|
||||
IconTrash,
|
||||
IconFileUpload,
|
||||
IconFileExport,
|
||||
IconCopy,
|
||||
IconMessage,
|
||||
IconDatabaseExport,
|
||||
IconAdjustmentsHorizontal,
|
||||
IconUsers
|
||||
} from '@tabler/icons-react'
|
||||
|
||||
// constant
|
||||
const icons = {
|
||||
IconTrash,
|
||||
IconFileUpload,
|
||||
IconFileExport,
|
||||
IconCopy,
|
||||
IconMessage,
|
||||
IconDatabaseExport,
|
||||
IconAdjustmentsHorizontal,
|
||||
IconUsers
|
||||
}
|
||||
|
||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||
|
||||
const agent_settings = {
|
||||
id: 'settings',
|
||||
title: '',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
id: 'viewMessages',
|
||||
title: 'View Messages',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconMessage
|
||||
},
|
||||
{
|
||||
id: 'viewLeads',
|
||||
title: 'View Leads',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconUsers
|
||||
},
|
||||
{
|
||||
id: 'chatflowConfiguration',
|
||||
title: 'Configuration',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconAdjustmentsHorizontal
|
||||
},
|
||||
{
|
||||
id: 'duplicateChatflow',
|
||||
title: 'Duplicate Agents',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconCopy
|
||||
},
|
||||
{
|
||||
id: 'loadChatflow',
|
||||
title: 'Load Agents',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconFileUpload
|
||||
},
|
||||
{
|
||||
id: 'exportChatflow',
|
||||
title: 'Export Agents',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconFileExport
|
||||
},
|
||||
{
|
||||
id: 'deleteChatflow',
|
||||
title: 'Delete Agents',
|
||||
type: 'item',
|
||||
url: '',
|
||||
icon: icons.IconTrash
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default agent_settings
|
||||
@@ -1,8 +1,18 @@
|
||||
// assets
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable, IconFiles } from '@tabler/icons-react'
|
||||
import {
|
||||
IconUsersGroup,
|
||||
IconHierarchy,
|
||||
IconBuildingStore,
|
||||
IconKey,
|
||||
IconTool,
|
||||
IconLock,
|
||||
IconRobot,
|
||||
IconVariable,
|
||||
IconFiles
|
||||
} from '@tabler/icons-react'
|
||||
|
||||
// constant
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable, IconFiles }
|
||||
const icons = { IconUsersGroup, IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable, IconFiles }
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
@@ -19,6 +29,15 @@ const dashboard = {
|
||||
icon: icons.IconHierarchy,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'agentflows',
|
||||
title: 'Agentflows',
|
||||
type: 'item',
|
||||
url: '/agentflows',
|
||||
icon: icons.IconUsersGroup,
|
||||
breadcrumbs: true,
|
||||
isBeta: true
|
||||
},
|
||||
{
|
||||
id: 'marketplaces',
|
||||
title: 'Marketplaces',
|
||||
@@ -68,7 +87,7 @@ const dashboard = {
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'documents',
|
||||
id: 'document-stores',
|
||||
title: 'Document Stores',
|
||||
type: 'item',
|
||||
url: '/document-stores',
|
||||
|
||||
@@ -22,6 +22,14 @@ const CanvasRoutes = {
|
||||
path: '/canvas/:id',
|
||||
element: <Canvas />
|
||||
},
|
||||
{
|
||||
path: '/agentcanvas',
|
||||
element: <Canvas />
|
||||
},
|
||||
{
|
||||
path: '/agentcanvas/:id',
|
||||
element: <Canvas />
|
||||
},
|
||||
{
|
||||
path: '/marketplace/:id',
|
||||
element: <MarketplaceCanvas />
|
||||
|
||||
@@ -7,6 +7,9 @@ import Loadable from '@/ui-component/loading/Loadable'
|
||||
// chatflows routing
|
||||
const Chatflows = Loadable(lazy(() => import('@/views/chatflows')))
|
||||
|
||||
// agents routing
|
||||
const Agentflows = Loadable(lazy(() => import('@/views/agentflows')))
|
||||
|
||||
// marketplaces routing
|
||||
const Marketplaces = Loadable(lazy(() => import('@/views/marketplaces')))
|
||||
|
||||
@@ -45,6 +48,10 @@ const MainRoutes = {
|
||||
path: '/chatflows',
|
||||
element: <Chatflows />
|
||||
},
|
||||
{
|
||||
path: '/agentflows',
|
||||
element: <Agentflows />
|
||||
},
|
||||
{
|
||||
path: '/marketplaces',
|
||||
element: <Marketplaces />
|
||||
|
||||
@@ -72,7 +72,7 @@ const StyledMenu = styled((props) => (
|
||||
}
|
||||
}))
|
||||
|
||||
export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
export default function FlowListMenu({ chatflow, isAgentCanvas, setError, updateFlowsApi }) {
|
||||
const { confirm } = useConfirm()
|
||||
const dispatch = useDispatch()
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
@@ -95,6 +95,8 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
const [speechToTextDialogOpen, setSpeechToTextDialogOpen] = useState(false)
|
||||
const [speechToTextDialogProps, setSpeechToTextDialogProps] = useState({})
|
||||
|
||||
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
@@ -213,7 +215,7 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
setAnchorEl(null)
|
||||
const confirmPayload = {
|
||||
title: `Delete`,
|
||||
description: `Delete chatflow ${chatflow.name}?`,
|
||||
description: `Delete ${title} ${chatflow.name}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
@@ -246,7 +248,7 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
setAnchorEl(null)
|
||||
try {
|
||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||
window.open(`${uiBaseURL}/canvas`, '_blank')
|
||||
window.open(`${uiBaseURL}/${isAgentCanvas ? 'agentcanvas' : 'canvas'}`, '_blank')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -259,7 +261,7 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `${chatflow.name} Chatflow.json`
|
||||
let exportFileDefaultName = `${chatflow.name} ${title}.json`
|
||||
|
||||
let linkElement = document.createElement('a')
|
||||
linkElement.setAttribute('href', dataUri)
|
||||
@@ -334,7 +336,7 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
<SaveChatflowDialog
|
||||
show={flowDialogOpen}
|
||||
dialogProps={{
|
||||
title: `Rename Chatflow`,
|
||||
title: `Rename ${title}`,
|
||||
confirmButtonName: 'Rename',
|
||||
cancelButtonName: 'Cancel'
|
||||
}}
|
||||
@@ -373,6 +375,7 @@ export default function FlowListMenu({ chatflow, setError, updateFlowsApi }) {
|
||||
|
||||
FlowListMenu.propTypes = {
|
||||
chatflow: PropTypes.object,
|
||||
isAgentCanvas: PropTypes.bool,
|
||||
setError: PropTypes.func,
|
||||
updateFlowsApi: PropTypes.object
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
ListItemText,
|
||||
Chip,
|
||||
Card,
|
||||
CardMedia
|
||||
CardMedia,
|
||||
CardContent
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import DatePicker from 'react-datepicker'
|
||||
@@ -31,6 +32,8 @@ 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 multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
|
||||
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
|
||||
import { IconFileExport, IconEraser, IconX, IconDownload } from '@tabler/icons-react'
|
||||
|
||||
// Project import
|
||||
@@ -185,6 +188,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
if (chatmsg.usedTools) msg.usedTools = JSON.parse(chatmsg.usedTools)
|
||||
if (chatmsg.fileAnnotations) msg.fileAnnotations = JSON.parse(chatmsg.fileAnnotations)
|
||||
if (chatmsg.feedback) msg.feedback = chatmsg.feedback?.content
|
||||
if (chatmsg.agentReasoning) msg.agentReasoning = JSON.parse(chatmsg.agentReasoning)
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, chatPK)) {
|
||||
obj[chatPK] = {
|
||||
@@ -319,6 +323,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
if (chatmsg.sourceDocuments) obj.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||
if (chatmsg.usedTools) obj.usedTools = JSON.parse(chatmsg.usedTools)
|
||||
if (chatmsg.fileAnnotations) obj.fileAnnotations = JSON.parse(chatmsg.fileAnnotations)
|
||||
if (chatmsg.agentReasoning) obj.agentReasoning = JSON.parse(chatmsg.agentReasoning)
|
||||
|
||||
loadedMessages.push(obj)
|
||||
}
|
||||
@@ -803,6 +808,97 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{message.agentReasoning && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
{message.agentReasoning.map((agent, index) => {
|
||||
return (
|
||||
<Card
|
||||
key={index}
|
||||
sx={{
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%'
|
||||
}}
|
||||
flexDirection='row'
|
||||
>
|
||||
<Box sx={{ height: 'auto', pr: 1 }}>
|
||||
<img
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
height: '25px',
|
||||
width: 'auto'
|
||||
}}
|
||||
src={
|
||||
agent.instructions
|
||||
? multiagent_supervisorPNG
|
||||
: multiagent_workerPNG
|
||||
}
|
||||
alt='agentPNG'
|
||||
/>
|
||||
</Box>
|
||||
<div>{agent.agentName}</div>
|
||||
</Stack>
|
||||
{agent.messages.length > 0 && (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{agent.messages.length > 1
|
||||
? agent.messages.join('\\n')
|
||||
: agent.messages[0]}
|
||||
</MemoizedReactMarkdown>
|
||||
)}
|
||||
{agent.instructions && <p>{agent.instructions}</p>}
|
||||
{agent.messages.length === 0 &&
|
||||
!agent.instructions && <p>Finished</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
|
||||
@@ -41,7 +41,7 @@ const StyledTableRow = styled(TableRow)(() => ({
|
||||
}
|
||||
}))
|
||||
|
||||
export const FlowListTable = ({ data, images, isLoading, filterFunction, updateFlowsApi, setError }) => {
|
||||
export const FlowListTable = ({ data, images, isLoading, filterFunction, updateFlowsApi, setError, isAgentCanvas }) => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
@@ -128,7 +128,10 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<Link to={`/canvas/${row.id}`} style={{ color: '#2196f3', textDecoration: 'none' }}>
|
||||
<Link
|
||||
to={`/${isAgentCanvas ? 'agentcanvas' : 'canvas'}/${row.id}`}
|
||||
style={{ color: '#2196f3', textDecoration: 'none' }}
|
||||
>
|
||||
{row.templateName || row.name}
|
||||
</Link>
|
||||
</Typography>
|
||||
@@ -211,7 +214,12 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
>
|
||||
<FlowListMenu chatflow={row} setError={setError} updateFlowsApi={updateFlowsApi} />
|
||||
<FlowListMenu
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
chatflow={row}
|
||||
setError={setError}
|
||||
updateFlowsApi={updateFlowsApi}
|
||||
/>
|
||||
</Stack>
|
||||
</StyledTableCell>
|
||||
</StyledTableRow>
|
||||
@@ -231,5 +239,6 @@ FlowListTable.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
filterFunction: PropTypes.func,
|
||||
updateFlowsApi: PropTypes.object,
|
||||
setError: PropTypes.func
|
||||
setError: PropTypes.func,
|
||||
isAgentCanvas: PropTypes.bool
|
||||
}
|
||||
|
||||
@@ -549,14 +549,14 @@ export const removeDuplicateURL = (message) => {
|
||||
if (!message.sourceDocuments) return newSourceDocuments
|
||||
|
||||
message.sourceDocuments.forEach((source) => {
|
||||
if (source.metadata && source.metadata.source) {
|
||||
if (source && source.metadata && source.metadata.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)
|
||||
}
|
||||
} else {
|
||||
} else if (source) {
|
||||
newSourceDocuments.push(source)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
// material-ui
|
||||
import { Box, Skeleton, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import ItemCard from '@/ui-component/cards/ItemCard'
|
||||
import { gridSpacing } from '@/store/constant'
|
||||
import AgentsEmptySVG from '@/assets/images/agents_empty.svg'
|
||||
import LoginDialog from '@/ui-component/dialog/LoginDialog'
|
||||
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
import { FlowListTable } from '@/ui-component/table/FlowListTable'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import ViewHeader from '@/layout/MainLayout/ViewHeader'
|
||||
import ErrorBoundary from '@/ErrorBoundary'
|
||||
|
||||
// API
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
||||
// const
|
||||
import { baseURL } from '@/store/constant'
|
||||
|
||||
// icons
|
||||
import { IconPlus, IconLayoutGrid, IconList } from '@tabler/icons-react'
|
||||
|
||||
// ==============================|| AGENTS ||============================== //
|
||||
|
||||
const Agentflows = () => {
|
||||
const navigate = useNavigate()
|
||||
const theme = useTheme()
|
||||
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [images, setImages] = useState({})
|
||||
const [search, setSearch] = useState('')
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
||||
const [loginDialogProps, setLoginDialogProps] = useState({})
|
||||
|
||||
const getAllAgentflows = useApi(chatflowsApi.getAllAgentflows)
|
||||
const [view, setView] = useState(localStorage.getItem('flowDisplayStyle') || 'card')
|
||||
|
||||
const handleChange = (event, nextView) => {
|
||||
if (nextView === null) return
|
||||
localStorage.setItem('flowDisplayStyle', nextView)
|
||||
setView(nextView)
|
||||
}
|
||||
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
|
||||
function filterFlows(data) {
|
||||
return (
|
||||
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
|
||||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1)
|
||||
)
|
||||
}
|
||||
|
||||
const onLoginClick = (username, password) => {
|
||||
localStorage.setItem('username', username)
|
||||
localStorage.setItem('password', password)
|
||||
navigate(0)
|
||||
}
|
||||
|
||||
const addNew = () => {
|
||||
navigate('/agentcanvas')
|
||||
}
|
||||
|
||||
const goToCanvas = (selectedAgentflow) => {
|
||||
navigate(`/agentcanvas/${selectedAgentflow.id}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAgentflows.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAgentflows.error) {
|
||||
if (getAllAgentflows.error?.response?.status === 401) {
|
||||
setLoginDialogProps({
|
||||
title: 'Login',
|
||||
confirmButtonName: 'Login'
|
||||
})
|
||||
setLoginDialogOpen(true)
|
||||
} else {
|
||||
setError(getAllAgentflows.error)
|
||||
}
|
||||
}
|
||||
}, [getAllAgentflows.error])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(getAllAgentflows.loading)
|
||||
}, [getAllAgentflows.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAgentflows.data) {
|
||||
try {
|
||||
const agentflows = getAllAgentflows.data
|
||||
const images = {}
|
||||
for (let i = 0; i < agentflows.length; i += 1) {
|
||||
const flowDataStr = agentflows[i].flowData
|
||||
const flowData = JSON.parse(flowDataStr)
|
||||
const nodes = flowData.nodes || []
|
||||
images[agentflows[i].id] = []
|
||||
for (let j = 0; j < nodes.length; j += 1) {
|
||||
const imageSrc = `${baseURL}/api/v1/node-icon/${nodes[j].data.name}`
|
||||
if (!images[agentflows[i].id].includes(imageSrc)) {
|
||||
images[agentflows[i].id].push(imageSrc)
|
||||
}
|
||||
}
|
||||
}
|
||||
setImages(images)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}, [getAllAgentflows.data])
|
||||
|
||||
return (
|
||||
<MainCard>
|
||||
{error ? (
|
||||
<ErrorBoundary error={error} />
|
||||
) : (
|
||||
<Stack flexDirection='column' sx={{ gap: 3 }}>
|
||||
<ViewHeader onSearchChange={onSearchChange} search={true} searchPlaceholder='Search Name or Category' title='Agents'>
|
||||
<ToggleButtonGroup
|
||||
sx={{ borderRadius: 2, maxHeight: 40 }}
|
||||
value={view}
|
||||
color='primary'
|
||||
exclusive
|
||||
onChange={handleChange}
|
||||
>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='card'
|
||||
title='Card View'
|
||||
>
|
||||
<IconLayoutGrid />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
sx={{
|
||||
borderColor: theme.palette.grey[900] + 25,
|
||||
borderRadius: 2,
|
||||
color: theme?.customization?.isDarkMode ? 'white' : 'inherit'
|
||||
}}
|
||||
variant='contained'
|
||||
value='list'
|
||||
title='List View'
|
||||
>
|
||||
<IconList />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<StyledButton variant='contained' onClick={addNew} startIcon={<IconPlus />} sx={{ borderRadius: 2, height: 40 }}>
|
||||
Add New
|
||||
</StyledButton>
|
||||
</ViewHeader>
|
||||
{!view || view === 'card' ? (
|
||||
<>
|
||||
{isLoading && !getAllAgentflows.data ? (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
<Skeleton variant='rounded' height={160} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
|
||||
{getAllAgentflows.data?.filter(filterFlows).map((data, index) => (
|
||||
<ItemCard key={index} onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<FlowListTable
|
||||
isAgentCanvas={true}
|
||||
data={getAllAgentflows.data}
|
||||
images={images}
|
||||
isLoading={isLoading}
|
||||
filterFunction={filterFlows}
|
||||
updateFlowsApi={getAllAgentflows}
|
||||
setError={setError}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && (!getAllAgentflows.data || getAllAgentflows.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '12vh', width: 'auto' }}
|
||||
src={AgentsEmptySVG}
|
||||
alt='AgentsEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Agents Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<LoginDialog show={loginDialogOpen} dialogProps={loginDialogProps} onConfirm={onLoginClick} />
|
||||
<ConfirmDialog />
|
||||
</MainCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default Agentflows
|
||||
@@ -355,7 +355,7 @@ const APIKey = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={APIEmptySVG}
|
||||
alt='APIEmptySVG'
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Box, Stack, Button, Skeleton } from '@mui/material'
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import ItemCard from '@/ui-component/cards/ItemCard'
|
||||
import { gridSpacing } from '@/store/constant'
|
||||
import ToolEmptySVG from '@/assets/images/tools_empty.svg'
|
||||
import AssistantEmptySVG from '@/assets/images/assistant_empty.svg'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import AssistantDialog from './AssistantDialog'
|
||||
import LoadAssistantDialog from './LoadAssistantDialog'
|
||||
@@ -145,9 +145,9 @@ const Assistants = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
src={ToolEmptySVG}
|
||||
alt='ToolEmptySVG'
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={AssistantEmptySVG}
|
||||
alt='AssistantEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Assistants Added Yet</div>
|
||||
|
||||
@@ -53,7 +53,10 @@ function a11yProps(index) {
|
||||
}
|
||||
}
|
||||
|
||||
const AddNodes = ({ nodesData, node }) => {
|
||||
const blacklistCategoriesForAgentCanvas = ['Agents', 'Memory', 'Record Manager']
|
||||
const allowedAgentModel = {}
|
||||
|
||||
const AddNodes = ({ nodesData, node, isAgentCanvas }) => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const dispatch = useDispatch()
|
||||
@@ -103,7 +106,17 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
}
|
||||
|
||||
const getSearchedNodes = (value) => {
|
||||
const passed = nodesData.filter((nd) => {
|
||||
if (isAgentCanvas) {
|
||||
const nodes = nodesData.filter((nd) => !blacklistCategoriesForAgentCanvas.includes(nd.category))
|
||||
const passed = nodes.filter((nd) => {
|
||||
const passesQuery = nd.name.toLowerCase().includes(value.toLowerCase())
|
||||
const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase())
|
||||
return passesQuery || passesCategory
|
||||
})
|
||||
return passed
|
||||
}
|
||||
const nodes = nodesData.filter((nd) => nd.category !== 'Multi Agents')
|
||||
const passed = nodes.filter((nd) => {
|
||||
const passesQuery = nd.name.toLowerCase().includes(value.toLowerCase())
|
||||
const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase())
|
||||
return passesQuery || passesCategory
|
||||
@@ -136,17 +149,57 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
}
|
||||
|
||||
const groupByCategory = (nodes, newTabValue, isFilter) => {
|
||||
const taggedNodes = groupByTags(nodes, newTabValue)
|
||||
const accordianCategories = {}
|
||||
const result = taggedNodes.reduce(function (r, a) {
|
||||
r[a.category] = r[a.category] || []
|
||||
r[a.category].push(a)
|
||||
accordianCategories[a.category] = isFilter ? true : false
|
||||
return r
|
||||
}, Object.create(null))
|
||||
setNodes(result)
|
||||
categorizeVectorStores(result, accordianCategories, isFilter)
|
||||
setCategoryExpanded(accordianCategories)
|
||||
if (isAgentCanvas) {
|
||||
const accordianCategories = {}
|
||||
const result = nodes.reduce(function (r, a) {
|
||||
r[a.category] = r[a.category] || []
|
||||
r[a.category].push(a)
|
||||
accordianCategories[a.category] = isFilter ? true : false
|
||||
return r
|
||||
}, Object.create(null))
|
||||
|
||||
const filteredResult = {}
|
||||
for (const category in result) {
|
||||
// Filter out blacklisted categories
|
||||
if (!blacklistCategoriesForAgentCanvas.includes(category)) {
|
||||
// Filter out LlamaIndex nodes
|
||||
const nodes = result[category].filter((nd) => !nd.tags || !nd.tags.includes('LlamaIndex'))
|
||||
if (!nodes.length) continue
|
||||
|
||||
// Only allow specific models for specific categories
|
||||
if (Object.keys(allowedAgentModel).includes(category)) {
|
||||
const allowedModels = allowedAgentModel[category]
|
||||
filteredResult[category] = nodes.filter((nd) => allowedModels.includes(nd.name))
|
||||
} else {
|
||||
filteredResult[category] = nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
setNodes(filteredResult)
|
||||
categorizeVectorStores(filteredResult, accordianCategories, isFilter)
|
||||
accordianCategories['Multi Agents'] = true
|
||||
setCategoryExpanded(accordianCategories)
|
||||
} else {
|
||||
const taggedNodes = groupByTags(nodes, newTabValue)
|
||||
const accordianCategories = {}
|
||||
const result = taggedNodes.reduce(function (r, a) {
|
||||
r[a.category] = r[a.category] || []
|
||||
r[a.category].push(a)
|
||||
accordianCategories[a.category] = isFilter ? true : false
|
||||
return r
|
||||
}, Object.create(null))
|
||||
|
||||
const filteredResult = {}
|
||||
for (const category in result) {
|
||||
if (category === 'Multi Agents') {
|
||||
continue
|
||||
}
|
||||
filteredResult[category] = result[category]
|
||||
}
|
||||
setNodes(filteredResult)
|
||||
categorizeVectorStores(filteredResult, accordianCategories, isFilter)
|
||||
setCategoryExpanded(accordianCategories)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAccordionChange = (category) => (event, isExpanded) => {
|
||||
@@ -271,62 +324,64 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
'aria-label': 'weight'
|
||||
}}
|
||||
/>
|
||||
<Tabs
|
||||
sx={{ position: 'relative', minHeight: '50px', height: '50px' }}
|
||||
variant='fullWidth'
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
aria-label='tabs'
|
||||
>
|
||||
{['LangChain', 'LlamaIndex'].map((item, index) => (
|
||||
<Tab
|
||||
icon={
|
||||
<div
|
||||
style={{
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
src={index === 0 ? LangChainPNG : LlamaindexPNG}
|
||||
alt={item}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
iconPosition='start'
|
||||
sx={{ minHeight: '50px', height: '50px' }}
|
||||
key={index}
|
||||
label={item}
|
||||
{...a11yProps(index)}
|
||||
></Tab>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 10,
|
||||
background: 'rgb(254,252,191)',
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1,
|
||||
width: 'max-content',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
fontSize: '0.65rem',
|
||||
fontWeight: 700
|
||||
}}
|
||||
{!isAgentCanvas && (
|
||||
<Tabs
|
||||
sx={{ position: 'relative', minHeight: '50px', height: '50px' }}
|
||||
variant='fullWidth'
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
aria-label='tabs'
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)' }}>BETA</span>
|
||||
</div>
|
||||
</Tabs>
|
||||
{['LangChain', 'LlamaIndex'].map((item, index) => (
|
||||
<Tab
|
||||
icon={
|
||||
<div
|
||||
style={{
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
src={index === 0 ? LangChainPNG : LlamaindexPNG}
|
||||
alt={item}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
iconPosition='start'
|
||||
sx={{ minHeight: '50px', height: '50px' }}
|
||||
key={index}
|
||||
label={item}
|
||||
{...a11yProps(index)}
|
||||
></Tab>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 10,
|
||||
background: 'rgb(254,252,191)',
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1,
|
||||
width: 'max-content',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
fontSize: '0.65rem',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)' }}>BETA</span>
|
||||
</div>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
</Box>
|
||||
@@ -334,7 +389,11 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
containerRef={(el) => {
|
||||
ps.current = el
|
||||
}}
|
||||
style={{ height: '100%', maxHeight: 'calc(100vh - 380px)', overflowX: 'hidden' }}
|
||||
style={{
|
||||
height: '100%',
|
||||
maxHeight: `calc(100vh - ${isAgentCanvas ? '300' : '380'}px)`,
|
||||
overflowX: 'hidden'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2, pt: 0 }}>
|
||||
<List
|
||||
@@ -503,7 +562,8 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
|
||||
AddNodes.propTypes = {
|
||||
nodesData: PropTypes.array,
|
||||
node: PropTypes.object
|
||||
node: PropTypes.object,
|
||||
isAgentCanvas: PropTypes.bool
|
||||
}
|
||||
|
||||
export default AddNodes
|
||||
|
||||
@@ -32,7 +32,7 @@ import ViewLeadsDialog from '@/ui-component/dialog/ViewLeadsDialog'
|
||||
|
||||
// ==============================|| CANVAS HEADER ||============================== //
|
||||
|
||||
const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => {
|
||||
const CanvasHeader = ({ chatflow, isAgentCanvas, handleSaveFlow, handleDeleteFlow, handleLoadFlow }) => {
|
||||
const theme = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate()
|
||||
@@ -54,6 +54,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
const [chatflowConfigurationDialogOpen, setChatflowConfigurationDialogOpen] = useState(false)
|
||||
const [chatflowConfigurationDialogProps, setChatflowConfigurationDialogProps] = useState({})
|
||||
|
||||
const title = isAgentCanvas ? 'Agents' : 'Chatflow'
|
||||
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const canvas = useSelector((state) => state.canvas)
|
||||
|
||||
@@ -82,14 +84,17 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
setUpsertHistoryDialogOpen(true)
|
||||
} else if (setting === 'chatflowConfiguration') {
|
||||
setChatflowConfigurationDialogProps({
|
||||
title: 'Chatflow Configuration',
|
||||
title: `${title} Configuration`,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setChatflowConfigurationDialogOpen(true)
|
||||
} else if (setting === 'duplicateChatflow') {
|
||||
try {
|
||||
localStorage.setItem('duplicatedFlowData', chatflow.flowData)
|
||||
window.open(`${uiBaseURL}/canvas`, '_blank')
|
||||
let flowData = chatflow.flowData
|
||||
const parsedFlowData = JSON.parse(flowData)
|
||||
flowData = JSON.stringify(parsedFlowData)
|
||||
localStorage.setItem('duplicatedFlowData', flowData)
|
||||
window.open(`${uiBaseURL}/${isAgentCanvas ? 'agentcanvas' : 'canvas'}`, '_blank')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -99,7 +104,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2)
|
||||
let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr)
|
||||
|
||||
let exportFileDefaultName = `${chatflow.name} Chatflow.json`
|
||||
let exportFileDefaultName = `${chatflow.name} ${title}.json`
|
||||
|
||||
let linkElement = document.createElement('a')
|
||||
linkElement.setAttribute('href', dataUri)
|
||||
@@ -192,12 +197,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
// if configuration dialog is open, update its data
|
||||
if (chatflowConfigurationDialogOpen) {
|
||||
setChatflowConfigurationDialogProps({
|
||||
title: 'Chatflow Configuration',
|
||||
title: `${title} Configuration`,
|
||||
chatflow
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [chatflow, chatflowConfigurationDialogOpen])
|
||||
}, [chatflow, title, chatflowConfigurationDialogOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -346,7 +351,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
)}
|
||||
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
<ButtonBase title={`Save ${title}`} sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
<Avatar
|
||||
variant='rounded'
|
||||
sx={{
|
||||
@@ -394,11 +399,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
onSettingsItemClick={onSettingsItemClick}
|
||||
onUploadFile={onUploadFile}
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
/>
|
||||
<SaveChatflowDialog
|
||||
show={flowDialogOpen}
|
||||
dialogProps={{
|
||||
title: `Save New Chatflow`,
|
||||
title: `Save New ${title}`,
|
||||
confirmButtonName: 'Save',
|
||||
cancelButtonName: 'Cancel'
|
||||
}}
|
||||
@@ -431,7 +437,8 @@ CanvasHeader.propTypes = {
|
||||
chatflow: PropTypes.object,
|
||||
handleSaveFlow: PropTypes.func,
|
||||
handleDeleteFlow: PropTypes.func,
|
||||
handleLoadFlow: PropTypes.func
|
||||
handleLoadFlow: PropTypes.func,
|
||||
isAgentCanvas: PropTypes.bool
|
||||
}
|
||||
|
||||
export default CanvasHeader
|
||||
|
||||
@@ -109,18 +109,27 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
||||
data.inputs.selectedLinks = links
|
||||
}
|
||||
|
||||
const getJSONValue = (templateValue) => {
|
||||
if (!templateValue) return ''
|
||||
const obj = {}
|
||||
const inputVariables = getInputVariables(templateValue)
|
||||
for (const inputVariable of inputVariables) {
|
||||
obj[inputVariable] = ''
|
||||
}
|
||||
if (Object.keys(obj).length) return JSON.stringify(obj)
|
||||
return ''
|
||||
}
|
||||
|
||||
const onEditJSONClicked = (value, inputParam) => {
|
||||
// Preset values if the field is format prompt values
|
||||
let inputValue = value
|
||||
if (inputParam.name === 'promptValues' && !value) {
|
||||
const obj = {}
|
||||
const templateValue =
|
||||
(data.inputs['template'] ?? '') + (data.inputs['systemMessagePrompt'] ?? '') + (data.inputs['humanMessagePrompt'] ?? '')
|
||||
const inputVariables = getInputVariables(templateValue)
|
||||
for (const inputVariable of inputVariables) {
|
||||
obj[inputVariable] = ''
|
||||
}
|
||||
if (Object.keys(obj).length) inputValue = JSON.stringify(obj)
|
||||
(data.inputs['template'] ?? '') +
|
||||
(data.inputs['systemMessagePrompt'] ?? '') +
|
||||
(data.inputs['humanMessagePrompt'] ?? '') +
|
||||
(data.inputs['workerPrompt'] ?? '')
|
||||
inputValue = getJSONValue(templateValue)
|
||||
}
|
||||
const dialogProp = {
|
||||
value: inputValue,
|
||||
@@ -386,7 +395,12 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
||||
<JsonEditorInput
|
||||
disabled={disabled}
|
||||
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
|
||||
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
|
||||
value={
|
||||
data.inputs[inputParam.name] ||
|
||||
inputParam.default ||
|
||||
getJSONValue(data.inputs['workerPrompt']) ||
|
||||
''
|
||||
}
|
||||
isDarkMode={customization.isDarkMode}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'reactflow/dist/style.css'
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { usePrompt } from '@/utils/usePrompt'
|
||||
import {
|
||||
REMOVE_DIRTY,
|
||||
SET_DIRTY,
|
||||
@@ -50,6 +49,7 @@ import {
|
||||
updateOutdatedNodeEdge
|
||||
} from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { usePrompt } from '@/utils/usePrompt'
|
||||
|
||||
// const
|
||||
import { FLOWISE_CREDENTIAL_ID } from '@/store/constant'
|
||||
@@ -67,7 +67,10 @@ const Canvas = () => {
|
||||
const templateFlowData = state ? state.templateFlowData : ''
|
||||
|
||||
const URLpath = document.location.pathname.toString().split('/')
|
||||
const chatflowId = URLpath[URLpath.length - 1] === 'canvas' ? '' : URLpath[URLpath.length - 1]
|
||||
const chatflowId =
|
||||
URLpath[URLpath.length - 1] === 'canvas' || URLpath[URLpath.length - 1] === 'agentcanvas' ? '' : URLpath[URLpath.length - 1]
|
||||
const isAgentCanvas = URLpath.includes('agentcanvas') ? true : false
|
||||
const canvasTitle = URLpath.includes('agentcanvas') ? 'Agent' : 'Chatflow'
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
@@ -75,7 +78,6 @@ const Canvas = () => {
|
||||
const canvas = useSelector((state) => state.canvas)
|
||||
const [canvasDataStore, setCanvasDataStore] = useState(canvas)
|
||||
const [chatflow, setChatflow] = useState(null)
|
||||
|
||||
const { reactFlowInstance, setReactFlowInstance } = useContext(flowContext)
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
@@ -99,7 +101,6 @@ const Canvas = () => {
|
||||
|
||||
const getNodesApi = useApi(nodesApi.getAllNodes)
|
||||
const createNewChatflowApi = useApi(chatflowsApi.createNewChatflow)
|
||||
const testChatflowApi = useApi(chatflowsApi.testChatflow)
|
||||
const updateChatflowApi = useApi(chatflowsApi.updateChatflow)
|
||||
const getSpecificChatflowApi = useApi(chatflowsApi.getSpecificChatflow)
|
||||
|
||||
@@ -159,7 +160,7 @@ const Canvas = () => {
|
||||
|
||||
setNodes(nodes)
|
||||
setEdges(flowData.edges || [])
|
||||
setDirty()
|
||||
setTimeout(() => setDirty(), 0)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -168,7 +169,7 @@ const Canvas = () => {
|
||||
const handleDeleteFlow = async () => {
|
||||
const confirmPayload = {
|
||||
title: `Delete`,
|
||||
description: `Delete chatflow ${chatflow.name}?`,
|
||||
description: `Delete ${canvasTitle} ${chatflow.name}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
@@ -178,7 +179,7 @@ const Canvas = () => {
|
||||
try {
|
||||
await chatflowsApi.deleteChatflow(chatflow.id)
|
||||
localStorage.removeItem(`${chatflow.id}_INTERNAL`)
|
||||
navigate('/')
|
||||
navigate(isAgentCanvas ? '/agentflows' : '/')
|
||||
} catch (error) {
|
||||
enqueueSnackbar({
|
||||
message: typeof error.response.data === 'object' ? error.response.data.message : error.response.data,
|
||||
@@ -221,7 +222,8 @@ const Canvas = () => {
|
||||
name: chatflowName,
|
||||
deployed: false,
|
||||
isPublic: false,
|
||||
flowData
|
||||
flowData,
|
||||
type: isAgentCanvas ? 'MULTIAGENT' : 'CHATFLOW'
|
||||
}
|
||||
createNewChatflowApi.request(newChatflowBody)
|
||||
} else {
|
||||
@@ -339,7 +341,7 @@ const Canvas = () => {
|
||||
const saveChatflowSuccess = () => {
|
||||
dispatch({ type: REMOVE_DIRTY })
|
||||
enqueueSnackbar({
|
||||
message: 'Chatflow saved',
|
||||
message: `${canvasTitle} saved`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
@@ -404,7 +406,7 @@ const Canvas = () => {
|
||||
setEdges(initialFlow.edges || [])
|
||||
dispatch({ type: SET_CHATFLOW, chatflow })
|
||||
} else if (getSpecificChatflowApi.error) {
|
||||
errorFailed(`Failed to retrieve chatflow: ${getSpecificChatflowApi.error.response.data.message}`)
|
||||
errorFailed(`Failed to retrieve ${canvasTitle}: ${getSpecificChatflowApi.error.response.data.message}`)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -416,9 +418,9 @@ const Canvas = () => {
|
||||
const chatflow = createNewChatflowApi.data
|
||||
dispatch({ type: SET_CHATFLOW, chatflow })
|
||||
saveChatflowSuccess()
|
||||
window.history.replaceState(null, null, `/canvas/${chatflow.id}`)
|
||||
window.history.replaceState(state, null, `/${isAgentCanvas ? 'agentcanvas' : 'canvas'}/${chatflow.id}`)
|
||||
} else if (createNewChatflowApi.error) {
|
||||
errorFailed(`Failed to save chatflow: ${createNewChatflowApi.error.response.data.message}`)
|
||||
errorFailed(`Failed to save ${canvasTitle}: ${createNewChatflowApi.error.response.data.message}`)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -430,33 +432,12 @@ const Canvas = () => {
|
||||
dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data })
|
||||
saveChatflowSuccess()
|
||||
} else if (updateChatflowApi.error) {
|
||||
errorFailed(`Failed to save chatflow: ${updateChatflowApi.error.response.data.message}`)
|
||||
errorFailed(`Failed to save ${canvasTitle}: ${updateChatflowApi.error.response.data.message}`)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updateChatflowApi.data, updateChatflowApi.error])
|
||||
|
||||
// Test chatflow failed
|
||||
useEffect(() => {
|
||||
if (testChatflowApi.error) {
|
||||
enqueueSnackbar({
|
||||
message: 'Test chatflow failed',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [testChatflowApi.error])
|
||||
|
||||
useEffect(() => {
|
||||
setChatflow(canvasDataStore.chatflow)
|
||||
if (canvasDataStore.chatflow) {
|
||||
@@ -485,7 +466,7 @@ const Canvas = () => {
|
||||
dispatch({
|
||||
type: SET_CHATFLOW,
|
||||
chatflow: {
|
||||
name: 'Untitled chatflow'
|
||||
name: `Untitled ${canvasTitle}`
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -550,6 +531,7 @@ const Canvas = () => {
|
||||
handleSaveFlow={handleSaveFlow}
|
||||
handleDeleteFlow={handleDeleteFlow}
|
||||
handleLoadFlow={handleLoadFlow}
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
/>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
@@ -582,7 +564,7 @@ const Canvas = () => {
|
||||
}}
|
||||
/>
|
||||
<Background color='#aaa' gap={16} />
|
||||
<AddNodes nodesData={getNodesApi.data} node={selectedNode} />
|
||||
<AddNodes isAgentCanvas={isAgentCanvas} nodesData={getNodesApi.data} node={selectedNode} />
|
||||
{isSyncNodesButtonEnabled && (
|
||||
<Fab
|
||||
sx={{
|
||||
@@ -604,7 +586,7 @@ const Canvas = () => {
|
||||
</Fab>
|
||||
)}
|
||||
{isUpsertButtonEnabled && <VectorStorePopUp chatflowid={chatflowId} />}
|
||||
<ChatPopUp chatflowid={chatflowId} />
|
||||
<ChatPopUp isAgentCanvas={isAgentCanvas} chatflowid={chatflowId} />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography
|
||||
Typography,
|
||||
Stack
|
||||
} from '@mui/material'
|
||||
import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
@@ -118,16 +119,34 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
updateChatflowApi.request(dialogProps.chatflowid, updateBody)
|
||||
}
|
||||
|
||||
const groupByNodeLabel = (nodes, isFilter = false) => {
|
||||
const accordianNodes = {}
|
||||
const result = nodes.reduce(function (r, a) {
|
||||
r[a.node] = r[a.node] || []
|
||||
r[a.node].push(a)
|
||||
accordianNodes[a.node] = isFilter ? true : false
|
||||
return r
|
||||
}, Object.create(null))
|
||||
const groupByNodeLabel = (nodes) => {
|
||||
const result = {}
|
||||
|
||||
nodes.forEach((item) => {
|
||||
const { node, nodeId, label, name, type } = item
|
||||
|
||||
if (!result[node]) {
|
||||
result[node] = {
|
||||
nodeIds: [],
|
||||
params: []
|
||||
}
|
||||
}
|
||||
|
||||
if (!result[node].nodeIds.includes(nodeId)) result[node].nodeIds.push(nodeId)
|
||||
|
||||
const param = { label, name, type }
|
||||
|
||||
if (!result[node].params.some((existingParam) => JSON.stringify(existingParam) === JSON.stringify(param))) {
|
||||
result[node].params.push(param)
|
||||
}
|
||||
})
|
||||
|
||||
// Sort the nodeIds array
|
||||
for (const node in result) {
|
||||
result[node].nodeIds.sort()
|
||||
}
|
||||
|
||||
setNodeConfig(result)
|
||||
setNodeConfigExpanded(accordianNodes)
|
||||
}
|
||||
|
||||
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||
@@ -481,12 +500,16 @@ query({
|
||||
|
||||
const getMultiConfigCodeWithFormData = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return `body_data = {
|
||||
"openAIApiKey[chatOpenAI_0]": "sk-my-openai-1st-key",
|
||||
"openAIApiKey[openAIEmbeddings_0]": "sk-my-openai-2nd-key"
|
||||
return `# Specify multiple values for a config parameter by specifying the node id
|
||||
body_data = {
|
||||
"openAIApiKey": {
|
||||
"chatOpenAI_0": "sk-my-openai-1st-key",
|
||||
"openAIEmbeddings_0": "sk-my-openai-2nd-key"
|
||||
}
|
||||
}`
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return `formData.append("openAIApiKey[chatOpenAI_0]", "sk-my-openai-1st-key")
|
||||
return `// Specify multiple values for a config parameter by specifying the node id
|
||||
formData.append("openAIApiKey[chatOpenAI_0]", "sk-my-openai-1st-key")
|
||||
formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
||||
} else if (codeLang === 'cURL') {
|
||||
return `-F "openAIApiKey[chatOpenAI_0]=sk-my-openai-1st-key" \\
|
||||
@@ -619,35 +642,34 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
||||
aria-controls={`nodes-accordian-${nodeLabel}`}
|
||||
id={`nodes-accordian-header-${nodeLabel}`}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Stack flexDirection='row' sx={{ gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<Typography variant='h5'>{nodeLabel}</Typography>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: 'rgb(254,252,191)',
|
||||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
marginLeft: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)', fontSize: '0.825rem' }}>
|
||||
{nodeConfig[nodeLabel][0].nodeId}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{nodeConfig[nodeLabel].nodeIds.length > 0 &&
|
||||
nodeConfig[nodeLabel].nodeIds.map((nodeId, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: 'rgb(254,252,191)',
|
||||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10
|
||||
}}
|
||||
>
|
||||
<span style={{ color: 'rgb(116,66,16)', fontSize: '0.825rem' }}>
|
||||
{nodeId}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<TableViewOnly
|
||||
rows={nodeConfig[nodeLabel].map((obj) => {
|
||||
// eslint-disable-next-line
|
||||
const { node, nodeId, ...rest } = obj
|
||||
return rest
|
||||
})}
|
||||
columns={Object.keys(nodeConfig[nodeLabel][0]).slice(-3)}
|
||||
rows={nodeConfig[nodeLabel].params}
|
||||
columns={Object.keys(nodeConfig[nodeLabel].params[0]).slice(-3)}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
@@ -197,7 +197,7 @@ const Chatflows = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
style={{ objectFit: 'cover', height: '25vh', width: 'auto' }}
|
||||
src={WorkflowEmptySVG}
|
||||
alt='WorkflowEmptySVG'
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ChatMessage } from './ChatMessage'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { IconEraser } from '@tabler/icons-react'
|
||||
|
||||
const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel, previews, setPreviews }) => {
|
||||
const ChatExpandDialog = ({ show, dialogProps, isAgentCanvas, onClear, onCancel, previews, setPreviews }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
@@ -50,6 +50,7 @@ const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel, previews, setP
|
||||
<ChatMessage
|
||||
isDialog={true}
|
||||
open={dialogProps.open}
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
chatflowid={dialogProps.chatflowid}
|
||||
previews={previews}
|
||||
setPreviews={setPreviews}
|
||||
@@ -64,6 +65,7 @@ const ChatExpandDialog = ({ show, dialogProps, onClear, onCancel, previews, setP
|
||||
ChatExpandDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
isAgentCanvas: PropTypes.bool,
|
||||
onClear: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
previews: PropTypes.array,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect, useCallback, Fragment } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import socketIOClient from 'socket.io-client'
|
||||
import { cloneDeep } from 'lodash'
|
||||
@@ -8,6 +8,7 @@ import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
import axios from 'axios'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import {
|
||||
Box,
|
||||
@@ -20,13 +21,28 @@ import {
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
OutlinedInput,
|
||||
Typography
|
||||
Typography,
|
||||
CardContent,
|
||||
Stack
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { IconCircleDot, IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconTrash, IconX, IconTool } from '@tabler/icons-react'
|
||||
import {
|
||||
IconCircleDot,
|
||||
IconDownload,
|
||||
IconSend,
|
||||
IconMicrophone,
|
||||
IconPhotoPlus,
|
||||
IconTrash,
|
||||
IconX,
|
||||
IconTool,
|
||||
IconSquareFilled
|
||||
} from '@tabler/icons-react'
|
||||
import robotPNG from '@/assets/images/robot.png'
|
||||
import userPNG from '@/assets/images/account.png'
|
||||
import multiagent_supervisorPNG from '@/assets/images/multiagent_supervisor.png'
|
||||
import multiagent_workerPNG from '@/assets/images/multiagent_worker.png'
|
||||
import audioUploadSVG from '@/assets/images/wave-sound.jpg'
|
||||
import nextAgentGIF from '@/assets/images/next-agent.gif'
|
||||
|
||||
// project import
|
||||
import { CodeBlock } from '@/ui-component/markdown/CodeBlock'
|
||||
@@ -34,12 +50,12 @@ import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMark
|
||||
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 { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||
import './audio-recording.css'
|
||||
|
||||
// api
|
||||
@@ -54,9 +70,11 @@ import useApi from '@/hooks/useApi'
|
||||
|
||||
// Const
|
||||
import { baseURL, maxScroll } from '@/store/constant'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||
|
||||
// Utils
|
||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow, getLocalStorageChatflow } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
|
||||
const messageImageStyle = {
|
||||
width: '128px',
|
||||
@@ -64,12 +82,18 @@ const messageImageStyle = {
|
||||
objectFit: 'cover'
|
||||
}
|
||||
|
||||
export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews }) => {
|
||||
export const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setPreviews }) => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const ps = useRef()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [userInput, setUserInput] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [messages, setMessages] = useState([
|
||||
@@ -83,7 +107,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
const [isChatFlowAvailableForSpeech, setIsChatFlowAvailableForSpeech] = useState(false)
|
||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||
const [chatId, setChatId] = useState(undefined)
|
||||
const [chatId, setChatId] = useState(uuidv4())
|
||||
const [isMessageStopping, setIsMessageStopping] = useState(false)
|
||||
|
||||
const inputRef = useRef(null)
|
||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||
@@ -287,6 +312,28 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
}
|
||||
}
|
||||
|
||||
const handleAbort = async () => {
|
||||
setIsMessageStopping(true)
|
||||
try {
|
||||
await chatmessageApi.abortMessage(chatflowid, chatId)
|
||||
} catch (error) {
|
||||
setIsMessageStopping(false)
|
||||
enqueueSnackbar({
|
||||
message: typeof error.response.data === 'object' ? error.response.data.message : error.response.data,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeletePreview = (itemToDelete) => {
|
||||
if (itemToDelete.type === 'file') {
|
||||
URL.revokeObjectURL(itemToDelete.preview) // Clean up for file
|
||||
@@ -357,6 +404,56 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
})
|
||||
}
|
||||
|
||||
const updateLastMessageAgentReasoning = (agentReasoning) => {
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||
allMessages[allMessages.length - 1].agentReasoning = JSON.parse(agentReasoning)
|
||||
return allMessages
|
||||
})
|
||||
}
|
||||
|
||||
const updateLastMessageNextAgent = (nextAgent) => {
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||
const lastAgentReasoning = allMessages[allMessages.length - 1].agentReasoning
|
||||
if (lastAgentReasoning && lastAgentReasoning.length > 0) {
|
||||
lastAgentReasoning.push({ nextAgent })
|
||||
}
|
||||
allMessages[allMessages.length - 1].agentReasoning = lastAgentReasoning
|
||||
return allMessages
|
||||
})
|
||||
}
|
||||
|
||||
const abortMessage = () => {
|
||||
setIsMessageStopping(false)
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||
const lastAgentReasoning = allMessages[allMessages.length - 1].agentReasoning
|
||||
if (lastAgentReasoning && lastAgentReasoning.length > 0) {
|
||||
allMessages[allMessages.length - 1].agentReasoning = lastAgentReasoning.filter((reasoning) => !reasoning.nextAgent)
|
||||
}
|
||||
return allMessages
|
||||
})
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 100)
|
||||
enqueueSnackbar({
|
||||
message: 'Message stopped',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateLastMessageUsedTools = (usedTools) => {
|
||||
setMessages((prevMessages) => {
|
||||
let allMessages = [...cloneDeep(prevMessages)]
|
||||
@@ -441,7 +538,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
return allMessages
|
||||
})
|
||||
|
||||
if (!chatId) setChatId(data.chatId)
|
||||
setChatId(data.chatId)
|
||||
|
||||
if (input === '' && data.question) {
|
||||
// the response contains the question even if it was in an audio format
|
||||
@@ -468,6 +565,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
sourceDocuments: data?.sourceDocuments,
|
||||
usedTools: data?.usedTools,
|
||||
fileAnnotations: data?.fileAnnotations,
|
||||
agentReasoning: data?.agentReasoning,
|
||||
type: 'apiMessage',
|
||||
feedback: null
|
||||
}
|
||||
@@ -535,6 +633,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
if (message.sourceDocuments) obj.sourceDocuments = JSON.parse(message.sourceDocuments)
|
||||
if (message.usedTools) obj.usedTools = JSON.parse(message.usedTools)
|
||||
if (message.fileAnnotations) obj.fileAnnotations = JSON.parse(message.fileAnnotations)
|
||||
if (message.agentReasoning) obj.agentReasoning = JSON.parse(message.agentReasoning)
|
||||
if (message.fileUploads) {
|
||||
obj.fileUploads = JSON.parse(message.fileUploads)
|
||||
obj.fileUploads.forEach((file) => {
|
||||
@@ -656,6 +755,12 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
socket.on('fileAnnotations', updateLastMessageFileAnnotations)
|
||||
|
||||
socket.on('token', updateLastMessage)
|
||||
|
||||
socket.on('agentReasoning', updateLastMessageAgentReasoning)
|
||||
|
||||
socket.on('nextAgent', updateLastMessageNextAgent)
|
||||
|
||||
socket.on('abort', abortMessage)
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -779,7 +884,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
const result = await leadsApi.addLead(body)
|
||||
if (result.data) {
|
||||
const data = result.data
|
||||
if (!chatId) setChatId(data.chatId)
|
||||
setChatId(data.chatId)
|
||||
setLocalStorageChatflow(chatflowid, data.chatId, { lead: { name: leadName, email: leadEmail, phone: leadPhone } })
|
||||
setIsLeadSaved(true)
|
||||
setLeadEmail(leadEmail)
|
||||
@@ -865,7 +970,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
}}
|
||||
>
|
||||
{message.usedTools.map((tool, index) => {
|
||||
return (
|
||||
return tool ? (
|
||||
<Chip
|
||||
size='small'
|
||||
key={index}
|
||||
@@ -877,7 +982,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
icon={<IconTool size={15} />}
|
||||
onClick={() => onSourceDialogClick(tool, 'Used Tools')}
|
||||
/>
|
||||
)
|
||||
) : null
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
@@ -925,6 +1030,183 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{message.agentReasoning && (
|
||||
<div style={{ display: 'block', flexDirection: 'row', width: '100%' }}>
|
||||
{message.agentReasoning.map((agent, index) => {
|
||||
return agent.nextAgent ? (
|
||||
<Card
|
||||
key={index}
|
||||
sx={{
|
||||
border: customization.isDarkMode ? 'none' : '1px solid #e0e0e0',
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
background: customization.isDarkMode
|
||||
? `linear-gradient(to top, #303030, #212121)`
|
||||
: `linear-gradient(to top, #f6f3fb, #f2f8fc)`,
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%'
|
||||
}}
|
||||
flexDirection='row'
|
||||
>
|
||||
<Box sx={{ height: 'auto', pr: 1 }}>
|
||||
<img
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
height: '35px',
|
||||
width: 'auto'
|
||||
}}
|
||||
src={nextAgentGIF}
|
||||
alt='agentPNG'
|
||||
/>
|
||||
</Box>
|
||||
<div>{agent.nextAgent}</div>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card
|
||||
key={index}
|
||||
sx={{
|
||||
border: customization.isDarkMode ? 'none' : '1px solid #e0e0e0',
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
background: customization.isDarkMode
|
||||
? `linear-gradient(to top, #303030, #212121)`
|
||||
: `linear-gradient(to top, #f6f3fb, #f2f8fc)`,
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%'
|
||||
}}
|
||||
flexDirection='row'
|
||||
>
|
||||
<Box sx={{ height: 'auto', pr: 1 }}>
|
||||
<img
|
||||
style={{
|
||||
objectFit: 'cover',
|
||||
height: '25px',
|
||||
width: 'auto'
|
||||
}}
|
||||
src={
|
||||
agent.instructions
|
||||
? multiagent_supervisorPNG
|
||||
: multiagent_workerPNG
|
||||
}
|
||||
alt='agentPNG'
|
||||
/>
|
||||
</Box>
|
||||
<div>{agent.agentName}</div>
|
||||
</Stack>
|
||||
{agent.usedTools && agent.usedTools.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{agent.usedTools.map((tool, index) => {
|
||||
return tool !== null ? (
|
||||
<Chip
|
||||
size='small'
|
||||
key={index}
|
||||
label={tool.tool}
|
||||
component='a'
|
||||
sx={{ mr: 1, mt: 1 }}
|
||||
variant='outlined'
|
||||
clickable
|
||||
icon={<IconTool size={15} />}
|
||||
onClick={() => onSourceDialogClick(tool, 'Used Tools')}
|
||||
/>
|
||||
) : null
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{agent.messages.length > 0 && (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{agent.messages.length > 1
|
||||
? agent.messages.join('\\n')
|
||||
: agent.messages[0]}
|
||||
</MemoizedReactMarkdown>
|
||||
)}
|
||||
{agent.instructions && <p>{agent.instructions}</p>}
|
||||
{agent.messages.length === 0 && !agent.instructions && <p>Finished</p>}
|
||||
{agent.sourceDocuments && agent.sourceDocuments.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: 'block',
|
||||
flexDirection: 'row',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{removeDuplicateURL(agent).map((source, index) => {
|
||||
const URL =
|
||||
source && source.metadata && source.metadata.source
|
||||
? isValidURL(source.metadata.source)
|
||||
: undefined
|
||||
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>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
{message.type === 'leadCaptureMessage' &&
|
||||
!getLocalStorageChatflow(chatflowid)?.lead &&
|
||||
@@ -1310,30 +1592,74 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)}
|
||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
||||
<IconButton
|
||||
type='submit'
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
edge='end'
|
||||
>
|
||||
{loading ? (
|
||||
<div>
|
||||
<CircularProgress color='inherit' size={20} />
|
||||
</div>
|
||||
) : (
|
||||
// Send icon SVG in input field
|
||||
<IconSend
|
||||
color={
|
||||
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||
? '#9e9e9e'
|
||||
: customization.isDarkMode
|
||||
? 'white'
|
||||
: '#1e88e5'
|
||||
}
|
||||
/>
|
||||
{!isAgentCanvas && (
|
||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
||||
<IconButton
|
||||
type='submit'
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
edge='end'
|
||||
>
|
||||
{loading ? (
|
||||
<div>
|
||||
<CircularProgress color='inherit' size={20} />
|
||||
</div>
|
||||
) : (
|
||||
// Send icon SVG in input field
|
||||
<IconSend
|
||||
color={
|
||||
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||
? '#9e9e9e'
|
||||
: customization.isDarkMode
|
||||
? 'white'
|
||||
: '#1e88e5'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)}
|
||||
{isAgentCanvas && (
|
||||
<>
|
||||
{!loading && (
|
||||
<InputAdornment position='end' sx={{ padding: '15px' }}>
|
||||
<IconButton
|
||||
type='submit'
|
||||
disabled={loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)}
|
||||
edge='end'
|
||||
>
|
||||
<IconSend
|
||||
color={
|
||||
loading || !chatflowid || (leadsConfig?.status && !isLeadSaved)
|
||||
? '#9e9e9e'
|
||||
: customization.isDarkMode
|
||||
? 'white'
|
||||
: '#1e88e5'
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
{loading && (
|
||||
<InputAdornment position='end' sx={{ padding: '15px', mr: 1 }}>
|
||||
<IconButton
|
||||
edge='end'
|
||||
title={isMessageStopping ? 'Stopping...' : 'Stop'}
|
||||
style={{ border: !isMessageStopping ? '2px solid red' : 'none' }}
|
||||
onClick={() => handleAbort()}
|
||||
disabled={isMessageStopping}
|
||||
>
|
||||
{isMessageStopping ? (
|
||||
<div>
|
||||
<CircularProgress color='error' size={20} />
|
||||
</div>
|
||||
) : (
|
||||
<IconSquareFilled size={15} color='red' />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
@@ -1356,6 +1682,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
||||
ChatMessage.propTypes = {
|
||||
open: PropTypes.bool,
|
||||
chatflowid: PropTypes.string,
|
||||
isAgentCanvas: PropTypes.bool,
|
||||
isDialog: PropTypes.bool,
|
||||
previews: PropTypes.array,
|
||||
setPreviews: PropTypes.func
|
||||
|
||||
@@ -26,7 +26,7 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba
|
||||
// Utils
|
||||
import { getLocalStorageChatflow, removeLocalStorageChatHistory } from '@/utils/genericHelper'
|
||||
|
||||
export const ChatPopUp = ({ chatflowid }) => {
|
||||
export const ChatPopUp = ({ chatflowid, isAgentCanvas }) => {
|
||||
const theme = useTheme()
|
||||
const { confirm } = useConfirm()
|
||||
const dispatch = useDispatch()
|
||||
@@ -201,7 +201,13 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
boxShadow
|
||||
shadow={theme.shadows[16]}
|
||||
>
|
||||
<ChatMessage chatflowid={chatflowid} open={open} previews={previews} setPreviews={setPreviews} />
|
||||
<ChatMessage
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
chatflowid={chatflowid}
|
||||
open={open}
|
||||
previews={previews}
|
||||
setPreviews={setPreviews}
|
||||
/>
|
||||
</MainCard>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
@@ -211,6 +217,7 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
<ChatExpandDialog
|
||||
show={showExpandDialog}
|
||||
dialogProps={expandDialogProps}
|
||||
isAgentCanvas={isAgentCanvas}
|
||||
onClear={clearChat}
|
||||
onCancel={() => setShowExpandDialog(false)}
|
||||
previews={previews}
|
||||
@@ -220,4 +227,4 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
)
|
||||
}
|
||||
|
||||
ChatPopUp.propTypes = { chatflowid: PropTypes.string }
|
||||
ChatPopUp.propTypes = { chatflowid: PropTypes.string, isAgentCanvas: PropTypes.bool }
|
||||
|
||||
@@ -327,7 +327,7 @@ const Documents = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={doc_store_empty}
|
||||
alt='doc_store_empty'
|
||||
/>
|
||||
|
||||
@@ -46,8 +46,9 @@ const MarketplaceCanvas = () => {
|
||||
}, [flowData])
|
||||
|
||||
const onChatflowCopy = (flowData) => {
|
||||
const isAgentCanvas = (flowData?.nodes || []).some((node) => node.data.category === 'Multi Agents')
|
||||
const templateFlowData = JSON.stringify(flowData)
|
||||
navigate(`/canvas`, { state: { templateFlowData } })
|
||||
navigate(`/${isAgentCanvas ? 'agentcanvas' : 'canvas'}`, { state: { templateFlowData } })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -63,7 +63,7 @@ TabPanel.propTypes = {
|
||||
}
|
||||
|
||||
const badges = ['POPULAR', 'NEW']
|
||||
const types = ['Chatflow', 'Tool']
|
||||
const types = ['Chatflow', 'Agentflow', 'Tool']
|
||||
const framework = ['Langchain', 'LlamaIndex']
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
@@ -413,7 +413,7 @@ const Marketplace = () => {
|
||||
badgeContent={data.badge}
|
||||
color={data.badge === 'POPULAR' ? 'primary' : 'error'}
|
||||
>
|
||||
{data.type === 'Chatflow' && (
|
||||
{(data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||
<ItemCard
|
||||
onClick={() => goToCanvas(data)}
|
||||
data={data}
|
||||
@@ -425,7 +425,7 @@ const Marketplace = () => {
|
||||
)}
|
||||
</Badge>
|
||||
)}
|
||||
{!data.badge && data.type === 'Chatflow' && (
|
||||
{!data.badge && (data.type === 'Chatflow' || data.type === 'Agentflow') && (
|
||||
<ItemCard onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
|
||||
)}
|
||||
{!data.badge && data.type === 'Tool' && (
|
||||
|
||||
@@ -14,10 +14,11 @@ import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||
import MainCard from '@/ui-component/cards/MainCard'
|
||||
import Transitions from '@/ui-component/extended/Transitions'
|
||||
import settings from '@/menu-items/settings'
|
||||
import agentsettings from '@/menu-items/agentsettings'
|
||||
|
||||
// ==============================|| SETTINGS ||============================== //
|
||||
|
||||
const Settings = ({ chatflow, isSettingsOpen, anchorEl, onSettingsItemClick, onUploadFile, onClose }) => {
|
||||
const Settings = ({ chatflow, isSettingsOpen, anchorEl, isAgentCanvas, onSettingsItemClick, onUploadFile, onClose }) => {
|
||||
const theme = useTheme()
|
||||
const [settingsMenu, setSettingsMenu] = useState([])
|
||||
const customization = useSelector((state) => state.customization)
|
||||
@@ -42,13 +43,15 @@ const Settings = ({ chatflow, isSettingsOpen, anchorEl, onSettingsItemClick, onU
|
||||
|
||||
useEffect(() => {
|
||||
if (chatflow && !chatflow.id) {
|
||||
const settingsMenu = settings.children.filter((menu) => menu.id === 'loadChatflow')
|
||||
const menus = isAgentCanvas ? agentsettings : settings
|
||||
const settingsMenu = menus.children.filter((menu) => menu.id === 'loadChatflow')
|
||||
setSettingsMenu(settingsMenu)
|
||||
} else if (chatflow && chatflow.id) {
|
||||
const settingsMenu = settings.children
|
||||
const menus = isAgentCanvas ? agentsettings : settings
|
||||
const settingsMenu = menus.children
|
||||
setSettingsMenu(settingsMenu)
|
||||
}
|
||||
}, [chatflow])
|
||||
}, [chatflow, isAgentCanvas])
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(isSettingsOpen)
|
||||
@@ -147,7 +150,8 @@ Settings.propTypes = {
|
||||
anchorEl: PropTypes.any,
|
||||
onSettingsItemClick: PropTypes.func,
|
||||
onUploadFile: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
onClose: PropTypes.func,
|
||||
isAgentCanvas: PropTypes.bool
|
||||
}
|
||||
|
||||
export default Settings
|
||||
|
||||
@@ -165,7 +165,7 @@ const Tools = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={ToolEmptySVG}
|
||||
alt='ToolEmptySVG'
|
||||
/>
|
||||
|
||||
@@ -218,7 +218,7 @@ const Variables = () => {
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: '16vh', width: 'auto' }}
|
||||
style={{ objectFit: 'cover', height: '20vh', width: 'auto' }}
|
||||
src={VariablesEmptySVG}
|
||||
alt='VariablesEmptySVG'
|
||||
/>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { CheckboxInput } from '@/ui-component/checkbox/Checkbox'
|
||||
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
||||
import { TableViewOnly } from '@/ui-component/table/Table'
|
||||
|
||||
import { IconX } from '@tabler/icons-react'
|
||||
import { IconX, IconBulb } from '@tabler/icons-react'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import pythonSVG from '@/assets/images/python.svg'
|
||||
import javascriptSVG from '@/assets/images/javascript.svg'
|
||||
@@ -216,6 +216,36 @@ query(formData).then((response) => {
|
||||
return ''
|
||||
}
|
||||
|
||||
const getMultiConfigCodeWithFormData = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return `# Specify multiple values for a config parameter by specifying the node id
|
||||
body_data = {
|
||||
"openAIApiKey": {
|
||||
"chatOpenAI_0": "sk-my-openai-1st-key",
|
||||
"openAIEmbeddings_0": "sk-my-openai-2nd-key"
|
||||
}
|
||||
}`
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return `// Specify multiple values for a config parameter by specifying the node id
|
||||
formData.append("openAIApiKey[chatOpenAI_0]", "sk-my-openai-1st-key")
|
||||
formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
||||
} else if (codeLang === 'cURL') {
|
||||
return `-F "openAIApiKey[chatOpenAI_0]=sk-my-openai-1st-key" \\
|
||||
-F "openAIApiKey[openAIEmbeddings_0]=sk-my-openai-2nd-key" \\`
|
||||
}
|
||||
}
|
||||
|
||||
const getMultiConfigCode = () => {
|
||||
return `{
|
||||
"overrideConfig": {
|
||||
"openAIApiKey": {
|
||||
"chatOpenAI_0": "sk-my-openai-1st-key",
|
||||
"openAIEmbeddings_0": "sk-my-openai-2nd-key"
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
const getLang = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return 'python'
|
||||
@@ -515,6 +545,44 @@ query(formData).then((response) => {
|
||||
showLineNumbers={false}
|
||||
wrapLines
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 10,
|
||||
background: '#d8f3dc',
|
||||
padding: 10,
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<IconBulb size={30} color='#2d6a4f' />
|
||||
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
||||
You can also specify multiple values for a config parameter by
|
||||
specifying the node id
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ padding: 10 }}>
|
||||
<CopyBlock
|
||||
theme={atomOneDark}
|
||||
text={
|
||||
isFormDataRequired
|
||||
? getMultiConfigCodeWithFormData(codeLang)
|
||||
: getMultiConfigCode()
|
||||
}
|
||||
language={getLang(codeLang)}
|
||||
showLineNumbers={false}
|
||||
wrapLines
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user