mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
Fix merge conflicts
This commit is contained in:
@@ -11,6 +11,7 @@ import FileCopyIcon from '@mui/icons-material/FileCopy'
|
||||
import FileDownloadIcon from '@mui/icons-material/Downloading'
|
||||
import FileDeleteIcon from '@mui/icons-material/Delete'
|
||||
import FileCategoryIcon from '@mui/icons-material/Category'
|
||||
import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt'
|
||||
import Button from '@mui/material/Button'
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||
import { IconX } from '@tabler/icons'
|
||||
@@ -22,8 +23,9 @@ import useConfirm from '@/hooks/useConfirm'
|
||||
import { uiBaseURL } from '@/store/constant'
|
||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions'
|
||||
|
||||
import SaveChatflowDialog from '../dialog/SaveChatflowDialog'
|
||||
import TagDialog from '../dialog/TagDialog'
|
||||
import SaveChatflowDialog from '@/dialog/SaveChatflowDialog'
|
||||
import TagDialog from '@/dialog/TagDialog'
|
||||
import StarterPromptsDialog from '@/dialog/StarterPromptsDialog'
|
||||
|
||||
import { generateExportFlowData } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
@@ -78,6 +80,8 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
||||
const [categoryDialogProps, setCategoryDialogProps] = useState({})
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const open = Boolean(anchorEl)
|
||||
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
||||
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
@@ -92,6 +96,20 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
||||
setFlowDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleFlowStarterPrompts = () => {
|
||||
setAnchorEl(null)
|
||||
setConversationStartersDialogProps({
|
||||
title: 'Starter Prompts - ' + chatflow.name,
|
||||
chatflow: chatflow
|
||||
})
|
||||
setConversationStartersDialogOpen(true)
|
||||
}
|
||||
|
||||
const saveFlowStarterPrompts = async () => {
|
||||
setConversationStartersDialogOpen(false)
|
||||
await updateFlowsApi.request()
|
||||
}
|
||||
|
||||
const saveFlowRename = async (chatflowName) => {
|
||||
const updateBody = {
|
||||
name: chatflowName,
|
||||
@@ -253,6 +271,10 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
||||
Export
|
||||
</MenuItem>
|
||||
<Divider sx={{ my: 0.5 }} />
|
||||
<MenuItem onClick={handleFlowStarterPrompts} disableRipple>
|
||||
<PictureInPictureAltIcon />
|
||||
Starter Prompts
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleFlowCategory} disableRipple>
|
||||
<FileCategoryIcon />
|
||||
Update Category
|
||||
@@ -279,6 +301,12 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
||||
onClose={() => setCategoryDialogOpen(false)}
|
||||
onSubmit={saveFlowCategory}
|
||||
/>
|
||||
<StarterPromptsDialog
|
||||
show={conversationStartersDialogOpen}
|
||||
dialogProps={conversationStartersDialogProps}
|
||||
onConfirm={saveFlowStarterPrompts}
|
||||
onCancel={() => setConversationStartersDialogOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { styled } from '@mui/material/styles'
|
||||
import ButtonBase from '@mui/material/ButtonBase'
|
||||
|
||||
export const ImageButton = styled(ButtonBase)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
height: 200,
|
||||
borderRadius: '10px',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '100% !important', // Overrides inline-style
|
||||
height: 100
|
||||
},
|
||||
'&:hover, &.Mui-focusVisible': {
|
||||
zIndex: 1,
|
||||
'& .MuiImageBackdrop-root': {
|
||||
opacity: 0.4
|
||||
},
|
||||
'& .MuiImageMarked-root': {
|
||||
opacity: 1
|
||||
},
|
||||
'& .MuiTypography-root': {
|
||||
border: '4px solid currentColor'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
export const ImageSrc = styled('span')({
|
||||
position: 'absolute',
|
||||
borderRadius: '10px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center 40%'
|
||||
})
|
||||
|
||||
export const ImageBackdrop = styled('span')(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
borderRadius: '10px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.common.black,
|
||||
opacity: 0.1,
|
||||
transition: theme.transitions.create('opacity')
|
||||
}))
|
||||
|
||||
export const ImageMarked = styled('span')(() => ({
|
||||
height: 25,
|
||||
width: 25,
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute',
|
||||
top: 'auto',
|
||||
left: 'auto',
|
||||
opacity: 0
|
||||
}))
|
||||
@@ -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,10 @@
|
||||
.button-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */
|
||||
scrollbar-width: none; /* For Firefox */
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Chip } from '@mui/material'
|
||||
import './StarterPromptsCard.css'
|
||||
|
||||
const StarterPromptsCard = ({ isGrid, starterPrompts, sx, onPromptClick }) => {
|
||||
return (
|
||||
<Box
|
||||
className={'button-container'}
|
||||
sx={{ width: '100%', maxWidth: isGrid ? 'inherit' : '400px', p: 1.5, display: 'flex', gap: 1, ...sx }}
|
||||
>
|
||||
{starterPrompts.map((sp, index) => (
|
||||
<Chip label={sp.prompt} className={'button'} key={index} onClick={(e) => onPromptClick(sp.prompt, e)} />
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
StarterPromptsCard.propTypes = {
|
||||
isGrid: PropTypes.bool,
|
||||
starterPrompts: PropTypes.array,
|
||||
sx: PropTypes.object,
|
||||
onPromptClick: PropTypes.func
|
||||
}
|
||||
|
||||
export default StarterPromptsCard
|
||||
@@ -30,8 +30,7 @@ import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||
import { Input } from '@/ui-component/input/Input'
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import langsmithPNG from '@/assets/images/langchain.png'
|
||||
import langfusePNG from '@/assets/images/langfuse.png'
|
||||
import llmonitorPNG from '@/assets/images/llmonitor.png'
|
||||
import lunarySVG from '@/assets/images/lunary.svg'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
@@ -72,7 +71,7 @@ const analyticProviders = [
|
||||
{
|
||||
label: 'LangFuse',
|
||||
name: 'langFuse',
|
||||
icon: langfusePNG,
|
||||
icon: langfuseSVG,
|
||||
url: 'https://langfuse.com',
|
||||
inputs: [
|
||||
{
|
||||
@@ -97,16 +96,16 @@ const analyticProviders = [
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'LLMonitor',
|
||||
name: 'llmonitor',
|
||||
icon: llmonitorPNG,
|
||||
url: 'https://llmonitor.com',
|
||||
label: 'Lunary',
|
||||
name: 'lunary',
|
||||
icon: lunarySVG,
|
||||
url: 'https://lunary.ai',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['llmonitorApi']
|
||||
credentialNames: ['lunaryApi']
|
||||
},
|
||||
{
|
||||
label: 'On/Off',
|
||||
|
||||
@@ -2,14 +2,24 @@ import { createPortal } from 'react-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||
|
||||
// MUI
|
||||
import { Button, Dialog, DialogActions, DialogContent, Typography } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar'
|
||||
import { LoadingButton } from '@mui/lab'
|
||||
|
||||
// Project Import
|
||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||
import { DarkCodeEditor } from '@/ui-component/editor/DarkCodeEditor'
|
||||
import { LightCodeEditor } from '@/ui-component/editor/LightCodeEditor'
|
||||
import { CodeEditor } from '@/ui-component/editor/CodeEditor'
|
||||
|
||||
// Store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
|
||||
// API
|
||||
import nodesApi from '@/api/nodes'
|
||||
import useApi from '@/hooks/useApi'
|
||||
|
||||
import './ExpandTextDialog.css'
|
||||
|
||||
const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
@@ -18,18 +28,30 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const theme = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const languageType = 'json'
|
||||
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [inputParam, setInputParam] = useState(null)
|
||||
const [languageType, setLanguageType] = useState('json')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [codeExecutedResult, setCodeExecutedResult] = useState('')
|
||||
|
||||
const executeCustomFunctionNodeApi = useApi(nodesApi.executeCustomFunctionNode)
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.value) setInputValue(dialogProps.value)
|
||||
if (dialogProps.inputParam) setInputParam(dialogProps.inputParam)
|
||||
if (dialogProps.inputParam) {
|
||||
setInputParam(dialogProps.inputParam)
|
||||
if (dialogProps.inputParam.type === 'code') {
|
||||
setLanguageType('js')
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setInputValue('')
|
||||
setLoading(false)
|
||||
setInputParam(null)
|
||||
setLanguageType('json')
|
||||
setCodeExecutedResult('')
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
@@ -39,11 +61,35 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(executeCustomFunctionNodeApi.loading)
|
||||
}, [executeCustomFunctionNodeApi.loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (executeCustomFunctionNodeApi.data) {
|
||||
if (typeof executeCustomFunctionNodeApi.data === 'object') {
|
||||
setCodeExecutedResult(JSON.stringify(executeCustomFunctionNodeApi.data, null, 2))
|
||||
} else {
|
||||
setCodeExecutedResult(executeCustomFunctionNodeApi.data)
|
||||
}
|
||||
}
|
||||
}, [executeCustomFunctionNodeApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (executeCustomFunctionNodeApi.error) {
|
||||
if (typeof executeCustomFunctionNodeApi.error === 'object' && executeCustomFunctionNodeApi.error?.response?.data) {
|
||||
setCodeExecutedResult(executeCustomFunctionNodeApi.error?.response?.data)
|
||||
} else if (typeof executeCustomFunctionNodeApi.error === 'string') {
|
||||
setCodeExecutedResult(executeCustomFunctionNodeApi.error)
|
||||
}
|
||||
}
|
||||
}, [executeCustomFunctionNodeApi.error])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog open={show} fullWidth maxWidth='md' aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description'>
|
||||
<DialogContent>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{inputParam && inputParam.type === 'string' && (
|
||||
{inputParam && (inputParam.type === 'string' || inputParam.type === 'code') && (
|
||||
<div style={{ flex: 70 }}>
|
||||
<Typography sx={{ mb: 2, ml: 1 }} variant='h4'>
|
||||
{inputParam.label}
|
||||
@@ -54,42 +100,66 @@ const ExpandTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
borderColor: theme.palette.grey['500'],
|
||||
borderRadius: '12px',
|
||||
height: '100%',
|
||||
maxHeight: 'calc(100vh - 220px)',
|
||||
maxHeight: languageType === 'js' ? 'calc(100vh - 250px)' : 'calc(100vh - 220px)',
|
||||
overflowX: 'hidden',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
{customization.isDarkMode ? (
|
||||
<DarkCodeEditor
|
||||
disabled={dialogProps.disabled}
|
||||
value={inputValue}
|
||||
onValueChange={(code) => setInputValue(code)}
|
||||
placeholder={inputParam.placeholder}
|
||||
type={languageType}
|
||||
style={{
|
||||
fontSize: '0.875rem',
|
||||
minHeight: 'calc(100vh - 220px)',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LightCodeEditor
|
||||
disabled={dialogProps.disabled}
|
||||
value={inputValue}
|
||||
onValueChange={(code) => setInputValue(code)}
|
||||
placeholder={inputParam.placeholder}
|
||||
type={languageType}
|
||||
style={{
|
||||
fontSize: '0.875rem',
|
||||
minHeight: 'calc(100vh - 220px)',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CodeEditor
|
||||
disabled={dialogProps.disabled}
|
||||
value={inputValue}
|
||||
height={languageType === 'js' ? 'calc(100vh - 250px)' : 'calc(100vh - 220px)'}
|
||||
theme={customization.isDarkMode ? 'dark' : 'light'}
|
||||
lang={languageType}
|
||||
placeholder={inputParam.placeholder}
|
||||
basicSetup={
|
||||
languageType === 'json'
|
||||
? { lineNumbers: false, foldGutter: false, autocompletion: false, highlightActiveLine: false }
|
||||
: {}
|
||||
}
|
||||
onValueChange={(code) => setInputValue(code)}
|
||||
/>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{languageType === 'js' && (
|
||||
<LoadingButton
|
||||
sx={{
|
||||
mt: 2,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)`
|
||||
},
|
||||
'&:disabled': {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
backgroundImage: `linear-gradient(rgb(0 0 0/50%) 0 0)`
|
||||
}
|
||||
}}
|
||||
loading={loading}
|
||||
variant='contained'
|
||||
fullWidth
|
||||
color='secondary'
|
||||
onClick={() => {
|
||||
setLoading(true)
|
||||
executeCustomFunctionNodeApi.request({ javascriptFunction: inputValue })
|
||||
}}
|
||||
>
|
||||
Execute
|
||||
</LoadingButton>
|
||||
)}
|
||||
{codeExecutedResult && (
|
||||
<div style={{ marginTop: '15px' }}>
|
||||
<CodeEditor
|
||||
disabled={true}
|
||||
value={codeExecutedResult}
|
||||
height='max-content'
|
||||
theme={customization.isDarkMode ? 'dark' : 'light'}
|
||||
lang={'js'}
|
||||
basicSetup={{ lineNumbers: false, foldGutter: false, autocompletion: false, highlightActiveLine: false }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
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 { IconEraser, IconTrash, IconX } 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 useNotifier from 'utils/useNotifier'
|
||||
|
||||
import {
|
||||
HIDE_CANVAS_DIALOG,
|
||||
SHOW_CANVAS_DIALOG,
|
||||
enqueueSnackbar as enqueueSnackbarAction,
|
||||
closeSnackbar as closeSnackbarAction
|
||||
} from 'store/actions'
|
||||
|
||||
const ManageScrapedLinksDialog = ({ show, dialogProps, onCancel, onSave }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
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)
|
||||
try {
|
||||
const fetchLinksResp = await scraperApi.fetchLinks(url, dialogProps.relativeLinksMethod, dialogProps.limit)
|
||||
if (fetchLinksResp.data) {
|
||||
setSelectedLinks(fetchLinksResp.data.links)
|
||||
enqueueSnackbar({
|
||||
message: 'Successfully fetched links',
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
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 handleRemoveAllLinks = () => {
|
||||
setSelectedLinks([])
|
||||
}
|
||||
|
||||
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
|
||||
disabled={!url}
|
||||
sx={{ borderRadius: '12px', mt: 1, display: 'flex', flexShrink: 0 }}
|
||||
size='small'
|
||||
variant='contained'
|
||||
onClick={handleFetchLinks}
|
||||
>
|
||||
Fetch Links
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
|
||||
<Typography sx={{ fontWeight: 500 }}>Scraped Links</Typography>
|
||||
{selectedLinks.length > 0 ? (
|
||||
<StyledButton
|
||||
sx={{ height: 'max-content', width: 'max-content' }}
|
||||
variant='outlined'
|
||||
color='error'
|
||||
title='Clear All Links'
|
||||
onClick={handleRemoveAllLinks}
|
||||
startIcon={<IconEraser />}
|
||||
>
|
||||
Clear All
|
||||
</StyledButton>
|
||||
) : null}
|
||||
</Box>
|
||||
<>
|
||||
{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
|
||||
@@ -132,6 +132,35 @@ const NodeInfoDialog = ({ show, dialogProps, onCancel }) => {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{dialogProps.data.tags &&
|
||||
dialogProps.data.tags.length &&
|
||||
dialogProps.data.tags.map((tag, index) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: 'max-content',
|
||||
borderRadius: 15,
|
||||
background: '#cae9ff',
|
||||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
marginTop: 5,
|
||||
marginLeft: 10,
|
||||
marginBottom: 5
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: '#023e7d',
|
||||
fontSize: '0.825rem'
|
||||
}}
|
||||
>
|
||||
{tag.toLowerCase()}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,600 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
// MUI
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Chip,
|
||||
Grid,
|
||||
InputLabel,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
Stack,
|
||||
IconButton,
|
||||
FormControl,
|
||||
Checkbox,
|
||||
MenuItem
|
||||
} from '@mui/material'
|
||||
import MuiAccordion from '@mui/material/Accordion'
|
||||
import MuiAccordionSummary from '@mui/material/AccordionSummary'
|
||||
import MuiAccordionDetails from '@mui/material/AccordionDetails'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'
|
||||
import ClearIcon from '@mui/icons-material/Clear'
|
||||
import { styled } from '@mui/material/styles'
|
||||
|
||||
//Project Import
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||
import promptEmptySVG from 'assets/images/prompt_empty.svg'
|
||||
|
||||
import useApi from 'hooks/useApi'
|
||||
import promptApi from 'api/prompt'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
|
||||
const NewLineToBr = ({ children = '' }) => {
|
||||
return children.split('\n').reduce(function (arr, line) {
|
||||
return arr.concat(line, <br />)
|
||||
}, [])
|
||||
}
|
||||
|
||||
const Accordion = styled((props) => <MuiAccordion disableGutters elevation={0} square {...props} />)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none'
|
||||
}
|
||||
}))
|
||||
|
||||
const AccordionSummary = styled((props) => (
|
||||
<MuiAccordionSummary expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />} {...props} />
|
||||
))(({ theme }) => ({
|
||||
backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, .05)' : 'rgba(0, 0, 0, .03)',
|
||||
flexDirection: 'row-reverse',
|
||||
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
|
||||
transform: 'rotate(180deg)'
|
||||
},
|
||||
'& .MuiAccordionSummary-content': {
|
||||
marginLeft: theme.spacing(1)
|
||||
}
|
||||
}))
|
||||
|
||||
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
borderTop: '1px solid rgba(0, 0, 0, .125)'
|
||||
}))
|
||||
|
||||
const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
const getAvailablePromptsApi = useApi(promptApi.getAvailablePrompts)
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
} else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [show, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (promptType && show) {
|
||||
setLoading(true)
|
||||
getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [promptType, show])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) {
|
||||
setAvailablePrompNameList(getAvailablePromptsApi.data.repos)
|
||||
if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getAvailablePromptsApi.data])
|
||||
|
||||
const ITEM_HEIGHT = 48
|
||||
const ITEM_PADDING_TOP = 8
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||
width: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const models = [
|
||||
{ id: 101, name: 'anthropic:claude-instant-1' },
|
||||
{ id: 102, name: 'anthropic:claude-instant-1.2' },
|
||||
{ id: 103, name: 'anthropic:claude-2' },
|
||||
{ id: 104, name: 'google:palm-2-chat-bison' },
|
||||
{ id: 105, name: 'google:palm-2-codechat-bison' },
|
||||
{ id: 106, name: 'google:palm-2-text-bison' },
|
||||
{ id: 107, name: 'meta:llama-2-13b-chat' },
|
||||
{ id: 108, name: 'meta:llama-2-70b-chat' },
|
||||
{ id: 109, name: 'openai:gpt-3.5-turbo' },
|
||||
{ id: 110, name: 'openai:gpt-4' },
|
||||
{ id: 111, name: 'openai:text-davinci-003' }
|
||||
]
|
||||
const [modelName, setModelName] = useState([])
|
||||
|
||||
const usecases = [
|
||||
{ id: 201, name: 'Agents' },
|
||||
{ id: 202, name: 'Agent Stimulation' },
|
||||
{ id: 203, name: 'Autonomous agents' },
|
||||
{ id: 204, name: 'Classification' },
|
||||
{ id: 205, name: 'Chatbots' },
|
||||
{ id: 206, name: 'Code understanding' },
|
||||
{ id: 207, name: 'Code writing' },
|
||||
{ id: 208, name: 'Evaluation' },
|
||||
{ id: 209, name: 'Extraction' },
|
||||
{ id: 210, name: 'Interacting with APIs' },
|
||||
{ id: 211, name: 'Multi-modal' },
|
||||
{ id: 212, name: 'QA over documents' },
|
||||
{ id: 213, name: 'Self-checking' },
|
||||
{ id: 214, name: 'SQL' },
|
||||
{ id: 215, name: 'Summarization' },
|
||||
{ id: 216, name: 'Tagging' }
|
||||
]
|
||||
const [usecase, setUsecase] = useState([])
|
||||
|
||||
const languages = [
|
||||
{ id: 301, name: 'Chinese' },
|
||||
{ id: 302, name: 'English' },
|
||||
{ id: 303, name: 'French' },
|
||||
{ id: 304, name: 'German' },
|
||||
{ id: 305, name: 'Russian' },
|
||||
{ id: 306, name: 'Spanish' }
|
||||
]
|
||||
const [language, setLanguage] = useState([])
|
||||
const [availablePrompNameList, setAvailablePrompNameList] = useState([])
|
||||
const [selectedPrompt, setSelectedPrompt] = useState({})
|
||||
|
||||
const [accordionExpanded, setAccordionExpanded] = useState(['prompt'])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleAccordionChange = (accordionName) => (event, isExpanded) => {
|
||||
const accordians = [...accordionExpanded]
|
||||
if (!isExpanded) setAccordionExpanded(accordians.filter((accr) => accr !== accordionName))
|
||||
else {
|
||||
accordians.push(accordionName)
|
||||
setAccordionExpanded(accordians)
|
||||
}
|
||||
}
|
||||
|
||||
const handleListItemClick = async (index, overridePromptNameList = []) => {
|
||||
const prompt = overridePromptNameList.length ? overridePromptNameList[index] : availablePrompNameList[index]
|
||||
|
||||
if (!prompt.detailed) {
|
||||
const createResp = await promptApi.getPrompt({
|
||||
promptName: prompt.full_name
|
||||
})
|
||||
if (createResp.data) {
|
||||
prompt.detailed = createResp.data.templates
|
||||
}
|
||||
}
|
||||
setSelectedPrompt(prompt)
|
||||
}
|
||||
|
||||
const fetchPrompts = async () => {
|
||||
let tags = promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&'
|
||||
modelName.forEach((item) => {
|
||||
tags += `tags=${item.name}&`
|
||||
})
|
||||
usecase.forEach((item) => {
|
||||
tags += `tags=${item.name}&`
|
||||
})
|
||||
language.forEach((item) => {
|
||||
tags += `tags=${item.name}&`
|
||||
})
|
||||
setLoading(true)
|
||||
getAvailablePromptsApi.request({ tags: tags })
|
||||
}
|
||||
|
||||
const removeDuplicates = (value) => {
|
||||
let duplicateRemoved = []
|
||||
|
||||
value.forEach((item) => {
|
||||
if (value.filter((o) => o.id === item.id).length === 1) {
|
||||
duplicateRemoved.push(item)
|
||||
}
|
||||
})
|
||||
return duplicateRemoved
|
||||
}
|
||||
|
||||
const handleModelChange = (event) => {
|
||||
const {
|
||||
target: { value }
|
||||
} = event
|
||||
|
||||
setModelName(removeDuplicates(value))
|
||||
}
|
||||
|
||||
const handleUsecaseChange = (event) => {
|
||||
const {
|
||||
target: { value }
|
||||
} = event
|
||||
|
||||
setUsecase(removeDuplicates(value))
|
||||
}
|
||||
const handleLanguageChange = (event) => {
|
||||
const {
|
||||
target: { value }
|
||||
} = event
|
||||
|
||||
setLanguage(removeDuplicates(value))
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth={'lg'}
|
||||
aria-labelledby='prompt-dialog-title'
|
||||
aria-describedby='prompt-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='prompt-dialog-title'>
|
||||
Langchain Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'})
|
||||
</DialogTitle>
|
||||
<DialogContent dividers sx={{ p: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', p: 2, pt: 1, alignItems: 'center' }}>
|
||||
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||
<InputLabel size='small' id='model-checkbox-label'>
|
||||
Model
|
||||
</InputLabel>
|
||||
<Select
|
||||
id='model-checkbox'
|
||||
labelId='model-checkbox-label'
|
||||
multiple
|
||||
size='small'
|
||||
value={modelName}
|
||||
onChange={handleModelChange}
|
||||
input={<OutlinedInput label='Model' />}
|
||||
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||
endAdornment={
|
||||
modelName.length ? (
|
||||
<IconButton sx={{ mr: 2 }} onClick={() => setModelName([])}>
|
||||
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
false
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
'.MuiSvgIcon-root ': {
|
||||
fill: customization.isDarkMode ? 'white !important' : ''
|
||||
}
|
||||
}}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{models.map((variant) => (
|
||||
<MenuItem key={variant.id} value={variant}>
|
||||
<Checkbox id={variant.id} checked={modelName.findIndex((item) => item.id === variant.id) >= 0} />
|
||||
<ListItemText primary={variant.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||
<InputLabel size='small' id='usecase-checkbox-label'>
|
||||
Usecase
|
||||
</InputLabel>
|
||||
<Select
|
||||
autoWidth={false}
|
||||
labelId='usecase-checkbox-label'
|
||||
id='usecase-checkbox'
|
||||
multiple
|
||||
size='small'
|
||||
value={usecase}
|
||||
onChange={handleUsecaseChange}
|
||||
input={<OutlinedInput label='Usecase' />}
|
||||
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||
endAdornment={
|
||||
usecase.length ? (
|
||||
<IconButton sx={{ mr: 2 }} onClick={() => setUsecase([])}>
|
||||
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
false
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
'.MuiSvgIcon-root ': {
|
||||
fill: customization.isDarkMode ? 'white !important' : ''
|
||||
}
|
||||
}}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{usecases.map((variant) => (
|
||||
<MenuItem key={variant.id} value={variant}>
|
||||
<Checkbox id={variant.id} checked={usecase.findIndex((item) => item.id === variant.id) >= 0} />
|
||||
<ListItemText primary={variant.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||
<InputLabel size='small' id='language-checkbox-label'>
|
||||
Language
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId='language-checkbox-label'
|
||||
id='language-checkbox'
|
||||
multiple
|
||||
size='small'
|
||||
value={language}
|
||||
onChange={handleLanguageChange}
|
||||
input={<OutlinedInput label='language' />}
|
||||
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||
endAdornment={
|
||||
language.length ? (
|
||||
<IconButton sx={{ mr: 2 }} onClick={() => setLanguage([])}>
|
||||
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
false
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
'.MuiSvgIcon-root ': {
|
||||
fill: customization.isDarkMode ? 'white !important' : ''
|
||||
}
|
||||
}}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{languages.map((variant) => (
|
||||
<MenuItem key={variant.id} value={variant}>
|
||||
<Checkbox id={variant.id} checked={language.findIndex((item) => item.id === variant.id) >= 0} />
|
||||
<ListItemText primary={variant.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl sx={{ width: '10%' }}>
|
||||
<Button disableElevation variant='outlined' onClick={fetchPrompts}>
|
||||
Search
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{loading && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%', pb: 3 }} flexDirection='column'>
|
||||
<Box sx={{ p: 5, height: 'auto' }}>
|
||||
<img style={{ objectFit: 'cover', height: '20vh', width: 'auto' }} src={promptEmptySVG} alt='promptEmptySVG' />
|
||||
</Box>
|
||||
<div>Please wait....loading Prompts</div>
|
||||
</Stack>
|
||||
)}
|
||||
{!loading && availablePrompNameList && availablePrompNameList.length === 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%', pb: 3 }} flexDirection='column'>
|
||||
<Box sx={{ p: 5, height: 'auto' }}>
|
||||
<img style={{ objectFit: 'cover', height: '20vh', width: 'auto' }} src={promptEmptySVG} alt='promptEmptySVG' />
|
||||
</Box>
|
||||
<div>No Available Prompts</div>
|
||||
</Stack>
|
||||
)}
|
||||
{!loading && availablePrompNameList && availablePrompNameList.length > 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||
<Box sx={{ width: '100%', p: 2 }}>
|
||||
<Grid xs={12} container spacing={1} justifyContent='center' alignItems='center'>
|
||||
<Grid xs={4} item sx={{ textAlign: 'left' }}>
|
||||
<Box sx={{ width: '100%', maxWidth: 360 }}>
|
||||
<Card variant='outlined' sx={{ height: 470, overflow: 'auto', borderRadius: 0 }}>
|
||||
<CardContent sx={{ p: 1 }}>
|
||||
<Typography sx={{ fontSize: 10 }} color='text.secondary' gutterBottom>
|
||||
Available Prompts
|
||||
</Typography>
|
||||
<List component='nav' aria-label='secondary mailbox folder'>
|
||||
{availablePrompNameList.map((item, index) => (
|
||||
<ListItemButton
|
||||
key={item.id}
|
||||
selected={item.id === selectedPrompt?.id}
|
||||
onClick={() => handleListItemClick(index)}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography sx={{ fontSize: 16, p: 1, fontWeight: 500 }}>
|
||||
{item.full_name}
|
||||
</Typography>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginTop: 5
|
||||
}}
|
||||
>
|
||||
{item.tags.map((tag, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={tag}
|
||||
style={{ marginRight: 5, marginBottom: 5 }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid xs={8} item sx={{ textAlign: 'left' }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Card sx={{ height: 470, overflow: 'auto' }}>
|
||||
<CardContent sx={{ p: 0.5 }}>
|
||||
<Accordion
|
||||
expanded={accordionExpanded.includes('prompt')}
|
||||
onChange={handleAccordionChange('prompt')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='panel2d-content'
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
id='panel2d-header'
|
||||
>
|
||||
<Typography>Prompt</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography sx={{ wordWrap: 'true' }} color='text.primary'>
|
||||
{selectedPrompt?.detailed?.map((item) => (
|
||||
<>
|
||||
<Typography sx={{ fontSize: 12 }} color='text.secondary' gutterBottom>
|
||||
{item.typeDisplay.toUpperCase()}
|
||||
</Typography>
|
||||
<Typography>
|
||||
<p
|
||||
style={{
|
||||
whiteSpace: 'pre-wrap -moz-pre-wrap -pre-wrap -o-pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
fontFamily: 'inherit',
|
||||
wordSpacing: '0.1rem',
|
||||
lineHeight: '1.5rem'
|
||||
}}
|
||||
>
|
||||
<NewLineToBr>{item.template}</NewLineToBr>
|
||||
</p>
|
||||
</Typography>
|
||||
</>
|
||||
))}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion
|
||||
expanded={accordionExpanded.includes('description')}
|
||||
onChange={handleAccordionChange('description')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='panel1d-content'
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
id='panel1d-header'
|
||||
>
|
||||
<Typography>Description</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography
|
||||
sx={{ wordWrap: 'true', wordSpacing: '0.1rem', lineHeight: '1.5rem' }}
|
||||
color='text.primary'
|
||||
>
|
||||
{selectedPrompt?.description}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion
|
||||
expanded={accordionExpanded.includes('readme')}
|
||||
onChange={handleAccordionChange('readme')}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls='panel3d-content'
|
||||
id='panel3d-header'
|
||||
>
|
||||
<Typography>Readme</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div
|
||||
style={{
|
||||
lineHeight: 1.75,
|
||||
'& a': {
|
||||
display: 'block',
|
||||
marginRight: '2.5rem',
|
||||
wordWrap: 'break-word',
|
||||
color: '#16bed7',
|
||||
fontWeight: 500
|
||||
},
|
||||
'& a:hover': { opacity: 0.8 },
|
||||
'& code': {
|
||||
color: '#0ab126',
|
||||
fontWeight: 500,
|
||||
whiteSpace: 'pre-wrap !important'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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()}
|
||||
isDialog={true}
|
||||
language={(match && match[1]) || ''}
|
||||
value={String(children).replace(/\n$/, '')}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{selectedPrompt?.readme}
|
||||
</MemoizedReactMarkdown>
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
{availablePrompNameList && availablePrompNameList.length > 0 && (
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton
|
||||
disabled={!selectedPrompt?.detailed}
|
||||
onClick={() => onSubmit(selectedPrompt.detailed)}
|
||||
variant='contained'
|
||||
>
|
||||
Load
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
)}
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
PromptLangsmithHubDialog.propTypes = {
|
||||
promptType: PropTypes.string,
|
||||
show: PropTypes.bool,
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
}
|
||||
|
||||
export default PromptLangsmithHubDialog
|
||||
@@ -0,0 +1,348 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
FormControl,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select
|
||||
} from '@mui/material'
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
import { SwitchInput } from 'ui-component/switch/Switch'
|
||||
import { Input } from 'ui-component/input/Input'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import openAISVG from 'assets/images/openai.svg'
|
||||
import assemblyAIPng from 'assets/images/assemblyai.png'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
|
||||
const speechToTextProviders = {
|
||||
openAIWhisper: {
|
||||
label: 'OpenAI Whisper',
|
||||
name: 'openAIWhisper',
|
||||
icon: openAISVG,
|
||||
url: 'https://platform.openai.com/docs/guides/speech-to-text',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
},
|
||||
{
|
||||
label: 'Language',
|
||||
name: 'language',
|
||||
type: 'string',
|
||||
description:
|
||||
'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.',
|
||||
placeholder: 'en',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Prompt',
|
||||
name: 'prompt',
|
||||
type: 'string',
|
||||
rows: 4,
|
||||
description: `An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.`,
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
step: 0.1,
|
||||
description: `The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.`,
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
},
|
||||
assemblyAiTranscribe: {
|
||||
label: 'Assembly AI',
|
||||
name: 'assemblyAiTranscribe',
|
||||
icon: assemblyAIPng,
|
||||
url: 'https://www.assemblyai.com/',
|
||||
inputs: [
|
||||
{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['assemblyAIApi']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const SpeechToTextDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [speechToText, setSpeechToText] = useState({})
|
||||
const [selectedProvider, setSelectedProvider] = useState('none')
|
||||
|
||||
const onSave = async () => {
|
||||
const speechToText = setValue(true, selectedProvider, 'status')
|
||||
try {
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
speechToText: JSON.stringify(speechToText)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Speech To Text Configuration Saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||
}
|
||||
onCancel()
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Speech To Text Configuration: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const setValue = (value, providerName, inputParamName) => {
|
||||
let newVal = {}
|
||||
if (!Object.prototype.hasOwnProperty.call(speechToText, providerName)) {
|
||||
newVal = { ...speechToText, [providerName]: {} }
|
||||
} else {
|
||||
newVal = { ...speechToText }
|
||||
}
|
||||
|
||||
newVal[providerName][inputParamName] = value
|
||||
if (inputParamName === 'status' && value === true) {
|
||||
// ensure that the others are turned off
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const provider = speechToTextProviders[key]
|
||||
if (provider.name !== providerName) {
|
||||
newVal[provider.name] = { ...speechToText[provider.name], status: false }
|
||||
}
|
||||
})
|
||||
}
|
||||
setSpeechToText(newVal)
|
||||
return newVal
|
||||
}
|
||||
|
||||
const handleProviderChange = (event) => {
|
||||
setSelectedProvider(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.chatflow && dialogProps.chatflow.speechToText) {
|
||||
try {
|
||||
const speechToText = JSON.parse(dialogProps.chatflow.speechToText)
|
||||
let selectedProvider = 'none'
|
||||
Object.keys(speechToTextProviders).forEach((key) => {
|
||||
const providerConfig = speechToText[key]
|
||||
if (providerConfig && providerConfig.status) {
|
||||
selectedProvider = key
|
||||
}
|
||||
})
|
||||
setSelectedProvider(selectedProvider)
|
||||
setSpeechToText(speechToText)
|
||||
} catch (e) {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setSpeechToText({})
|
||||
setSelectedProvider('none')
|
||||
}
|
||||
}, [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 = (
|
||||
<Dialog
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
Speech To Text Configuration
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box fullWidth sx={{ my: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography>Speech To Text Providers</Typography>
|
||||
<FormControl fullWidth>
|
||||
<Select value={selectedProvider} onChange={handleProviderChange}>
|
||||
<MenuItem value='none'>None</MenuItem>
|
||||
<MenuItem value='openAIWhisper'>OpenAI Whisper</MenuItem>
|
||||
<MenuItem value='assemblyAiTranscribe'>Assembly AI</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{selectedProvider !== 'none' && (
|
||||
<>
|
||||
<ListItem style={{ padding: 0, margin: 0 }} alignItems='center'>
|
||||
<ListItemAvatar>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 10,
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt='AI'
|
||||
src={speechToTextProviders[selectedProvider].icon}
|
||||
/>
|
||||
</div>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={speechToTextProviders[selectedProvider].label}
|
||||
secondary={
|
||||
<a target='_blank' rel='noreferrer' href={speechToTextProviders[selectedProvider].url}>
|
||||
{speechToTextProviders[selectedProvider].url}
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
{speechToTextProviders[selectedProvider].inputs.map((inputParam, index) => (
|
||||
<Box key={index} sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
{inputParam.label}
|
||||
{!inputParam.optional && <span style={{ color: 'red' }}> *</span>}
|
||||
{inputParam.description && (
|
||||
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
{inputParam.type === 'credential' && (
|
||||
<CredentialInputHandler
|
||||
key={speechToText[selectedProvider]?.credentialId}
|
||||
data={
|
||||
speechToText[selectedProvider]?.credentialId
|
||||
? { credential: speechToText[selectedProvider].credentialId }
|
||||
: {}
|
||||
}
|
||||
inputParam={inputParam}
|
||||
onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')}
|
||||
/>
|
||||
)}
|
||||
{inputParam.type === 'boolean' && (
|
||||
<SwitchInput
|
||||
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
|
||||
value={
|
||||
speechToText[selectedProvider]
|
||||
? speechToText[selectedProvider][inputParam.name]
|
||||
: inputParam.default ?? false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
|
||||
<Input
|
||||
inputParam={inputParam}
|
||||
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
|
||||
value={
|
||||
speechToText[selectedProvider]
|
||||
? speechToText[selectedProvider][inputParam.name]
|
||||
: inputParam.default ?? ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{inputParam.type === 'options' && (
|
||||
<Dropdown
|
||||
name={inputParam.name}
|
||||
options={inputParam.options}
|
||||
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
|
||||
value={
|
||||
speechToText[selectedProvider]
|
||||
? speechToText[selectedProvider][inputParam.name]
|
||||
: inputParam.default ?? 'choose an option'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton
|
||||
disabled={selectedProvider !== 'none' && !speechToText[selectedProvider]?.credentialId}
|
||||
variant='contained'
|
||||
onClick={onSave}
|
||||
>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
SpeechToTextDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func
|
||||
}
|
||||
|
||||
export default SpeechToTextDialog
|
||||
@@ -0,0 +1,252 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
OutlinedInput,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
Box,
|
||||
List,
|
||||
InputAdornment
|
||||
} from '@mui/material'
|
||||
import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons'
|
||||
|
||||
// Project import
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
|
||||
// store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// API
|
||||
import chatflowsApi from 'api/chatflows'
|
||||
|
||||
const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [inputFields, setInputFields] = useState([
|
||||
{
|
||||
prompt: ''
|
||||
}
|
||||
])
|
||||
|
||||
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||
|
||||
const addInputField = () => {
|
||||
setInputFields([
|
||||
...inputFields,
|
||||
{
|
||||
prompt: ''
|
||||
}
|
||||
])
|
||||
}
|
||||
const removeInputFields = (index) => {
|
||||
const rows = [...inputFields]
|
||||
rows.splice(index, 1)
|
||||
setInputFields(rows)
|
||||
}
|
||||
|
||||
const handleChange = (index, evnt) => {
|
||||
const { name, value } = evnt.target
|
||||
const list = [...inputFields]
|
||||
list[index][name] = value
|
||||
setInputFields(list)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
try {
|
||||
let value = {
|
||||
starterPrompts: {
|
||||
...inputFields
|
||||
}
|
||||
}
|
||||
chatbotConfig.starterPrompts = value.starterPrompts
|
||||
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||
})
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Conversation Starter Prompts Saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||
}
|
||||
onConfirm()
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Conversation Starter Prompts: ${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 (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) {
|
||||
try {
|
||||
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||
setChatbotConfig(chatbotConfig || {})
|
||||
if (chatbotConfig.starterPrompts) {
|
||||
let inputFields = []
|
||||
Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => {
|
||||
if (chatbotConfig.starterPrompts[key]) {
|
||||
inputFields.push(chatbotConfig.starterPrompts[key])
|
||||
}
|
||||
})
|
||||
setInputFields(inputFields)
|
||||
} else {
|
||||
setInputFields([
|
||||
{
|
||||
prompt: ''
|
||||
}
|
||||
])
|
||||
}
|
||||
} catch (e) {
|
||||
setInputFields([
|
||||
{
|
||||
prompt: ''
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
return () => {}
|
||||
}, [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
|
||||
onClose={onCancel}
|
||||
open={show}
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title || 'Conversation Starter Prompts'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: 10,
|
||||
background: '#d8f3dc',
|
||||
padding: 10
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<IconBulb size={30} color='#2d6a4f' />
|
||||
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
||||
Starter prompts will only be shown when there is no messages on the chat
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
||||
<List>
|
||||
{inputFields.map((data, index) => {
|
||||
return (
|
||||
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||
<Box sx={{ width: '95%', mb: 1 }}>
|
||||
<OutlinedInput
|
||||
sx={{ width: '100%' }}
|
||||
key={index}
|
||||
type='text'
|
||||
onChange={(e) => handleChange(index, e)}
|
||||
size='small'
|
||||
value={data.prompt}
|
||||
name='prompt'
|
||||
endAdornment={
|
||||
<InputAdornment position='end' sx={{ padding: '2px' }}>
|
||||
{inputFields.length > 1 && (
|
||||
<IconButton
|
||||
sx={{ height: 30, width: 30 }}
|
||||
size='small'
|
||||
color='error'
|
||||
disabled={inputFields.length === 1}
|
||||
onClick={() => removeInputFields(index)}
|
||||
edge='end'
|
||||
>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ width: '5%', mb: 1 }}>
|
||||
{index === inputFields.length - 1 && (
|
||||
<IconButton color='primary' onClick={addInputField}>
|
||||
<IconPlus />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<StyledButton variant='contained' onClick={onSave}>
|
||||
Save
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
StarterPromptsDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default StarterPromptsDialog
|
||||
@@ -21,7 +21,9 @@ import {
|
||||
DialogTitle,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Chip
|
||||
Chip,
|
||||
Card,
|
||||
CardMedia
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import DatePicker from 'react-datepicker'
|
||||
@@ -47,7 +49,7 @@ import useApi from '@/hooks/useApi'
|
||||
import useConfirm from '@/hooks/useConfirm'
|
||||
|
||||
// Utils
|
||||
import { isValidURL, removeDuplicateURL } from '@/utils/genericHelper'
|
||||
import { getOS, isValidURL, removeDuplicateURL } from '@/utils/genericHelper'
|
||||
import useNotifier from '@/utils/useNotifier'
|
||||
import { baseURL } from '@/store/constant'
|
||||
|
||||
@@ -69,6 +71,12 @@ DatePickerCustomInput.propTypes = {
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
const messageImageStyle = {
|
||||
width: '128px',
|
||||
height: '128px',
|
||||
objectFit: 'cover'
|
||||
}
|
||||
|
||||
const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
const dispatch = useDispatch()
|
||||
@@ -92,6 +100,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
|
||||
const getChatmessageApi = useApi(chatmessageApi.getAllChatmessageFromChatflow)
|
||||
const getChatmessageFromPKApi = useApi(chatmessageApi.getChatmessageFromPK)
|
||||
const getStoragePathFromServer = useApi(chatmessageApi.getStoragePath)
|
||||
let storagePath = ''
|
||||
|
||||
const onStartDateSelected = (date) => {
|
||||
setStartDate(date)
|
||||
@@ -120,16 +130,35 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const exportMessages = () => {
|
||||
const exportMessages = async () => {
|
||||
if (!storagePath && getStoragePathFromServer.data) {
|
||||
storagePath = getStoragePathFromServer.data.storagePath
|
||||
}
|
||||
const obj = {}
|
||||
let fileSeparator = '/'
|
||||
if ('windows' === getOS()) {
|
||||
fileSeparator = '\\'
|
||||
}
|
||||
for (let i = 0; i < allChatlogs.length; i += 1) {
|
||||
const chatmsg = allChatlogs[i]
|
||||
const chatPK = getChatPK(chatmsg)
|
||||
let filePaths = []
|
||||
if (chatmsg.fileUploads) {
|
||||
chatmsg.fileUploads = JSON.parse(chatmsg.fileUploads)
|
||||
chatmsg.fileUploads.forEach((file) => {
|
||||
if (file.type === 'stored-file') {
|
||||
filePaths.push(
|
||||
`${storagePath}${fileSeparator}${chatmsg.chatflowid}${fileSeparator}${chatmsg.chatId}${fileSeparator}${file.name}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
const msg = {
|
||||
content: chatmsg.content,
|
||||
role: chatmsg.role === 'apiMessage' ? 'bot' : 'user',
|
||||
time: chatmsg.createdDate
|
||||
}
|
||||
if (filePaths.length) msg.filePaths = filePaths
|
||||
if (chatmsg.sourceDocuments) msg.sourceDocuments = JSON.parse(chatmsg.sourceDocuments)
|
||||
if (chatmsg.usedTools) msg.usedTools = JSON.parse(chatmsg.usedTools)
|
||||
if (chatmsg.fileAnnotations) msg.fileAnnotations = JSON.parse(chatmsg.fileAnnotations)
|
||||
@@ -249,6 +278,14 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
})
|
||||
}
|
||||
}
|
||||
if (chatmsg.fileUploads) {
|
||||
chatmsg.fileUploads = JSON.parse(chatmsg.fileUploads)
|
||||
chatmsg.fileUploads.forEach((file) => {
|
||||
if (file.type === 'stored-file') {
|
||||
file.data = `${baseURL}/api/v1/get-upload-file?chatflowId=${chatmsg.chatflowid}&chatId=${chatmsg.chatId}&fileName=${file.name}`
|
||||
}
|
||||
})
|
||||
}
|
||||
const obj = {
|
||||
...chatmsg,
|
||||
message: chatmsg.content,
|
||||
@@ -357,6 +394,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (getChatmessageApi.data) {
|
||||
getStoragePathFromServer.request()
|
||||
|
||||
setAllChatLogs(getChatmessageApi.data)
|
||||
const chatPK = processChatLogs(getChatmessageApi.data)
|
||||
setSelectedMessageIndex(0)
|
||||
@@ -593,8 +632,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
sx={{
|
||||
background:
|
||||
message.type === 'apiMessage' ? theme.palette.asyncSelect.main : '',
|
||||
pl: 1,
|
||||
pr: 1
|
||||
py: '1rem',
|
||||
px: '1.5rem'
|
||||
}}
|
||||
key={index}
|
||||
style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }}
|
||||
@@ -644,6 +683,51 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{message.fileUploads && message.fileUploads.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
{message.fileUploads.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
{item.mime.startsWith('image/') ? (
|
||||
<Card
|
||||
key={index}
|
||||
sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
maxWidth: 128,
|
||||
marginRight: '10px',
|
||||
flex: '0 0 auto'
|
||||
}}
|
||||
>
|
||||
<CardMedia
|
||||
component='img'
|
||||
image={item.data}
|
||||
sx={{ height: 64 }}
|
||||
alt={'preview'}
|
||||
style={messageImageStyle}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
<audio controls='controls'>
|
||||
Your browser does not support the <audio>
|
||||
tag.
|
||||
<source src={item.data} type={item.mime} />
|
||||
</audio>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className='markdownanswer'>
|
||||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { json } from '@codemirror/lang-json'
|
||||
import { vscodeDark } from '@uiw/codemirror-theme-vscode'
|
||||
import { sublime } from '@uiw/codemirror-theme-sublime'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
export const CodeEditor = ({ value, height, theme, lang, placeholder, disabled = false, basicSetup = {}, onValueChange }) => {
|
||||
const customStyle = EditorView.baseTheme({
|
||||
'&': {
|
||||
color: '#191b1f',
|
||||
padding: '10px'
|
||||
},
|
||||
'.cm-placeholder': {
|
||||
color: 'rgba(120, 120, 120, 0.5)'
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
height={height ?? 'calc(100vh - 220px)'}
|
||||
theme={theme === 'dark' ? (lang === 'js' ? vscodeDark : sublime) : 'none'}
|
||||
extensions={
|
||||
lang === 'js'
|
||||
? [javascript({ jsx: true }), EditorView.lineWrapping, customStyle]
|
||||
: [json(), EditorView.lineWrapping, customStyle]
|
||||
}
|
||||
onChange={onValueChange}
|
||||
readOnly={disabled}
|
||||
editable={!disabled}
|
||||
basicSetup={basicSetup}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
CodeEditor.propTypes = {
|
||||
value: PropTypes.string,
|
||||
height: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
basicSetup: PropTypes.object,
|
||||
onValueChange: PropTypes.func
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import Editor from 'react-simple-code-editor'
|
||||
import { highlight, languages } from 'prismjs/components/prism-core'
|
||||
import 'prismjs/components/prism-clike'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-json'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import './prism-dark.css'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Editor
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
highlight={(code) => highlight(code, type === 'json' ? languages.json : languages.js)}
|
||||
padding={10}
|
||||
onValueChange={onValueChange}
|
||||
onMouseUp={onMouseUp}
|
||||
onBlur={onBlur}
|
||||
tabSize={4}
|
||||
style={{
|
||||
...style,
|
||||
background: theme.palette.codeEditor.main
|
||||
}}
|
||||
textareaClassName='editor__textarea'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
DarkCodeEditor.propTypes = {
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
onValueChange: PropTypes.func,
|
||||
onMouseUp: PropTypes.func,
|
||||
onBlur: PropTypes.func
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import Editor from 'react-simple-code-editor'
|
||||
import { highlight, languages } from 'prismjs/components/prism-core'
|
||||
import 'prismjs/components/prism-clike'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-json'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import './prism-light.css'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
export const LightCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Editor
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
highlight={(code) => highlight(code, type === 'json' ? languages.json : languages.js)}
|
||||
padding={10}
|
||||
onValueChange={onValueChange}
|
||||
onMouseUp={onMouseUp}
|
||||
onBlur={onBlur}
|
||||
tabSize={4}
|
||||
style={{
|
||||
...style,
|
||||
background: theme.palette.card.main
|
||||
}}
|
||||
textareaClassName='editor__textarea'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
LightCodeEditor.propTypes = {
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
onValueChange: PropTypes.func,
|
||||
onMouseUp: PropTypes.func,
|
||||
onBlur: PropTypes.func
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
pre[class*='language-'],
|
||||
code[class*='language-'] {
|
||||
color: #d4d4d4;
|
||||
font-size: 13px;
|
||||
text-shadow: none;
|
||||
font-family: Menlo, Monaco, Consolas, 'Andale Mono', 'Ubuntu Mono', 'Courier New', monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*='language-']::selection,
|
||||
code[class*='language-']::selection,
|
||||
pre[class*='language-'] *::selection,
|
||||
code[class*='language-'] *::selection {
|
||||
text-shadow: none;
|
||||
background: #264f78;
|
||||
}
|
||||
|
||||
@media print {
|
||||
pre[class*='language-'],
|
||||
code[class*='language-'] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
pre[class*='language-'] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*='language-'] {
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 0.3em;
|
||||
color: #db4c69;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
/*********************************************************
|
||||
* Tokens
|
||||
*/
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.doctype .token.doctype-tag {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.doctype .token.name {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog {
|
||||
color: #6a9955;
|
||||
}
|
||||
|
||||
.token.punctuation,
|
||||
.language-html .language-css .token.punctuation,
|
||||
.language-html .language-javascript .token.punctuation {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.inserted,
|
||||
.token.unit {
|
||||
color: #b5cea8;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.deleted {
|
||||
color: #ce9178;
|
||||
}
|
||||
|
||||
.language-css .token.string.url {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.token.operator.arrow {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: #ce9178;
|
||||
}
|
||||
|
||||
.token.atrule .token.rule {
|
||||
color: #c586c0;
|
||||
}
|
||||
|
||||
.token.atrule .token.url {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.atrule .token.url .token.function {
|
||||
color: #dcdcaa;
|
||||
}
|
||||
|
||||
.token.atrule .token.url .token.punctuation {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.keyword.module,
|
||||
.token.keyword.control-flow {
|
||||
color: #c586c0;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.function .token.maybe-class-name {
|
||||
color: #dcdcaa;
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: #d16969;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.constant {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.class-name,
|
||||
.token.maybe-class-name {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
.token.console {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.parameter {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.interpolation {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.punctuation.interpolation-punctuation {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.variable,
|
||||
.token.imports .token.maybe-class-name,
|
||||
.token.exports .token.maybe-class-name {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.selector {
|
||||
color: #d7ba7d;
|
||||
}
|
||||
|
||||
.token.escape {
|
||||
color: #d7ba7d;
|
||||
}
|
||||
|
||||
.token.tag {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.tag .token.punctuation {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.token.cdata {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.token.attr-name {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.token.attr-value,
|
||||
.token.attr-value .token.punctuation {
|
||||
color: #ce9178;
|
||||
}
|
||||
|
||||
.token.attr-value .token.punctuation.attr-equals {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #569cd6;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
/*********************************************************
|
||||
* Language Specific
|
||||
*/
|
||||
|
||||
pre[class*='language-javascript'],
|
||||
code[class*='language-javascript'],
|
||||
pre[class*='language-jsx'],
|
||||
code[class*='language-jsx'],
|
||||
pre[class*='language-typescript'],
|
||||
code[class*='language-typescript'],
|
||||
pre[class*='language-tsx'],
|
||||
code[class*='language-tsx'] {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
pre[class*='language-css'],
|
||||
code[class*='language-css'] {
|
||||
color: #ce9178;
|
||||
}
|
||||
|
||||
pre[class*='language-html'],
|
||||
code[class*='language-html'] {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.language-regex .token.anchor {
|
||||
color: #dcdcaa;
|
||||
}
|
||||
|
||||
.language-html .token.punctuation {
|
||||
color: #808080;
|
||||
}
|
||||
/*********************************************************
|
||||
* Line highlighting
|
||||
*/
|
||||
pre[class*='language-'] > code[class*='language-'] {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.line-highlight.line-highlight {
|
||||
background: #f7ebc6;
|
||||
box-shadow: inset 5px 0 0 #f7d87c;
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
color: #90a4ae;
|
||||
background: #fafafa;
|
||||
font-family: Roboto Mono, monospace;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*='language-']::-moz-selection,
|
||||
pre[class*='language-']::-moz-selection,
|
||||
code[class*='language-'] ::-moz-selection,
|
||||
pre[class*='language-'] ::-moz-selection {
|
||||
background: #cceae7;
|
||||
color: #263238;
|
||||
}
|
||||
|
||||
code[class*='language-']::selection,
|
||||
pre[class*='language-']::selection,
|
||||
code[class*='language-'] ::selection,
|
||||
pre[class*='language-'] ::selection {
|
||||
background: #cceae7;
|
||||
color: #263238;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*='language-'] {
|
||||
white-space: normal;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
pre[class*='language-'] {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
margin: 0.5em 0;
|
||||
padding: 1.25em 1em;
|
||||
}
|
||||
|
||||
.language-css > code,
|
||||
.language-sass > code,
|
||||
.language-scss > code {
|
||||
color: #f76d47;
|
||||
}
|
||||
|
||||
[class*='language-'] .namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.attr-name {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.attr-value {
|
||||
color: #f6a434;
|
||||
}
|
||||
|
||||
.token.attribute {
|
||||
color: #f6a434;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.builtin {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.cdata {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.char {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.class {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.class-name {
|
||||
color: #6182b8;
|
||||
}
|
||||
|
||||
.token.comment {
|
||||
color: #aabfc9;
|
||||
}
|
||||
|
||||
.token.constant {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.token.doctype {
|
||||
color: #aabfc9;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.hexcode {
|
||||
color: #f76d47;
|
||||
}
|
||||
|
||||
.token.id {
|
||||
color: #7c4dff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: #7c4dff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #f76d47;
|
||||
}
|
||||
|
||||
.token.operator {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.prolog {
|
||||
color: #aabfc9;
|
||||
}
|
||||
|
||||
.token.property {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.pseudo-class {
|
||||
color: #f6a434;
|
||||
}
|
||||
|
||||
.token.pseudo-element {
|
||||
color: #f6a434;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #39adb5;
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: #6182b8;
|
||||
}
|
||||
|
||||
.token.selector {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.token.string {
|
||||
color: #f6a434;
|
||||
}
|
||||
|
||||
.token.symbol {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.token.tag {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.token.unit {
|
||||
color: #f76d47;
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.token.variable {
|
||||
color: #e53935;
|
||||
}
|
||||
@@ -1,23 +1,10 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FormControl, OutlinedInput, Popover } from '@mui/material'
|
||||
import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog'
|
||||
import { FormControl, OutlinedInput, InputBase, Popover } from '@mui/material'
|
||||
import SelectVariable from '@/ui-component/json/SelectVariable'
|
||||
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
|
||||
|
||||
export const Input = ({
|
||||
inputParam,
|
||||
value,
|
||||
nodes,
|
||||
edges,
|
||||
nodeId,
|
||||
onChange,
|
||||
disabled = false,
|
||||
showDialog,
|
||||
dialogProps,
|
||||
onDialogCancel,
|
||||
onDialogConfirm
|
||||
}) => {
|
||||
export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, disabled = false }) => {
|
||||
const [myValue, setMyValue] = useState(value ?? '')
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const [availableNodesForVariable, setAvailableNodesForVariable] = useState([])
|
||||
@@ -63,39 +50,66 @@ export const Input = ({
|
||||
|
||||
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>
|
||||
{showDialog && (
|
||||
<ExpandTextDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={onDialogCancel}
|
||||
onConfirm={(newValue, inputParamName) => {
|
||||
setMyValue(newValue)
|
||||
onDialogConfirm(newValue, inputParamName)
|
||||
}}
|
||||
></ExpandTextDialog>
|
||||
{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 && (
|
||||
@@ -131,11 +145,7 @@ Input.propTypes = {
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
showDialog: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
nodes: PropTypes.array,
|
||||
edges: PropTypes.array,
|
||||
nodeId: PropTypes.string,
|
||||
onDialogCancel: PropTypes.func,
|
||||
onDialogConfirm: PropTypes.func
|
||||
nodeId: PropTypes.string
|
||||
}
|
||||
|
||||
@@ -141,8 +141,17 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={node.data.inputs.chainName ? node.data.inputs.chainName : node.data.id}
|
||||
secondary={`${selectedOutputAnchor?.label ?? 'output'} from ${node.data.label}`}
|
||||
primary={
|
||||
node.data.inputs.chainName ??
|
||||
node.data.inputs.functionName ??
|
||||
node.data.inputs.variableName ??
|
||||
node.data.id
|
||||
}
|
||||
secondary={
|
||||
node.data.name === 'ifElseFunction'
|
||||
? `${node.data.description}`
|
||||
: `${selectedOutputAnchor?.label ?? 'output'} from ${node.data.label}`
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
|
||||
@@ -147,8 +147,8 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi })
|
||||
}
|
||||
|
||||
FlowListTable.propTypes = {
|
||||
data: PropTypes.object,
|
||||
images: PropTypes.array,
|
||||
data: PropTypes.array,
|
||||
images: PropTypes.object,
|
||||
filterFunction: PropTypes.func,
|
||||
updateFlowsApi: PropTypes.object
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import Table from '@mui/material/Table'
|
||||
import TableBody from '@mui/material/TableBody'
|
||||
import TableCell, { tableCellClasses } from '@mui/material/TableCell'
|
||||
import TableContainer from '@mui/material/TableContainer'
|
||||
import TableHead from '@mui/material/TableHead'
|
||||
import TableRow from '@mui/material/TableRow'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import { Button, Typography } from '@mui/material'
|
||||
|
||||
const StyledTableCell = styled(TableCell)(({ theme }) => ({
|
||||
[`&.${tableCellClasses.head}`]: {
|
||||
backgroundColor: theme.palette.common.black,
|
||||
color: theme.palette.common.white
|
||||
},
|
||||
[`&.${tableCellClasses.body}`]: {
|
||||
fontSize: 14
|
||||
}
|
||||
}))
|
||||
|
||||
const StyledTableRow = styled(TableRow)(({ theme }) => ({
|
||||
'&:nth-of-type(odd)': {
|
||||
backgroundColor: theme.palette.action.hover
|
||||
},
|
||||
// hide last border
|
||||
'&:last-child td, &:last-child th': {
|
||||
border: 0
|
||||
}
|
||||
}))
|
||||
|
||||
export const MarketplaceTable = ({ data, filterFunction, filterByBadge, filterByType, filterByFramework, goToCanvas, goToTool }) => {
|
||||
const openTemplate = (selectedTemplate) => {
|
||||
if (selectedTemplate.flowData) {
|
||||
goToCanvas(selectedTemplate)
|
||||
} else {
|
||||
goToTool(selectedTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer style={{ marginTop: '30', border: 1 }} component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} size='small' aria-label='a dense table'>
|
||||
<TableHead>
|
||||
<TableRow sx={{ marginTop: '10', backgroundColor: 'primary' }}>
|
||||
<StyledTableCell component='th' scope='row' style={{ width: '15%' }} key='0'>
|
||||
Name
|
||||
</StyledTableCell>
|
||||
<StyledTableCell component='th' scope='row' style={{ width: '5%' }} key='1'>
|
||||
Type
|
||||
</StyledTableCell>
|
||||
<StyledTableCell style={{ width: '35%' }} key='2'>
|
||||
Description
|
||||
</StyledTableCell>
|
||||
<StyledTableCell style={{ width: '35%' }} key='3'>
|
||||
Nodes
|
||||
</StyledTableCell>
|
||||
<StyledTableCell component='th' scope='row' style={{ width: '5%' }} key='4'>
|
||||
|
||||
</StyledTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data
|
||||
.filter(filterByBadge)
|
||||
.filter(filterByType)
|
||||
.filter(filterFunction)
|
||||
.filter(filterByFramework)
|
||||
.map((row, index) => (
|
||||
<StyledTableRow key={index}>
|
||||
<TableCell key='0'>
|
||||
<Typography
|
||||
sx={{ fontSize: '1.2rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
|
||||
>
|
||||
<Button onClick={() => openTemplate(row)} sx={{ textAlign: 'left' }}>
|
||||
{row.templateName || row.name}
|
||||
</Button>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell key='1'>
|
||||
<Typography>{row.type}</Typography>
|
||||
</TableCell>
|
||||
<TableCell key='2'>
|
||||
<Typography sx={{ overflowWrap: 'break-word', whiteSpace: 'pre-line' }}>
|
||||
{row.description || ''}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell key='3'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginTop: 5
|
||||
}}
|
||||
>
|
||||
{row.categories &&
|
||||
row.categories
|
||||
.split(',')
|
||||
.map((tag, index) => (
|
||||
<Chip
|
||||
variant='outlined'
|
||||
key={index}
|
||||
size='small'
|
||||
label={tag.toUpperCase()}
|
||||
style={{ marginRight: 3, marginBottom: 3 }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell key='4'>
|
||||
<Typography>
|
||||
{row.badge &&
|
||||
row.badge
|
||||
.split(';')
|
||||
.map((tag, index) => (
|
||||
<Chip
|
||||
color={tag === 'POPULAR' ? 'primary' : 'error'}
|
||||
key={index}
|
||||
size='small'
|
||||
label={tag.toUpperCase()}
|
||||
style={{ marginRight: 5, marginBottom: 5 }}
|
||||
/>
|
||||
))}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
MarketplaceTable.propTypes = {
|
||||
data: PropTypes.array,
|
||||
filterFunction: PropTypes.func,
|
||||
filterByBadge: PropTypes.func,
|
||||
filterByType: PropTypes.func,
|
||||
filterByFramework: PropTypes.func,
|
||||
goToTool: PropTypes.func,
|
||||
goToCanvas: PropTypes.func
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user