From 71486b12cf9ae1b0ca6ebb9c5aec990b780c250d Mon Sep 17 00:00:00 2001 From: xuyaqist Date: Mon, 28 Sep 2026 15:17:38 +0800 Subject: [PATCH 1/7] update version style --- frontend/app/[locale]/agents/agent-version.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/[locale]/agents/agent-version.tsx b/frontend/app/[locale]/agents/agent-version.tsx index 401c31df24..b9598adefa 100644 --- a/frontend/app/[locale]/agents/agent-version.tsx +++ b/frontend/app/[locale]/agents/agent-version.tsx @@ -128,7 +128,7 @@ export default function AgentVersionManage({ ) : ( -
+
{agentVersionList.map((version) => ( Date: Tue, 29 Sep 2026 10:52:16 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8F=B3=E4=B8=8B?= =?UTF-8?q?=E8=A7=92=E2=80=9C=E8=81=94=E7=B3=BB=E6=88=91=E4=BB=AC=E2=80=9D?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B6=85=E7=BA=A7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/UserManageComp.tsx | 90 ++++++++++--------- .../app/[locale]/resource-manage/page.tsx | 1 - .../components/navigation/FooterLayout.tsx | 12 --- 3 files changed, 46 insertions(+), 57 deletions(-) diff --git a/frontend/app/[locale]/resource-manage/components/UserManageComp.tsx b/frontend/app/[locale]/resource-manage/components/UserManageComp.tsx index 76518e58d5..e362c49f57 100644 --- a/frontend/app/[locale]/resource-manage/components/UserManageComp.tsx +++ b/frontend/app/[locale]/resource-manage/components/UserManageComp.tsx @@ -1216,9 +1216,9 @@ export default function UserManageComp() { }; return ( -
+
{/* Page header: grouped header without dividing line */} -
+
@@ -1253,12 +1253,15 @@ export default function UserManageComp() { )}
-
- - -
-
-
+
+ {isSuperAdmin && ( + + +
+
{ @@ -1286,42 +1289,41 @@ export default function UserManageComp() { />
-
- - - -
-
- {!hasSelectedTenant ? ( -
- ) : isEditingTenantName ? ( - setEditingTenantName(e.target.value)} - onBlur={saveTenantName} - onKeyDown={handleTenantNameKeyDown} - className="text-lg font-semibold text-gray-900 dark:text-gray-100" - placeholder={t("tenantResources.tenants.name")} - /> - ) : ( -
-

- {currentTenantName || - (directTenantLoading - ? t("tenantResources.tenants.loading") - : t("tenantResources.tenants.name"))} -

- -
- )} -
+ + + )} + +
+ {hasSelectedTenant && ( +
+ {isEditingTenantName ? ( + setEditingTenantName(e.target.value)} + onBlur={saveTenantName} + onKeyDown={handleTenantNameKeyDown} + className="text-lg font-semibold text-gray-900 dark:text-gray-100" + placeholder={t("tenantResources.tenants.name")} + /> + ) : ( +
+

+ {currentTenantName || + (directTenantLoading + ? t("tenantResources.tenants.loading") + : t("tenantResources.tenants.name"))} +

+ +
+ )} +
+ )} -
- +
{tenantId ? ( ) : ( -
+
diff --git a/frontend/app/[locale]/resource-manage/page.tsx b/frontend/app/[locale]/resource-manage/page.tsx index 8469187b75..fe1b782a4e 100644 --- a/frontend/app/[locale]/resource-manage/page.tsx +++ b/frontend/app/[locale]/resource-manage/page.tsx @@ -19,7 +19,6 @@ export default function TenantResourcesPage() { <> diff --git a/frontend/components/navigation/FooterLayout.tsx b/frontend/components/navigation/FooterLayout.tsx index c8e473c261..99339b53f7 100644 --- a/frontend/components/navigation/FooterLayout.tsx +++ b/frontend/components/navigation/FooterLayout.tsx @@ -31,18 +31,6 @@ export function FooterLayout() { > {t("page.termsOfUse")} - - {t("page.contactUs")} - - - {t("page.aboutUs")} -
)}
From 651bb5c03f41876664c46ca3f1624d742f61783f Mon Sep 17 00:00:00 2001 From: xuyaqist Date: Tue, 29 Sep 2026 16:56:57 +0800 Subject: [PATCH 3/7] fix(frontend): keep agent conversations visible after returning --- .../newchat/assistant-ui/thread-list.tsx | 122 ++++++++++-------- .../assistant-ui/threadlist-sidebar.tsx | 37 ++++-- .../assistant-ui/visible-thread-ids.ts | 15 +++ frontend/app/[locale]/newchat/page.tsx | 27 ++-- frontend/features/workbench/WorkbenchPage.tsx | 11 +- .../component/agentChatPageDeepLink.test.tsx | 37 +++++- 6 files changed, 164 insertions(+), 85 deletions(-) create mode 100644 frontend/app/[locale]/newchat/assistant-ui/visible-thread-ids.ts diff --git a/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx b/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx index 3a218ed69e..8c2233e4d4 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/thread-list.tsx @@ -39,6 +39,7 @@ import log from "@/lib/logger"; import { conversationService } from "@/services/conversationService"; import type { FC } from "react"; import { setPendingThreadOperationId } from "../adapter/conversation-thread-list-adapter"; +import { getVisibleThreadIds } from "./visible-thread-ids"; // Conversation status indicator component const ConversationStatusIndicator: FC<{ @@ -89,7 +90,8 @@ const useBatchSelection = (): BatchSelectionValue | null => export const BatchSelectionProvider: FC<{ children: ReactNode; onNewConversation?: () => void | Promise; -}> = ({ children, onNewConversation }) => { + serverConversationIds?: ReadonlyMap; +}> = ({ children, onNewConversation, serverConversationIds }) => { const { t } = useTranslation(); const aui = useAui(); const { confirm } = useConfirmModal(); @@ -109,8 +111,13 @@ export const BatchSelectionProvider: FC<{ }, []); const selectAllVisible = useCallback(() => { - setSelectedIds(() => new Set(threadIds)); - }, [threadIds]); + setSelectedIds( + () => + new Set( + getVisibleThreadIds(threadIds, threadItems, serverConversationIds) + ) + ); + }, [threadIds, threadItems, serverConversationIds]); const clear = useCallback(() => setSelectedIds(new Set()), []); @@ -132,9 +139,10 @@ export const BatchSelectionProvider: FC<{ ); const conversationIds: number[] = []; for (const id of selectedIds) { - const remoteId = itemsById.get(id)?.remoteId; - const num = Number(remoteId); - if (remoteId && Number.isInteger(num) && num > 0) { + const conversationId = + serverConversationIds?.get(id) || itemsById.get(id)?.remoteId; + const num = Number(conversationId); + if (conversationId && Number.isInteger(num) && num > 0) { conversationIds.push(num); } } @@ -144,7 +152,8 @@ export const BatchSelectionProvider: FC<{ // If so, the main panel must switch to a fresh thread after reload, // otherwise it would keep pointing at a now-deleted conversation. const activeRemoteId = mainThreadId - ? itemsById.get(mainThreadId)?.remoteId + ? serverConversationIds?.get(mainThreadId) || + itemsById.get(mainThreadId)?.remoteId : undefined; const activeConversationId = Number(activeRemoteId); const activeDeleted = @@ -184,6 +193,7 @@ export const BatchSelectionProvider: FC<{ selectedIds, threadItems, mainThreadId, + serverConversationIds, confirm, t, aui, @@ -295,39 +305,39 @@ export const BatchSidebarFooter: FC<{ onSwitchToLegacy?: () => void }> = ({ interface ThreadListProps { generatedTitles?: ReadonlyMap; + serverConversationIds?: ReadonlyMap; } export const ThreadList: FC = ({ generatedTitles, + serverConversationIds, }) => { const { t } = useTranslation(); const completedConversations = useMemo(() => new Set(), []); const isLoading = useAuiState((s) => s.threads.isLoading); const isLoadingMore = useAuiState((s) => s.threads.isLoadingMore); const hasMore = useAuiState((s) => s.threads.hasMore); + const threadIds = useAuiState((s) => s.threads.threadIds); + const threadItems = useAuiState((s) => s.threads.threadItems); + const visibleThreadIds = getVisibleThreadIds( + threadIds, + threadItems, + serverConversationIds + ); return (
s.threads.isLoading}> - - !s.threads.isLoading && s.threads.threadIds.length === 0 - } - > - - - - !s.threads.isLoading && s.threads.threadIds.length > 0 - } - > + {!isLoading && visibleThreadIds.length === 0 && } + {!isLoading && visibleThreadIds.length > 0 && ( - + )} { interface ThreadListItemsProps { completedConversations: Set; generatedTitles?: ReadonlyMap; + visibleThreadIds: string[]; } const ThreadListItems: FC = ({ completedConversations, generatedTitles, + visibleThreadIds, }) => { const { t } = useTranslation(); - const groups = useThreadListGroups(); + const groups = useThreadListGroups(visibleThreadIds); const GroupedThreadListItem = useMemo( - () => () => ( - - ), - [completedConversations, generatedTitles] - ); - - if (!groups) { - return ( - - {() => ( + () => + function GroupedThreadListItem() { + return ( - )} - - ); - } + ); + }, + [completedConversations, generatedTitles] + ); // Render each thread by index so we can interleave group labels between // recency buckets without giving up the runtime's per-item context. @@ -402,12 +404,14 @@ const ThreadListItems: FC = ({
{groups.map((group) => ( -
- {t(group.label)} -
+ {group.label && ( +
+ {t(group.label)} +
+ )} {group.entries.map(({ id, index }) => ( { +// Build ordered recency groups for the visible thread list. +const useThreadListGroups = (visibleThreadIds: string[]): ThreadListGroup[] => { const threadIds = useAuiState((s) => s.threads.threadIds); const threadItems = useAuiState((s) => s.threads.threadItems); - return useMemo(() => { + return useMemo(() => { const itemsById = new Map( ( threadItems as ReadonlyArray<{ @@ -462,8 +465,6 @@ const useThreadListGroups = (): ThreadListGroup[] | null => { const raw = itemsById.get(id)?.custom?.lastMessageAt; return raw ? new Date(raw) : undefined; }); - if (!dates.some(Boolean)) return null; - const now = new Date(); const startOfToday = new Date( now.getFullYear(), @@ -475,8 +476,18 @@ const useThreadListGroups = (): ThreadListGroup[] | null => { dates[index]?.getTime() ?? Number.MAX_SAFE_INTEGER; const indices = threadIds .map((_, index) => index) + .filter((index) => visibleThreadIds.includes(threadIds[index])) .sort((a, b) => time(b) - time(a)); + if (!indices.some((index) => dates[index])) { + return [ + { + label: "", + entries: indices.map((index) => ({ id: threadIds[index], index })), + }, + ]; + } + const result: ThreadListGroup[] = []; for (const index of indices) { const label = dateGroupLabel(dates[index], startOfToday); @@ -489,7 +500,7 @@ const useThreadListGroups = (): ThreadListGroup[] | null => { } } return result; - }, [threadIds, threadItems]); + }, [threadIds, threadItems, visibleThreadIds]); }; const ThreadListSkeleton: FC = () => { @@ -554,7 +565,10 @@ const ThreadListItemContent: FC = ({ const threadListItem = aui.threadListItem; const thread = threadListItem.getState(); const title = - generatedTitles?.get(thread.id) ?? thread.title ?? t("chat.thread.newChat"); + generatedTitles?.get(thread.id) ?? + (thread.remoteId ? generatedTitles?.get(thread.remoteId) : undefined) ?? + thread.title ?? + t("chat.thread.newChat"); const handleRename = useCallback( async (newTitle: string) => { @@ -633,9 +647,7 @@ const ThreadListItemContent: FC = ({ /> - - {title} - + {title} {title} diff --git a/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx b/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx index a98cf1b117..3731bc5e6d 100644 --- a/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx +++ b/frontend/app/[locale]/newchat/assistant-ui/threadlist-sidebar.tsx @@ -27,6 +27,7 @@ import { useTranslation } from "react-i18next"; interface ThreadListSidebarProps extends SidebarProps { className?: string; generatedTitles?: ReadonlyMap; + serverConversationIds?: ReadonlyMap; onPrepareNewConversation?: () => void; onNewConversation?: () => void | Promise; showLegacySwitch?: boolean; @@ -34,6 +35,7 @@ interface ThreadListSidebarProps extends SidebarProps { export function ThreadListSidebar({ generatedTitles, + serverConversationIds, onPrepareNewConversation, onNewConversation, showLegacySwitch = true, @@ -104,7 +106,10 @@ export function ThreadListSidebar({ className="h-full w-64 min-w-64 max-w-64 p-2" style={{ backgroundColor: "#F2F8FF" }} > - +
- - - {t("chat.sidebar.newConversation")} - + {onNewConversation ? ( + + ) : ( + + + {t("chat.sidebar.newConversation")} + + )}
- + , + serverConversationIds?: ReadonlyMap +): string[] => { + const remoteIds = new Map( + threadItems.map((item) => [item.id, item.remoteId]) + ); + return threadIds.filter((id) => { + const conversationId = Number( + serverConversationIds?.get(id) || remoteIds.get(id) + ); + return Number.isInteger(conversationId) && conversationId > 0; + }); +}; diff --git a/frontend/app/[locale]/newchat/page.tsx b/frontend/app/[locale]/newchat/page.tsx index 5d705c7027..c34757e40a 100644 --- a/frontend/app/[locale]/newchat/page.tsx +++ b/frontend/app/[locale]/newchat/page.tsx @@ -160,7 +160,6 @@ const PersistentChatHome: FC = () => { setSelectedAgent={setSelectedAgent} isLoadingAgents={isLoadingAgents} agents={agents} - onAgentSelected={handleAgentSelected} onBack={handleBack} isDictationConfigured={isDictationConfigured(modelConfig?.stt)} /> @@ -179,7 +178,6 @@ const HomeContent: FC<{ setSelectedAgent: (agent: Agent | null) => void; isLoadingAgents: boolean; agents: Agent[]; - onAgentSelected: (agent: Agent) => void; onBack: () => void; isDictationConfigured: boolean; }> = ({ @@ -188,7 +186,6 @@ const HomeContent: FC<{ setSelectedAgent, isLoadingAgents, agents, - onAgentSelected, onBack, isDictationConfigured, }) => { @@ -247,7 +244,9 @@ const HomeContent: FC<{ const [generatedTitles, setGeneratedTitles] = useState>( new Map() ); - const [, forceServerIdTick] = useState(0); + const [serverConversationIds, setServerConversationIds] = useState< + Map + >(new Map()); const handleServerConversationId = useCallback( (threadId: string, serverId: string, initialQuestion?: string) => { @@ -257,10 +256,8 @@ const HomeContent: FC<{ if (previous !== numericId) { map.set(threadId, numericId); cacheHistoricalChatMode(numericId, chatMode); - // Trigger a re-render so the `setRunConfig` effect below picks up the - // new id. We don't store the map in state because we never need to - // diff/render it directly — only react when an entry changes. - forceServerIdTick((tick) => tick + 1); + // Keep the sidebar and run configuration in sync with the server id. + setServerConversationIds(new Map(map)); } if (initialQuestion && previous !== numericId) { @@ -269,6 +266,7 @@ const HomeContent: FC<{ setGeneratedTitles((titles) => { const next = new Map(titles); next.set(threadId, title); + next.set(numericId, title); return next; }); }) @@ -727,6 +725,7 @@ const HomeContent: FC<{ const handleThreadBack = useCallback(async () => { shouldRestoreAgentRef.current = false; await runtime.threads.switchToNewThread(); + await runtime.threads.reload(); onBack(); }, [onBack, runtime]); @@ -737,22 +736,23 @@ const HomeContent: FC<{ }, [onBack]); const handleNewConversation = useCallback(async () => { - handlePrepareNewConversation(); + shouldRestoreAgentRef.current = false; await runtime.threads.switchToNewThread(); - }, [handlePrepareNewConversation, runtime]); + await runtime.threads.reload(); + onBack(); + }, [onBack, runtime]); const handleAgentSelectedFromLanding = useCallback( async (agent: Agent) => { shouldRestoreAgentRef.current = true; - await runtime.threads.switchToNewThread(); const thread = runtime.threads.getItemById( runtime.threads.getState().mainThreadId ); await thread.initialize(); await thread.updateCustom({ agentId: agent.id }); - onAgentSelected(agent); + setSelectedAgent(agent); }, - [onAgentSelected, runtime] + [runtime, setSelectedAgent] ); // Conditional rendering must happen after all hooks @@ -770,6 +770,7 @@ const HomeContent: FC<{ diff --git a/frontend/features/workbench/WorkbenchPage.tsx b/frontend/features/workbench/WorkbenchPage.tsx index 9e66858443..ee0d3b6c00 100644 --- a/frontend/features/workbench/WorkbenchPage.tsx +++ b/frontend/features/workbench/WorkbenchPage.tsx @@ -195,7 +195,9 @@ const HomeContent: FC = () => { const [generatedTitles, setGeneratedTitles] = useState>( new Map() ); - const [, forceServerIdTick] = useState(0); + const [serverConversationIds, setServerConversationIds] = useState< + Map + >(new Map()); const handleServerConversationId = useCallback( (threadId: string, serverId: string, initialQuestion?: string) => { @@ -205,10 +207,8 @@ const HomeContent: FC = () => { if (previous !== numericId) { map.set(threadId, numericId); cacheHistoricalChatMode(numericId, chatMode); - // Trigger a re-render so the `setRunConfig` effect below picks up the - // new id. We don't store the map in state because we never need to - // diff/render it directly — only react when an entry changes. - forceServerIdTick((tick) => tick + 1); + // Keep the sidebar and run configuration in sync with the server id. + setServerConversationIds(new Map(map)); if (workbenchState.configVersion === 0) { dispatchWorkbench({ type: "set-version", version: 1 }); } @@ -1167,6 +1167,7 @@ const HomeContent: FC = () => { diff --git a/frontend/tests/component/agentChatPageDeepLink.test.tsx b/frontend/tests/component/agentChatPageDeepLink.test.tsx index 3e8e53d578..cab7c694dc 100644 --- a/frontend/tests/component/agentChatPageDeepLink.test.tsx +++ b/frontend/tests/component/agentChatPageDeepLink.test.tsx @@ -7,13 +7,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import Home from "../../app/[locale]/newchat/page"; const switchToNewThread = vi.fn(); +const reloadThreads = vi.fn(); +const initializeThread = vi.fn().mockResolvedValue(undefined); +const updateThreadCustom = vi.fn().mockResolvedValue(undefined); const runtimeMock = { threads: { switchToNewThread, + reload: reloadThreads, getItemById: vi.fn(() => ({ - initialize: vi.fn().mockResolvedValue(undefined), - updateCustom: vi.fn().mockResolvedValue(undefined), + initialize: initializeThread, + updateCustom: updateThreadCustom, })), getState: vi.fn(() => ({ mainThreadId: "main-thread" })), }, @@ -130,6 +134,9 @@ vi.mock("react-i18next", () => ({ describe("newchat Agent deep-link page wiring", () => { beforeEach(() => { switchToNewThread.mockReset(); + reloadThreads.mockReset().mockResolvedValue(undefined); + initializeThread.mockClear(); + updateThreadCustom.mockClear(); window.history.replaceState({}, "", "/zh/newchat?agent_id=41"); }); @@ -161,6 +168,30 @@ describe("newchat Agent deep-link page wiring", () => { expect( await screen.findByText("Agent chat: Fixed Agent") ).toBeInTheDocument(); - expect(switchToNewThread.mock.calls.length).toBe(callsAfterDeepLink + 3); + expect(switchToNewThread.mock.calls.length).toBe(callsAfterDeepLink + 1); + expect(reloadThreads).toHaveBeenCalledTimes(1); + expect(initializeThread).toHaveBeenCalledTimes(1); + expect(updateThreadCustom).toHaveBeenCalledWith({ agentId: agent.id }); + }); + + it("reloads the saved conversation before returning to the Agent list", async () => { + switchToNewThread.mockResolvedValue(undefined); + let finishReload!: () => void; + reloadThreads.mockImplementation( + () => + new Promise((resolve) => { + finishReload = resolve; + }) + ); + const user = userEvent.setup(); + render(); + await screen.findByText("Agent chat: Fixed Agent"); + + await user.click(screen.getByRole("button", { name: "back to list" })); + expect(reloadThreads).toHaveBeenCalledTimes(1); + expect(screen.getByText("Agent chat: Fixed Agent")).toBeInTheDocument(); + + finishReload(); + expect(await screen.findByText("Agent list")).toBeInTheDocument(); }); }); From fccf800fa383d23ba130e71752822f1ac3bd5b1d Mon Sep 17 00:00:00 2001 From: xuyaqist Date: Tue, 29 Sep 2026 17:15:09 +0800 Subject: [PATCH 4/7] fix: show repository status in paged agent list --- backend/apps/agent_app.py | 4 + .../management/services/agent/management.py | 37 ++++++ .../agent-space/components/MyAgentCard.tsx | 63 ++++------ .../app/[locale]/agent-space/my-agent.tsx | 13 +- .../components/McpToolsSearchFilterBar.tsx | 8 +- .../components/MineMcpServiceCard.tsx | 6 +- .../skill-space/components/MineSkillsView.tsx | 47 ++++--- .../useAgentRepositoryListings.ts | 1 + frontend/public/locales/en/common.json | 2 +- frontend/public/locales/zh/common.json | 2 +- frontend/services/agentConfigService.ts | 5 + .../tests/component/agentUsageGuide.test.tsx | 118 ++++++++++++++++++ frontend/types/agentConfig.ts | 7 +- test/backend/app/test_agent_app.py | 2 + test/backend/services/test_agent_service.py | 50 ++++++++ 15 files changed, 284 insertions(+), 81 deletions(-) diff --git a/backend/apps/agent_app.py b/backend/apps/agent_app.py index 89390b2997..a5a2135754 100644 --- a/backend/apps/agent_app.py +++ b/backend/apps/agent_app.py @@ -837,6 +837,9 @@ async def list_agent_page_api( search_tag_predicates: Optional[str] = Query(None, description="Text-search tag predicates as JSON"), page: int = Query(1, ge=1, description="Page number starting from 1"), page_size: int = Query(20, ge=1, le=100, description="Items per page"), + include_repository_info: bool = Query( + False, description="Include repository listings for agents on this page" + ), authorization: Optional[str] = Header(None), request: Request = None, ): @@ -857,6 +860,7 @@ async def list_agent_page_api( "search": search, "page": page, "page_size": page_size, + "include_repository_info": include_repository_info, } if created_by: kwargs["created_by"] = created_by diff --git a/backend/management/services/agent/management.py b/backend/management/services/agent/management.py index 2e12564abd..f95f33a0a6 100644 --- a/backend/management/services/agent/management.py +++ b/backend/management/services/agent/management.py @@ -14,6 +14,7 @@ from agents.create_agent_info import create_tool_config_list from utils.agent_transfer_utils import portable_tool_params, validate_import_tool_params from services.agent_version_service import publish_version_impl +from consts.agent_repository import STATUS_PENDING_REVIEW, STATUS_REJECTED, STATUS_SHARED from consts.const import TOOL_TYPE_MAPPING, \ MODEL_CONFIG_MAPPING, CAN_EDIT_ALL_USER_ROLES, PERMISSION_PRIVATE from consts.exceptions import ( @@ -67,6 +68,7 @@ ) from database import skill_db from management.services.skill.service import SkillService +from database.agent_repository_db import list_agent_repository_by_agent_ids from database.agent_version_db import batch_search_version_names, query_version_list from database.group_db import query_group_ids_by_user from database.user_tenant_db import get_user_tenant_by_user_id @@ -932,6 +934,7 @@ async def list_agent_page_impl( created_by_not: Optional[str] = None, tag_predicates: Optional[list] = None, search_tag_predicates: Optional[list] = None, + include_repository_info: bool = False, ) -> Dict[str, Any]: """List visible agents with server-side filters and pagination.""" if created_by and created_by_not: @@ -1127,6 +1130,40 @@ async def list_agent_page_impl( ) agent["version_label"] = version.get("version_name") agent["version_create_time"] = version.get("create_time") + if include_repository_info: + for scope_tenant_id in tenant_ids: + scoped_agents = [ + agent for scope, agent in paged_scoped_agents + if scope == scope_tenant_id + ] + if not scoped_agents: + continue + repository_records = list_agent_repository_by_agent_ids( + [int(agent["agent_id"]) for agent in scoped_agents], + statuses=(STATUS_PENDING_REVIEW, STATUS_REJECTED, STATUS_SHARED), + publisher_tenant_id=scope_tenant_id, + ) + repository_by_agent_id: dict[int, list[dict]] = {} + for record in repository_records: + created_at = record.get("create_time") + repository_by_agent_id.setdefault(int(record["agent_id"]), []).append( + { + "agent_repository_id": record["agent_repository_id"], + "status": record["status"], + "version_no": record["version_no"], + "version_label": record.get("version_name"), + "create_time": ( + created_at.isoformat() + if hasattr(created_at, "isoformat") + else created_at + ), + "content": record.get("content"), + } + ) + for agent in scoped_agents: + agent["repository_info"] = repository_by_agent_id.get( + int(agent["agent_id"]), [] + ) paged_agents = [agent for _, agent in paged_scoped_agents] return { "items": paged_agents, diff --git a/frontend/app/[locale]/agent-space/components/MyAgentCard.tsx b/frontend/app/[locale]/agent-space/components/MyAgentCard.tsx index c997191b2b..7d8b8d64fc 100644 --- a/frontend/app/[locale]/agent-space/components/MyAgentCard.tsx +++ b/frontend/app/[locale]/agent-space/components/MyAgentCard.tsx @@ -1,9 +1,7 @@ "use client"; -import { Button, Dropdown, Spin, Tooltip } from "antd"; +import { Button, Dropdown, Tooltip } from "antd"; import type { MenuProps } from "antd"; -import { useState } from "react"; -import { useAgentRepositoryListings } from "@/hooks/agentRepository/useAgentRepositoryListings"; import { ClipboardCheck, Clock, @@ -21,7 +19,7 @@ import { getUnavailableReasonLabels } from "@/lib/agentLabelMapper"; import { formatMineDate, getMineCardMenuActions, - toMineRepositoryInfo, + getMineCardRepositoryStatusBadge, type MineCardMenuAction, } from "@/lib/agentRepositoryMine"; import type { MyEditableAgentItem } from "@/types/agentRepository"; @@ -69,16 +67,6 @@ export function MyAgentCard({ isDeleting = false, }: MyAgentCardProps) { const { t } = useTranslation("common"); - const [menuOpen, setMenuOpen] = useState(false); - const { - data: listingData, - isLoading: isListingLoading, - isError: isListingError, - refetch, - } = useAgentRepositoryListings( - { agent_id: agent.agent_id, page: 1, page_size: 100 }, - menuOpen || guideMenuOpen === true - ); const title = agent.name?.trim() || t("agentRepository.card.untitled"); const description = @@ -88,8 +76,6 @@ export function MyAgentCard({ agent.unavailable_reasons ?? [], t ); - const repositoryInfo = toMineRepositoryInfo(listingData?.items ?? []); - const agentWithRepository = { ...agent, repository_info: repositoryInfo }; const { canOpen: published } = getAgentUsageGuideAccess({ currentVersionNo: agent.current_version_no, permission: agent.permission, @@ -99,9 +85,10 @@ export function MyAgentCard({ const canEdit = agent.permission !== "READ_ONLY"; const canView = (agent.current_version_no ?? 0) > 0; const canEvaluate = canView; - const menuActions = listingData - ? getMineCardMenuActions(agentWithRepository) - : []; + const menuActions = getMineCardMenuActions(agent); + const repositoryBadge = getMineCardRepositoryStatusBadge( + agent.repository_info + ); const menuItems: MenuProps["items"] = menuActions.map((action) => { const icon = @@ -122,29 +109,13 @@ export function MyAgentCard({ return; } onViewReview( - agentWithRepository, + agent, action === "reviewUpdate" ? "reviewUpdate" : "review" ); }, }; }); - if (isListingLoading) { - menuItems.unshift({ - key: "loading", - label: , - disabled: true, - }); - } else if (isListingError) { - menuItems.unshift({ - key: "retry", - label: t("repository.common.retry"), - onClick: () => { - void refetch(); - }, - }); - } - if (canEvaluate) { menuItems.push({ key: "evaluate", @@ -223,7 +194,7 @@ export function MyAgentCard({ ))}
@@ -888,7 +881,7 @@ function toMyAgentItem(agent: Agent): MyEditableAgentItem { version_create_time: agent.version_create_time ?? null, permission: agent.permission, tags: agent.tags, - repository_info: [], + repository_info: agent.repository_info ?? [], }; } diff --git a/frontend/app/[locale]/mcp-space/components/McpToolsSearchFilterBar.tsx b/frontend/app/[locale]/mcp-space/components/McpToolsSearchFilterBar.tsx index 83e9a04223..29b13a732c 100644 --- a/frontend/app/[locale]/mcp-space/components/McpToolsSearchFilterBar.tsx +++ b/frontend/app/[locale]/mcp-space/components/McpToolsSearchFilterBar.tsx @@ -72,13 +72,7 @@ export default function McpToolsSearchFilterBar({ }`} > {item.label} - - {item.count} - + {item.count} ); diff --git a/frontend/app/[locale]/mcp-space/components/MineMcpServiceCard.tsx b/frontend/app/[locale]/mcp-space/components/MineMcpServiceCard.tsx index 23c316872b..3e74165e36 100644 --- a/frontend/app/[locale]/mcp-space/components/MineMcpServiceCard.tsx +++ b/frontend/app/[locale]/mcp-space/components/MineMcpServiceCard.tsx @@ -304,7 +304,11 @@ export default function MineMcpServiceCard({ - ) : null} {canEdit ? (