feat(roundtable): 深链接第一步任务/行动展示 + xdfx 行动详情接入会商

- Step1 深链接(taskId)下:左栏顶部「业务链选择」隐藏,改显当前任务(rwfx/3qfx:任务名称+描述)
  或当前行动(xdfx:行动名称+叙事);顶栏「业务链条」入口隐藏;「新建任务」改「重新分析」
  (复位 autoStarted + resetToStep1,对同一任务重做意图分析)
- xdfx 行动详情接入会商:fetchActionDetail 取到即 persistActionDetail 缓存;新增
  readActionDetail/persistActionDetail + ACTION_DETAIL_STORAGE_KEY;resolveTaskDeeplinkTarget
  会商分支为 xdfx 把 actionId/actionName 带入 roundtablePrefill
- xdfx 首条交给任务理解的消息 = 任务名称 + 任务描述 + 行动名称
- 「原任务已有结果」(目的树预览 headerLeftSlot) 仅 rwfx-A(6BF) 显示

注:行动详情 mock(actionName/narration) 在 config.yaml(按惯例不提交,单独管理)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-20 20:05:27 +08:00
1 parent b1b1ff98f5
commit b513a0d56c
2 files changed
+160 -15

No files matched your search

+38 -1
View File
@@ -35,6 +35,9 @@ export interface CopTaskDetail {
/** 任务详情整包暂存的 sessionStorage key(目标页/后续可读取)。 */
export const COP_TASK_DETAIL_STORAGE_KEY = "login.copTaskDetail";
/** 行动详情整包暂存的 sessionStorage key(xdfx 行动分析:会商/单智能体读取行动名称/叙事)。 */
export const ACTION_DETAIL_STORAGE_KEY = "login.actionDetail";
/** goPath 基础码 → 业务码(配置管理 businessCode)。 */
const GOPATH_BUSINESS_CODE: Record<string, string> = {
"3qfx": "3Q", // 三情分析
@@ -111,6 +114,25 @@ export function readCopTaskDetail(): CopTaskDetail | null {
}
}
/** 把行动详情整包暂存到 sessionStorage(xdfx 行动分析用;隐私模式/quota 异常时静默失败)。 */
export function persistActionDetail(detail: ActionDetail): void {
try {
sessionStorage.setItem(ACTION_DETAIL_STORAGE_KEY, JSON.stringify(detail));
} catch {
/* noop */
}
}
/** 读取暂存的行动详情(会商左上角「当前行动」、单智能体行动详情卡都可读)。 */
export function readActionDetail(): ActionDetail | null {
try {
const raw = sessionStorage.getItem(ACTION_DETAIL_STORAGE_KEY);
return raw ? (JSON.parse(raw) as ActionDetail) : null;
} catch {
return null;
}
}
/** 行动详情接口 ``{action_detail_url}/{actionId}`` 返回的 ``data`` 字段(xdfx 行动分析用)。 */
export interface ActionDetail {
/** 行动id(深链 ?taskId= 传入的就是它)。 */
@@ -148,7 +170,7 @@ export async function fetchActionDetail(actionId: string): Promise<ActionDetail
const data = body && typeof body === "object" ? (body as { data?: unknown }).data : null;
if (!data || typeof data !== "object") return null;
const d = data as Record<string, unknown>;
return {
const detail: ActionDetail = {
id: d.id != null ? String(d.id) : "",
taskId: d.taskId != null ? String(d.taskId) : "",
purposeId: d.purposeId != null ? String(d.purposeId) : "",
@@ -157,6 +179,10 @@ export async function fetchActionDetail(actionId: string): Promise<ActionDetail
typeof d.actionStatus === "number" || typeof d.actionStatus === "string" ? d.actionStatus : "",
narration: typeof d.narration === "string" ? d.narration : "",
};
// 取到即整包暂存:登录解析任务id 时(resolveDeeplinkTaskId→fetchTaskIdFromAction)就会缓存它,
// 之后会商左上角「当前行动」、单智能体行动详情卡都能直接 readActionDetail(),避免重复请求。
persistActionDetail(detail);
return detail;
}
/**
@@ -228,6 +254,15 @@ export async function resolveTaskDeeplinkTarget(
if (parsed.roundtable) {
// 多智能体会商:嵌进对应业务的「任务管理」布局;businessCode 让会商页自动选用配置的业务链条;
// taskId 让会商页按任务隔离存取草稿(一个 taskId 多条记录),并触发第三步自动结构化输出。
// xdfx(行动分析):根是「行动」——把行动名称随首条消息交给任务理解,左上角展示「当前行动」。
// 行动详情登录解析任务id 时已缓存(fetchActionDetail 内 persistActionDetail);这里优先读缓存。
let actionName = "";
let actionId = "";
if (parsed.businessCode === XDFX_BUSINESS_CODE && rawId) {
actionId = rawId;
const ad = readActionDetail() ?? (await fetchActionDetail(rawId));
actionName = ad?.actionName ?? "";
}
return {
path: `${taskBase}/roundtable`,
state: {
@@ -236,6 +271,8 @@ export async function resolveTaskDeeplinkTarget(
taskGoal: detail.taskContent,
businessCode: parsed.businessCode,
taskId: taskId || undefined,
...(actionId ? { actionId } : {}),
...(actionName ? { actionName } : {}),
autoStart: true,
},
},
@@ -46,6 +46,7 @@ import {
Loader2,
Pencil,
Plus,
RotateCw,
Save,
Settings,
Sparkles,
@@ -80,6 +81,7 @@ import { RecommendAgentsDialog } from "@/roundtable-planning/components/Recommen
import { BusinessChainListDialog } from "@/roundtable-planning/components/BusinessChainListDialog";
import { DispatchChainJobModal } from "@/roundtable-planning/components/DispatchChainJobModal";
import { Step1Panel } from "@/roundtable-planning/components/Step1Panel";
import { readActionDetail, readCopTaskDetail } from "@/core/auth/task-deeplink";
import { Step2Panel } from "@/roundtable-planning/components/Step2Panel";
import { Step3Panel } from "@/roundtable-planning/components/Step3Panel";
import type {
@@ -159,6 +161,10 @@ interface RoundtablePrefill {
businessCode?: string;
/** 外部任务 id(无界抽屉):聊天记录按此存取、打开时回显最新一条。 */
taskId?: string;
/** xdfx 深链:原始行动 id(URL ?taskId= 传入的就是它)。供会商取行动详情用。 */
actionId?: string;
/** xdfx 深链:行动名称(注入 Step 1 首条用户消息——任务名称+任务描述+行动名称)。 */
actionName?: string;
/** 是否进入即自动开跑 Step 1 意图分析。 */
autoStart?: boolean;
}
@@ -1083,7 +1089,13 @@ export default function RoundtablePlanningPage() {
if (!step1.intentThreadId || step1.intentReady) return;
const title = prefill.taskTitle?.trim();
const goal = prefill.taskGoal?.trim();
const text = [title ? `任务名称:${title}` : "", goal ? `任务描述:${goal}` : ""]
// xdfx(行动分析):除任务名称/描述外,再带上「行动名称」一起交给任务理解智能体。
const actionName = prefill.businessCode === "7BF" ? prefill.actionName?.trim() : "";
const text = [
title ? `任务名称:${title}` : "",
goal ? `任务描述:${goal}` : "",
actionName ? `行动名称:${actionName}` : "",
]
.filter(Boolean)
.join("\n");
if (!text) return;
@@ -1485,6 +1497,15 @@ export default function RoundtablePlanningPage() {
triggerToast("✨ 已开始新任务,请在第一步与任务理解智能体澄清需求");
}, [resetToStep1, triggerToast]);
// 深链接(taskId)模式专用的「重新分析」:清空第一步会话、保留当前任务(prefill 不动),并复位
// autoStarted 闸门,让 C2 自动把「任务名称/描述」重新作为首条消息发出,对同一任务重做意图分析。
// 与「新建任务」(全新空白任务) 区分——深链接下任务是固定的,重来=重新分析这条任务。
const handleReanalyze = useCallback(() => {
autoStartedRef.current = false;
resetToStep1();
triggerToast("🔄 已重新分析当前任务");
}, [resetToStep1, triggerToast]);
// 无界抽屉保活复用:父页面重开抽屉时 `bus.$emit('roundtable:reset')` → 子应用回到第一步。
// 非无界环境下 onWujieEvent 返回 no-op,安全无副作用。
useEffect(() => {
@@ -1857,7 +1878,8 @@ export default function RoundtablePlanningPage() {
</button>
)}
{!inWujieEmbed && (
{/* 深链接(taskId)模式:业务链由 businessCode 自动决定,隐藏手动「业务链条」入口。 */}
{!inWujieEmbed && !prefillRef.current?.taskId && (
<button
id="business-chain-btn"
onClick={() => setIsChainListOpen(true)}
@@ -1869,15 +1891,28 @@ export default function RoundtablePlanningPage() {
</button>
)}
<button
id="new-task-btn"
onClick={handleStartNewTask}
title="清空当前会话,从第一步开始一个全新任务"
className="bg-emerald-600 text-white hover:bg-emerald-700 text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all shadow-sm shadow-emerald-500/10 active:scale-95"
>
<Sparkles className="w-3.5 h-3.5" />
新建任务
</button>
{/* 深链接(taskId)模式:任务固定,把「新建任务」换成「重新分析」——清空会话、对同一任务重做意图分析。 */}
{prefillRef.current?.taskId ? (
<button
id="reanalyze-task-btn"
onClick={handleReanalyze}
title="清空当前会话,对当前任务重新做意图分析"
className="bg-emerald-600 text-white hover:bg-emerald-700 text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all shadow-sm shadow-emerald-500/10 active:scale-95"
>
<RotateCw className="w-3.5 h-3.5" />
重新分析
</button>
) : (
<button
id="new-task-btn"
onClick={handleStartNewTask}
title="清空当前会话,从第一步开始一个全新任务"
className="bg-emerald-600 text-white hover:bg-emerald-700 text-xs font-bold px-3 py-2 rounded-xl flex items-center gap-1.5 transition-all shadow-sm shadow-emerald-500/10 active:scale-95"
>
<Sparkles className="w-3.5 h-3.5" />
新建任务
</button>
)}
<button
id="save-draft-header-btn"
@@ -1969,6 +2004,7 @@ export default function RoundtablePlanningPage() {
onOpenDraft={drafts.handleLoadDraft}
activeDraftId={drafts.currentDraftId}
entryRailTaskId={prefillRef.current?.taskId ?? null}
entryRailBusinessCode={prefillRef.current?.businessCode ?? null}
/>
{/* MIDDLE VIEWPORT */}
@@ -1979,7 +2015,10 @@ export default function RoundtablePlanningPage() {
{currentStep === 1 && (
<Step1Panel
headerLeftSlot={
prefillRef.current?.taskId && purposeLevels.length > 0 ? (
// 「原任务已有结果」(目的树预览)只在 rwfx-A(6BF)模式显示;xdfx/3qfx 不显示。
prefillRef.current?.taskId &&
prefillRef.current?.businessCode === "6BF" &&
purposeLevels.length > 0 ? (
<PurposeFlowPreview
levels={purposeLevels}
onOpen={() => setIsTaskPurposeOpen(true)}
@@ -2498,6 +2537,8 @@ interface LeftSidebarProps {
activeDraftId: string | null;
/** 深链 taskId 模式:Step1 落地左栏「分析记录」只列该任务的草稿。 */
entryRailTaskId?: string | null;
/** 深链业务码:左上角卡片据此决定显示「当前任务」(rwfx/3qfx) 还是「当前行动」(xdfx)。 */
entryRailBusinessCode?: string | null;
}
function Step2SandboxSidebarSync({
@@ -2544,6 +2585,7 @@ function LeftSidebar({
onOpenDraft,
activeDraftId,
entryRailTaskId,
entryRailBusinessCode,
}: LeftSidebarProps) {
// 整片脱敏:左侧面板(任务概览/参与角色/人工参与等)的所有文本——标题、动态内容、
// 嵌套子组件文本一并脱敏。消息列表与沙箱是另外的容器,不挂此 Hook,保持原文。
@@ -2681,6 +2723,7 @@ function LeftSidebar({
onOpenDraft={onOpenDraft}
activeDraftId={activeDraftId}
taskId={entryRailTaskId ?? null}
businessCode={entryRailBusinessCode ?? null}
/>
)}
</section>
@@ -2727,6 +2770,7 @@ function RoundtableEntryRail({
onOpenDraft,
activeDraftId,
taskId,
businessCode,
}: {
selectedChainId: string | null;
onSelectChain: (chain: BusinessChain) => void;
@@ -2734,12 +2778,25 @@ function RoundtableEntryRail({
activeDraftId: string | null;
/** 深链 taskId 模式:分析记录只列该任务的草稿。 */
taskId?: string | null;
/** 深链业务码:xdfx(7BF) 左上角显示「当前行动」,其余显示「当前任务」。 */
businessCode?: string | null;
}) {
const [chains, setChains] = useState<BusinessChain[]>([]);
const [expandedChainId, setExpandedChainId] = useState<string | null>(null);
const { drafts: records, refresh } = useRoundtableDrafts(taskId);
// 深链接(taskId)模式:左上角不显示「业务链选择」(链条已由 businessCode 自动决定),改为展示
// 当前任务(rwfx/3qfx)或当前行动(xdfx)。xdfx 的根是「行动」,故展示行动名称 + 叙事。
const deeplink = Boolean(taskId);
const isXdfx = businessCode === "7BF";
const taskDetail = deeplink && !isXdfx ? readCopTaskDetail() : null;
const actionDetail = deeplink && isXdfx ? readActionDetail() : null;
useEffect(() => {
// 深链接模式不展示业务链选择 → 不必拉公共链条。
if (deeplink) {
void refresh();
return;
}
let cancelled = false;
void listChains("public")
.then((all) => {
@@ -2752,7 +2809,7 @@ function RoundtableEntryRail({
return () => {
cancelled = true;
};
}, [refresh]);
}, [refresh, deeplink]);
const formatTime = (value: string) => {
const date = new Date(value);
@@ -2768,7 +2825,57 @@ function RoundtableEntryRail({
return (
<>
{/* 业务链(公共,前 3 个) */}
{/* 深链接(taskId)模式:左上角展示「当前任务」(rwfx/3qfx)或「当前行动」(xdfx),替代业务链选择。 */}
{deeplink ? (
isXdfx ? (
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)]">
<div className="flex items-center gap-2 mb-3">
<span className="flex items-center justify-center w-5 h-5 rounded-md bg-primary text-primary-foreground">
<Target className="w-3 h-3" strokeWidth={2.5} />
</span>
<h2 className="text-sm font-semibold tracking-wide text-slate-900">当前行动</h2>
</div>
<div className="space-y-2">
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400">行动名称</p>
<p className="mt-0.5 text-xs font-semibold leading-relaxed text-slate-800">
{actionDetail?.actionName?.trim() || "(未获取到行动名称)"}
</p>
</div>
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400">行动叙事</p>
<p className="mt-0.5 whitespace-pre-wrap text-[11px] leading-relaxed text-slate-600">
{actionDetail?.narration?.trim() || "(未获取到行动叙事)"}
</p>
</div>
</div>
</div>
) : (
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)]">
<div className="flex items-center gap-2 mb-3">
<span className="flex items-center justify-center w-5 h-5 rounded-md bg-primary text-primary-foreground">
<Clipboard className="w-3 h-3" strokeWidth={2.5} />
</span>
<h2 className="text-sm font-semibold tracking-wide text-slate-900">当前任务</h2>
</div>
<div className="space-y-2">
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400">任务名称</p>
<p className="mt-0.5 text-xs font-semibold leading-relaxed text-slate-800">
{taskDetail?.taskName?.trim() || "(未获取到任务名称)"}
</p>
</div>
<div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400">任务描述</p>
<p className="mt-0.5 whitespace-pre-wrap text-[11px] leading-relaxed text-slate-600">
{taskDetail?.taskContent?.trim() || "(未获取到任务描述)"}
</p>
</div>
</div>
</div>
)
) : (
/* 业务链(公共,前 3 个) */
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] transition-shadow duration-300 hover:shadow-[0_4px_24px_rgba(45,37,34,0.04)]">
<div className="flex items-center gap-2 mb-3">
<span className="flex items-center justify-center w-5 h-5 rounded-md bg-primary text-primary-foreground">
@@ -2949,6 +3056,7 @@ function RoundtableEntryRail({
</div>
)}
</div>
)}
{/* 分析记录(圆桌历史草稿) */}
<div className="bg-white border border-[#ECE7E4] rounded-2xl p-4 shadow-[0_4px_20px_rgba(0,0,0,0.02)] flex-1 flex flex-col min-h-[200px] overflow-hidden">