- 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>
121 lines
5.2 KiB
TypeScript
121 lines
5.2 KiB
TypeScript
/**
|
||
* 任务深链(``goPath=rwfx``)的运行时配置:从后端公开只读接口
|
||
* ``GET /api/public/config/task-deeplink`` 读取(地址、测试开关、测试假数据全在后端
|
||
* ``config.yaml`` 的 ``task_deeplink`` 段,改完即时生效,前端无需重建)。
|
||
*
|
||
* 两种用法:
|
||
* - 命令式(登录深链流程,非 React 环境):``fetchTaskDeeplinkConfig()`` 拉取并模块级缓存;
|
||
* ``getCachedTaskDeeplinkConfig()`` 同步读缓存(拼跳转地址用)。
|
||
* - React 组件:``useTaskDeeplinkConfig()``(见 [[task-purpose]] 的面板/列表)。
|
||
*
|
||
* 拉取失败 → 用内置默认值(``test=false`` + 真实 URL)兜底,功能不整体挂掉。
|
||
*/
|
||
|
||
import { apiFetch } from "@/core/api/fetch-client";
|
||
import { getBackendBaseURL } from "@/core/config";
|
||
|
||
export interface TaskDeeplinkConfig {
|
||
/** 目的树查询走假数据。 */
|
||
test: boolean;
|
||
/** 删除接口走假数据(该接口未开发完时打开)。 */
|
||
deleteTest: boolean;
|
||
/** 行动→任务解析走假数据(xdfx 行动id→任务id)。 */
|
||
actionTest: boolean;
|
||
/** 目的树查询完整 URL(调用追加 ?taskId=)。 */
|
||
purposeDetailUrl: string;
|
||
/** 删除完整 URL(调用追加 ?taskId=)。 */
|
||
deleteUrl: string;
|
||
/** B 系统树形完整编辑页 URL(跳转追加 ?id=)。 */
|
||
treemapEditUrl: string;
|
||
/** B 系统树形下一步页 URL(跳转追加 ?id=)。 */
|
||
treemapNextUrl: string;
|
||
/** 行动详情接口基址(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;
|
||
};
|
||
}
|
||
|
||
/** 内置默认值(后端拿不到时兜底;真实 URL + 关闭测试)。 */
|
||
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: {},
|
||
};
|
||
|
||
/** 后端 snake_case 响应 → 前端 camelCase;缺字段回退默认值。 */
|
||
function normalize(body: unknown): TaskDeeplinkConfig {
|
||
const o = body && typeof body === "object" ? (body as Record<string, unknown>) : {};
|
||
const d = DEFAULT_TASK_DEEPLINK_CONFIG;
|
||
const str = (v: unknown, fallback: string) =>
|
||
typeof v === "string" && v.trim() ? v : fallback;
|
||
const mock = o.mock && typeof o.mock === "object" ? (o.mock as Record<string, unknown>) : {};
|
||
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,
|
||
},
|
||
};
|
||
}
|
||
|
||
let cached: TaskDeeplinkConfig | null = null;
|
||
let inflight: Promise<TaskDeeplinkConfig> | null = null;
|
||
|
||
/** 拉取(带模块级缓存 + 去重);失败回退默认值。 */
|
||
export async function fetchTaskDeeplinkConfig(): Promise<TaskDeeplinkConfig> {
|
||
if (cached) return cached;
|
||
if (inflight) return inflight;
|
||
inflight = (async () => {
|
||
try {
|
||
const res = await apiFetch(`${getBackendBaseURL()}/api/public/config/task-deeplink`);
|
||
if (!res.ok) throw new Error(`task-deeplink config ${res.status}`);
|
||
cached = normalize(await res.json());
|
||
} catch {
|
||
cached = { ...DEFAULT_TASK_DEEPLINK_CONFIG };
|
||
} finally {
|
||
inflight = null;
|
||
}
|
||
return cached;
|
||
})();
|
||
return inflight;
|
||
}
|
||
|
||
/** 同步读缓存(未拉取过则返回默认值)。 */
|
||
export function getCachedTaskDeeplinkConfig(): TaskDeeplinkConfig {
|
||
return cached ?? DEFAULT_TASK_DEEPLINK_CONFIG;
|
||
}
|
||
|
||
/** 在完整 URL(可能含 `#` 片段)后追加查询参数,供跳转/调用拼 id/taskId。 */
|
||
export function appendQueryParam(url: string, key: string, value: string): string {
|
||
if (!url) return "";
|
||
const sep = url.includes("?") ? "&" : "?";
|
||
return `${url}${sep}${key}=${encodeURIComponent(value)}`;
|
||
}
|