diff --git a/CLAUDE.md b/CLAUDE.md index 7e0bd149..24bf01d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,7 +84,7 @@ The frontend (`frontend-web/`) is a React 19 + Vite SPA: - **LLM streaming**: `@langchain/langgraph-sdk` streams messages from the backend via SSE - **UI**: Radix UI primitives + TailwindCSS v4 + shadcn-style components in `src/components/ui/` - **Shims**: `src/shims/` provides `next/link` and `next/navigation` polyfills (original code was Next.js) -- **Login URL params** (`LoginPage.tsx`, frontend-only — the backend never sees these): besides the auth params (`ticket`/`userId`/`env`/`theme`/`yUserId`/`token`/`access_token`/`authToken`/`password`), the page supports a **task deep-link** via `?taskId=&goPath=`. After login it calls the consumer API `GET {VITE_AIPI_CONSUMER_URL}/taskAnalysSearch/cop-task-detail?taskId=` (header `Authorization: Bearer admin`, auto-injected by `utils/request.ts`), stashes the full `data` (`id/overview/taskDirection/taskName/taskContent`) into `sessionStorage["login.copTaskDetail"]`, then routes by `goPath`: base codes `3qfx`/`rwfx`/`xdfx` → that business's configured **single agent** Q&A (resolved via `/api/business-mapping`, opening message in `sessionStorage.pendingAgentMessage`); the `-A` suffix (`3qfx-A`/…) → **multi-agent roundtable** with `roundtablePrefill.businessCode` so the page auto-selects the configured 业务链条. goPath→businessCode: `3qfx`→`3Q`, `rwfx`→`6BF`, `xdfx`→`7BF`. Any failure (no consumer URL / fetch error / unmapped goPath / no configured agent) silently falls back to the default home. Helpers: `core/auth/task-deeplink.ts`. +- **Login URL params** (`LoginPage.tsx`, frontend-only — the backend never sees these): besides the auth params (`ticket`/`userId`/`env`/`theme`/`yUserId`/`token`/`access_token`/`authToken`/`password`), the page supports a **task deep-link** via `?taskId=&goPath=`. After login it calls the consumer API `GET {VITE_AIPI_CONSUMER_URL}/taskAnalysSearch/cop-task-detail?taskId=` (header `Authorization: Bearer admin`, auto-injected by `utils/request.ts`), stashes the full `data` (`id/overview/taskDirection/taskName/taskContent`) into `sessionStorage["login.copTaskDetail"]`, then routes by `goPath`: base codes `3qfx`/`rwfx`/`xdfx` → that business's configured **single agent** Q&A (resolved via `/api/business-mapping`, opening message in `sessionStorage.pendingAgentMessage`); the `-A` suffix (`3qfx-A`/…) → **multi-agent roundtable** with `roundtablePrefill.businessCode` so the page auto-selects the configured 业务链条. goPath→businessCode: `3qfx`→`3Q`, `rwfx`→`6BF`, `xdfx`→`7BF`. **Both targets render embedded under the 任务管理 layout** — nested routes under `task-management//{roundtable,agent/:agent_id/chats/:thread_id}` in `StrategyRoutes.tsx` (`` = `plan-task-sentiment` / `plan-task/plan-task-fx` / `plan-task/plan-task-action`, matching the COH中任务 menu paths in `sidebar-menu.ts`) so the global sidebar keeps 任务管理 → COH中任务 → the matching business highlighted. The agent chat is rendered via `TaskEmbeddedAgentChat`, which passes `AgentChatPage`'s `chatBasePath` prop so the post-send URL rewrite + new-chat button stay in the task-management context (instead of the standalone `/agents/...` route). Any failure (no consumer URL / fetch error / unmapped goPath / no configured agent) silently falls back to the default home. Helpers: `core/auth/task-deeplink.ts`. ### Configuration diff --git a/frontend-web/src/App.tsx b/frontend-web/src/App.tsx index 0c191a09..88ee59df 100644 --- a/frontend-web/src/App.tsx +++ b/frontend-web/src/App.tsx @@ -86,12 +86,22 @@ function AppProviders({ children }: { children: React.ReactNode }) { ); } +/** + * 根路由 `/` → `/login` 重定向,**保留 query string**。 + * 外部系统的任务深链落在 `#/?taskId=...&goPath=...`,裸 `` 会丢掉 + * query,导致 LoginPage 读不到参数;这里把 search 一并带过去。 + */ +function RootRedirect() { + const { search } = useLocation(); + return ; +} + export function App() { return ( - } /> + } /> } /> } /> } /> diff --git a/frontend-web/src/core/auth/task-deeplink.ts b/frontend-web/src/core/auth/task-deeplink.ts index c9289b79..f375ea01 100644 --- a/frontend-web/src/core/auth/task-deeplink.ts +++ b/frontend-web/src/core/auth/task-deeplink.ts @@ -37,6 +37,16 @@ const GOPATH_BUSINESS_CODE: Record = { xdfx: "7BF", // 行动分析(XDFX) }; +/** + * 业务码 → 「任务管理」布局下的路由前缀(与 sidebar-menu.ts 的 COH 中任务菜单路径一致)。 + * 会商 / 智能体问答都挂在此前缀下,保证侧边栏停留在 任务管理 → COH中任务 → 对应业务 高亮。 + */ +const BUSINESS_TASK_BASE: Record = { + "3Q": "/page/strategy/task-management/plan-task-sentiment", + "6BF": "/page/strategy/task-management/plan-task/plan-task-fx", + "7BF": "/page/strategy/task-management/plan-task/plan-task-action", +}; + export interface ParsedGoPath { businessCode: string; /** true = 多智能体会商(``-A`` 后缀);false = 单智能体问答。 */ @@ -125,10 +135,13 @@ export async function resolveTaskDeeplinkTarget( ): Promise { const parsed = parseGoPath(goPath); if (!parsed) return null; + const taskBase = BUSINESS_TASK_BASE[parsed.businessCode]; + if (!taskBase) return null; if (parsed.roundtable) { + // 多智能体会商:嵌进对应业务的「任务管理」布局;businessCode 让会商页自动选用配置的业务链条。 return { - path: "/page/strategy/qa/multi-agent-roundtable", + path: `${taskBase}/roundtable`, state: { roundtablePrefill: { taskTitle: detail.taskName, @@ -157,5 +170,6 @@ export async function resolveTaskDeeplinkTarget( } catch { /* noop */ } - return { path: `/page/strategy/agents/${agentId}/chats/new` }; + // 单智能体问答:同样嵌进对应业务的「任务管理」布局。 + return { path: `${taskBase}/agent/${agentId}/chats/new` }; } diff --git a/frontend-web/src/open-canvas/pages/AIWritingPage.tsx b/frontend-web/src/open-canvas/pages/AIWritingPage.tsx index 8df94935..c403bb45 100644 --- a/frontend-web/src/open-canvas/pages/AIWritingPage.tsx +++ b/frontend-web/src/open-canvas/pages/AIWritingPage.tsx @@ -217,6 +217,81 @@ function AIWritingLayout({ hideAdvancedFields, initialNotebookId, initialDocumen ); } +/** + * 写作页主体(侧栏 + 内容区)。放在 Provider 内部,以便读 useAIWriting 状态: + * 进入写作(status !== idle 或查看历史)后自动收起左侧研究模板栏,让写作区更宽。 + */ +function AIWritingBody({ hideAdvancedFields, initialNotebookId, initialDocumentIds, studioToken }: { hideAdvancedFields: boolean } & NotebookProps) { + const { state } = useAIWriting(); + const { isHistoryMode } = useAIWritingHistory(); + + // 需求 0616 #5:左栏点「笔记本 / 所有空间」→ 在右侧内嵌打开对应页,替换右侧内容 + // (写作布局不卸载,仅 display:none 隐藏,避免 WritingSetupChat 重挂丢线程)。 + const [rightView, setRightView] = useState(null); + const overlayActive = rightView != null; + const viewingNotebookId = rightView?.kind === 'notebook' ? rightView.id : null; + + // 进入写作(开始生成 / 查看历史)后收起研究模板侧栏;嵌入态(studioToken)始终不展示。 + const showSidebar = !studioToken && state.status === 'idle' && !isHistoryMode; + + return ( +
+ {/* 需求 0616 #5:课题研究左栏(研究模板 + 笔记本)。进入写作后收起。 */} + {showSidebar && ( + setRightView({ kind: 'notebook', id })} + onOpenAllSpaces={() => setRightView({ kind: 'allSpaces' })} + /> + )} + {/* 右侧:写作布局常驻(仅 display:none 隐藏,避免 WritingSetupChat 重挂丢线程), + 查看笔记本 / 所有空间时在其上叠加内嵌页。 */} +
+
+ +
+ {overlayActive && ( +
+
+ + {rightView.kind === 'notebook' && ( + + )} +
+
+ {rightView.kind === 'notebook' ? ( + // 笔记本详情页内部用 ChatBox(useArtifacts) → 需 ChatRuntime 提供 Provider。 + + + + ) : ( +
+ setRightView({ kind: 'notebook', id })} + /> +
+ )} +
+
+ )} +
+
+ ); +} + export default function AIWritingPage({ hideAdvancedFields = false, initialNotebookId, @@ -228,12 +303,6 @@ export default function AIWritingPage({ initialDocumentIds?: number[]; studioToken?: string; } = {}) { - // 需求 0616 #5:左栏点「笔记本 / 所有空间」→ 在右侧内嵌打开对应页,替换右侧内容 - // (写作布局不卸载,仅 display:none 隐藏,避免 WritingSetupChat 重挂丢线程)。 - const [rightView, setRightView] = useState(null); - const overlayActive = rightView != null; - const viewingNotebookId = rightView?.kind === 'notebook' ? rightView.id : null; - return ( @@ -247,60 +316,12 @@ export default function AIWritingPage({ >
-
- {/* 需求 0616 #5:课题研究左栏(研究模板 + 笔记本)。studio 嵌入态(studioToken)隐藏。 */} - {!studioToken && ( - setRightView({ kind: 'notebook', id })} - onOpenAllSpaces={() => setRightView({ kind: 'allSpaces' })} - /> - )} - {/* 右侧:写作布局常驻(仅 display:none 隐藏,避免 WritingSetupChat 重挂丢线程), - 查看笔记本 / 所有空间时在其上叠加内嵌页。 */} -
-
- -
- {overlayActive && ( -
-
- - {rightView.kind === 'notebook' && ( - - )} -
-
- {rightView.kind === 'notebook' ? ( - // 笔记本详情页内部用 ChatBox(useArtifacts) → 需 ChatRuntime 提供 Provider。 - - - - ) : ( -
- setRightView({ kind: 'notebook', id })} - /> -
- )} -
-
- )} -
-
+
diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index e1e4861a..1938ff18 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -33,10 +33,16 @@ import { desensitize } from "@/lib/desensitize"; export default function AgentChatPage({ embeddedAgentId, + chatBasePath, }: { /** 当作为「通用问答右侧嵌入」渲染时由父组件传入的 agent id(替代路由参数)。 * 传入即视为嵌入态:路由仍停留在 /chats/new,仅右侧问答区换成该智能体的对话。 */ embeddedAgentId?: string; + /** 智能体对话所在的路由前缀(到 `/chats` 为止,不含 thread_id)。 + * 默认 `${routeBase}/agents/${agent_id}/chats`;任务深链把对话嵌进「任务管理」布局时, + * 传入如 `/page/strategy/task-management/plan-task-sentiment/agent/${id}/chats`, + * 使开新会话 / 首条消息后的 URL 改写都停留在任务管理上下文(侧边栏保持对应业务高亮)。 */ + chatBasePath?: string; } = {}) { const routeBase = useRouteBase(); const { t } = useI18n(); @@ -48,6 +54,8 @@ export default function AgentChatPage({ }>(); const agent_id = embeddedAgentId ?? agentIdFromPath; const isEmbedded = !!embeddedAgentId; + // 对话路由前缀:调用方可覆盖(任务深链嵌入态),否则用当前 routeBase 下的独立智能体对话路径。 + const agentChatsBase = chatBasePath ?? `${routeBase}/agents/${agent_id}/chats`; // If ChatPage stashed an opening message for this agent, skip the landing // screen on first render so the user doesn't see a flash of the welcome UI @@ -100,7 +108,7 @@ export default function AgentChatPage({ history.replaceState( null, "", - `${window.location.pathname}${window.location.search}#${routeBase}/agents/${agent_id}/chats/${createdThreadId}`, + `${window.location.pathname}${window.location.search}#${agentChatsBase}/${createdThreadId}`, ); }, onFinish: (state) => { @@ -221,7 +229,7 @@ export default function AgentChatPage({ router.push( isEmbedded ? `/page/workspace/chats/new?agent=${agent_id}` - : `${routeBase}/agents/${agent_id}/chats/new`, + : `${agentChatsBase}/new`, ); }} > diff --git a/frontend-web/src/pages/StrategyRoutes.tsx b/frontend-web/src/pages/StrategyRoutes.tsx index c028919e..51bd0f81 100644 --- a/frontend-web/src/pages/StrategyRoutes.tsx +++ b/frontend-web/src/pages/StrategyRoutes.tsx @@ -1,4 +1,4 @@ -import { Navigate, Route, Routes } from "react-router-dom"; +import { Navigate, Route, Routes, useParams } from "react-router-dom"; import { Toaster } from "sonner"; import { SidebarProvider } from "@/components/ui/sidebar"; @@ -50,6 +50,20 @@ import AdminScheduledTasksPage from "./AdminScheduledTasksPage"; import AdminUserActivityPage from "./AdminUserActivityPage"; import { ChatRuntime } from "./WorkspaceRoutes"; +/** + * 任务深链:把智能体问答嵌进「任务管理」布局。读路由参数 agent_id,给 AgentChatPage + * 传入以 taskBase 为前缀的对话路由(开新会话 / URL 改写都停留在任务管理上下文, + * 侧边栏保持对应业务高亮)。 + */ +function TaskEmbeddedAgentChat({ taskBase }: { taskBase: string }) { + const { agent_id } = useParams<{ agent_id: string }>(); + return ( + + + + ); +} + export default function StrategyRoutes() { return ( @@ -113,6 +127,21 @@ export default function StrategyRoutes() { /> } /> + {/* 任务深链(3qfx / 3qfx-A):把智能体问答 / 会商嵌进 3QFX 业务上下文。 */} + + + + } + /> + + } + /> } /> + {/* 任务深链(rwfx / rwfx-A):嵌进 任务FX 业务上下文。 */} + + + + } + /> + + } + /> } /> + {/* 任务深链(xdfx / xdfx-A):嵌进 XDFX 业务上下文。 */} + + + + } + /> + + } + /> } /> } /> } />