Merge branch 'main' into FEATURE/Vision

# Conflicts:
#	packages/components/nodes/chains/ConversationChain/ConversationChain.ts
#	packages/server/src/index.ts
#	packages/server/src/utils/index.ts
This commit is contained in:
Henry
2024-02-02 02:54:06 +00:00
136 changed files with 5054 additions and 2019 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "flowise-ui",
"version": "1.4.6",
"version": "1.4.9",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://flowiseai.com",
"author": {
+8
View File
@@ -0,0 +1,8 @@
import client from './client'
const fetchAllLinks = (url, relativeLinksMethod) =>
client.get(`/fetch-links?url=${encodeURIComponent(url)}&relativeLinksMethod=${relativeLinksMethod}`)
export default {
fetchAllLinks
}
+1 -1
View File
@@ -36,7 +36,7 @@ const settings = {
},
{
id: 'enableSpeechToText',
title: 'Enable Speech to Text',
title: 'Speech to Text',
type: 'item',
url: '',
icon: icons.IconMicrophone
@@ -0,0 +1,21 @@
// material-ui
import { styled } from '@mui/material/styles'
// project imports
import MainCard from './MainCard'
const NodeCardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main,
color: theme.darkTextPrimary,
border: 'solid 1px',
borderColor: theme.palette.primary[200] + 75,
width: '300px',
height: 'auto',
padding: '10px',
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
'&:hover': {
borderColor: theme.palette.primary.main
}
}))
export default NodeCardWrapper
@@ -0,0 +1,184 @@
import PropTypes from 'prop-types'
import { createPortal } from 'react-dom'
import { useDispatch } from 'react-redux'
import { useState, useEffect } from 'react'
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
IconButton,
OutlinedInput,
Stack,
Typography
} from '@mui/material'
import { IconTrash } from '@tabler/icons'
import PerfectScrollbar from 'react-perfect-scrollbar'
import { BackdropLoader } from 'ui-component/loading/BackdropLoader'
import { StyledButton } from 'ui-component/button/StyledButton'
import scraperApi from 'api/scraper'
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
const ManageScrapedLinksDialog = ({ show, dialogProps, onCancel, onSave }) => {
const portalElement = document.getElementById('portal')
const dispatch = useDispatch()
const [loading, setLoading] = useState(false)
const [selectedLinks, setSelectedLinks] = useState([])
const [url, setUrl] = useState('')
useEffect(() => {
if (dialogProps.url) setUrl(dialogProps.url)
if (dialogProps.selectedLinks) setSelectedLinks(dialogProps.selectedLinks)
return () => {
setLoading(false)
setSelectedLinks([])
setUrl('')
}
}, [dialogProps])
useEffect(() => {
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
else dispatch({ type: HIDE_CANVAS_DIALOG })
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
}, [show, dispatch])
const handleFetchLinks = async () => {
setLoading(true)
const fetchLinksResp = await scraperApi.fetchAllLinks(url, 'webCrawl')
if (fetchLinksResp.data) {
setSelectedLinks(fetchLinksResp.data.links)
}
setLoading(false)
}
const handleChangeLink = (index, event) => {
const { value } = event.target
const links = [...selectedLinks]
links[index] = value
setSelectedLinks(links)
}
const handleRemoveLink = (index) => {
const links = [...selectedLinks]
links.splice(index, 1)
setSelectedLinks(links)
}
const handleSaveLinks = () => {
onSave(url, selectedLinks)
}
const component = show ? (
<Dialog
onClose={onCancel}
open={show}
fullWidth
maxWidth='sm'
aria-labelledby='manage-scraped-links-dialog-title'
aria-describedby='manage-scraped-links-dialog-description'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='manage-scraped-links-dialog-title'>
{dialogProps.title || `Manage Scraped Links - ${url}`}
</DialogTitle>
<DialogContent>
<Box sx={{ mb: 4 }}>
<Stack flexDirection='row' gap={1} sx={{ width: '100%' }}>
<FormControl sx={{ mt: 1, width: '100%', display: 'flex', flexShrink: 1 }} size='small'>
<OutlinedInput
id='url'
size='small'
type='text'
value={url}
name='url'
onChange={(e) => {
setUrl(e.target.value)
}}
/>
</FormControl>
<Button
sx={{ borderRadius: '12px', mt: 1, display: 'flex', flexShrink: 0 }}
size='small'
variant='contained'
onClick={handleFetchLinks}
>
Fetch Links
</Button>
</Stack>
</Box>
<Typography sx={{ mb: 2, fontWeight: 500 }}>Scraped Links</Typography>
<>
{loading && <BackdropLoader open={loading} />}
{selectedLinks.length > 0 ? (
<PerfectScrollbar
style={{
height: '100%',
maxHeight: '320px',
overflowX: 'hidden',
display: 'flex',
flexDirection: 'column',
gap: 4
}}
>
{selectedLinks.map((link, index) => (
<div key={index} style={{ display: 'flex', width: '100%' }}>
<Box sx={{ display: 'flex', width: '100%' }}>
<OutlinedInput
sx={{ width: '100%' }}
key={index}
type='text'
onChange={(e) => handleChangeLink(index, e)}
size='small'
value={link}
name={`link_${index}`}
/>
</Box>
<Box sx={{ width: 'auto', flexGrow: 1 }}>
<IconButton
sx={{ height: 30, width: 30 }}
size='small'
color='error'
onClick={() => handleRemoveLink(index)}
edge='end'
>
<IconTrash />
</IconButton>
</Box>
</div>
))}
</PerfectScrollbar>
) : (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography sx={{ my: 2 }}>Links scraped from the URL will appear here</Typography>
</div>
)}
</>
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>Cancel</Button>
<StyledButton variant='contained' onClick={handleSaveLinks}>
Save
</StyledButton>
</DialogActions>
</Dialog>
) : null
return createPortal(component, portalElement)
}
ManageScrapedLinksDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func,
onSave: PropTypes.func
}
export default ManageScrapedLinksDialog
+62 -24
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import { FormControl, OutlinedInput, Popover } from '@mui/material'
import { FormControl, OutlinedInput, InputBase, Popover } from '@mui/material'
import SelectVariable from 'ui-component/json/SelectVariable'
import { getAvailableNodesForVariable } from 'utils/genericHelper'
@@ -50,29 +50,67 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disab
return (
<>
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
<OutlinedInput
id={inputParam.name}
size='small'
disabled={disabled}
type={getInputType(inputParam.type)}
placeholder={inputParam.placeholder}
multiline={!!inputParam.rows}
rows={inputParam.rows ?? 1}
value={myValue}
name={inputParam.name}
onChange={(e) => {
setMyValue(e.target.value)
onChange(e.target.value)
}}
inputProps={{
step: inputParam.step ?? 1,
style: {
height: inputParam.rows ? '90px' : 'inherit'
}
}}
/>
</FormControl>
{inputParam.name === 'note' ? (
<FormControl sx={{ width: '100%', height: 'auto' }} size='small'>
<InputBase
id={nodeId}
size='small'
disabled={disabled}
type={getInputType(inputParam.type)}
placeholder={inputParam.placeholder}
multiline={!!inputParam.rows}
minRows={inputParam.rows ?? 1}
value={myValue}
name={inputParam.name}
onChange={(e) => {
setMyValue(e.target.value)
onChange(e.target.value)
}}
inputProps={{
step: inputParam.step ?? 1,
style: {
border: 'none',
background: 'none',
color: '#212121'
}
}}
sx={{
border: 'none',
background: 'none',
padding: '10px 14px',
textarea: {
'&::placeholder': {
color: '#616161'
}
}
}}
/>
</FormControl>
) : (
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
<OutlinedInput
id={inputParam.name}
size='small'
disabled={disabled}
type={getInputType(inputParam.type)}
placeholder={inputParam.placeholder}
multiline={!!inputParam.rows}
rows={inputParam.rows ?? 1}
value={myValue}
name={inputParam.name}
onChange={(e) => {
setMyValue(e.target.value)
onChange(e.target.value)
}}
inputProps={{
step: inputParam.step ?? 1,
style: {
height: inputParam.rows ? '90px' : 'inherit'
}
}}
/>
</FormControl>
)}
<div ref={ref}></div>
{inputParam?.acceptVariable && (
<Popover
@@ -0,0 +1,12 @@
import { styled } from '@mui/material/styles'
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'
const NodeTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.nodeToolTip.background,
color: theme.palette.nodeToolTip.color,
boxShadow: theme.shadows[1]
}
}))
export default NodeTooltip
+8 -30
View File
@@ -3,12 +3,13 @@ import { useContext, useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
// material-ui
import { styled, useTheme } from '@mui/material/styles'
import { useTheme } from '@mui/material/styles'
import { IconButton, Box, Typography, Divider, Button } from '@mui/material'
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'
import Tooltip from '@mui/material/Tooltip'
// project imports
import MainCard from 'ui-component/cards/MainCard'
import NodeCardWrapper from '../../ui-component/cards/NodeCardWrapper'
import NodeTooltip from '../../ui-component/tooltip/NodeTooltip'
import NodeInputHandler from './NodeInputHandler'
import NodeOutputHandler from './NodeOutputHandler'
import AdditionalParamsDialog from 'ui-component/dialog/AdditionalParamsDialog'
@@ -19,28 +20,6 @@ import { baseURL } from 'store/constant'
import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/icons'
import { flowContext } from 'store/context/ReactFlowContext'
const CardWrapper = styled(MainCard)(({ theme }) => ({
background: theme.palette.card.main,
color: theme.darkTextPrimary,
border: 'solid 1px',
borderColor: theme.palette.primary[200] + 75,
width: '300px',
height: 'auto',
padding: '10px',
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)',
'&:hover': {
borderColor: theme.palette.primary.main
}
}))
const LightTooltip = styled(({ className, ...props }) => <Tooltip {...props} classes={{ popper: className }} />)(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.nodeToolTip.background,
color: theme.palette.nodeToolTip.color,
boxShadow: theme.shadows[1]
}
}))
// ===========================|| CANVAS NODE ||=========================== //
const CanvasNode = ({ data }) => {
@@ -93,7 +72,7 @@ const CanvasNode = ({ data }) => {
return (
<>
<CardWrapper
<NodeCardWrapper
content={false}
sx={{
padding: 0,
@@ -101,7 +80,7 @@ const CanvasNode = ({ data }) => {
}}
border={false}
>
<LightTooltip
<NodeTooltip
open={!canvas.canvasDialogShow && open}
onClose={handleClose}
onOpen={handleOpen}
@@ -242,13 +221,12 @@ const CanvasNode = ({ data }) => {
</Typography>
</Box>
<Divider />
{data.outputAnchors.map((outputAnchor, index) => (
<NodeOutputHandler key={index} outputAnchor={outputAnchor} data={data} />
))}
</Box>
</LightTooltip>
</CardWrapper>
</NodeTooltip>
</NodeCardWrapper>
<AdditionalParamsDialog
show={showDialog}
dialogProps={dialogProps}
@@ -28,6 +28,8 @@ import ToolDialog from 'views/tools/ToolDialog'
import AssistantDialog from 'views/assistants/AssistantDialog'
import ExpandTextDialog from 'ui-component/dialog/ExpandTextDialog'
import FormatPromptValuesDialog from 'ui-component/dialog/FormatPromptValuesDialog'
import PromptLangsmithHubDialog from 'ui-component/dialog/PromptLangsmithHubDialog'
import ManageScrapedLinksDialog from 'ui-component/dialog/ManageScrapedLinksDialog'
import CredentialInputHandler from './CredentialInputHandler'
// utils
@@ -35,7 +37,6 @@ import { getInputVariables } from 'utils/genericHelper'
// const
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
import PromptLangsmithHubDialog from '../../ui-component/dialog/PromptLangsmithHubDialog'
const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant']
@@ -62,22 +63,25 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false)
const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({})
const [showPromptHubDialog, setShowPromptHubDialog] = useState(false)
const [showManageScrapedLinksDialog, setShowManageScrapedLinksDialog] = useState(false)
const [manageScrapedLinksDialogProps, setManageScrapedLinksDialogProps] = useState({})
const onExpandDialogClicked = (value, inputParam) => {
const dialogProp = {
const dialogProps = {
value,
inputParam,
disabled,
confirmButtonName: 'Save',
cancelButtonName: 'Cancel'
}
setExpandDialogProps(dialogProp)
setExpandDialogProps(dialogProps)
setShowExpandDialog(true)
}
const onShowPromptHubButtonClicked = () => {
setShowPromptHubDialog(true)
}
const onShowPromptHubButtonSubmit = (templates) => {
setShowPromptHubDialog(false)
for (const t of templates) {
@@ -86,6 +90,24 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
}
}
}
const onManageLinksDialogClicked = (url, selectedLinks) => {
const dialogProps = {
url,
selectedLinks,
confirmButtonName: 'Save',
cancelButtonName: 'Cancel'
}
setManageScrapedLinksDialogProps(dialogProps)
setShowManageScrapedLinksDialog(true)
}
const onManageLinksDialogSave = (url, links) => {
setShowManageScrapedLinksDialog(false)
data.inputs.url = url
data.inputs.selectedLinks = links
}
const onEditJSONClicked = (value, inputParam) => {
// Preset values if the field is format prompt values
let inputValue = value
@@ -436,6 +458,37 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
</div>
</>
)}
{(data.name === 'cheerioWebScraper' ||
data.name === 'puppeteerWebScraper' ||
data.name === 'playwrightWebScraper') &&
inputParam.name === 'url' && (
<>
<Button
style={{
display: 'flex',
flexDirection: 'row',
width: '100%'
}}
disabled={disabled}
sx={{ borderRadius: '12px', width: '100%', mt: 1 }}
variant='outlined'
onClick={() =>
onManageLinksDialogClicked(
data.inputs[inputParam.name] ?? inputParam.default ?? '',
data.inputs.selectedLinks
)
}
>
Manage Links
</Button>
<ManageScrapedLinksDialog
show={showManageScrapedLinksDialog}
dialogProps={manageScrapedLinksDialogProps}
onCancel={() => setShowManageScrapedLinksDialog(false)}
onSave={onManageLinksDialogSave}
/>
</>
)}
</Box>
</>
)}
+103
View File
@@ -0,0 +1,103 @@
import PropTypes from 'prop-types'
import { useContext, useState } from 'react'
import { useSelector } from 'react-redux'
// material-ui
import { useTheme } from '@mui/material/styles'
// project imports
import NodeCardWrapper from '../../ui-component/cards/NodeCardWrapper'
import NodeTooltip from '../../ui-component/tooltip/NodeTooltip'
import { IconButton, Box } from '@mui/material'
import { IconCopy, IconTrash } from '@tabler/icons'
import { Input } from 'ui-component/input/Input'
// const
import { flowContext } from '../../store/context/ReactFlowContext'
const StickyNote = ({ data }) => {
const theme = useTheme()
const canvas = useSelector((state) => state.canvas)
const { deleteNode, duplicateNode } = useContext(flowContext)
const [inputParam] = data.inputParams
const [open, setOpen] = useState(false)
const handleClose = () => {
setOpen(false)
}
const handleOpen = () => {
setOpen(true)
}
return (
<>
<NodeCardWrapper
content={false}
sx={{
padding: 0,
borderColor: data.selected ? theme.palette.primary.main : theme.palette.text.secondary,
backgroundColor: data.selected ? '#FFDC00' : '#FFE770'
}}
border={false}
>
<NodeTooltip
open={!canvas.canvasDialogShow && open}
onClose={handleClose}
onOpen={handleOpen}
disableFocusListener={true}
title={
<div
style={{
background: 'transparent',
display: 'flex',
flexDirection: 'column'
}}
>
<IconButton
title='Duplicate'
onClick={() => {
duplicateNode(data.id)
}}
sx={{ height: '35px', width: '35px', '&:hover': { color: theme?.palette.primary.main } }}
color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'}
>
<IconCopy />
</IconButton>
<IconButton
title='Delete'
onClick={() => {
deleteNode(data.id)
}}
sx={{ height: '35px', width: '35px', '&:hover': { color: 'red' } }}
color={theme?.customization?.isDarkMode ? theme.colors?.paper : 'inherit'}
>
<IconTrash />
</IconButton>
</div>
}
placement='right-start'
>
<Box>
<Input
key={data.id}
inputParam={inputParam}
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
nodes={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getNodes() : []}
edges={inputParam?.acceptVariable && reactFlowInstance ? reactFlowInstance.getEdges() : []}
nodeId={data.id}
/>
</Box>
</NodeTooltip>
</NodeCardWrapper>
</>
)
}
StickyNote.propTypes = {
data: PropTypes.object
}
export default StickyNote
+3 -2
View File
@@ -21,6 +21,7 @@ import { useTheme } from '@mui/material/styles'
// project imports
import CanvasNode from './CanvasNode'
import ButtonEdge from './ButtonEdge'
import StickyNote from './StickyNote'
import CanvasHeader from './CanvasHeader'
import AddNodes from './AddNodes'
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
@@ -46,7 +47,7 @@ import useNotifier from 'utils/useNotifier'
// const
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
const nodeTypes = { customNode: CanvasNode }
const nodeTypes = { customNode: CanvasNode, stickyNote: StickyNote }
const edgeTypes = { buttonedge: ButtonEdge }
// ==============================|| CANVAS ||============================== //
@@ -276,7 +277,7 @@ const Canvas = () => {
const newNode = {
id: newNodeId,
position,
type: 'customNode',
type: nodeData.type !== 'StickyNote' ? 'customNode' : 'stickyNote',
data: initNode(nodeData, newNodeId)
}
@@ -130,8 +130,8 @@
width: 100%;
height: 100%;
display: flex;
align-items: start;
justify-content: start;
align-items: flex-start;
justify-content: flex-start;
flex-direction: column;
position: relative;
}
@@ -155,7 +155,7 @@
overflow-y: scroll;
display: flex;
justify-content: center;
align-items: start;
align-items: flex-start;
flex-grow: 1;
}
@@ -101,13 +101,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
const isFileAllowedForUpload = (file) => {
const constraints = getAllowChatFlowUploads.data
/**
* {isUploadAllowed: boolean, uploadFileSizeAndTypes: Array<{ fileTypes: string[], maxUploadSize: number }>}
* {isImageUploadAllowed: boolean, imgUploadSizeAndTypes: Array<{ fileTypes: string[], maxUploadSize: number }>}
*/
let acceptFile = false
if (constraints.isUploadAllowed) {
if (constraints.isImageUploadAllowed) {
const fileType = file.type
const sizeInMB = file.size / 1024 / 1024
constraints.uploadFileSizeAndTypes.map((allowed) => {
constraints.imgUploadSizeAndTypes.map((allowed) => {
if (allowed.fileTypes.includes(fileType) && sizeInMB <= allowed.maxUploadSize) {
acceptFile = true
}
@@ -473,7 +473,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
obj.fileUploads = JSON.parse(message.fileUploads)
obj.fileUploads.forEach((file) => {
if (file.type === 'stored-file') {
file.data = `${baseURL}/api/v1/get-upload-file/${file.name}?chatId=${chatId}`
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatflowid}&chatId=${chatId}&fileName=${file.name}`
}
})
}
@@ -497,8 +497,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
// Get chatflow uploads capability
useEffect(() => {
if (getAllowChatFlowUploads.data) {
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isUploadAllowed ?? false)
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.allowSpeechToText ?? false)
setIsChatFlowAvailableForUploads(getAllowChatFlowUploads.data?.isImageUploadAllowed ?? false)
setIsChatFlowAvailableForSpeech(getAllowChatFlowUploads.data?.isSpeechToTextEnabled ?? false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getAllowChatFlowUploads.data])
@@ -603,10 +603,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
onDrop={handleDrop}
/>
)}
{isDragActive && getAllowChatFlowUploads.data?.isUploadAllowed && (
{isDragActive && getAllowChatFlowUploads.data?.isImageUploadAllowed && (
<Box className='drop-overlay'>
<Typography variant='h2'>Drop here to upload</Typography>
{getAllowChatFlowUploads.data.uploadFileSizeAndTypes.map((allowed) => {
{getAllowChatFlowUploads.data.imgUploadSizeAndTypes.map((allowed) => {
return (
<>
<Typography variant='subtitle1'>{allowed.fileTypes?.join(', ')}</Typography>
@@ -11,10 +11,10 @@ import { useTheme } from '@mui/material/styles'
// project imports
import MarketplaceCanvasNode from './MarketplaceCanvasNode'
import MarketplaceCanvasHeader from './MarketplaceCanvasHeader'
import StickyNote from '../canvas/StickyNote'
const nodeTypes = { customNode: MarketplaceCanvasNode }
const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote }
const edgeTypes = { buttonedge: '' }
// ==============================|| CANVAS ||============================== //