Merge branch 'main' into feature/conversation-starters

This commit is contained in:
Henry
2023-12-12 22:34:42 +00:00
66 changed files with 2258 additions and 943 deletions
@@ -23,7 +23,6 @@ import useConfirm from 'hooks/useConfirm'
import { uiBaseURL } from '../../store/constant'
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '../../store/actions'
import ConfirmDialog from '../dialog/ConfirmDialog'
import SaveChatflowDialog from '../dialog/SaveChatflowDialog'
import TagDialog from '../dialog/TagDialog'
@@ -286,7 +285,6 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
Delete
</MenuItem>
</StyledMenu>
<ConfirmDialog />
<SaveChatflowDialog
show={flowDialogOpen}
dialogProps={{
@@ -0,0 +1,587 @@
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) {
getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [promptType])
useEffect(() => {
if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) {
setAvailablePrompNameList(getAvailablePromptsApi.data.repos)
if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos)
}
// 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 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}&`
})
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>
{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>
)}
{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
@@ -69,7 +69,9 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi })
<Typography
sx={{ fontSize: '1.2rem', fontWeight: 500, overflowWrap: 'break-word', whiteSpace: 'pre-line' }}
>
<Button onClick={() => goToCanvas(row)}>{row.templateName || row.name}</Button>
<Button onClick={() => goToCanvas(row)} sx={{ textAlign: 'left' }}>
{row.templateName || row.name}
</Button>
</Typography>
</TableCell>
<TableCell key='1'>
@@ -145,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
}