diff --git a/packages/server/src/database/entities/ChatFlow.ts b/packages/server/src/database/entities/ChatFlow.ts index 376a100b4fa..b3131c2ea46 100644 --- a/packages/server/src/database/entities/ChatFlow.ts +++ b/packages/server/src/database/entities/ChatFlow.ts @@ -36,4 +36,7 @@ export class ChatFlow implements IChatFlow { @UpdateDateColumn() updatedDate: Date + + @Column({ nullable: true, type: 'text' }) + category?: string } diff --git a/packages/server/src/database/migrations/mysql/1699900910291-AddCategoryToChatFlow.ts b/packages/server/src/database/migrations/mysql/1699900910291-AddCategoryToChatFlow.ts new file mode 100644 index 00000000000..424f3b0e0b3 --- /dev/null +++ b/packages/server/src/database/migrations/mysql/1699900910291-AddCategoryToChatFlow.ts @@ -0,0 +1,12 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddCategoryToChatFlow1699900910291 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const columnExists = await queryRunner.hasColumn('chat_flow', 'category') + if (!columnExists) queryRunner.query(`ALTER TABLE \`chat_flow\` ADD COLUMN \`category\` TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`chat_flow\` DROP COLUMN \`category\`;`) + } +} diff --git a/packages/server/src/database/migrations/mysql/index.ts b/packages/server/src/database/migrations/mysql/index.ts index eff089cda07..8f9824a865a 100644 --- a/packages/server/src/database/migrations/mysql/index.ts +++ b/packages/server/src/database/migrations/mysql/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' export const mysqlMigrations = [ @@ -21,5 +22,6 @@ export const mysqlMigrations = [ AddChatHistory1694658767766, AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, + AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237 ] diff --git a/packages/server/src/database/migrations/postgres/1699900910291-AddCategoryToChatFlow.ts b/packages/server/src/database/migrations/postgres/1699900910291-AddCategoryToChatFlow.ts new file mode 100644 index 00000000000..f5d96439128 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/1699900910291-AddCategoryToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddCategoryToChatFlow1699900910291 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN IF NOT EXISTS "category" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "category";`) + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 93d02f3e00c..d196fbc16b5 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' export const postgresMigrations = [ @@ -21,5 +22,6 @@ export const postgresMigrations = [ AddChatHistory1694658756136, AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, + AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237 ] diff --git a/packages/server/src/database/migrations/sqlite/1699900910291-AddCategoryToChatFlow.ts b/packages/server/src/database/migrations/sqlite/1699900910291-AddCategoryToChatFlow.ts new file mode 100644 index 00000000000..270b299882e --- /dev/null +++ b/packages/server/src/database/migrations/sqlite/1699900910291-AddCategoryToChatFlow.ts @@ -0,0 +1,11 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddCategoryToChatFlow1699900910291 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" ADD COLUMN "category" TEXT;`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" DROP COLUMN "category";`) + } +} diff --git a/packages/server/src/database/migrations/sqlite/index.ts b/packages/server/src/database/migrations/sqlite/index.ts index edba59305cd..fdd83064ac1 100644 --- a/packages/server/src/database/migrations/sqlite/index.ts +++ b/packages/server/src/database/migrations/sqlite/index.ts @@ -8,6 +8,7 @@ import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic' import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory' import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity' import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage' +import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow' import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage' export const sqliteMigrations = [ @@ -21,5 +22,6 @@ export const sqliteMigrations = [ AddChatHistory1694657778173, AddAssistantEntity1699325775451, AddUsedToolsToChatMessage1699481607341, + AddCategoryToChatFlow1699900910291, AddFileAnnotationsToChatMessage1700271021237 ] diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 57245f2cd99..365776738be 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -356,8 +356,12 @@ export class App { this.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow) const result = await this.AppDataSource.getRepository(ChatFlow).save(chatflow) - // Update chatflowpool inSync to false, to build Langchain again because data has been changed - this.chatflowPool.updateInSync(chatflow.id, false) + // chatFlowPool is initialized only when a flow is opened + // if the user attempts to rename/update category without opening any flow, chatFlowPool will be undefined + if (this.chatflowPool) { + // Update chatflowpool inSync to false, to build Langchain again because data has been changed + this.chatflowPool.updateInSync(chatflow.id, false) + } return res.json(result) }) diff --git a/packages/ui/src/ui-component/button/FlowListMenu.js b/packages/ui/src/ui-component/button/FlowListMenu.js new file mode 100644 index 00000000000..b242d2cb257 --- /dev/null +++ b/packages/ui/src/ui-component/button/FlowListMenu.js @@ -0,0 +1,291 @@ +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import PropTypes from 'prop-types' + +import { styled, alpha } from '@mui/material/styles' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' +import EditIcon from '@mui/icons-material/Edit' +import Divider from '@mui/material/Divider' +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 Button from '@mui/material/Button' +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' +import { IconX } from '@tabler/icons' + +import chatflowsApi from 'api/chatflows' + +import useApi from '../../hooks/useApi' +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' + +import { generateExportFlowData } from '../../utils/genericHelper' +import useNotifier from '../../utils/useNotifier' + +const StyledMenu = styled((props) => ( + +))(({ theme }) => ({ + '& .MuiPaper-root': { + borderRadius: 6, + marginTop: theme.spacing(1), + minWidth: 180, + boxShadow: + 'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px', + '& .MuiMenu-list': { + padding: '4px 0' + }, + '& .MuiMenuItem-root': { + '& .MuiSvgIcon-root': { + fontSize: 18, + color: theme.palette.text.secondary, + marginRight: theme.spacing(1.5) + }, + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity) + } + } + } +})) + +export default function FlowListMenu({ chatflow, updateFlowsApi }) { + const { confirm } = useConfirm() + const dispatch = useDispatch() + const updateChatflowApi = useApi(chatflowsApi.updateChatflow) + + useNotifier() + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) + const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) + + const [flowDialogOpen, setFlowDialogOpen] = useState(false) + const [categoryDialogOpen, setCategoryDialogOpen] = useState(false) + const [categoryDialogProps, setCategoryDialogProps] = useState({}) + const [anchorEl, setAnchorEl] = useState(null) + const open = Boolean(anchorEl) + + const handleClick = (event) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + const handleFlowRename = () => { + setAnchorEl(null) + setFlowDialogOpen(true) + } + + const saveFlowRename = async (chatflowName) => { + const updateBody = { + name: chatflowName, + chatflow + } + try { + await updateChatflowApi.request(chatflow.id, updateBody) + await updateFlowsApi.request() + } 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) => ( + + ) + } + }) + } + } + + const handleFlowCategory = () => { + setAnchorEl(null) + if (chatflow.category) { + setCategoryDialogProps({ + category: chatflow.category.split(';') + }) + } + setCategoryDialogOpen(true) + } + + const saveFlowCategory = async (categories) => { + setCategoryDialogOpen(false) + // save categories as string + const categoryTags = categories.join(';') + const updateBody = { + category: categoryTags, + chatflow + } + try { + await updateChatflowApi.request(chatflow.id, updateBody) + await updateFlowsApi.request() + } 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) => ( + + ) + } + }) + } + } + + const handleDelete = async () => { + setAnchorEl(null) + const confirmPayload = { + title: `Delete`, + description: `Delete chatflow ${chatflow.name}?`, + confirmButtonName: 'Delete', + cancelButtonName: 'Cancel' + } + const isConfirmed = await confirm(confirmPayload) + + if (isConfirmed) { + try { + await chatflowsApi.deleteChatflow(chatflow.id) + await updateFlowsApi.request() + } 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) => ( + + ) + } + }) + } + } + } + + const handleDuplicate = () => { + setAnchorEl(null) + try { + localStorage.setItem('duplicatedFlowData', chatflow.flowData) + window.open(`${uiBaseURL}/canvas`, '_blank') + } catch (e) { + console.error(e) + } + } + + const handleExport = () => { + setAnchorEl(null) + try { + const flowData = JSON.parse(chatflow.flowData) + let dataStr = JSON.stringify(generateExportFlowData(flowData), null, 2) + let dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr) + + let exportFileDefaultName = `${chatflow.name} Chatflow.json` + + let linkElement = document.createElement('a') + linkElement.setAttribute('href', dataUri) + linkElement.setAttribute('download', exportFileDefaultName) + linkElement.click() + } catch (e) { + console.error(e) + } + } + + return ( +
+ + + + + Rename + + + + Duplicate + + + + Export + + + + + Update Category + + + + + Delete + + + + setFlowDialogOpen(false)} + onConfirm={saveFlowRename} + /> + setCategoryDialogOpen(false)} + onSubmit={saveFlowCategory} + /> +
+ ) +} + +FlowListMenu.propTypes = { + chatflow: PropTypes.object, + updateFlowsApi: PropTypes.object +} diff --git a/packages/ui/src/ui-component/button/StyledButton.js b/packages/ui/src/ui-component/button/StyledButton.js index 6e0c707864e..29e17f804d7 100644 --- a/packages/ui/src/ui-component/button/StyledButton.js +++ b/packages/ui/src/ui-component/button/StyledButton.js @@ -1,5 +1,6 @@ import { styled } from '@mui/material/styles' import { Button } from '@mui/material' +import MuiToggleButton from '@mui/material/ToggleButton' export const StyledButton = styled(Button)(({ theme, color = 'primary' }) => ({ color: 'white', @@ -9,3 +10,10 @@ export const StyledButton = styled(Button)(({ theme, color = 'primary' }) => ({ backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)` } })) + +export const StyledToggleButton = styled(MuiToggleButton)(({ theme, color = 'primary' }) => ({ + '&.Mui-selected, &.Mui-selected:hover': { + color: 'white', + backgroundColor: theme.palette[color].main + } +})) diff --git a/packages/ui/src/ui-component/dialog/TagDialog.js b/packages/ui/src/ui-component/dialog/TagDialog.js new file mode 100644 index 00000000000..82c35dde62b --- /dev/null +++ b/packages/ui/src/ui-component/dialog/TagDialog.js @@ -0,0 +1,110 @@ +import { useState, useEffect } from 'react' +import Dialog from '@mui/material/Dialog' +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import TextField from '@mui/material/TextField' +import Chip from '@mui/material/Chip' +import PropTypes from 'prop-types' +import { DialogActions, DialogContent, DialogTitle, Typography } from '@mui/material' + +const TagDialog = ({ isOpen, dialogProps, onClose, onSubmit }) => { + const [inputValue, setInputValue] = useState('') + const [categoryValues, setCategoryValues] = useState([]) + + const handleInputChange = (event) => { + setInputValue(event.target.value) + } + + const handleInputKeyDown = (event) => { + if (event.key === 'Enter' && inputValue.trim()) { + event.preventDefault() + if (!categoryValues.includes(inputValue)) { + setCategoryValues([...categoryValues, inputValue]) + setInputValue('') + } + } + } + + const handleDeleteTag = (categoryToDelete) => { + setCategoryValues(categoryValues.filter((category) => category !== categoryToDelete)) + } + + const handleSubmit = (event) => { + event.preventDefault() + let newCategories = [...categoryValues] + if (inputValue.trim() && !categoryValues.includes(inputValue)) { + newCategories = [...newCategories, inputValue] + setCategoryValues(newCategories) + } + onSubmit(newCategories) + } + + useEffect(() => { + if (dialogProps.category) setCategoryValues(dialogProps.category) + + return () => { + setInputValue('') + setCategoryValues([]) + } + }, [dialogProps]) + + return ( + + + Set Chatflow Category Tags + + + +
+ {categoryValues.length > 0 && ( +
+ {categoryValues.map((category, index) => ( + handleDeleteTag(category)} + style={{ marginRight: 5, marginBottom: 5 }} + /> + ))} +
+ )} + + + Enter a tag and press enter to add it to the list. You can add as many tags as you want. + + +
+
+ + + + +
+ ) +} + +TagDialog.propTypes = { + isOpen: PropTypes.bool, + dialogProps: PropTypes.object, + onClose: PropTypes.func, + onSubmit: PropTypes.func +} + +export default TagDialog diff --git a/packages/ui/src/ui-component/table/FlowListTable.js b/packages/ui/src/ui-component/table/FlowListTable.js new file mode 100644 index 00000000000..e3baa2e28ef --- /dev/null +++ b/packages/ui/src/ui-component/table/FlowListTable.js @@ -0,0 +1,152 @@ +import PropTypes from 'prop-types' +import { useNavigate } from 'react-router-dom' +import moment from 'moment' +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, Stack, Typography } from '@mui/material' +import FlowListMenu from '../button/FlowListMenu' + +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 FlowListTable = ({ data, images, filterFunction, updateFlowsApi }) => { + const navigate = useNavigate() + const goToCanvas = (selectedChatflow) => { + navigate(`/canvas/${selectedChatflow.id}`) + } + + return ( + <> + + + + + + Name + + + Category + + + Nodes + + + Last Modified Date + + + Actions + + + + + {data.filter(filterFunction).map((row, index) => ( + + + + + + + +
+   + {row.category && + row.category + .split(';') + .map((tag, index) => ( + + ))} +
+
+ + {images[row.id] && ( +
+ {images[row.id].slice(0, images[row.id].length > 5 ? 5 : images[row.id].length).map((img) => ( +
+ +
+ ))} + {images[row.id].length > 5 && ( + + + {images[row.id].length - 5} More + + )} +
+ )} +
+ {moment(row.updatedDate).format('MMMM Do, YYYY')} + + + + + +
+ ))} +
+
+
+ + ) +} + +FlowListTable.propTypes = { + data: PropTypes.object, + images: PropTypes.array, + filterFunction: PropTypes.func, + updateFlowsApi: PropTypes.object +} diff --git a/packages/ui/src/ui-component/toolbar/Toolbar.js b/packages/ui/src/ui-component/toolbar/Toolbar.js new file mode 100644 index 00000000000..f72ba339f4b --- /dev/null +++ b/packages/ui/src/ui-component/toolbar/Toolbar.js @@ -0,0 +1,24 @@ +import * as React from 'react' +import ViewListIcon from '@mui/icons-material/ViewList' +import ViewModuleIcon from '@mui/icons-material/ViewModule' +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup' +import { StyledToggleButton } from '../button/StyledButton' + +export default function Toolbar() { + const [view, setView] = React.useState('list') + + const handleChange = (event, nextView) => { + setView(nextView) + } + + return ( + + + + + + + + + ) +} diff --git a/packages/ui/src/views/apikey/index.js b/packages/ui/src/views/apikey/index.js index a2b2e639f09..e08baac2d28 100644 --- a/packages/ui/src/views/apikey/index.js +++ b/packages/ui/src/views/apikey/index.js @@ -16,7 +16,11 @@ import { Paper, IconButton, Popover, - Typography + Typography, + Toolbar, + TextField, + InputAdornment, + ButtonGroup } from '@mui/material' import { useTheme } from '@mui/material/styles' @@ -37,7 +41,7 @@ import useConfirm from 'hooks/useConfirm' import useNotifier from 'utils/useNotifier' // Icons -import { IconTrash, IconEdit, IconCopy, IconX, IconPlus, IconEye, IconEyeOff } from '@tabler/icons' +import { IconTrash, IconEdit, IconCopy, IconX, IconPlus, IconEye, IconEyeOff, IconSearch } from '@tabler/icons' import APIEmptySVG from 'assets/images/api_empty.svg' // ==============================|| APIKey ||============================== // @@ -59,6 +63,14 @@ const APIKey = () => { const [showApiKeys, setShowApiKeys] = useState([]) const openPopOver = Boolean(anchorEl) + const [search, setSearch] = useState('') + const onSearchChange = (event) => { + setSearch(event.target.value) + } + function filterKeys(data) { + return data.keyName.toLowerCase().indexOf(search.toLowerCase()) > -1 + } + const { confirm } = useConfirm() const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys) @@ -171,12 +183,53 @@ const APIKey = () => { <> -

