From 2dfb34f7ab37038a39b5c336e668a4ad30230c24 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Wed, 5 Aug 2026 19:32:45 +0800 Subject: [PATCH] feat(agent-list): support multi-select canvas category filtering (#17843) --- api/apps/restful_apis/agent_api.py | 62 ++++++++++++++++--- api/db/services/canvas_service.py | 6 +- .../test_agents_webhook_unit.py | 4 +- web/src/hooks/use-agent-request.ts | 14 ++--- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/api/apps/restful_apis/agent_api.py b/api/apps/restful_apis/agent_api.py index 8d825a4a19..127b9f386a 100644 --- a/api/apps/restful_apis/agent_api.py +++ b/api/apps/restful_apis/agent_api.py @@ -682,7 +682,7 @@ _COMPILATION_TEMPLATE_GROUP_CATEGORY = "compilation_template_group" @add_tenant_id_to_kwargs def list_agents(tenant_id): keywords = request.args.get("keywords", "") - canvas_category = request.args.get("canvas_category") + canvas_category_list = [item for item in request.args.get("canvas_category", "").strip().split(",") if item] canvas_type = request.args.get("canvas_type") owner_ids = [item for item in request.args.get("owner_ids", "").strip().split(",") if item] tags = [item for item in request.args.get("tags", "").strip().split(",") if item] @@ -708,10 +708,11 @@ def list_agents(tenant_id): else: effective_owner_ids = list(authorized_owner_ids) - # Groups-only: an explicit ``compilation_template_group`` category returns - # just the caller's template groups (no agents) via list_saved, so the - # frontend can render a dedicated tab. list_saved paginates in Python. - if canvas_category == _COMPILATION_TEMPLATE_GROUP_CATEGORY: + # Groups-only: when ``compilation_template_group`` is the only selected + # category, return just the caller's template groups (no agents) via + # list_saved, so the frontend can render a dedicated tab. list_saved + # paginates in Python. + if canvas_category_list == [_COMPILATION_TEMPLATE_GROUP_CATEGORY]: from api.db.services.compilation_template_group_service import CompilationTemplateGroupService try: @@ -727,11 +728,17 @@ def list_agents(tenant_id): groups = groups[start : start + items_per_page] return get_json_result(data={"canvas": groups, "total": total}) + # Split selected categories: ``compilation_template_group`` is synthetic + # (resolves to template groups, not agents); everything else filters + # agents by canvas_category IN (...). + wants_groups = _COMPILATION_TEMPLATE_GROUP_CATEGORY in canvas_category_list + agent_categories = [c for c in canvas_category_list if c != _COMPILATION_TEMPLATE_GROUP_CATEGORY] + # Merge mode: with no ``canvas_category`` (and no agent-only filters), list # the caller's compilation template groups alongside agents, interleaved by # ``update_time``. ``canvas_type`` / ``tags`` are agent-only concepts, so # their presence keeps the response agent-only. - merge_groups = not canvas_category and not canvas_type and not tags + merge_groups = not canvas_category_list and not canvas_type and not tags if merge_groups: from api.db.services.compilation_template_group_service import CompilationTemplateGroupService @@ -777,6 +784,47 @@ def list_agents(tenant_id): return get_json_result(data={"canvas": items, "total": total}) + # Mixed mode: both template groups and agent categories are selected - fetch + # agents filtered by the agent categories and merge with template groups, + # interleaved by ``update_time`` (same merge strategy as merge mode). + if wants_groups and agent_categories: + from api.db.services.compilation_template_group_service import CompilationTemplateGroupService + + agents, _ = UserCanvasService.get_by_tenant_ids( + effective_owner_ids, + tenant_id, + 0, + 0, + order_by, + desc, + keywords, + agent_categories, + tags, + canvas_type, + ) + try: + groups = CompilationTemplateGroupService.list_saved(tenant_id, keywords, "", order_by, desc) + except Exception: + logging.exception("list_agents: compilation template group mixed failed for tenant=%s", tenant_id) + groups = [] + + items = [] + for agent in agents: + agent["type"] = "agent" + items.append(agent) + for group in groups: + group["type"] = _COMPILATION_TEMPLATE_GROUP_CATEGORY + group["title"] = group["name"] + items.append(group) + items.sort(key=lambda item: item.get("update_time") or 0, reverse=desc) + + total = len(items) + if page_number and items_per_page: + start = (page_number - 1) * items_per_page + items = items[start : start + items_per_page] + + return get_json_result(data={"canvas": items, "total": total}) + canvas, total = UserCanvasService.get_by_tenant_ids( effective_owner_ids, tenant_id, @@ -785,7 +833,7 @@ def list_agents(tenant_id): order_by, desc, keywords, - canvas_category, + agent_categories, tags, canvas_type, ) diff --git a/api/db/services/canvas_service.py b/api/db/services/canvas_service.py index e9b7d5750b..4bc3fd9d85 100644 --- a/api/db/services/canvas_service.py +++ b/api/db/services/canvas_service.py @@ -130,7 +130,7 @@ class UserCanvasService(CommonService): orderby, desc, keywords, - canvas_category=None, + canvas_category_list=None, tags=None, canvas_type=None, ): @@ -160,8 +160,8 @@ class UserCanvasService(CommonService): ) else: agents = cls.model.select(*fields).join(User, on=(cls.model.user_id == User.id)).where(owner_filter) - if canvas_category: - agents = agents.where(cls.model.canvas_category == canvas_category) + if canvas_category_list: + agents = agents.where(cls.model.canvas_category.in_(canvas_category_list)) if canvas_type: agents = agents.where(cls.model.canvas_type == canvas_type) if tags: diff --git a/test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py b/test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py index ae11b029bb..1b558fb4e7 100644 --- a/test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py +++ b/test/testcases/test_web_api/test_agent_app/test_agents_webhook_unit.py @@ -521,7 +521,7 @@ def test_agents_crud_unit_branches(monkeypatch): captured = {} - def fake_get_by_tenant_ids(owner_ids, tenant_id, page, page_size, orderby, desc, keywords, canvas_category, tags): + def fake_get_by_tenant_ids(owner_ids, tenant_id, page, page_size, orderby, desc, keywords, canvas_category_list, tags, canvas_type=None): captured["owner_ids"] = owner_ids captured["tenant_id"] = tenant_id captured["page"] = page @@ -529,7 +529,7 @@ def test_agents_crud_unit_branches(monkeypatch): captured["orderby"] = orderby captured["desc"] = desc captured["keywords"] = keywords - captured["canvas_category"] = canvas_category + captured["canvas_category_list"] = canvas_category_list captured["tags"] = tags return [{"id": "agent-1"}], 1 diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 7903bea25d..91f79e4c86 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -102,14 +102,14 @@ const buildAgentListParams = ({ page, pageSize, keywords, - canvasCategory, + canvasCategoryIds, ownerIds, tags, }: { page: number; pageSize: number; keywords?: string; - canvasCategory?: string; + canvasCategoryIds?: string[]; ownerIds?: string[]; tags?: string[]; }) => { @@ -121,8 +121,8 @@ const buildAgentListParams = ({ if (keywords) { params.keywords = keywords; } - if (canvasCategory) { - params.canvas_category = canvasCategory; + if (Array.isArray(canvasCategoryIds) && canvasCategoryIds.length > 0) { + params.canvas_category = canvasCategoryIds.join(','); } if (Array.isArray(ownerIds) && ownerIds.length > 0) { params.owner_ids = ownerIds.join(','); @@ -139,8 +139,8 @@ export const useFetchAgentListByPage = () => { const { pagination, setPagination } = useGetPaginationWithRouter(); const debouncedSearchString = useDebounce(searchString, { wait: 500 }); const { filterValue, handleFilterSubmit } = useHandleFilterSubmit(); - const canvasCategory = Array.isArray(filterValue.canvasCategory) - ? (filterValue.canvasCategory[0] as string | undefined) + const canvasCategoryIds = Array.isArray(filterValue.canvasCategory) + ? (filterValue.canvasCategory as string[]) : undefined; const owner = filterValue.owner; const tags = Array.isArray(filterValue.tags) ? filterValue.tags : undefined; @@ -149,7 +149,7 @@ export const useFetchAgentListByPage = () => { page: pagination.current, pageSize: pagination.pageSize, keywords: debouncedSearchString, - canvasCategory, + canvasCategoryIds, ownerIds: Array.isArray(owner) ? owner : undefined, tags, });