diff --git a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx index 50add085..85e9bfc2 100644 --- a/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/RoundtablePlanningPage.tsx @@ -66,6 +66,8 @@ import { useModels } from "@/core/models/hooks"; import { getAvatar, Man1Avatar, RobotAvatar } from "@/roundtable-planning/components/Avatars"; import { ImportRoundtableDraftDialog } from "@/components/workspace/roundtable-draft-share-dialogs"; import { TaskPurposeDialog } from "@/strategy-components/components/task-components/TaskPurposeDialog"; +import { TaskJumpBar } from "@/strategy-components/components/task-components/TaskJumpBar"; +import type { TaskBusiness } from "@/core/auth/task-buttons"; import { derivePurposeLevels, fetchTaskPurposeDetail, @@ -133,6 +135,16 @@ import { listBusinessMappings } from "@/strategy-components/api/business-mapping import { emitWujieEvent, isWujieEnv, onWujieEvent, readWujieProps } from "@/core/embed/wujie"; import { collectRoundtableDeliverables } from "@/roundtable-planning/lib/roundtable-deliverables"; +/** + * 业务码 → 任务工作区业务码(``TaskBusiness``),用于深链 taskId 模式底部「快捷跳转」栏 + * (与单智能体问答共用 ``TaskJumpBar``)。与 task-deeplink.ts 的 GOPATH_BUSINESS_CODE 反向一致。 + */ +const BUSINESS_CODE_TO_TASK_BUSINESS: Record = { + "3Q": "3qfx", + "6BF": "rwfx", + "7BF": "xdfx", +}; + /** 从任务页带进来的预填(经 navigation state 传递)。见会商自动化串联(模块 C)。 */ interface RoundtablePrefill { /** 任务名称(注入 Step 1 首条用户消息)。 */ @@ -1911,6 +1923,17 @@ export default function RoundtablePlanningPage() { onSaveIntent={handleSaveIntent} /> + + {/* 深链 taskId 模式:底部「快捷跳转」栏(与单智能体问答共用,按业务取按钮管理配置)。 */} + {prefillRef.current?.taskId && + prefillRef.current?.businessCode && + BUSINESS_CODE_TO_TASK_BUSINESS[prefillRef.current.businessCode] && ( + + )} ); } diff --git a/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx b/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx index 91a337f0..bee28ea7 100644 --- a/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx +++ b/frontend-web/src/strategy-components/components/task-components/TaskFxWorkspace.tsx @@ -16,7 +16,7 @@ import { useEffect, useMemo, useState } from "react"; import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; -import { ArrowRight, ExternalLink, Loader2, Maximize2, MessageSquare, PlusSquare, Trash2 } from "lucide-react"; +import { Loader2, MessageSquare, PlusSquare, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -30,26 +30,14 @@ import { } from "@/components/ui/dialog"; import { deleteTaskPurpose, readTaskContext } from "@/core/auth/task-purpose"; import { readCopTaskDetail } from "@/core/auth/task-deeplink"; -import { - DEFAULT_TASK_DEEPLINK_CONFIG, - fetchTaskDeeplinkConfig, - type TaskDeeplinkConfig, -} from "@/core/auth/task-deeplink-config"; -import { - TASK_BUSINESSES, - buttonsForBusiness, - resolveTaskButtons, - runTaskButton, - type TaskBusiness, -} from "@/core/auth/task-buttons"; -import { useTaskButtons } from "@/strategy-components/api/task-buttons"; +import { TASK_BUSINESSES, type TaskBusiness } from "@/core/auth/task-buttons"; import { useTaskThreads } from "@/core/threads/use-task-threads"; import { titleOfThread } from "@/core/threads/utils"; import { desensitize } from "@/lib/desensitize"; import { cn } from "@/lib/utils"; import AgentChatPage from "@/pages/AgentChatPage"; import { ChatRuntime } from "@/pages/WorkspaceRoutes"; -import { TaskPurposeDialog } from "@/strategy-components/components/task-components/TaskPurposePanel"; +import { TaskJumpBar } from "@/strategy-components/components/task-components/TaskJumpBar"; function useChatsBase(taskBase: string): { taskId: string; agentId: string; chatsBase: string } { const { agent_id } = useParams<{ agent_id: string }>(); @@ -97,79 +85,6 @@ function parseThreadId(pathname: string, chatsBase: string): string | null { return seg && seg !== "new" ? seg : null; } -/** - * 底部「快捷跳转」栏:横跨整个工作区底部,按配置顺序渲染该业务启用的跳转按钮(按钮管理里排序)。 - * 后端为准,整张表为空才回退种子默认;地址依赖实时深链配置,故把配置拉进 state。见 [[task-buttons]]。 - */ -function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: string }) { - const navigate = useNavigate(); - const [cfg, setCfg] = useState(() => DEFAULT_TASK_DEEPLINK_CONFIG); - useEffect(() => { - let alive = true; - void fetchTaskDeeplinkConfig().then((c) => { - if (alive) setCfg(c); - }); - return () => { - alive = false; - }; - }, []); - const { data: storedButtons = [] } = useTaskButtons(); - const jumpButtons = useMemo( - () => buttonsForBusiness(resolveTaskButtons(storedButtons, cfg), business), - [storedButtons, cfg, business], - ); - - // 目的树弹框(purpose 类型按钮)由组件层打开。 - const [purposeOpen, setPurposeOpen] = useState(false); - // xdfx:深链原始「行动id」(与解析后的任务id 不同才算真行动id),弹框据此换回任务id 再查目的。 - const ctxActionId = readTaskContext()?.actionId; - const purposeActionId = - business === "xdfx" && ctxActionId && ctxActionId !== taskId ? ctxActionId : undefined; - - if (jumpButtons.length === 0) return null; - - const handleRun = (btn: (typeof jumpButtons)[number]) => { - if (btn.linkType === "purpose") { - setPurposeOpen(true); - return; - } - void runTaskButton(btn, { - taskId, - actionId: readTaskContext()?.actionId, - navigate, - onError: (m) => toast.error(m), - }); - }; - - return ( -
- {jumpButtons.map((btn) => ( - - ))} - -
- ); -} - /** * 左侧任务对话列表:顶部「当前任务」卡片 +「新建对话」;下方对话列表(1:N)。 * 跳转按钮已移到工作区底部「快捷跳转」栏(见 ``TaskJumpBar``)。 diff --git a/frontend-web/src/strategy-components/components/task-components/TaskJumpBar.tsx b/frontend-web/src/strategy-components/components/task-components/TaskJumpBar.tsx new file mode 100644 index 00000000..fa6d86b5 --- /dev/null +++ b/frontend-web/src/strategy-components/components/task-components/TaskJumpBar.tsx @@ -0,0 +1,111 @@ +/** + * 任务深链的底部「快捷跳转」栏:横跨整个工作区底部,按配置顺序渲染该业务启用的跳转 + * 按钮(按钮管理里排序)。单智能体问答(``TaskFxWorkspace``)与多智能体会商 + * (``RoundtablePlanningPage`` 深链 taskId 模式)共用。 + * + * 后端为准,整张表为空才回退种子默认;地址依赖实时深链配置,故把配置拉进 state。 + * 见 [[task-buttons]]。 + */ + +import { useEffect, useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { ArrowRight, ExternalLink, Maximize2 } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { readTaskContext } from "@/core/auth/task-purpose"; +import { + DEFAULT_TASK_DEEPLINK_CONFIG, + fetchTaskDeeplinkConfig, + type TaskDeeplinkConfig, +} from "@/core/auth/task-deeplink-config"; +import { + buttonsForBusiness, + resolveTaskButtons, + runTaskButton, + type TaskBusiness, +} from "@/core/auth/task-buttons"; +import { useTaskButtons } from "@/strategy-components/api/task-buttons"; +import { desensitize } from "@/lib/desensitize"; +import { TaskPurposeDialog } from "@/strategy-components/components/task-components/TaskPurposePanel"; + +export function TaskJumpBar({ + business, + taskId, + roundtable, +}: { + business: TaskBusiness; + taskId: string; + /** 多智能体会商深链模式:不展示「编辑」与「弹框查看」(purpose)按钮。 */ + roundtable?: boolean; +}) { + const navigate = useNavigate(); + const [cfg, setCfg] = useState(() => DEFAULT_TASK_DEEPLINK_CONFIG); + useEffect(() => { + let alive = true; + void fetchTaskDeeplinkConfig().then((c) => { + if (alive) setCfg(c); + }); + return () => { + alive = false; + }; + }, []); + const { data: storedButtons = [] } = useTaskButtons(); + const jumpButtons = useMemo(() => { + const list = buttonsForBusiness(resolveTaskButtons(storedButtons, cfg), business); + // 会商页:去掉「编辑」与「弹框查看」(会商页头部已有「任务目的」入口)。 + return roundtable + ? list.filter((b) => b.linkType !== "purpose" && b.label.trim() !== "编辑") + : list; + }, [storedButtons, cfg, business, roundtable]); + + // 目的树弹框(purpose 类型按钮)由组件层打开。 + const [purposeOpen, setPurposeOpen] = useState(false); + // xdfx:深链原始「行动id」(与解析后的任务id 不同才算真行动id),弹框据此换回任务id 再查目的。 + const ctxActionId = readTaskContext()?.actionId; + const purposeActionId = + business === "xdfx" && ctxActionId && ctxActionId !== taskId ? ctxActionId : undefined; + + if (jumpButtons.length === 0) return null; + + const handleRun = (btn: (typeof jumpButtons)[number]) => { + if (btn.linkType === "purpose") { + setPurposeOpen(true); + return; + } + void runTaskButton(btn, { + taskId, + actionId: readTaskContext()?.actionId, + navigate, + onError: (m) => toast.error(m), + }); + }; + + return ( +
+ {jumpButtons.map((btn) => ( + + ))} + +
+ ); +}