API Keys 

- - - }> - Create Key - + + +

API Keys 

+ + + + ) + }} + /> + + + + } + > + Create Key + + + +
+
{apiKeys.length <= 0 && ( @@ -199,7 +252,7 @@ const APIKey = () => { - {apiKeys.map((key, index) => ( + {apiKeys.filter(filterKeys).map((key, index) => ( {key.keyName} diff --git a/packages/ui/src/views/chatflows/index.js b/packages/ui/src/views/chatflows/index.js index 6712623e640..3c4b897289f 100644 --- a/packages/ui/src/views/chatflows/index.js +++ b/packages/ui/src/views/chatflows/index.js @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom' import { useSelector } from 'react-redux' // material-ui -import { Grid, Box, Stack } from '@mui/material' +import { Grid, Box, Stack, Toolbar, ToggleButton, ButtonGroup, InputAdornment, TextField } from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports @@ -11,7 +11,6 @@ import MainCard from 'ui-component/cards/MainCard' import ItemCard from 'ui-component/cards/ItemCard' import { gridSpacing } from 'store/constant' import WorkflowEmptySVG from 'assets/images/workflow_empty.svg' -import { StyledButton } from 'ui-component/button/StyledButton' import LoginDialog from 'ui-component/dialog/LoginDialog' // API @@ -24,7 +23,11 @@ import useApi from 'hooks/useApi' import { baseURL } from 'store/constant' // icons -import { IconPlus } from '@tabler/icons' +import { IconPlus, IconSearch, IconLayoutGrid, IconList } from '@tabler/icons' +import * as React from 'react' +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup' +import { FlowListTable } from '../../ui-component/table/FlowListTable' +import { StyledButton } from '../../ui-component/button/StyledButton' // ==============================|| CHATFLOWS ||============================== // @@ -35,10 +38,28 @@ const Chatflows = () => { const [isLoading, setLoading] = useState(true) const [images, setImages] = useState({}) + const [search, setSearch] = useState('') const [loginDialogOpen, setLoginDialogOpen] = useState(false) const [loginDialogProps, setLoginDialogProps] = useState({}) const getAllChatflowsApi = useApi(chatflowsApi.getAllChatflows) + const [view, setView] = React.useState(localStorage.getItem('flowDisplayStyle') || 'card') + + const handleChange = (event, nextView) => { + localStorage.setItem('flowDisplayStyle', nextView) + setView(nextView) + } + + const onSearchChange = (event) => { + setSearch(event.target.value) + } + + function filterFlows(data) { + return ( + data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 || + (data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) + ) + } const onLoginClick = (username, password) => { localStorage.setItem('username', username) @@ -102,26 +123,86 @@ const Chatflows = () => { return ( - -

Chatflows

- - - - }> - Add New - + + + +

Chatflows

+ + + + ) + }} + /> + + + + + + + + + + + + + + + }> + Add New + + + +
+
+ {!isLoading && (!view || view === 'card') && getAllChatflowsApi.data && ( + + {getAllChatflowsApi.data.filter(filterFlows).map((data, index) => ( + + goToCanvas(data)} data={data} images={images[data.id]} /> + + ))} -
+ )} + {!isLoading && view === 'list' && getAllChatflowsApi.data && ( + + )}
- - {!isLoading && - getAllChatflowsApi.data && - getAllChatflowsApi.data.map((data, index) => ( - - goToCanvas(data)} data={data} images={images[data.id]} /> - - ))} - + {!isLoading && (!getAllChatflowsApi.data || getAllChatflowsApi.data.length === 0) && ( diff --git a/packages/ui/src/views/credentials/index.js b/packages/ui/src/views/credentials/index.js index 9db990a7cef..31e35831495 100644 --- a/packages/ui/src/views/credentials/index.js +++ b/packages/ui/src/views/credentials/index.js @@ -4,7 +4,23 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import moment from 'moment' // material-ui -import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material' +import { + Button, + Box, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + IconButton, + Toolbar, + TextField, + InputAdornment, + ButtonGroup +} from '@mui/material' import { useTheme } from '@mui/material/styles' // project imports @@ -25,7 +41,7 @@ import useConfirm from 'hooks/useConfirm' import useNotifier from 'utils/useNotifier' // Icons -import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons' +import { IconTrash, IconEdit, IconX, IconPlus, IconSearch } from '@tabler/icons' import CredentialEmptySVG from 'assets/images/credential_empty.svg' // const @@ -56,6 +72,14 @@ const Credentials = () => { const getAllCredentialsApi = useApi(credentialsApi.getAllCredentials) const getAllComponentsCredentialsApi = useApi(credentialsApi.getAllComponentsCredentials) + const [search, setSearch] = useState('') + const onSearchChange = (event) => { + setSearch(event.target.value) + } + function filterCredentials(data) { + return data.credentialName.toLowerCase().indexOf(search.toLowerCase()) > -1 + } + const listCredential = () => { const dialogProp = { title: 'Add New Credential', @@ -168,17 +192,53 @@ const Credentials = () => { <> -

Credentials 

- - - } - > - Add Credential - + + +

Credentials 

+ + + + ) + }} + /> + + + + } + > + Add Credential + + + +
+
{credentials.length <= 0 && ( @@ -205,7 +265,7 @@ const Credentials = () => {
- {credentials.map((credential, index) => ( + {credentials.filter(filterCredentials).map((credential, index) => (