mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
Merge branch 'main' of https://github.com/use-the-fork/Flowise into maintenance/pnpm-vite-jsx
# Conflicts: # packages/ui/src/views/canvas/index.jsx # packages/ui/src/views/chatflows/APICodeDialog.jsx # packages/ui/src/views/chatmessage/ChatMessage.jsx
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import client from './client'
|
||||
|
||||
const upsertVectorStore = (id, input) => client.post(`/vector/internal-upsert/${id}`, input)
|
||||
|
||||
export default {
|
||||
upsertVectorStore
|
||||
}
|
||||
@@ -31,6 +31,11 @@ $orangeLight: #fbe9e7;
|
||||
$orangeMain: #ffab91;
|
||||
$orangeDark: #d84315;
|
||||
|
||||
// brown
|
||||
$tealLight: #76c893;
|
||||
$tealMain: #52b69a;
|
||||
$tealDark: #34a0a4;
|
||||
|
||||
// warning
|
||||
$warningLight: #fff8e1;
|
||||
$warningMain: #ffe57f;
|
||||
@@ -46,6 +51,9 @@ $grey600: #757575;
|
||||
$grey700: #616161;
|
||||
$grey900: #212121;
|
||||
|
||||
// transparent
|
||||
$transparent: #ffffff00;
|
||||
|
||||
// ==============================|| DARK THEME VARIANTS ||============================== //
|
||||
|
||||
// paper & background
|
||||
@@ -111,6 +119,11 @@ $darkTextSecondary: #8492c4;
|
||||
orangeMain: $orangeMain;
|
||||
orangeDark: $orangeDark;
|
||||
|
||||
// orange
|
||||
tealLight: $tealLight;
|
||||
tealMain: $tealMain;
|
||||
tealDark: $tealDark;
|
||||
|
||||
// warning
|
||||
warningLight: $warningLight;
|
||||
warningMain: $warningMain;
|
||||
@@ -154,4 +167,7 @@ $darkTextSecondary: #8492c4;
|
||||
darkSecondaryDark: $darkSecondaryDark;
|
||||
darkSecondary200: $darkSecondary200;
|
||||
darkSecondary800: $darkSecondary800;
|
||||
|
||||
// transparent
|
||||
transparent: $transparent;
|
||||
}
|
||||
|
||||
@@ -97,9 +97,8 @@ export const ReactFlowContext = ({ children }) => {
|
||||
selected: false
|
||||
}
|
||||
|
||||
const dataKeys = ['inputParams', 'inputAnchors', 'outputAnchors']
|
||||
|
||||
for (const key of dataKeys) {
|
||||
const inputKeys = ['inputParams', 'inputAnchors']
|
||||
for (const key of inputKeys) {
|
||||
for (const item of duplicatedNode.data[key]) {
|
||||
if (item.id) {
|
||||
item.id = item.id.replace(id, newNodeId)
|
||||
@@ -107,6 +106,35 @@ export const ReactFlowContext = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const outputKeys = ['outputAnchors']
|
||||
for (const key of outputKeys) {
|
||||
for (const item of duplicatedNode.data[key]) {
|
||||
if (item.id) {
|
||||
item.id = item.id.replace(id, newNodeId)
|
||||
}
|
||||
if (item.options) {
|
||||
for (const output of item.options) {
|
||||
output.id = output.id.replace(id, newNodeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear connected inputs
|
||||
for (const inputName in duplicatedNode.data.inputs) {
|
||||
if (
|
||||
typeof duplicatedNode.data.inputs[inputName] === 'string' &&
|
||||
duplicatedNode.data.inputs[inputName].startsWith('{{') &&
|
||||
duplicatedNode.data.inputs[inputName].endsWith('}}')
|
||||
) {
|
||||
duplicatedNode.data.inputs[inputName] = ''
|
||||
} else if (Array.isArray(duplicatedNode.data.inputs[inputName])) {
|
||||
duplicatedNode.data.inputs[inputName] = duplicatedNode.data.inputs[inputName].filter(
|
||||
(item) => !(typeof item === 'string' && item.startsWith('{{') && item.endsWith('}}'))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
reactFlowInstance.setNodes([...nodes, duplicatedNode])
|
||||
dispatch({ type: SET_DIRTY })
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export default function themePalette(theme) {
|
||||
return {
|
||||
mode: theme?.customization?.navType,
|
||||
transparent: theme.colors?.transparent,
|
||||
common: {
|
||||
black: theme.colors?.darkPaper,
|
||||
dark: theme.colors?.darkPrimaryMain
|
||||
@@ -34,6 +35,11 @@ export default function themePalette(theme) {
|
||||
main: theme.colors?.orangeMain,
|
||||
dark: theme.colors?.orangeDark
|
||||
},
|
||||
teal: {
|
||||
light: theme.colors?.tealLight,
|
||||
main: theme.colors?.tealMain,
|
||||
dark: theme.colors?.tealDark
|
||||
},
|
||||
warning: {
|
||||
light: theme.colors?.warningLight,
|
||||
main: theme.colors?.warningMain,
|
||||
|
||||
@@ -106,6 +106,32 @@ const NodeInfoDialog = ({ show, dialogProps, onCancel }) => {
|
||||
<span style={{ color: '#606c38', fontSize: '0.825rem' }}>version {dialogProps.data.version}</span>
|
||||
</div>
|
||||
)}
|
||||
{dialogProps.data.badge && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: dialogProps.data.badge === 'DEPRECATING' ? '#ffe57f' : '#52b69a',
|
||||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
marginTop: 5,
|
||||
marginLeft: 10,
|
||||
marginBottom: 5
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: dialogProps.data.badge !== 'DEPRECATING' ? 'white' : 'inherit',
|
||||
fontSize: '0.825rem'
|
||||
}}
|
||||
>
|
||||
{dialogProps.data.badge}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,7 +149,14 @@ const NodeInfoDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</div>
|
||||
)}
|
||||
{getNodeConfigApi.data && getNodeConfigApi.data.length > 0 && (
|
||||
<TableViewOnly rows={getNodeConfigApi.data} columns={Object.keys(getNodeConfigApi.data[0]).slice(-3)} />
|
||||
<TableViewOnly
|
||||
rows={getNodeConfigApi.data.map((obj) => {
|
||||
// eslint-disable-next-line
|
||||
const { node, nodeId, ...rest } = obj
|
||||
return rest
|
||||
})}
|
||||
columns={Object.keys(getNodeConfigApi.data[0]).slice(-3)}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper } from '@mui/material'
|
||||
|
||||
export const TableViewOnly = ({ columns, rows }) => {
|
||||
export const TableViewOnly = ({ columns, rows, sx }) => {
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<Table sx={{ minWidth: 650, ...sx }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((col, index) => (
|
||||
@@ -16,11 +16,9 @@ export const TableViewOnly = ({ columns, rows }) => {
|
||||
<TableBody>
|
||||
{rows.map((row, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{Object.keys(row)
|
||||
.slice(-3)
|
||||
.map((key, index) => (
|
||||
<TableCell key={index}>{row[key]}</TableCell>
|
||||
))}
|
||||
{Object.keys(row).map((key, index) => (
|
||||
<TableCell key={index}>{row[key]}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@@ -32,5 +30,6 @@ export const TableViewOnly = ({ columns, rows }) => {
|
||||
|
||||
TableViewOnly.propTypes = {
|
||||
rows: PropTypes.array,
|
||||
columns: PropTypes.array
|
||||
columns: PropTypes.array,
|
||||
sx: PropTypes.object
|
||||
}
|
||||
|
||||
@@ -332,6 +332,57 @@ export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle)
|
||||
return parentNodes
|
||||
}
|
||||
|
||||
export const getUpsertDetails = (nodes, edges) => {
|
||||
const vsNodes = nodes.filter(
|
||||
(node) =>
|
||||
node.data.category === 'Vector Stores' && !node.data.label.includes('Upsert') && !node.data.label.includes('Load Existing')
|
||||
)
|
||||
const vsNodeIds = vsNodes.map((vs) => vs.data.id)
|
||||
|
||||
const upsertNodes = []
|
||||
const seenVsNodeIds = []
|
||||
for (const edge of edges) {
|
||||
if (vsNodeIds.includes(edge.source) || vsNodeIds.includes(edge.target)) {
|
||||
const vsNode = vsNodes.find((node) => node.data.id === edge.source || node.data.id === edge.target)
|
||||
if (!vsNode || seenVsNodeIds.includes(vsNode.data.id)) continue
|
||||
seenVsNodeIds.push(vsNode.data.id)
|
||||
|
||||
// Found Vector Store Node, proceed to find connected Document Loader node
|
||||
let connectedDocs = []
|
||||
|
||||
if (vsNode.data.inputs.document) connectedDocs = [...new Set(vsNode.data.inputs.document)]
|
||||
|
||||
if (connectedDocs.length) {
|
||||
const innerNodes = [vsNode]
|
||||
|
||||
if (vsNode.data.inputs.embeddings) {
|
||||
const embeddingsId = vsNode.data.inputs.embeddings.replace(/{{|}}/g, '').split('.')[0]
|
||||
innerNodes.push(nodes.find((node) => node.data.id === embeddingsId))
|
||||
}
|
||||
|
||||
for (const doc of connectedDocs) {
|
||||
const docId = doc.replace(/{{|}}/g, '').split('.')[0]
|
||||
const docNode = nodes.find((node) => node.data.id === docId)
|
||||
if (docNode) innerNodes.push(docNode)
|
||||
|
||||
// Found Document Loader Node, proceed to find connected Text Splitter node
|
||||
if (docNode && docNode.data.inputs.textSplitter) {
|
||||
const textSplitterId = docNode.data.inputs.textSplitter.replace(/{{|}}/g, '').split('.')[0]
|
||||
const textSplitterNode = nodes.find((node) => node.data.id === textSplitterId)
|
||||
if (textSplitterNode) innerNodes.push(textSplitterNode)
|
||||
}
|
||||
}
|
||||
|
||||
upsertNodes.push({
|
||||
vectorNode: vsNode,
|
||||
nodes: innerNodes.reverse()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return upsertNodes
|
||||
}
|
||||
|
||||
export const rearrangeToolsOrdering = (newValues, sourceNodeId) => {
|
||||
// RequestsGet and RequestsPost have to be in order before other tools
|
||||
newValues.push(`{{${sourceNodeId}.data.instance}}`)
|
||||
@@ -458,3 +509,106 @@ export const formatDataGridRows = (rows) => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const setLocalStorageChatflow = (chatflowid, chatId, chatHistory) => {
|
||||
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||
const obj = {}
|
||||
if (chatId) obj.chatId = chatId
|
||||
if (chatHistory) obj.chatHistory = chatHistory
|
||||
|
||||
if (!chatDetails) {
|
||||
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj))
|
||||
} else {
|
||||
try {
|
||||
const parsedChatDetails = JSON.parse(chatDetails)
|
||||
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify({ ...parsedChatDetails, ...obj }))
|
||||
} catch (e) {
|
||||
const chatId = chatDetails
|
||||
obj.chatId = chatId
|
||||
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unshiftFiles = (configData) => {
|
||||
const filesConfig = configData.find((config) => config.name === 'files')
|
||||
if (filesConfig) {
|
||||
configData = configData.filter((config) => config.name !== 'files')
|
||||
configData.unshift(filesConfig)
|
||||
}
|
||||
return configData
|
||||
}
|
||||
|
||||
export const getConfigExamplesForJS = (configData, bodyType, isMultiple, stopNodeId) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `"example"`
|
||||
if (config.type === 'string') exampleVal = `"example"`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.type === 'json') exampleVal = `{ "key": "val" }`
|
||||
else if (config.name === 'files') exampleVal = `input.files[0]`
|
||||
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n`
|
||||
if (i === loop - 1 && bodyType !== 'json')
|
||||
finalStr += !isMultiple
|
||||
? ``
|
||||
: stopNodeId
|
||||
? `formData.append("stopNodeId", "${stopNodeId}")\n`
|
||||
: `formData.append("question", "Hey, how are you?")\n`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
export const getConfigExamplesForPython = (configData, bodyType, isMultiple, stopNodeId) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `"example"`
|
||||
if (config.type === 'string') exampleVal = `"example"`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.type === 'json') exampleVal = `{ "key": "val" }`
|
||||
else if (config.name === 'files') continue
|
||||
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},`
|
||||
if (i === loop - 1 && bodyType !== 'json')
|
||||
finalStr += !isMultiple
|
||||
? `\n`
|
||||
: stopNodeId
|
||||
? `\n "stopNodeId": "${stopNodeId}"\n`
|
||||
: `\n "question": "Hey, how are you?"\n`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
export const getConfigExamplesForCurl = (configData, bodyType, isMultiple, stopNodeId) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `example`
|
||||
if (config.type === 'string') exampleVal = bodyType === 'json' ? `"example"` : `example`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.type === 'json') exampleVal = `{key:val}`
|
||||
else if (config.name === 'files')
|
||||
exampleVal = `@/home/user1/Desktop/example${config.type.includes(',') ? config.type.split(',')[0] : config.type}`
|
||||
finalStr += bodyType === 'json' ? `"${config.name}": ${exampleVal}` : `\n -F "${config.name}=${exampleVal}"`
|
||||
if (i === loop - 1)
|
||||
finalStr +=
|
||||
bodyType === 'json'
|
||||
? ` }`
|
||||
: !isMultiple
|
||||
? ``
|
||||
: stopNodeId
|
||||
? ` \\\n -F "stopNodeId=${stopNodeId}"`
|
||||
: ` \\\n -F "question=Hey, how are you?"`
|
||||
else finalStr += bodyType === 'json' ? `, ` : ` \\`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
Paper,
|
||||
Popper,
|
||||
Stack,
|
||||
Typography
|
||||
Typography,
|
||||
Chip
|
||||
} from '@mui/material'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
|
||||
@@ -56,6 +57,24 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
const prevOpen = useRef(open)
|
||||
const ps = useRef()
|
||||
|
||||
// Temporary method to handle Deprecating Vector Store and New ones
|
||||
const categorizeVectorStores = (nodes, accordianCategories, isFilter) => {
|
||||
const obj = { ...nodes }
|
||||
const vsNodes = obj['Vector Stores'] ?? []
|
||||
const deprecatingNodes = []
|
||||
const newNodes = []
|
||||
for (const vsNode of vsNodes) {
|
||||
if (vsNode.badge === 'DEPRECATING') deprecatingNodes.push(vsNode)
|
||||
else newNodes.push(vsNode)
|
||||
}
|
||||
delete obj['Vector Stores']
|
||||
obj['Vector Stores;DEPRECATING'] = deprecatingNodes
|
||||
accordianCategories['Vector Stores;DEPRECATING'] = isFilter ? true : false
|
||||
obj['Vector Stores;NEW'] = newNodes
|
||||
accordianCategories['Vector Stores;NEW'] = isFilter ? true : false
|
||||
setNodes(obj)
|
||||
}
|
||||
|
||||
const scrollTop = () => {
|
||||
const curr = ps.current
|
||||
if (curr) {
|
||||
@@ -95,6 +114,7 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
return r
|
||||
}, Object.create(null))
|
||||
setNodes(result)
|
||||
categorizeVectorStores(result, accordianCategories, isFilter)
|
||||
setCategoryExpanded(accordianCategories)
|
||||
}
|
||||
|
||||
@@ -137,6 +157,8 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
groupByCategory(nodesData)
|
||||
dispatch({ type: SET_COMPONENT_NODES, componentNodes: nodesData })
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodesData, dispatch])
|
||||
|
||||
return (
|
||||
@@ -249,69 +271,135 @@ const AddNodes = ({ nodesData, node }) => {
|
||||
>
|
||||
{Object.keys(nodes)
|
||||
.sort()
|
||||
.map((category) => (
|
||||
<Accordion
|
||||
expanded={categoryExpanded[category] || false}
|
||||
onChange={handleAccordionChange(category)}
|
||||
key={category}
|
||||
disableGutters
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`nodes-accordian-${category}`}
|
||||
id={`nodes-accordian-header-${category}`}
|
||||
.map((category) =>
|
||||
category === 'Vector Stores' ? (
|
||||
<></>
|
||||
) : (
|
||||
<Accordion
|
||||
expanded={categoryExpanded[category] || false}
|
||||
onChange={handleAccordionChange(category)}
|
||||
key={category}
|
||||
disableGutters
|
||||
>
|
||||
<Typography variant='h5'>{category}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{nodes[category].map((node, index) => (
|
||||
<div
|
||||
key={node.name}
|
||||
onDragStart={(event) => onDragStart(event, node)}
|
||||
draggable
|
||||
>
|
||||
<ListItemButton
|
||||
sx={{
|
||||
p: 0,
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
cursor: 'move'
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`nodes-accordian-${category}`}
|
||||
id={`nodes-accordian-header-${category}`}
|
||||
>
|
||||
{category.split(';').length > 1 ? (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<ListItem alignItems='center'>
|
||||
<ListItemAvatar>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
<Typography variant='h5'>{category.split(';')[0]}</Typography>
|
||||
|
||||
<Chip
|
||||
sx={{
|
||||
width: 'max-content',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.65rem',
|
||||
background:
|
||||
category.split(';')[1] === 'DEPRECATING'
|
||||
? theme.palette.warning.main
|
||||
: theme.palette.teal.main,
|
||||
color:
|
||||
category.split(';')[1] !== 'DEPRECATING'
|
||||
? 'white'
|
||||
: 'inherit'
|
||||
}}
|
||||
size='small'
|
||||
label={category.split(';')[1]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Typography variant='h5'>{category}</Typography>
|
||||
)}
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{nodes[category].map((node, index) => (
|
||||
<div
|
||||
key={node.name}
|
||||
onDragStart={(event) => onDragStart(event, node)}
|
||||
draggable
|
||||
>
|
||||
<ListItemButton
|
||||
sx={{
|
||||
p: 0,
|
||||
borderRadius: `${customization.borderRadius}px`,
|
||||
cursor: 'move'
|
||||
}}
|
||||
>
|
||||
<ListItem alignItems='center'>
|
||||
<ListItemAvatar>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 10,
|
||||
objectFit: 'contain'
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
alt={node.name}
|
||||
src={`${baseURL}/api/v1/node-icon/${node.name}`}
|
||||
/>
|
||||
</div>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={node.label}
|
||||
secondary={node.description}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
{index === nodes[category].length - 1 ? null : <Divider />}
|
||||
</div>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 10,
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={node.name}
|
||||
src={`${baseURL}/api/v1/node-icon/${node.name}`}
|
||||
/>
|
||||
</div>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<span>{node.label}</span>
|
||||
|
||||
{node.badge && (
|
||||
<Chip
|
||||
sx={{
|
||||
width: 'max-content',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.65rem',
|
||||
background:
|
||||
node.badge === 'DEPRECATING'
|
||||
? theme.palette.warning
|
||||
.main
|
||||
: theme.palette.teal
|
||||
.main,
|
||||
color:
|
||||
node.badge !== 'DEPRECATING'
|
||||
? 'white'
|
||||
: 'inherit'
|
||||
}}
|
||||
size='small'
|
||||
label={node.badge}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
secondary={node.description}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
{index === nodes[category].length - 1 ? null : <Divider />}
|
||||
</div>
|
||||
))}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
)}
|
||||
</List>
|
||||
</Box>
|
||||
</PerfectScrollbar>
|
||||
|
||||
@@ -83,8 +83,10 @@ const CanvasNode = ({ data }) => {
|
||||
if (componentNode) {
|
||||
if (!data.version) {
|
||||
setWarningMessage(nodeVersionEmptyMessage(componentNode.version))
|
||||
} else {
|
||||
if (componentNode.version > data.version) setWarningMessage(nodeOutdatedMessage(data.version, componentNode.version))
|
||||
} else if (data.version && componentNode.version > data.version) {
|
||||
setWarningMessage(nodeOutdatedMessage(data.version, componentNode.version))
|
||||
} else if (componentNode.badge === 'DEPRECATING') {
|
||||
setWarningMessage('This node will be deprecated in the next release. Change to a new node tagged with NEW')
|
||||
}
|
||||
}
|
||||
}, [canvas.componentNodes, data.name, data.version])
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
SET_CHATFLOW,
|
||||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction
|
||||
} from '@/store/actions'
|
||||
} from 'store/actions'
|
||||
import { omit, cloneDeep } from 'lodash'
|
||||
|
||||
// material-ui
|
||||
@@ -23,27 +23,28 @@ import CanvasNode from './CanvasNode'
|
||||
import ButtonEdge from './ButtonEdge'
|
||||
import CanvasHeader from './CanvasHeader'
|
||||
import AddNodes from './AddNodes'
|
||||
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||
import { ChatPopUp } from '@/views/chatmessage/ChatPopUp'
|
||||
import { flowContext } from '@/store/context/ReactFlowContext'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
import { ChatPopUp } from 'views/chatmessage/ChatPopUp'
|
||||
import { VectorStorePopUp } from 'views/vectorstore/VectorStorePopUp'
|
||||
import { flowContext } from 'store/context/ReactFlowContext'
|
||||
|
||||
// API
|
||||
import nodesApi from '@/api/nodes'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import nodesApi from 'api/nodes'
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
import useConfirm from '@/hooks/useConfirm'
|
||||
import useApi from 'hooks/useApi'
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
|
||||
// icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// utils
|
||||
import { getUniqueNodeId, initNode, getEdgeLabelName, rearrangeToolsOrdering } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { getUniqueNodeId, initNode, getEdgeLabelName, rearrangeToolsOrdering, getUpsertDetails } from 'utils/genericHelper'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// const
|
||||
import { FLOWISE_CREDENTIAL_ID } from '@/store/constant'
|
||||
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
|
||||
|
||||
const nodeTypes = { customNode: CanvasNode }
|
||||
const edgeTypes = { buttonedge: ButtonEdge }
|
||||
@@ -81,6 +82,7 @@ const Canvas = () => {
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState()
|
||||
|
||||
const [selectedNode, setSelectedNode] = useState(null)
|
||||
const [isUpsertButtonEnabled, setIsUpsertButtonEnabled] = useState(false)
|
||||
|
||||
const reactFlowWrapper = useRef(null)
|
||||
|
||||
@@ -167,6 +169,7 @@ const Canvas = () => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
await chatflowsApi.deleteChatflow(chatflow.id)
|
||||
localStorage.removeItem(`${chatflow.id}_INTERNAL`)
|
||||
navigate(-1)
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
@@ -339,6 +342,12 @@ const Canvas = () => {
|
||||
dispatch({ type: SET_DIRTY })
|
||||
}
|
||||
|
||||
const checkIfUpsertAvailable = (nodes, edges) => {
|
||||
const upsertNodeDetails = getUpsertDetails(nodes, edges)
|
||||
if (upsertNodeDetails.length) setIsUpsertButtonEnabled(true)
|
||||
else setIsUpsertButtonEnabled(false)
|
||||
}
|
||||
|
||||
// ==============================|| useEffect ||============================== //
|
||||
|
||||
// Get specific chatflow successful
|
||||
@@ -409,7 +418,13 @@ const Canvas = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [testChatflowApi.error])
|
||||
|
||||
useEffect(() => setChatflow(canvasDataStore.chatflow), [canvasDataStore.chatflow])
|
||||
useEffect(() => {
|
||||
setChatflow(canvasDataStore.chatflow)
|
||||
if (canvasDataStore.chatflow) {
|
||||
const flowData = canvasDataStore.chatflow.flowData ? JSON.parse(canvasDataStore.chatflow.flowData) : []
|
||||
checkIfUpsertAvailable(flowData.nodes || [], flowData.edges || [])
|
||||
}
|
||||
}, [canvasDataStore.chatflow])
|
||||
|
||||
// Initialization
|
||||
useEffect(() => {
|
||||
@@ -524,6 +539,7 @@ const Canvas = () => {
|
||||
/>
|
||||
<Background color='#aaa' gap={16} />
|
||||
<AddNodes nodesData={getNodesApi.data} node={selectedNode} />
|
||||
{isUpsertButtonEnabled && <VectorStorePopUp chatflowid={chatflowId} />}
|
||||
<ChatPopUp chatflowid={chatflowId} />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
||||
@@ -20,34 +20,36 @@ import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
|
||||
// Project import
|
||||
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import ShareChatbot from './ShareChatbot'
|
||||
import EmbedChat from './EmbedChat'
|
||||
import Configuration from './Configuration'
|
||||
|
||||
// Const
|
||||
import { baseURL } from '@/store/constant'
|
||||
import { SET_CHATFLOW } from '@/store/actions'
|
||||
import { baseURL } from 'store/constant'
|
||||
import { SET_CHATFLOW } from 'store/actions'
|
||||
|
||||
// Images
|
||||
import pythonSVG from '@/assets/images/python.svg'
|
||||
import javascriptSVG from '@/assets/images/javascript.svg'
|
||||
import cURLSVG from '@/assets/images/cURL.svg'
|
||||
import EmbedSVG from '@/assets/images/embed.svg'
|
||||
import ShareChatbotSVG from '@/assets/images/sharing.png'
|
||||
import settingsSVG from '@/assets/images/settings.svg'
|
||||
import pythonSVG from 'assets/images/python.svg'
|
||||
import javascriptSVG from 'assets/images/javascript.svg'
|
||||
import cURLSVG from 'assets/images/cURL.svg'
|
||||
import EmbedSVG from 'assets/images/embed.svg'
|
||||
import ShareChatbotSVG from 'assets/images/sharing.png'
|
||||
import settingsSVG from 'assets/images/settings.svg'
|
||||
import { IconBulb } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import apiKeyApi from '@/api/apikey'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import configApi from '@/api/config'
|
||||
import apiKeyApi from 'api/apikey'
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
import configApi from 'api/config'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
import { CheckboxInput } from '@/ui-component/checkbox/Checkbox'
|
||||
import { TableViewOnly } from '@/ui-component/table/Table'
|
||||
import useApi from 'hooks/useApi'
|
||||
import { CheckboxInput } from 'ui-component/checkbox/Checkbox'
|
||||
import { TableViewOnly } from 'ui-component/table/Table'
|
||||
|
||||
import { IconBulb } from '@tabler/icons'
|
||||
import Configuration from './Configuration'
|
||||
// Helpers
|
||||
import { unshiftFiles, getConfigExamplesForJS, getConfigExamplesForPython, getConfigExamplesForCurl } from 'utils/genericHelper'
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props
|
||||
@@ -77,67 +79,6 @@ function a11yProps(index) {
|
||||
}
|
||||
}
|
||||
|
||||
const unshiftFiles = (configData) => {
|
||||
const filesConfig = configData.find((config) => config.name === 'files')
|
||||
if (filesConfig) {
|
||||
configData = configData.filter((config) => config.name !== 'files')
|
||||
configData.unshift(filesConfig)
|
||||
}
|
||||
return configData
|
||||
}
|
||||
|
||||
const getConfigExamplesForJS = (configData, bodyType) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `"example"`
|
||||
if (config.type === 'string') exampleVal = `"example"`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.name === 'files') exampleVal = `input.files[0]`
|
||||
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n`
|
||||
if (i === loop - 1 && bodyType !== 'json') finalStr += `formData.append("question", "Hey, how are you?")\n`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
const getConfigExamplesForPython = (configData, bodyType) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `"example"`
|
||||
if (config.type === 'string') exampleVal = `"example"`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.name === 'files') continue
|
||||
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},`
|
||||
if (i === loop - 1 && bodyType !== 'json') finalStr += `\n "question": "Hey, how are you?"\n`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
const getConfigExamplesForCurl = (configData, bodyType) => {
|
||||
let finalStr = ''
|
||||
configData = unshiftFiles(configData)
|
||||
const loop = Math.min(configData.length, 4)
|
||||
for (let i = 0; i < loop; i += 1) {
|
||||
const config = configData[i]
|
||||
let exampleVal = `example`
|
||||
if (config.type === 'string') exampleVal = bodyType === 'json' ? `"example"` : `example`
|
||||
else if (config.type === 'boolean') exampleVal = `true`
|
||||
else if (config.type === 'number') exampleVal = `1`
|
||||
else if (config.name === 'files') exampleVal = `@/home/user1/Desktop/example${config.type}`
|
||||
finalStr += bodyType === 'json' ? `"${config.name}": ${exampleVal}` : `\n -F "${config.name}=${exampleVal}"`
|
||||
if (i === loop - 1) finalStr += bodyType === 'json' ? ` }` : ` \\\n -F "question=Hey, how are you?"`
|
||||
else finalStr += bodyType === 'json' ? `, ` : ` \\`
|
||||
}
|
||||
return finalStr
|
||||
}
|
||||
|
||||
const APICodeDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const navigate = useNavigate()
|
||||
@@ -334,7 +275,8 @@ query({"question": "Hey, how are you?"}).then((response) => {
|
||||
const getConfigCodeWithFormData = (codeLang, configData) => {
|
||||
if (codeLang === 'Python') {
|
||||
configData = unshiftFiles(configData)
|
||||
const fileType = configData[0].type
|
||||
let fileType = configData[0].type
|
||||
if (fileType.includes(',')) fileType = fileType.split(',')[0]
|
||||
return `import requests
|
||||
|
||||
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
|
||||
@@ -384,7 +326,8 @@ query(formData).then((response) => {
|
||||
const getConfigCodeWithFormDataWithAuth = (codeLang, configData) => {
|
||||
if (codeLang === 'Python') {
|
||||
configData = unshiftFiles(configData)
|
||||
const fileType = configData[0].type
|
||||
let fileType = configData[0].type
|
||||
if (fileType.includes(',')) fileType = fileType.split(',')[0]
|
||||
return `import requests
|
||||
|
||||
API_URL = "${baseURL}/api/v1/prediction/${dialogProps.chatflowid}"
|
||||
@@ -700,7 +643,11 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<TableViewOnly
|
||||
rows={nodeConfig[nodeLabel]}
|
||||
rows={nodeConfig[nodeLabel].map((obj) => {
|
||||
// eslint-disable-next-line
|
||||
const { node, nodeId, ...rest } = obj
|
||||
return rest
|
||||
})}
|
||||
columns={Object.keys(nodeConfig[nodeLabel][0]).slice(-3)}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
|
||||
@@ -14,25 +14,25 @@ import { useTheme } from '@mui/material/styles'
|
||||
import { IconSend, IconDownload } from '@tabler/icons'
|
||||
|
||||
// project import
|
||||
import { CodeBlock } from '@/ui-component/markdown/CodeBlock'
|
||||
import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMarkdown'
|
||||
import SourceDocDialog from '@/ui-component/dialog/SourceDocDialog'
|
||||
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||
import SourceDocDialog from 'ui-component/dialog/SourceDocDialog'
|
||||
import './ChatMessage.css'
|
||||
|
||||
// api
|
||||
import chatmessageApi from '@/api/chatmessage'
|
||||
import chatflowsApi from '@/api/chatflows'
|
||||
import predictionApi from '@/api/prediction'
|
||||
import chatmessageApi from 'api/chatmessage'
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
import predictionApi from 'api/prediction'
|
||||
|
||||
// Hooks
|
||||
import useApi from '@/hooks/useApi'
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
// Const
|
||||
import { baseURL, maxScroll } from '@/store/constant'
|
||||
import { baseURL, maxScroll } from 'store/constant'
|
||||
|
||||
import robotPNG from '@/assets/images/robot.png'
|
||||
import userPNG from '@/assets/images/account.png'
|
||||
import { isValidURL, removeDuplicateURL } from '@/utils/genericHelper'
|
||||
import robotPNG from 'assets/images/robot.png'
|
||||
import userPNG from 'assets/images/account.png'
|
||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
||||
|
||||
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
const theme = useTheme()
|
||||
@@ -128,10 +128,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
|
||||
if (response.data) {
|
||||
const data = response.data
|
||||
if (!chatId) {
|
||||
setChatId(data.chatId)
|
||||
localStorage.setItem(`${chatflowid}_INTERNAL`, data.chatId)
|
||||
}
|
||||
|
||||
if (!chatId) setChatId(data.chatId)
|
||||
|
||||
if (!isChatFlowAvailableToStream) {
|
||||
let text = ''
|
||||
if (data.text) text = data.text
|
||||
@@ -149,7 +148,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
setLocalStorageChatflow(chatflowid, data.chatId, messages)
|
||||
setLoading(false)
|
||||
setUserInput('')
|
||||
setTimeout(() => {
|
||||
@@ -202,7 +201,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
if (getChatmessageApi.data?.length) {
|
||||
const chatId = getChatmessageApi.data[0]?.chatId
|
||||
setChatId(chatId)
|
||||
localStorage.setItem(`${chatflowid}_INTERNAL`, chatId)
|
||||
const loadedMessages = getChatmessageApi.data.map((message) => {
|
||||
const obj = {
|
||||
message: message.content,
|
||||
@@ -214,6 +212,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||
return obj
|
||||
})
|
||||
setMessages((prevMessages) => [...prevMessages, ...loadedMessages])
|
||||
setLocalStorageChatflow(chatflowid, chatId, messages)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -85,8 +85,10 @@ export const ChatPopUp = ({ chatflowid }) => {
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const chatId = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||
await chatmessageApi.deleteChatmessage(chatflowid, { chatId, chatType: 'INTERNAL' })
|
||||
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
||||
if (!chatDetails) return
|
||||
const objChatDetails = JSON.parse(chatDetails)
|
||||
await chatmessageApi.deleteChatmessage(chatflowid, { chatId: objChatDetails.chatId, chatType: 'INTERNAL' })
|
||||
localStorage.removeItem(`${chatflowid}_INTERNAL`)
|
||||
resetChatDialog()
|
||||
enqueueSnackbar({
|
||||
|
||||
@@ -0,0 +1,556 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useContext, useState, useEffect } from 'react'
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||
import { CopyBlock, atomOneDark } from 'react-code-blocks'
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Button,
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography
|
||||
} from '@mui/material'
|
||||
|
||||
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'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import pythonSVG from 'assets/images/python.svg'
|
||||
import javascriptSVG from 'assets/images/javascript.svg'
|
||||
import cURLSVG from 'assets/images/cURL.svg'
|
||||
|
||||
import useApi from 'hooks/useApi'
|
||||
import configApi from 'api/config'
|
||||
import vectorstoreApi from 'api/vectorstore'
|
||||
|
||||
// Utils
|
||||
import {
|
||||
getUpsertDetails,
|
||||
getFileName,
|
||||
unshiftFiles,
|
||||
getConfigExamplesForJS,
|
||||
getConfigExamplesForPython,
|
||||
getConfigExamplesForCurl
|
||||
} from 'utils/genericHelper'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// Store
|
||||
import { flowContext } from 'store/context/ReactFlowContext'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
import { baseURL } from 'store/constant'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props
|
||||
return (
|
||||
<div
|
||||
role='tabpanel'
|
||||
hidden={value !== index}
|
||||
id={`attachment-tabpanel-${index}`}
|
||||
aria-labelledby={`attachment-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ p: 1 }}>{children}</Box>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
}
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `attachment-tab-${index}`,
|
||||
'aria-controls': `attachment-tabpanel-${index}`
|
||||
}
|
||||
}
|
||||
|
||||
const VectorStoreDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const { reactFlowInstance } = useContext(flowContext)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
const getConfigApi = useApi(configApi.getConfig)
|
||||
|
||||
const [nodes, setNodes] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isFormDataRequired, setIsFormDataRequired] = useState({})
|
||||
const [nodeConfigExpanded, setNodeConfigExpanded] = useState({})
|
||||
const [nodeCheckboxExpanded, setCheckboxExpanded] = useState({})
|
||||
const [tabValue, setTabValue] = useState(0)
|
||||
const [expandedVectorNodeId, setExpandedVectorNodeId] = useState('')
|
||||
const [configData, setConfigData] = useState({})
|
||||
|
||||
const reformatConfigData = (configData, nodes) => {
|
||||
return configData.filter((item1) => nodes.some((item2) => item1.nodeId === item2.id))
|
||||
}
|
||||
|
||||
const getCode = (codeLang, vectorNodeId, isMultiple, configData) => {
|
||||
if (codeLang === 'Python') {
|
||||
return `import requests
|
||||
|
||||
API_URL = "${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid}"
|
||||
|
||||
def query(payload):
|
||||
response = requests.post(API_URL, json=payload)
|
||||
return response.json()
|
||||
|
||||
output = query({
|
||||
${isMultiple ? `"stopNodeId": "${vectorNodeId}",\n ` : ``}"overrideConfig": {${getConfigExamplesForPython(
|
||||
configData,
|
||||
'json',
|
||||
isMultiple,
|
||||
vectorNodeId
|
||||
)}
|
||||
}
|
||||
})
|
||||
`
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return `async function query(data) {
|
||||
const response = await fetch(
|
||||
"${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid}",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
|
||||
query({
|
||||
${isMultiple ? `"stopNodeId": "${vectorNodeId}",\n ` : ``}"overrideConfig": {${getConfigExamplesForJS(
|
||||
configData,
|
||||
'json',
|
||||
isMultiple,
|
||||
vectorNodeId
|
||||
)}
|
||||
}
|
||||
}).then((response) => {
|
||||
console.log(response);
|
||||
});
|
||||
`
|
||||
} else if (codeLang === 'cURL') {
|
||||
return `curl ${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid} \\
|
||||
-X POST \\
|
||||
${
|
||||
isMultiple
|
||||
? `-d '{"stopNodeId": "${vectorNodeId}", "overrideConfig": {${getConfigExamplesForCurl(
|
||||
configData,
|
||||
'json',
|
||||
isMultiple,
|
||||
vectorNodeId
|
||||
)}}' \\`
|
||||
: `-d '{"overrideConfig": {${getConfigExamplesForCurl(configData, 'json', isMultiple, vectorNodeId)}}' \\`
|
||||
}
|
||||
-H "Content-Type: application/json"`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const getCodeWithFormData = (codeLang, vectorNodeId, isMultiple, configData) => {
|
||||
if (codeLang === 'Python') {
|
||||
configData = unshiftFiles(configData)
|
||||
let fileType = configData[0].type
|
||||
if (fileType.includes(',')) fileType = fileType.split(',')[0]
|
||||
return `import requests
|
||||
|
||||
API_URL = "${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid}"
|
||||
|
||||
# use form data to upload files
|
||||
form_data = {
|
||||
"files": ${`('example${fileType}', open('example${fileType}', 'rb'))`}
|
||||
}
|
||||
body_data = {${getConfigExamplesForPython(configData, 'formData', isMultiple, vectorNodeId)}}
|
||||
|
||||
def query(form_data, body_data):
|
||||
response = requests.post(API_URL, files=form_data, data=body_data)
|
||||
return response.json()
|
||||
|
||||
output = query(form_data, body_data)
|
||||
`
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return `// use FormData to upload files
|
||||
let formData = new FormData();
|
||||
${getConfigExamplesForJS(configData, 'formData', isMultiple, vectorNodeId)}
|
||||
async function query(formData) {
|
||||
const response = await fetch(
|
||||
"${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid}",
|
||||
{
|
||||
method: "POST",
|
||||
body: formData
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
|
||||
query(formData).then((response) => {
|
||||
console.log(response);
|
||||
});
|
||||
`
|
||||
} else if (codeLang === 'cURL') {
|
||||
return `curl ${baseURL}/api/v1/vector/upsert/${dialogProps.chatflowid} \\
|
||||
-X POST \\${getConfigExamplesForCurl(configData, 'formData', isMultiple, vectorNodeId)} \\
|
||||
-H "Content-Type: multipart/form-data"`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const getLang = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return 'python'
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return 'javascript'
|
||||
} else if (codeLang === 'cURL') {
|
||||
return 'bash'
|
||||
}
|
||||
return 'python'
|
||||
}
|
||||
|
||||
const getSVG = (codeLang) => {
|
||||
if (codeLang === 'Python') {
|
||||
return pythonSVG
|
||||
} else if (codeLang === 'JavaScript') {
|
||||
return javascriptSVG
|
||||
} else if (codeLang === 'Embed') {
|
||||
return EmbedSVG
|
||||
} else if (codeLang === 'cURL') {
|
||||
return cURLSVG
|
||||
} else if (codeLang === 'Share Chatbot') {
|
||||
return ShareChatbotSVG
|
||||
} else if (codeLang === 'Configuration') {
|
||||
return settingsSVG
|
||||
}
|
||||
return pythonSVG
|
||||
}
|
||||
|
||||
const handleAccordionChange = (nodeLabel) => (event, isExpanded) => {
|
||||
const accordianNodes = { ...nodeConfigExpanded }
|
||||
accordianNodes[nodeLabel] = isExpanded
|
||||
setNodeConfigExpanded(accordianNodes)
|
||||
}
|
||||
|
||||
const onCheckBoxChanged = (vectorNodeId) => {
|
||||
const checkboxNodes = { ...nodeCheckboxExpanded }
|
||||
if (Object.keys(checkboxNodes).includes(vectorNodeId)) checkboxNodes[vectorNodeId] = !checkboxNodes[vectorNodeId]
|
||||
else checkboxNodes[vectorNodeId] = true
|
||||
|
||||
if (checkboxNodes[vectorNodeId] === true) getConfigApi.request(dialogProps.chatflowid)
|
||||
setCheckboxExpanded(checkboxNodes)
|
||||
setExpandedVectorNodeId(vectorNodeId)
|
||||
|
||||
const newIsFormDataRequired = { ...isFormDataRequired }
|
||||
newIsFormDataRequired[vectorNodeId] = false
|
||||
setIsFormDataRequired(newIsFormDataRequired)
|
||||
const newNodes = nodes.find((node) => node.vectorNode.data.id === vectorNodeId)?.nodes ?? []
|
||||
|
||||
for (const node of newNodes) {
|
||||
if (node.data.inputParams.find((param) => param.type === 'file')) {
|
||||
newIsFormDataRequired[vectorNodeId] = true
|
||||
setIsFormDataRequired(newIsFormDataRequired)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onUpsertClicked = async (vectorStoreNode) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await vectorstoreApi.upsertVectorStore(dialogProps.chatflowid, { stopNodeId: vectorStoreNode.data.id })
|
||||
enqueueSnackbar({
|
||||
message: 'Succesfully upserted vector store. You can start chatting now!',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: errorData,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getNodeDetail = (node) => {
|
||||
const nodeDetails = []
|
||||
const inputKeys = Object.keys(node.data.inputs)
|
||||
for (let i = 0; i < node.data.inputParams.length; i += 1) {
|
||||
if (inputKeys.includes(node.data.inputParams[i].name)) {
|
||||
nodeDetails.push({
|
||||
label: node.data.inputParams[i].label,
|
||||
name: node.data.inputParams[i].name,
|
||||
type: node.data.inputParams[i].type,
|
||||
value:
|
||||
node.data.inputParams[i].type === 'file'
|
||||
? getFileName(node.data.inputs[node.data.inputParams[i].name])
|
||||
: node.data.inputs[node.data.inputParams[i].name] ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
return nodeDetails
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getConfigApi.data) {
|
||||
const newConfigData = { ...configData }
|
||||
newConfigData[expandedVectorNodeId] = reformatConfigData(
|
||||
getConfigApi.data,
|
||||
nodes.find((node) => node.vectorNode.data.id === expandedVectorNodeId)?.nodes ?? []
|
||||
)
|
||||
setConfigData(newConfigData)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getConfigApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps && reactFlowInstance) {
|
||||
const nodes = reactFlowInstance.getNodes()
|
||||
const edges = reactFlowInstance.getEdges()
|
||||
setNodes(getUpsertDetails(nodes, edges))
|
||||
}
|
||||
|
||||
return () => {
|
||||
setNodes([])
|
||||
setLoading(false)
|
||||
setIsFormDataRequired({})
|
||||
setNodeConfigExpanded({})
|
||||
setCheckboxExpanded({})
|
||||
setTabValue(0)
|
||||
setConfigData({})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth='md'
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
sx={{ overflow: 'visible' }}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ display: 'flex', justifyContent: 'flex-end', flexDirection: 'column' }}>
|
||||
<PerfectScrollbar
|
||||
style={{
|
||||
height: '100%',
|
||||
maxHeight: 'calc(100vh - 220px)',
|
||||
overflowX: 'hidden'
|
||||
}}
|
||||
>
|
||||
{nodes.length > 0 &&
|
||||
nodes.map((data, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
{data.nodes.length > 0 &&
|
||||
data.nodes.map((node, index) => {
|
||||
return (
|
||||
<Accordion
|
||||
expanded={nodeConfigExpanded[node.data.id] || false}
|
||||
onChange={handleAccordionChange(node.data.id)}
|
||||
key={index}
|
||||
disableGutters
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`nodes-accordian-${node.data.name}`}
|
||||
id={`nodes-accordian-header-${node.data.name}`}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={node.data.name}
|
||||
src={`${baseURL}/api/v1/node-icon/${node.data.name}`}
|
||||
/>
|
||||
</div>
|
||||
<Typography variant='h5'>{node.data.label}</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' }}>
|
||||
{node.data.id}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<TableViewOnly
|
||||
sx={{ minWidth: 'max-content' }}
|
||||
rows={getNodeDetail(node)}
|
||||
columns={Object.keys(getNodeDetail(node)[0])}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
})}
|
||||
<Box sx={{ p: 2 }}>
|
||||
<CheckboxInput
|
||||
key={JSON.stringify(nodeCheckboxExpanded)}
|
||||
label='Show API'
|
||||
value={nodeCheckboxExpanded[data.vectorNode.data.id]}
|
||||
onChange={() => onCheckBoxChanged(data.vectorNode.data.id)}
|
||||
/>
|
||||
{nodeCheckboxExpanded[data.vectorNode.data.id] && (
|
||||
<div>
|
||||
<Tabs value={tabValue} onChange={(event, val) => setTabValue(val)} aria-label='tabs'>
|
||||
{['Python', 'JavaScript', 'cURL'].map((codeLang, index) => (
|
||||
<Tab
|
||||
icon={
|
||||
<img
|
||||
style={{ objectFit: 'cover', height: 15, width: 'auto' }}
|
||||
src={getSVG(codeLang)}
|
||||
alt='code'
|
||||
/>
|
||||
}
|
||||
iconPosition='start'
|
||||
key={index}
|
||||
label={codeLang}
|
||||
{...a11yProps(index)}
|
||||
></Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
{nodeCheckboxExpanded[data.vectorNode.data.id] &&
|
||||
isFormDataRequired[data.vectorNode.data.id] !== undefined &&
|
||||
configData[data.vectorNode.data.id] &&
|
||||
configData[data.vectorNode.data.id].length > 0 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
{['Python', 'JavaScript', 'cURL'].map((codeLang, index) => (
|
||||
<TabPanel key={index} value={tabValue} index={index}>
|
||||
<CopyBlock
|
||||
theme={atomOneDark}
|
||||
text={
|
||||
isFormDataRequired[data.vectorNode.data.id]
|
||||
? getCodeWithFormData(
|
||||
codeLang,
|
||||
data.vectorNode.data.id,
|
||||
nodes.length > 1 ? true : false,
|
||||
configData[data.vectorNode.data.id]
|
||||
)
|
||||
: getCode(
|
||||
codeLang,
|
||||
data.vectorNode.data.id,
|
||||
nodes.length > 1 ? true : false,
|
||||
configData[data.vectorNode.data.id]
|
||||
)
|
||||
}
|
||||
language={getLang(codeLang)}
|
||||
showLineNumbers={false}
|
||||
wrapLines
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
{loading && <BackdropLoader open={loading} />}
|
||||
{!loading && (
|
||||
<Button
|
||||
sx={{ color: 'white' }}
|
||||
fullWidth
|
||||
variant='contained'
|
||||
color='teal'
|
||||
title='Upsert'
|
||||
onClick={() => onUpsertClicked(data.vectorNode)}
|
||||
>
|
||||
Upsert
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</PerfectScrollbar>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
VectorStoreDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func
|
||||
}
|
||||
|
||||
export default VectorStoreDialog
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Button } from '@mui/material'
|
||||
import { IconDatabaseImport, IconX } from '@tabler/icons'
|
||||
|
||||
// project import
|
||||
import { StyledFab } from 'ui-component/button/StyledFab'
|
||||
import VectorStoreDialog from './VectorStoreDialog'
|
||||
|
||||
// api
|
||||
import vectorstoreApi from 'api/vectorstore'
|
||||
|
||||
// Hooks
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// Const
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
|
||||
export const VectorStorePopUp = ({ chatflowid }) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [showExpandDialog, setShowExpandDialog] = useState(false)
|
||||
const [expandDialogProps, setExpandDialogProps] = useState({})
|
||||
|
||||
const anchorRef = useRef(null)
|
||||
const prevOpen = useRef(open)
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen((prevopen) => !prevopen)
|
||||
const props = {
|
||||
open: true,
|
||||
title: 'Upsert Vector Store',
|
||||
chatflowid
|
||||
}
|
||||
setExpandDialogProps(props)
|
||||
setShowExpandDialog(true)
|
||||
}
|
||||
|
||||
const onUpsert = async () => {
|
||||
try {
|
||||
await vectorstoreApi.upsertVectorStore(chatflowid, {})
|
||||
enqueueSnackbar({
|
||||
message: 'Succesfully upserted vector store',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: errorData,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (prevOpen.current === true && open === false) {
|
||||
anchorRef.current.focus()
|
||||
}
|
||||
prevOpen.current = open
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, chatflowid])
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledFab
|
||||
sx={{ position: 'absolute', right: 80, top: 20 }}
|
||||
ref={anchorRef}
|
||||
size='small'
|
||||
color='teal'
|
||||
aria-label='upsert'
|
||||
title='Upsert Vector Database'
|
||||
onClick={handleToggle}
|
||||
>
|
||||
{open ? <IconX /> : <IconDatabaseImport />}
|
||||
</StyledFab>
|
||||
<VectorStoreDialog
|
||||
show={showExpandDialog}
|
||||
dialogProps={expandDialogProps}
|
||||
onUpsert={onUpsert}
|
||||
onCancel={() => {
|
||||
setShowExpandDialog(false)
|
||||
setOpen((prevopen) => !prevopen)
|
||||
}}
|
||||
></VectorStoreDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
VectorStorePopUp.propTypes = { chatflowid: PropTypes.string }
|
||||
Reference in New Issue
Block a user