feat(roundtable): 深链会商页引入底部快捷跳转栏(去编辑/弹框查看)
抽出 TaskJumpBar 为独立组件,单智能体问答与多智能体会商共用;会商深链 taskId 模式按业务渲染按钮管理配置,并隐藏「编辑」与「弹框查看」按钮。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bd5623ebc5
commit
3a315798e9
3 files changed
+137
-88
No files matched your search
@@ -66,6 +66,8 @@ import { useModels } from "@/core/models/hooks";
|
|||||||
import { getAvatar, Man1Avatar, RobotAvatar } from "@/roundtable-planning/components/Avatars";
|
import { getAvatar, Man1Avatar, RobotAvatar } from "@/roundtable-planning/components/Avatars";
|
||||||
import { ImportRoundtableDraftDialog } from "@/components/workspace/roundtable-draft-share-dialogs";
|
import { ImportRoundtableDraftDialog } from "@/components/workspace/roundtable-draft-share-dialogs";
|
||||||
import { TaskPurposeDialog } from "@/strategy-components/components/task-components/TaskPurposeDialog";
|
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 {
|
import {
|
||||||
derivePurposeLevels,
|
derivePurposeLevels,
|
||||||
fetchTaskPurposeDetail,
|
fetchTaskPurposeDetail,
|
||||||
@@ -133,6 +135,16 @@ import { listBusinessMappings } from "@/strategy-components/api/business-mapping
|
|||||||
import { emitWujieEvent, isWujieEnv, onWujieEvent, readWujieProps } from "@/core/embed/wujie";
|
import { emitWujieEvent, isWujieEnv, onWujieEvent, readWujieProps } from "@/core/embed/wujie";
|
||||||
import { collectRoundtableDeliverables } from "@/roundtable-planning/lib/roundtable-deliverables";
|
import { collectRoundtableDeliverables } from "@/roundtable-planning/lib/roundtable-deliverables";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务码 → 任务工作区业务码(``TaskBusiness``),用于深链 taskId 模式底部「快捷跳转」栏
|
||||||
|
* (与单智能体问答共用 ``TaskJumpBar``)。与 task-deeplink.ts 的 GOPATH_BUSINESS_CODE 反向一致。
|
||||||
|
*/
|
||||||
|
const BUSINESS_CODE_TO_TASK_BUSINESS: Record<string, TaskBusiness> = {
|
||||||
|
"3Q": "3qfx",
|
||||||
|
"6BF": "rwfx",
|
||||||
|
"7BF": "xdfx",
|
||||||
|
};
|
||||||
|
|
||||||
/** 从任务页带进来的预填(经 navigation state 传递)。见会商自动化串联(模块 C)。 */
|
/** 从任务页带进来的预填(经 navigation state 传递)。见会商自动化串联(模块 C)。 */
|
||||||
interface RoundtablePrefill {
|
interface RoundtablePrefill {
|
||||||
/** 任务名称(注入 Step 1 首条用户消息)。 */
|
/** 任务名称(注入 Step 1 首条用户消息)。 */
|
||||||
@@ -1911,6 +1923,17 @@ export default function RoundtablePlanningPage() {
|
|||||||
onSaveIntent={handleSaveIntent}
|
onSaveIntent={handleSaveIntent}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* 深链 taskId 模式:底部「快捷跳转」栏(与单智能体问答共用,按业务取按钮管理配置)。 */}
|
||||||
|
{prefillRef.current?.taskId &&
|
||||||
|
prefillRef.current?.businessCode &&
|
||||||
|
BUSINESS_CODE_TO_TASK_BUSINESS[prefillRef.current.businessCode] && (
|
||||||
|
<TaskJumpBar
|
||||||
|
business={BUSINESS_CODE_TO_TASK_BUSINESS[prefillRef.current.businessCode]}
|
||||||
|
taskId={prefillRef.current.taskId}
|
||||||
|
roundtable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-88
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom";
|
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 { toast } from "sonner";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -30,26 +30,14 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { deleteTaskPurpose, readTaskContext } from "@/core/auth/task-purpose";
|
import { deleteTaskPurpose, readTaskContext } from "@/core/auth/task-purpose";
|
||||||
import { readCopTaskDetail } from "@/core/auth/task-deeplink";
|
import { readCopTaskDetail } from "@/core/auth/task-deeplink";
|
||||||
import {
|
import { TASK_BUSINESSES, type TaskBusiness } from "@/core/auth/task-buttons";
|
||||||
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 { useTaskThreads } from "@/core/threads/use-task-threads";
|
import { useTaskThreads } from "@/core/threads/use-task-threads";
|
||||||
import { titleOfThread } from "@/core/threads/utils";
|
import { titleOfThread } from "@/core/threads/utils";
|
||||||
import { desensitize } from "@/lib/desensitize";
|
import { desensitize } from "@/lib/desensitize";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import AgentChatPage from "@/pages/AgentChatPage";
|
import AgentChatPage from "@/pages/AgentChatPage";
|
||||||
import { ChatRuntime } from "@/pages/WorkspaceRoutes";
|
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 } {
|
function useChatsBase(taskBase: string): { taskId: string; agentId: string; chatsBase: string } {
|
||||||
const { agent_id } = useParams<{ agent_id: 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;
|
return seg && seg !== "new" ? seg : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 底部「快捷跳转」栏:横跨整个工作区底部,按配置顺序渲染该业务启用的跳转按钮(按钮管理里排序)。
|
|
||||||
* 后端为准,整张表为空才回退种子默认;地址依赖实时深链配置,故把配置拉进 state。见 [[task-buttons]]。
|
|
||||||
*/
|
|
||||||
function TaskJumpBar({ business, taskId }: { business: TaskBusiness; taskId: string }) {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [cfg, setCfg] = useState<TaskDeeplinkConfig>(() => 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 (
|
|
||||||
<div className="border-border/70 bg-background/90 flex shrink-0 flex-wrap items-center justify-end gap-3 border-t px-5 py-3 backdrop-blur-xl dark:border-[#1b477e]/70 dark:bg-[#061b35]/85">
|
|
||||||
{jumpButtons.map((btn) => (
|
|
||||||
<Button
|
|
||||||
key={btn.id}
|
|
||||||
className="h-11 gap-2 border-transparent bg-[var(--color-main)] px-6 text-base text-white shadow-sm hover:bg-[var(--brand-950)]"
|
|
||||||
onClick={() => handleRun(btn)}
|
|
||||||
title={desensitize(btn.label)}
|
|
||||||
>
|
|
||||||
<span className="truncate">{desensitize(btn.label)}</span>
|
|
||||||
{btn.linkType === "url" ? (
|
|
||||||
<ExternalLink className="h-5 w-5 shrink-0" />
|
|
||||||
) : btn.linkType === "purpose" ? (
|
|
||||||
<Maximize2 className="h-5 w-5 shrink-0" />
|
|
||||||
) : (
|
|
||||||
<ArrowRight className="h-5 w-5 shrink-0" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
<TaskPurposeDialog
|
|
||||||
taskId={taskId}
|
|
||||||
open={purposeOpen}
|
|
||||||
onOpenChange={setPurposeOpen}
|
|
||||||
resolveFromActionId={purposeActionId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 左侧任务对话列表:顶部「当前任务」卡片 +「新建对话」;下方对话列表(1:N)。
|
* 左侧任务对话列表:顶部「当前任务」卡片 +「新建对话」;下方对话列表(1:N)。
|
||||||
* 跳转按钮已移到工作区底部「快捷跳转」栏(见 ``TaskJumpBar``)。
|
* 跳转按钮已移到工作区底部「快捷跳转」栏(见 ``TaskJumpBar``)。
|
||||||
|
|||||||
@@ -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<TaskDeeplinkConfig>(() => 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 (
|
||||||
|
<div className="border-border/70 bg-background/90 flex shrink-0 flex-wrap items-center justify-end gap-3 border-t px-5 py-3 backdrop-blur-xl dark:border-[#1b477e]/70 dark:bg-[#061b35]/85">
|
||||||
|
{jumpButtons.map((btn) => (
|
||||||
|
<Button
|
||||||
|
key={btn.id}
|
||||||
|
className="h-11 gap-2 border-transparent bg-[var(--color-main)] px-6 text-base text-white shadow-sm hover:bg-[var(--brand-950)]"
|
||||||
|
onClick={() => handleRun(btn)}
|
||||||
|
title={desensitize(btn.label)}
|
||||||
|
>
|
||||||
|
<span className="truncate">{desensitize(btn.label)}</span>
|
||||||
|
{btn.linkType === "url" ? (
|
||||||
|
<ExternalLink className="h-5 w-5 shrink-0" />
|
||||||
|
) : btn.linkType === "purpose" ? (
|
||||||
|
<Maximize2 className="h-5 w-5 shrink-0" />
|
||||||
|
) : (
|
||||||
|
<ArrowRight className="h-5 w-5 shrink-0" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<TaskPurposeDialog
|
||||||
|
taskId={taskId}
|
||||||
|
open={purposeOpen}
|
||||||
|
onOpenChange={setPurposeOpen}
|
||||||
|
resolveFromActionId={purposeActionId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user