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:
Henry Heng
2024-05-21 16:36:42 +01:00
committed by GitHub
parent 95f1090bed
commit 8ebc4dcfd5
92 changed files with 7216 additions and 701 deletions
+3
View File
@@ -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,
+3 -1
View File
@@ -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
+22 -3
View File
@@ -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',
+8
View File
@@ -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
View File
@@ -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
}
+2 -2
View File
@@ -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)
}
})
+218
View File
@@ -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
+1 -1
View File
@@ -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'
/>
+4 -4
View File
@@ -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>
+130 -70
View File
@@ -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
+17 -10
View File
@@ -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}
/>
)}
+19 -37
View File
@@ -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>
+1 -1
View File
@@ -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,
+360 -33
View File
@@ -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 }
+1 -1
View File
@@ -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 (
+3 -3
View File
@@ -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' && (
+9 -5
View File
@@ -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
+1 -1
View File
@@ -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'
/>
+1 -1
View File
@@ -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>