diff --git a/web/src/app/(main)/chat/page.tsx b/web/src/app/(main)/chat/page.tsx index 90ba8a0..696f4cd 100644 --- a/web/src/app/(main)/chat/page.tsx +++ b/web/src/app/(main)/chat/page.tsx @@ -46,7 +46,7 @@ import { } from "@/components/ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/lib/api"; import { shouldSubmitOnKey, useChatSendMode } from "@/lib/chat-send-mode"; @@ -908,6 +908,7 @@ export default function ChatPage() { const [agents, setAgents] = React.useState([]); const [profiles, setProfiles] = React.useState([]); const [convs, setConvs] = React.useState([]); + const [agentFilter, setAgentFilter] = React.useState(null); const [selectedId, setSelectedId] = React.useState(null); const [renamingId, setRenamingId] = React.useState(null); const [renameText, setRenameText] = React.useState(""); @@ -978,17 +979,44 @@ export default function ChatPage() { const selected = React.useMemo(() => convs.find((c) => c.id === selectedId) ?? null, [convs, selectedId]); const agentByKey = React.useMemo(() => new Map(agents.map((agent) => [agent.key, agent])), [agents]); + const filteredConversations = React.useMemo( + () => (agentFilter === null ? convs : convs.filter((conversation) => conversation.agent_key === agentFilter)), + [convs, agentFilter], + ); const visibleConversations = React.useMemo( - () => convs.slice(0, visibleConversationCount), - [convs, visibleConversationCount], + () => filteredConversations.slice(0, visibleConversationCount), + [filteredConversations, visibleConversationCount], ); // conversation agents: custom agents + conversational built-ins (role=assistant, // e.g. Auto / 渗透测试). The orchestration built-ins (goals/planner/mainagent/worker) // are task-specific and stay hidden from the chat page. const chatAgents = React.useMemo(() => agents.filter((a) => !a.builtin || a.role === "assistant"), [agents]); + const agentFilterOptions = React.useMemo(() => { + const counts = new Map(); + for (const conversation of convs) { + counts.set(conversation.agent_key, (counts.get(conversation.agent_key) ?? 0) + 1); + } + // Include historical sources even if their Agent has since been disabled or deleted. + const keys = new Set([...chatAgents.map((agent) => agent.key), ...counts.keys()]); + if (agentFilter !== null) keys.add(agentFilter); + return [...keys] + .map((key) => ({ key, name: agentByKey.get(key)?.name || key, count: counts.get(key) ?? 0 })) + .sort((a, b) => a.name.localeCompare(b.name, "zh-CN")); + }, [convs, chatAgents, agentByKey, agentFilter]); + const conversationCountLabel = + agentFilter === null ? `共 ${convs.length} 个` : `${filteredConversations.length} / ${convs.length} 个`; + + function changeAgentFilter(key: string | null) { + setAgentFilter(key); + setVisibleConversationCount(CONVERSATION_LIST_PAGE); + setSelectedConversationIds(new Set()); + setBulkDeleteOpen(false); + setRenamingId(null); + } const selectedConversationCount = selectedConversationIds.size; - const allConversationsSelected = convs.length > 0 && selectedConversationCount === convs.length; + const allConversationsSelected = + filteredConversations.length > 0 && selectedConversationCount === filteredConversations.length; const someConversationsSelected = selectedConversationCount > 0 && !allConversationsSelected; let conversationHeaderChecked: boolean | "indeterminate" = false; if (allConversationsSelected) conversationHeaderChecked = true; @@ -1004,7 +1032,9 @@ export default function ChatPage() { }, []); function toggleAllConversations(checked: boolean) { - setSelectedConversationIds(checked ? new Set(convs.map((conversation) => conversation.id)) : new Set()); + setSelectedConversationIds( + checked ? new Set(filteredConversations.map((conversation) => conversation.id)) : new Set(), + ); } function exitSelectionMode() { @@ -1117,16 +1147,37 @@ export default function ChatPage() { + {convs.length > 0 && (selectionMode ? (
toggleAllConversations(checked === true)} - aria-label="选择全部对话" + aria-label="选择当前筛选的全部对话" + disabled={filteredConversations.length === 0 || bulkDeleting} /> - {selectedConversationCount > 0 ? `已选 ${selectedConversationCount} 个` : `共 ${convs.length} 个`} + {selectedConversationCount > 0 ? `已选 ${selectedConversationCount} 个` : conversationCountLabel} {selectedConversationCount > 0 && (
))} - +
- {convs.length === 0 &&

暂无对话

} + {filteredConversations.length === 0 && ( +

+ {agentFilter === null ? "暂无对话" : "该 Agent 暂无对话"} +

+ )} {visibleConversations.map((c) => ( ))} - {visibleConversationCount < convs.length && ( + {visibleConversationCount < filteredConversations.length && (