From 519e4c0c01c595b2fd4b6a78989fc964d8ed16d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Tue, 1 Sep 2026 14:51:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E4=BF=AE=E5=A4=8D=20Strategy/Wor?= =?UTF-8?q?kspace=20=E7=BB=9F=E4=B8=80=E8=81=8A=E5=A4=A9=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E4=B8=8E=E5=8E=86=E5=8F=B2=E8=B7=AF=E7=94=B1=E5=BD=92=E5=B1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workspace/agents/agent-chat-sidebar.tsx | 23 +++------ .../workspace/general-qa-sidebar.tsx | 51 +++++++++++-------- .../workspace/workspace-sidebar.tsx | 4 +- frontend-web/src/pages/AgentChatPage.tsx | 48 ++++++++++------- frontend-web/src/pages/ChatPage.tsx | 18 ++++--- frontend-web/src/pages/StrategyRoutes.tsx | 27 +++++++--- frontend-web/src/pages/WorkspaceRoutes.tsx | 6 ++- 7 files changed, 107 insertions(+), 70 deletions(-) diff --git a/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx b/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx index 4ac99677..45f39694 100644 --- a/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx +++ b/frontend-web/src/components/workspace/agents/agent-chat-sidebar.tsx @@ -64,7 +64,7 @@ import { import { useDeleteThread, useRenameThread, - useThreadsPage, + useThreads, useToggleThreadFavorite, useToggleThreadPin, } from "@/core/threads/hooks"; @@ -351,8 +351,8 @@ export function AgentChatSidebar({ activeAgentId }: { activeAgentId?: string }) } /** - * 当前智能体的「最近对话」面板(参考首页 GeneralQaSidebar 的「最近的对话」区块)。 - * 按 agent_id 在服务端过滤;支持重命名/置顶/收藏/分享/导出/删除。 + * 「最近的对话」面板(参考首页 GeneralQaSidebar 的「最近的对话」区块)。 + * 跨智能体的近期会话,点击打开;支持重命名/置顶/收藏/分享/导出/删除。 */ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { const { t } = useI18n(); @@ -361,12 +361,7 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { const pathOf = usePathOfThread(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); - const { data: threadPage } = useThreadsPage({ - page: 1, - pageSize: 50, - metadata: { agent_id: activeAgentId ?? "" }, - }); - const threads = threadPage?.threads ?? []; + const { data: threads = [] } = useThreads(); const { mutate: deleteThread } = useDeleteThread(); const { mutate: renameThread } = useRenameThread(); const { toggle: togglePin } = useToggleThreadPin(); @@ -378,8 +373,8 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { const [renameValue, setRenameValue] = useState(""); const [shareThreadId, setShareThreadId] = useState(null); - // 服务端先按当前智能体过滤;客户端再排除调度/会商/报告结构&写作配置向导等 - // 系统会话。智能体创建(bootstrap)会话保留展示(带机器人图标、点击回创建页);置顶优先。 + // 排除调度/会商/报告结构&写作配置向导等系统会话,与首页一致; + // 智能体创建(bootstrap)会话保留展示(带机器人图标、点击回创建页);置顶优先。 const visibleThreads = useMemo( () => sortThreadsPinnedFirst(threads.filter((thread) => !isHiddenFromRecentChats(thread))), [threads], @@ -484,11 +479,7 @@ function RecentChatsPane({ activeAgentId }: { activeAgentId?: string }) { ) : ( filteredThreads.map((thread) => { const isBoot = isBootstrapThread(thread); - // 正常记录依据自身保存的 agent_id 跳转;早期记录可能尚未写入 - // agent_id,此时保留当前智能体上下文,避免从智能体页误落到通用问答。 - const href = isBoot - ? bootstrapResumePath(thread.thread_id) - : pathOf(thread, { agent_id: activeAgentId }); + const href = isBoot ? bootstrapResumePath(thread.thread_id) : pathOf(thread); const isActive = href === pathname; const pinned = isThreadPinned(thread); const favorited = isThreadFavorite(thread); diff --git a/frontend-web/src/components/workspace/general-qa-sidebar.tsx b/frontend-web/src/components/workspace/general-qa-sidebar.tsx index 46d26231..43e6062d 100644 --- a/frontend-web/src/components/workspace/general-qa-sidebar.tsx +++ b/frontend-web/src/components/workspace/general-qa-sidebar.tsx @@ -7,7 +7,8 @@ * 共享的 WorkspaceNavChatList / FrequentAgentsList / RecentChatList 里塞会越来越难 * 维护,故单独成一个组件,只在 `/page/workspace/chats*` 的全量布局下挂载,互不影响。 * - * 三块结构(自上而下): + * 可挂在 workspace / strategy 两个普通问答路由下;导航目标由 RouteBaseProvider + * 决定。三块结构(自上而下): * 1. 「新对话」胶囊按钮 + 收起整列的圆形按钮; * 2. 「常用智能体」—— 彩色图标卡(名称 + 两行简介,命中为选中态:主色描边 + 右侧主色条); * 3. 「最近的对话」—— 计数徽标 + 折叠、历史搜索框、会话列表(悬浮「更多」菜单: @@ -85,6 +86,7 @@ import { titleOfThread, } from "@/core/threads/utils"; import { buildFullChatNavigationPath } from "@/core/page-layout/chats-iframe-route"; +import { useRouteBase } from "@/core/route-base"; import { env } from "@/env"; import { desensitize } from "@/lib/desensitize"; import { isIMEComposing } from "@/lib/ime"; @@ -93,11 +95,6 @@ import { cn } from "@/lib/utils"; import { agentColor } from "./agent-icon-color"; import { SidebarCollapseButton } from "./workspace-header"; -const NEW_CHAT_PATH = "/page/workspace/chats/new"; -/** 「更多」常用智能体跳转目标:智能体管理页。 */ -const MORE_AGENTS_HREF = "/page/workspace/agents"; -/** 「更多最近对话」跳转目标。 */ -const MORE_CHATS_HREF = "/page/workspace/chats"; /** 常用智能体最多展示个数。 */ const MAX_FREQUENT = 4; @@ -142,6 +139,7 @@ function SectionLabel({ export function GeneralQaSidebar() { const { t } = useI18n(); + const routeBase = useRouteBase(); // 智能体图标配色分主题:深蓝主题用规范蓝-青系,其它(浅色)保持原高彩度色,互不影响。 const { theme } = useTheme(); const isDark = theme === "dark"; @@ -188,9 +186,14 @@ export function GeneralQaSidebar() { const searchParams = useSearchParams(); const activeAgentId = searchParams.get("agent"); const currentSearch = searchParams.toString(); - const newChatHref = buildFullChatNavigationPath(NEW_CHAT_PATH, currentSearch); - const moreAgentsHref = buildFullChatNavigationPath(MORE_AGENTS_HREF, currentSearch); - const moreChatsHref = buildFullChatNavigationPath(MORE_CHATS_HREF, currentSearch); + const chatsBasePath = `${routeBase}/chats`; + const newChatPath = `${chatsBasePath}/new`; + const newChatHref = buildFullChatNavigationPath(newChatPath, currentSearch); + const moreAgentsHref = buildFullChatNavigationPath( + `${routeBase}/agents`, + currentSearch, + ); + const moreChatsHref = buildFullChatNavigationPath(chatsBasePath, currentSearch); // 点击常用智能体 = 在通用问答右侧**原地嵌入**该智能体的对话页:停留在 /chats/new, // 仅给 URL 加 ?agent=,由 NewChatContent 把右侧问答区换成 AgentChatPage。 @@ -199,19 +202,18 @@ export function GeneralQaSidebar() { (agentId: string) => { navigate( buildFullChatNavigationPath( - `${NEW_CHAT_PATH}?agent=${encodeURIComponent(agentId)}`, + routeBase === "/page/workspace" + ? `${newChatPath}?agent=${encodeURIComponent(agentId)}` + : `${routeBase}/agents/${encodeURIComponent(agentId)}/chats/new`, currentSearch, ), ); }, - [currentSearch, navigate], + [currentSearch, navigate, newChatPath, routeBase], ); // ── 最近的对话取数 + 搜索 ── - const { thread_id: threadIdFromPath, agent_id: agentIdFromPath } = useParams<{ - thread_id: string; - agent_id?: string; - }>(); + const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const { data: threads = [] } = useThreads(); const { mutate: deleteThread } = useDeleteThread(); const { mutate: renameThread } = useRenameThread(); @@ -224,6 +226,14 @@ export function GeneralQaSidebar() { const [renameValue, setRenameValue] = useState(""); const [shareThreadId, setShareThreadId] = useState(null); + const pathForThread = useCallback( + (thread: AgentThread) => + isBootstrapThread(thread) + ? listPathOfThread(thread) + : pathOfThread(thread, undefined, routeBase), + [routeBase], + ); + const visibleThreads = useMemo( () => // 智能体创建(bootstrap)会话也展示(带机器人图标、点击回创建页); @@ -254,21 +264,22 @@ export function GeneralQaSidebar() { deleteThread({ threadId }); if (threadId === threadIdFromPath) { const threadIndex = threads.findIndex((th) => th.thread_id === threadId); - let nextThreadPath = pathOfThread("new", { agent_id: agentIdFromPath }); + let nextThreadPath = newChatPath; if (threadIndex > -1) { if (threads[threadIndex + 1]) { - nextThreadPath = listPathOfThread(threads[threadIndex + 1]!); + nextThreadPath = pathForThread(threads[threadIndex + 1]!); } else if (threads[threadIndex - 1]) { - nextThreadPath = listPathOfThread(threads[threadIndex - 1]!); + nextThreadPath = pathForThread(threads[threadIndex - 1]!); } } void router.push(buildFullChatNavigationPath(nextThreadPath, currentSearch)); } }, [ - agentIdFromPath, currentSearch, deleteThread, + newChatPath, + pathForThread, router, threadIdFromPath, threads, @@ -467,7 +478,7 @@ export function GeneralQaSidebar() { ) : ( filteredThreads.map((thread) => { const href = buildFullChatNavigationPath( - listPathOfThread(thread), + pathForThread(thread), currentSearch, ); const isActive = href === pathname; diff --git a/frontend-web/src/components/workspace/workspace-sidebar.tsx b/frontend-web/src/components/workspace/workspace-sidebar.tsx index 7614b40b..aa131c33 100644 --- a/frontend-web/src/components/workspace/workspace-sidebar.tsx +++ b/frontend-web/src/components/workspace/workspace-sidebar.tsx @@ -34,7 +34,9 @@ export function WorkspaceSidebar({ // 通用问答(全量模式)左栏:新建回答 → 常用智能体 → 聊天记录(需求 0616 #1)。 // 仅在通用问答(/page/workspace/chats*)下启用三块式布局;点击常用智能体后是 // 「原地嵌入」(停留在 /chats/new?agent=...,仅右侧问答区换成智能体对话),左侧栏不变。 - const isGeneralQa = pathname.startsWith("/page/workspace/chats"); + const isGeneralQa = + pathname.startsWith("/page/workspace/chats") || + pathname.startsWith("/page/strategy/chats"); if (forceCollapsed) { return ( diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index 57e0d7cb..7121f32b 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -3,6 +3,7 @@ import { BotIcon, Compass, + PanelLeftCloseIcon, PanelLeftOpenIcon, PanelRightOpenIcon, PlusSquare, @@ -14,7 +15,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } fro import type { PromptInputMessage } from "@/components/ai-elements/prompt-input"; import { Button } from "@/components/ui/button"; -import { useSidebarSafe } from "@/components/ui/sidebar"; import { AgentChatSidebar } from "@/components/workspace/agents/agent-chat-sidebar"; import { AgentDetailPanel } from "@/components/workspace/agents/agent-detail-panel"; import { AgentQuestionChips } from "@/components/workspace/agents/agent-question-chips"; @@ -87,6 +87,7 @@ import { AGENTFX_REPORT_JSON_OUTPUT_PATH, notifyTaskReportSaved, } from "@/strategy-components/lib/agentfx-report-schema"; +import { cn } from "@/lib/utils"; import type { Message } from "@langchain/langgraph-sdk"; /** 内置「模板数据转换器」agent 的稳定 id。仅此 agent 的对话页显示「页面模板 + 渲染预览」调试入口。 */ @@ -227,7 +228,9 @@ export default function AgentChatPage({ const { t } = useI18n(); const [showFollowups, setShowFollowups] = useState(false); const router = useRouter(); - const workspaceSidebar = useSidebarSafe(); + // 独立智能体页的左列是 AgentChatSidebar,而不是外层 WorkspaceSidebar; + // 由本页维护其收展状态,避免顶部按钮切到了不可见的另一层侧栏。 + const [agentSidebarOpen, setAgentSidebarOpen] = useState(true); const { agent_id: agentIdFromPath } = useParams<{ agent_id: string; @@ -797,7 +800,11 @@ export default function AgentChatPage({ {showAgentList && (
@@ -809,23 +816,25 @@ export default function AgentChatPage({
{/* Header in normal flow, fixed height. */}
- {/* 工作区左栏收起后,在智能体聊天区左上角提供展开入口。 */} - {showAgentList && - !isEmbedded && - workspaceSidebar && - !workspaceSidebar.open && ( - - )} + )} + + )}
handleSubmit({ text, files: [] })} collapsed={detailCollapsed} onCollapsedChange={setDetailCollapsed} + showRecentChats={!taskCtxOn} compactAppearanceActions={compactAppearanceActions} />
diff --git a/frontend-web/src/pages/ChatPage.tsx b/frontend-web/src/pages/ChatPage.tsx index e516a9f2..4878610a 100644 --- a/frontend-web/src/pages/ChatPage.tsx +++ b/frontend-web/src/pages/ChatPage.tsx @@ -46,7 +46,7 @@ import { isEmbedActive, isHideChromeActive } from "@/core/embed/embed-session"; import { isWujieEnv } from "@/core/embed/wujie"; import { useSidebar } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; -import { MessageSquarePlus, PanelLeftOpen } from "lucide-react"; +import { MessageSquarePlus, PanelLeftClose, PanelLeftOpen } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useAgentSelector, useAgentsPaginated } from "@/core/agents"; import { useI18n } from "@/core/i18n/hooks"; @@ -728,18 +728,22 @@ export default function ChatPage() { )} >
- {/* 左栏收起后,展开入口固定在聊天区左上角。 */} - {compactChrome && !historySidebarOpen && !compactActionsHidden && ( + {/* 左上角始终提供左栏切换;展开态显示“收起”,收起态显示“展开”。 */} + {compactChrome && !compactActionsHidden && ( )} - {compactChrome && !historySidebarOpen && !compactActionsHidden && ( + {compactChrome && !compactActionsHidden && (
+ ); +} + export default function StrategyRoutes() { const storedAuth = getStoredAuth(); const canManageBusinessMappings = !storedAuth || storedAuth.system_role === "admin"; @@ -342,11 +361,7 @@ export default function StrategyRoutes() { } /> - - - } + element={} /> } /> diff --git a/frontend-web/src/pages/WorkspaceRoutes.tsx b/frontend-web/src/pages/WorkspaceRoutes.tsx index cf1cb3dc..8c573aef 100644 --- a/frontend-web/src/pages/WorkspaceRoutes.tsx +++ b/frontend-web/src/pages/WorkspaceRoutes.tsx @@ -182,7 +182,11 @@ export function WorkspaceLayout({ '--header-height': headerHidden ? '0rem' : '4rem', } as CSSProperties } - key={String(compactWorkspaceSidebar)} + // Agent and general-chat routes share this layout component. Keep their + // sidebar state separate: otherwise React preserves the collapsed agent + // state when a history item switches to `/chats/:thread_id`, while the + // new page's header button is meant to control GeneralQaSidebar. + key={`${String(compactWorkspaceSidebar)}:${isGeneralQa ? 'general' : 'workspace'}`} data-page-sidebar-collapsed={pageSidebarCollapsed} defaultOpen={ forceWorkspaceSidebarCollapsed