mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 19:00:59 +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:
@@ -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