feat: 3qfx/xdfx 任务深链 1:N 工作区 + xdfx 行动id解析 + 3q详情链接

- 3qfx/xdfx 单智能体改走任务工作区(TaskFxWorkspace):按 taskId+agent_id 隔离的
  1:N 对话列表;3qfx 纯绑定(taskBind 自动发开场信息),xdfx 带目的树闸门(taskGate)
- 左上角「当前任务」卡片:留存查询到的任务名称+简介(readCopTaskDetail)
- rwfx 工作区新增「切换到三情分析」按钮,带同一 taskId 进入 3qfx
- xdfx 深链传入的是行动id:resolveDeeplinkTaskId 经 action 接口解析出真正任务id
  (可配置 action_detail_url + action_test/mock.action_detail,离线可测)
- 目的树面板「弹框查看」旁新增可配置「3q详情」链接(detail_3q_url + ?id=任务id)
- AgentChatPage 拆分 taskGate/taskBind;use-task-threads 增加 agentId 过滤隔离

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-18 11:37:05 +08:00
1 parent 47bf0c2a1b
commit ecb79ce49c
13 files changed
+347 -95

No files matched your search

@@ -19,6 +19,8 @@ export interface TaskDeeplinkConfig {
test: boolean;
/** 删除接口走假数据(该接口未开发完时打开)。 */
deleteTest: boolean;
/** 行动→任务解析走假数据(xdfx 行动id→任务id)。 */
actionTest: boolean;
/** 目的树查询完整 URL(调用追加 ?taskId=)。 */
purposeDetailUrl: string;
/** 删除完整 URL(调用追加 ?taskId=)。 */
@@ -27,10 +29,16 @@ export interface TaskDeeplinkConfig {
treemapEditUrl: string;
/** B 系统树形下一步页 URL(跳转追加 ?id=)。 */
treemapNextUrl: string;
/** 测试假数据:purposeDetail = 原始 {state,msg,data};delete = {message,success}。 */
/** 行动详情接口基址(xdfx 行动→任务):调用追加 /{actionId},返回 data.taskId 为真正任务id。 */
actionDetailUrl: string;
/** 「3q详情」详情页 URL(rwfx 目的树面板链接,跳转追加 ?id= 任务id);留空则前端提示未配置。 */
detail3qUrl: string;
/** 测试假数据:purposeDetail = 原始 {state,msg,data};delete = {message,success};
* actionDetail = 原始 {state,msg,data:{taskId,...}}。 */
mock: {
purposeDetail?: unknown;
delete?: { message?: string; success?: boolean };
actionDetail?: unknown;
};
}
@@ -38,10 +46,13 @@ export interface TaskDeeplinkConfig {
export const DEFAULT_TASK_DEEPLINK_CONFIG: TaskDeeplinkConfig = {
test: false,
deleteTest: false,
actionTest: false,
purposeDetailUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/getTaskPurposeDetail",
deleteUrl: "https://ch1.b.uat.4.cn/consumer/taskAnalyseSearch/delete",
treemapEditUrl: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-edit",
treemapNextUrl: "https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-mode-treemap-full-next",
actionDetailUrl: "https://ch1.b.uat.4.cn/consumer/plan/action",
detail3qUrl: "",
mock: {},
};
@@ -55,16 +66,21 @@ function normalize(body: unknown): TaskDeeplinkConfig {
return {
test: o.test === true,
deleteTest: o.delete_test === true,
actionTest: o.action_test === true,
purposeDetailUrl: str(o.purpose_detail_url, d.purposeDetailUrl),
deleteUrl: str(o.delete_url, d.deleteUrl),
treemapEditUrl: str(o.treemap_edit_url, d.treemapEditUrl),
treemapNextUrl: str(o.treemap_next_url, d.treemapNextUrl),
actionDetailUrl: str(o.action_detail_url, d.actionDetailUrl),
// detail_3q_url 允许为空(地址待定),不回退默认;只在配了非空时才生效。
detail3qUrl: typeof o.detail_3q_url === "string" ? o.detail_3q_url : d.detail3qUrl,
mock: {
purposeDetail: mock.purpose_detail,
delete:
mock.delete && typeof mock.delete === "object"
? (mock.delete as { message?: string; success?: boolean })
: undefined,
actionDetail: mock.action_detail,
},
};
}
+48 -21
View File
@@ -17,10 +17,11 @@
import { requestCrudService } from "@/service/request-axios-service";
import { clearTaskContext, persistTaskContext } from "@/core/auth/task-purpose";
import { fetchTaskDeeplinkConfig } from "@/core/auth/task-deeplink-config";
import { listBusinessMappings } from "@/strategy-components/api/business-mapping";
/** rwfx 业务码:单智能体走「任务工作区 + 目的树闸门」(其它业务保持原问答逻辑)。 */
const RWFX_BUSINESS_CODE = "6BF";
/** xdfx 业务码:深链传入的是「行动id」,需先调 action 接口解析出真正的「任务id」。 */
const XDFX_BUSINESS_CODE = "7BF";
/** consumer 接口返回的任务详情(data 字段)。 */
export interface CopTaskDetail {
@@ -110,6 +111,44 @@ export function readCopTaskDetail(): CopTaskDetail | null {
}
}
/**
* xdfx 深链:用「行动id」调行动详情接口 ``{action_detail_url}/{actionId}``,取出 ``data.taskId``
* 作为真正的「任务id」。``action_test=true`` 时直接用后端配置里的假数据(接口未通时用)。
* 缺地址 / 返回不合法 → 返回 ""(调用方据此回退)。
*/
export async function fetchTaskIdFromAction(actionId: string): Promise<string> {
if (!actionId) return "";
const cfg = await fetchTaskDeeplinkConfig();
let body: unknown;
if (cfg.actionTest) {
body = cfg.mock.actionDetail;
} else if (cfg.actionDetailUrl) {
const base = cfg.actionDetailUrl.replace(/\/+$/, "");
// service 拦截器已统一注入 Authorization: Bearer admin 并返回整包 body;行动id 走路径段。
body = await requestCrudService.get(`${base}/${encodeURIComponent(actionId)}`);
} else {
return "";
}
const data = body && typeof body === "object" ? (body as { data?: unknown }).data : null;
if (!data || typeof data !== "object") return "";
const taskId = (data as Record<string, unknown>).taskId;
return taskId != null && taskId !== "" ? String(taskId) : "";
}
/**
* 据 goPath 把深链 URL 里的原始 id 解析成真正的「任务id」:
* - xdfx(行动分析):URL 传入的是「行动id」,调 action 接口换成 ``data.taskId``;
* 解析失败返回 ""(让调用方回退默认首页,避免拿行动id当任务id污染后续查询/绑定)。
* - 其它业务:URL 传入的本就是任务id,原样返回。
*/
export async function resolveDeeplinkTaskId(goPath: string, rawId: string): Promise<string> {
const parsed = parseGoPath(goPath);
if (parsed?.businessCode === XDFX_BUSINESS_CODE) {
return fetchTaskIdFromAction(rawId);
}
return rawId;
}
export interface TaskDeeplinkTarget {
path: string;
/** react-router navigate 的 state(多智能体会商用 roundtablePrefill)。 */
@@ -148,7 +187,7 @@ export async function resolveTaskDeeplinkTarget(
const taskBase = BUSINESS_TASK_BASE[parsed.businessCode];
if (!taskBase) return null;
// 清掉上一次深链可能残留的任务上下文(仅 rwfx 单智能体会重新写入)。
// 清掉上一次深链可能残留的任务上下文(单智能体分支会用本次任务重新写入)。
clearTaskContext();
if (parsed.roundtable) {
@@ -177,22 +216,10 @@ export async function resolveTaskDeeplinkTarget(
}
if (!agentId) return null;
if (parsed.businessCode === RWFX_BUSINESS_CODE) {
// rwfx:走「任务工作区」——暂存任务上下文(taskId + 开场信息),跳到 chats 索引,
// 由工作区按 taskId 决定开最新历史对话 / 新建(新建走目的树闸门)。不设 pendingAgentMessage:
// 开场信息在闸门通过后由目的树面板发出。
persistTaskContext({ taskId, agentId, openingText: buildOpeningText(detail, taskId) });
return { path: `${taskBase}/agent/${agentId}/chats` };
}
// 其它单智能体问答:保持原逻辑(pendingAgentMessage 自动发出 + chats/new)。
try {
sessionStorage.setItem(
"pendingAgentMessage",
JSON.stringify({ agentId, text: buildOpeningText(detail, taskId) }),
);
} catch {
/* noop */
}
return { path: `${taskBase}/agent/${agentId}/chats/new` };
// 单智能体问答统一走「任务工作区」(任务↔对话 1:N,按 taskId + agentId 隔离不同业务的对话列表):
// 暂存任务上下文(taskId + agentId + 开场信息),跳到 chats 索引,由工作区按 taskId 决定
// 开最新历史对话 / 新建。rwfx 在工作区里额外过「目的树闸门」;3qfx/xdfx 仅 1:N 列表,
// 新建对话直接带开场信息自动发出(见 task-purpose / AgentChatPage 的 taskGate / taskBind)。
persistTaskContext({ taskId, agentId, openingText: buildOpeningText(detail, taskId) });
return { path: `${taskBase}/agent/${agentId}/chats` };
}
@@ -190,6 +190,12 @@ export function buildTreemapNextUrl(taskId: string): string {
return url && taskId ? appendQueryParam(url, "id", taskId) : "";
}
/** 「3q详情」详情页地址(目的树面板链接,新标签打开);追加 ?id=任务id。地址未配置时返回 ""。 */
export function buildDetail3qUrl(taskId: string): string {
const url = getCachedTaskDeeplinkConfig().detail3qUrl;
return url && taskId ? appendQueryParam(url, "id", taskId) : "";
}
/* --------------------------- 任务上下文暂存 --------------------------- */
export function persistTaskContext(ctx: TaskContext): void {