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:
1 parent
b1b1ff98f5
commit
b513a0d56c
2 files changed
+160
-15
No files matched your search
@@ -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">
|
||||
|
||||
Reference in new issue
Block a user