Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/server/src/controllers/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ const deleteChatflow = async (req: Request, res: Response, next: NextFunction) =
const getAllChatflows = async (req: Request, res: Response, next: NextFunction) => {
try {
const { page, limit } = getPageAndLimitParams(req)
const search = typeof req.query.search === 'string' ? req.query.search : undefined

const apiResponse = await chatflowsService.getAllChatflows(
req.query?.type as ChatflowType,
req.user?.activeWorkspaceId,
page,
limit
limit,
search
)
return res.json(apiResponse)
} catch (error) {
Expand Down
19 changes: 14 additions & 5 deletions packages/server/src/services/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,14 @@ const deleteChatflow = async (chatflowId: string, orgId: string, workspaceId: st
}
}

const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: number = -1, limit: number = -1) => {
const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page: number = -1, limit: number = -1, search?: string) => {
try {
const appServer = getRunningExpressApp()

const queryBuilder = appServer.AppDataSource.getRepository(ChatFlow)
.createQueryBuilder('chat_flow')
.orderBy('chat_flow.updatedDate', 'DESC')

if (page > 0 && limit > 0) {
queryBuilder.skip((page - 1) * limit)
queryBuilder.take(limit)
}
if (type === 'MULTIAGENT') {
queryBuilder.andWhere('chat_flow.type = :type', { type: 'MULTIAGENT' })
} else if (type === 'AGENTFLOW') {
Expand All @@ -161,6 +157,19 @@ const getAllChatflows = async (type?: ChatflowType, workspaceId?: string, page:
queryBuilder.andWhere('chat_flow.type = :type', { type: 'CHATFLOW' })
}
if (workspaceId) queryBuilder.andWhere('chat_flow.workspaceId = :workspaceId', { workspaceId })

// Apply search filter before pagination
if (search) {
queryBuilder.andWhere(
'(LOWER(chat_flow.name) LIKE :search OR LOWER(chat_flow.category) LIKE :search OR LOWER(chat_flow.id) LIKE :search)',
{ search: `%${search.toLowerCase()}%` }
)
}

if (page > 0 && limit > 0) {
queryBuilder.skip((page - 1) * limit)
queryBuilder.take(limit)
}
const [data, total] = await queryBuilder.getManyAndCount()

if (page > 0 && limit > 0) {
Expand Down
14 changes: 2 additions & 12 deletions packages/ui/src/ui-component/table/FlowListTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,7 @@ const getLocalStorageKeyName = (name, isAgentCanvas) => {
return (isAgentCanvas ? 'agentcanvas' : 'chatflowcanvas') + '_' + name
}

export const FlowListTable = ({
data,
images = {},
icons = {},
isLoading,
filterFunction,
updateFlowsApi,
setError,
isAgentCanvas,
isAgentflowV2
}) => {
export const FlowListTable = ({ data, images = {}, icons = {}, isLoading, updateFlowsApi, setError, isAgentCanvas, isAgentflowV2 }) => {
const { hasPermission } = useAuth()
const isActionsAvailable = isAgentCanvas
? hasPermission('agentflows:update,agentflows:delete,agentflows:config,agentflows:domains,templates:flowexport,agentflows:export')
Expand Down Expand Up @@ -186,7 +176,7 @@ export const FlowListTable = ({
</>
) : (
<>
{sortedData.filter(filterFunction).map((row, index) => (
{sortedData.map((row, index) => (
<StyledTableRow key={index}>
<StyledTableCell key='0'>
<Tooltip title={row.templateName || row.name}>
Expand Down
40 changes: 28 additions & 12 deletions packages/ui/src/views/agentflows/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'

Expand Down Expand Up @@ -54,17 +54,24 @@ const Agentflows = () => {
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
const [total, setTotal] = useState(0)

const searchTimeoutRef = useRef(null)

const onChange = (page, pageLimit) => {
setCurrentPage(page)
setPageLimit(pageLimit)
refresh(page, pageLimit, agentflowVersion)
}

const refresh = (page, limit, nextView) => {
const refresh = (page, limit, nextView, searchValue) => {
const params = {
page: page || currentPage,
limit: limit || pageLimit
}
// Always include search parameter, even if empty (to clear server-side filter)
const currentSearch = searchValue !== undefined ? searchValue : search
if (currentSearch) {
params.search = currentSearch
}
getAllAgentflows.request(nextView === 'v2' ? 'AGENTFLOW' : 'MULTIAGENT', params)
}
Comment on lines +57 to 76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a significant amount of duplicated logic for handling search, pagination, and data fetching between this component (Agentflows) and Chatflows (packages/ui/src/views/chatflows/index.jsx). This duplication can make future maintenance more difficult and error-prone.

Consider extracting this shared logic into a custom hook (e.g., useFlowList). This would centralize the state and effects, reduce code duplication, and make both components cleaner and easier to maintain.

An example of what the hook could look like:

const useFlowList = (apiFunc, type) => {
    const [search, setSearch] = useState('');
    const [currentPage, setCurrentPage] = useState(1);
    // ... other states and logic

    const onSearchChange = (event) => {
        // ... debounced search logic
    };

    useEffect(() => {
        // ... data fetching and cleanup
    }, []);

    return {
        // ... returned values and functions
    };
}


Expand All @@ -82,15 +89,19 @@ const Agentflows = () => {
}

const onSearchChange = (event) => {
setSearch(event.target.value)
}
const newSearch = event.target.value
setSearch(newSearch)
setCurrentPage(1)

function filterFlows(data) {
return (
data.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) ||
data.id.toLowerCase().indexOf(search.toLowerCase()) > -1
)
// Clear existing timeout
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current)
}

// Debounce search - trigger refresh after user stops typing
searchTimeoutRef.current = setTimeout(() => {
refresh(1, pageLimit, agentflowVersion, newSearch)
}, 300)
}

const addNew = () => {
Expand All @@ -116,6 +127,12 @@ const Agentflows = () => {
useEffect(() => {
refresh(currentPage, pageLimit, agentflowVersion)

// Cleanup timeout on unmount
return () => {
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down Expand Up @@ -304,7 +321,7 @@ const Agentflows = () => {
<>
{!view || view === 'card' ? (
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
{getAllAgentflows.data?.data.filter(filterFlows).map((data, index) => (
{getAllAgentflows.data?.data.map((data, index) => (
<ItemCard
key={index}
onClick={() => goToCanvas(data)}
Expand All @@ -322,7 +339,6 @@ const Agentflows = () => {
images={images}
icons={icons}
isLoading={isLoading}
filterFunction={filterFlows}
updateFlowsApi={getAllAgentflows}
setError={setError}
/>
Expand Down
41 changes: 29 additions & 12 deletions packages/ui/src/views/chatflows/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import { useNavigate } from 'react-router-dom'

// material-ui
Expand Down Expand Up @@ -49,17 +49,24 @@ const Chatflows = () => {
const [pageLimit, setPageLimit] = useState(DEFAULT_ITEMS_PER_PAGE)
const [total, setTotal] = useState(0)

const searchTimeoutRef = useRef(null)

const onChange = (page, pageLimit) => {
setCurrentPage(page)
setPageLimit(pageLimit)
applyFilters(page, pageLimit)
}

const applyFilters = (page, limit) => {
const applyFilters = (page, limit, searchValue) => {
const params = {
page: page || currentPage,
limit: limit || pageLimit
}
// Always include search parameter, even if empty (to clear server-side filter)
const currentSearch = searchValue !== undefined ? searchValue : search
if (currentSearch) {
params.search = currentSearch
}
getAllChatflowsApi.request(params)
}

Expand All @@ -70,15 +77,19 @@ const Chatflows = () => {
}

const onSearchChange = (event) => {
setSearch(event.target.value)
}
const newSearch = event.target.value
setSearch(newSearch)
setCurrentPage(1)

// Clear existing timeout
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current)
}

function filterFlows(data) {
return (
data?.name.toLowerCase().indexOf(search.toLowerCase()) > -1 ||
(data.category && data.category.toLowerCase().indexOf(search.toLowerCase()) > -1) ||
data?.id.toLowerCase().indexOf(search.toLowerCase()) > -1
)
// Debounce search - trigger refresh after user stops typing
searchTimeoutRef.current = setTimeout(() => {
applyFilters(1, pageLimit, newSearch)
}, 300)
}

const addNew = () => {
Expand All @@ -91,6 +102,13 @@ const Chatflows = () => {

useEffect(() => {
applyFilters(currentPage, pageLimit)

// Cleanup timeout on unmount
return () => {
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down Expand Up @@ -196,7 +214,7 @@ const Chatflows = () => {
<>
{!view || view === 'card' ? (
<Box display='grid' gridTemplateColumns='repeat(3, 1fr)' gap={gridSpacing}>
{getAllChatflowsApi.data?.data?.filter(filterFlows).map((data, index) => (
{getAllChatflowsApi.data?.data?.map((data, index) => (
<ItemCard key={index} onClick={() => goToCanvas(data)} data={data} images={images[data.id]} />
))}
</Box>
Expand All @@ -205,7 +223,6 @@ const Chatflows = () => {
data={getAllChatflowsApi.data?.data}
images={images}
isLoading={isLoading}
filterFunction={filterFlows}
updateFlowsApi={getAllChatflowsApi}
setError={setError}
/>
Expand Down