Merge branch 'ji' into master
# Conflicts: # offline-backend-20260512/backend/CLAUDE.md # offline-backend-20260512/backend/app/gateway/app.py # offline-backend-20260512/backend/app/gateway/deps.py
This commit is contained in:
commit
77aa895a70
30 files changed
+1710
-176
No files matched your search
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* 任务工作区(任务深链 rwfx / 3qfx / xdfx,见 ``TaskFxWorkspace``)左侧栏的
|
||||
* **可配置跳转按钮**。
|
||||
*
|
||||
* 数据源优先级:后端 ``/api/task-buttons``(全局共享、admin 整体替换)。**仅当整张表
|
||||
* 为空(全新部署、从未保存过)**时回退到代码种子默认(``defaultTaskButtons``,从实时
|
||||
* 任务深链配置读出 rwfx 的 4 个历史按钮,3qfx/xdfx 为空)——这样真实跳转地址通过实时
|
||||
* config 进种子,避免写死;一旦保存过,后端即唯一数据源。
|
||||
*
|
||||
* 按钮两类(``linkType``):``url`` 外链(``window.open``,可选追加 ``?id=任务id``)/
|
||||
* ``business`` 内部切换到另一业务(复用 [[task-deeplink]] 的 ``resolveTaskDeeplinkTarget``)。
|
||||
*/
|
||||
|
||||
import {
|
||||
appendQueryParam,
|
||||
type TaskDeeplinkConfig,
|
||||
} from "@/core/auth/task-deeplink-config";
|
||||
import {
|
||||
readCopTaskDetail,
|
||||
resolveTaskDeeplinkTarget,
|
||||
} from "@/core/auth/task-deeplink";
|
||||
|
||||
/** 任务工作区业务码(与路由 taskBase 一一对应)。 */
|
||||
export type TaskBusiness = "rwfx" | "3qfx" | "xdfx";
|
||||
|
||||
/** 业务元数据:每个 = 一个任务深链页面。``goPath`` 为深链 ``?goPath=`` 值;``desc`` 给管理页说明。 */
|
||||
export const TASK_BUSINESSES: {
|
||||
code: TaskBusiness;
|
||||
/** 页面名(tab 主标题)。 */
|
||||
name: string;
|
||||
/** 深链 goPath 值。 */
|
||||
goPath: string;
|
||||
/** 页面说明。 */
|
||||
desc: string;
|
||||
}[] = [
|
||||
{
|
||||
code: "rwfx",
|
||||
name: "任务分析",
|
||||
goPath: "rwfx",
|
||||
desc: "完整版任务工作区(带目的树闸门),通过 ?goPath=rwfx 进入。",
|
||||
},
|
||||
{
|
||||
code: "3qfx",
|
||||
name: "三情分析",
|
||||
goPath: "3qfx",
|
||||
desc: "精简版工作区(无目的树闸门),通过 ?goPath=3qfx 进入。",
|
||||
},
|
||||
{
|
||||
code: "xdfx",
|
||||
name: "行动分析",
|
||||
goPath: "xdfx",
|
||||
desc: "精简版工作区,通过 ?goPath=xdfx 进入(深链 taskId 实为行动id)。",
|
||||
},
|
||||
];
|
||||
|
||||
/** ``url``=外链 / ``business``=切换业务 / ``purpose``=弹出任务目的树表格(无需 target)。 */
|
||||
export type TaskButtonLinkType = "url" | "business" | "purpose";
|
||||
|
||||
/** 追加的 id 取哪种:``task``=任务id(默认)/ ``action``=行动id(仅 xdfx 二者不同)。 */
|
||||
export type TaskButtonIdKind = "task" | "action";
|
||||
|
||||
/** 一个可配置跳转按钮。 */
|
||||
export interface TaskButton {
|
||||
id: string;
|
||||
business: TaskBusiness;
|
||||
label: string;
|
||||
linkType: TaskButtonLinkType;
|
||||
/** ``url``=完整地址;``business``=目标业务码(如 ``3qfx``)。 */
|
||||
target: string;
|
||||
/** ``url`` 类型是否追加 id 查询参数。 */
|
||||
appendTaskId: boolean;
|
||||
/** 追加的查询参数名(如 ``id`` / ``task_id``);空则用 ``id``。 */
|
||||
idParam: string;
|
||||
/**
|
||||
* ``url`` 追加 id 时取任务id 还是行动id。仅 xdfx(行动分析)二者不同:深链原始 ``?taskId=``
|
||||
* 实为行动id,任务id 由 action 接口解析。其它业务两者相同。
|
||||
*/
|
||||
idKind: TaskButtonIdKind;
|
||||
enabled: boolean;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
/** 内部切换可选的目标业务(``business`` 类型按钮的下拉项)。 */
|
||||
export const TASK_BUSINESS_TARGETS: { code: TaskBusiness; label: string }[] = [
|
||||
{ code: "3qfx", label: "三情分析" },
|
||||
{ code: "rwfx", label: "任务分析" },
|
||||
{ code: "xdfx", label: "行动分析" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 代码种子默认按钮(仅在后端整张表为空时使用):
|
||||
* - rwfx:历史 4 个按钮(编辑/下一步/3q详情,地址取自实时任务深链配置)+ 切换到三情/行动分析;
|
||||
* - 3qfx / xdfx:互相切换到另外两个业务。
|
||||
* 即「三个业务都带切换按钮」。已保存过则用页面「导入默认」把缺失项补进来。
|
||||
*/
|
||||
export function defaultTaskButtons(cfg: TaskDeeplinkConfig): TaskButton[] {
|
||||
const sw = (
|
||||
id: string,
|
||||
business: TaskBusiness,
|
||||
label: string,
|
||||
target: TaskBusiness,
|
||||
sortOrder: number,
|
||||
): TaskButton => ({
|
||||
id,
|
||||
business,
|
||||
label,
|
||||
linkType: "business",
|
||||
target,
|
||||
appendTaskId: false,
|
||||
idParam: "id",
|
||||
idKind: "task",
|
||||
enabled: true,
|
||||
sortOrder,
|
||||
});
|
||||
// 目的树弹框按钮(无需 target)。
|
||||
const pz = (id: string, business: TaskBusiness, sortOrder: number): TaskButton => ({
|
||||
id,
|
||||
business,
|
||||
label: "弹框查看",
|
||||
linkType: "purpose",
|
||||
target: "",
|
||||
appendTaskId: false,
|
||||
idParam: "id",
|
||||
idKind: "task",
|
||||
enabled: true,
|
||||
sortOrder,
|
||||
});
|
||||
return [
|
||||
// rwfx(任务分析):外链 + 切换
|
||||
{
|
||||
id: "rwfx-edit",
|
||||
business: "rwfx",
|
||||
label: "编辑",
|
||||
linkType: "url",
|
||||
target: cfg.treemapEditUrl,
|
||||
appendTaskId: true,
|
||||
idParam: "id",
|
||||
idKind: "task",
|
||||
enabled: true,
|
||||
sortOrder: 0,
|
||||
},
|
||||
{
|
||||
id: "rwfx-next",
|
||||
business: "rwfx",
|
||||
label: "下一步",
|
||||
linkType: "url",
|
||||
target: cfg.treemapNextUrl,
|
||||
appendTaskId: true,
|
||||
idParam: "id",
|
||||
idKind: "task",
|
||||
enabled: true,
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
id: "rwfx-detail3q",
|
||||
business: "rwfx",
|
||||
label: "3q详情",
|
||||
linkType: "url",
|
||||
target: cfg.detail3qUrl,
|
||||
appendTaskId: true,
|
||||
idParam: "id",
|
||||
idKind: "task",
|
||||
enabled: true,
|
||||
sortOrder: 2,
|
||||
},
|
||||
sw("rwfx-switch3q", "rwfx", "切换到三情分析", "3qfx", 3),
|
||||
sw("rwfx-switchxd", "rwfx", "切换到行动分析", "xdfx", 4),
|
||||
pz("rwfx-purpose", "rwfx", 5),
|
||||
// 3qfx(三情分析):切换到任务/行动分析
|
||||
sw("3qfx-switchrw", "3qfx", "切换到任务分析", "rwfx", 0),
|
||||
sw("3qfx-switchxd", "3qfx", "切换到行动分析", "xdfx", 1),
|
||||
// xdfx(行动分析):切换到任务/三情分析 + 弹框查看
|
||||
sw("xdfx-switchrw", "xdfx", "切换到任务分析", "rwfx", 0),
|
||||
sw("xdfx-switch3q", "xdfx", "切换到三情分析", "3qfx", 1),
|
||||
pz("xdfx-purpose", "xdfx", 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际生效的按钮列表:后端有任意一条记录时即以后端为准;整张表为空才回退种子默认。
|
||||
* (与 [[敏感词管理]] 的种子语义一致:空=从未配置→用种子;非空=已配置→后端说了算,
|
||||
* 哪怕某业务被清空也不回退。)
|
||||
*/
|
||||
export function resolveTaskButtons(
|
||||
stored: TaskButton[],
|
||||
cfg: TaskDeeplinkConfig,
|
||||
): TaskButton[] {
|
||||
return stored.length > 0 ? stored : defaultTaskButtons(cfg);
|
||||
}
|
||||
|
||||
/** 取某业务下、启用的按钮,按 sortOrder 升序。 */
|
||||
export function buttonsForBusiness(
|
||||
buttons: TaskButton[],
|
||||
business: TaskBusiness,
|
||||
): TaskButton[] {
|
||||
return buttons
|
||||
.filter((b) => b.business === business && b.enabled)
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
}
|
||||
|
||||
export interface RunTaskButtonCtx {
|
||||
/** 任务id(xdfx 为 action 接口解析后的真实任务id;其它业务即深链原始 id)。 */
|
||||
taskId: string;
|
||||
/** 行动id(xdfx 深链原始 ?taskId=;其它业务通常等于 taskId)。 */
|
||||
actionId?: string;
|
||||
navigate: (path: string, opts?: { state?: Record<string, unknown> }) => void;
|
||||
/** 跳转/打开失败时的提示回调(默认 noop)。 */
|
||||
onError?: (message: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个跳转按钮:
|
||||
* - ``url``:拼地址(可选追加 ``?id=``,取任务id 或行动id 视 ``idKind``)新标签打开;地址为空提示未配置。
|
||||
* - ``business``:复用深链解析(会按目标业务重绑任务上下文)后 ``navigate``(始终用任务id)。
|
||||
*/
|
||||
export async function runTaskButton(btn: TaskButton, ctx: RunTaskButtonCtx): Promise<void> {
|
||||
const fail = (m: string) => ctx.onError?.(m);
|
||||
// purpose(目的树弹框)由组件层处理(需打开 React 弹框),此处不动作。
|
||||
if (btn.linkType === "purpose") return;
|
||||
if (btn.linkType === "url") {
|
||||
const base = btn.target.trim();
|
||||
if (!base) {
|
||||
fail(`未配置「${btn.label}」的跳转地址`);
|
||||
return;
|
||||
}
|
||||
// 行动分析(xdfx)可选追加行动id;否则用任务id。行动id 缺失时回退任务id。
|
||||
const id = btn.idKind === "action" ? ctx.actionId || ctx.taskId : ctx.taskId;
|
||||
const param = (btn.idParam || "id").trim() || "id";
|
||||
const url = btn.appendTaskId && id ? appendQueryParam(base, param, id) : base;
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
return;
|
||||
}
|
||||
// business:内部切换到另一业务
|
||||
const detail = readCopTaskDetail();
|
||||
if (!detail || !ctx.taskId) {
|
||||
fail("缺少任务信息,无法跳转");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const target = await resolveTaskDeeplinkTarget(btn.target, detail, ctx.taskId);
|
||||
if (target) ctx.navigate(target.path, { state: target.state });
|
||||
else fail(`未配置「${btn.label}」对应的业务智能体`);
|
||||
} catch {
|
||||
fail("跳转失败,请稍后再试");
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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)。 */
|
||||
@@ -135,20 +174,23 @@ function buildOpeningText(detail: CopTaskDetail, taskId: string): string {
|
||||
* ``sessionStorage.pendingAgentMessage``(AgentChatPage 自动发出)。
|
||||
* 无法识别 goPath / 未配置智能体时返回 null(调用方回退到默认首页)。
|
||||
*
|
||||
* @param taskId 深链 URL 的原始 ``?taskId=``(外部任务 id,用于按任务隔离草稿;与
|
||||
* consumer 返回的 detail.id 区分——后者不保证等于外部任务 id)。
|
||||
* @param taskId 真实任务 id(xdfx 已由 action 接口解析;其它业务即深链原始 ?taskId=)。
|
||||
* 用于按任务隔离草稿/对话;与 consumer 返回的 detail.id 区分。
|
||||
* @param rawId 深链 URL 的原始 ``?taskId=``(xdfx 为「行动id」)。供需追加行动id 的按钮用;
|
||||
* 省略时回退 taskId。
|
||||
*/
|
||||
export async function resolveTaskDeeplinkTarget(
|
||||
goPath: string,
|
||||
detail: CopTaskDetail,
|
||||
taskId: string,
|
||||
rawId?: string,
|
||||
): Promise<TaskDeeplinkTarget | null> {
|
||||
const parsed = parseGoPath(goPath);
|
||||
if (!parsed) return null;
|
||||
const taskBase = BUSINESS_TASK_BASE[parsed.businessCode];
|
||||
if (!taskBase) return null;
|
||||
|
||||
// 清掉上一次深链可能残留的任务上下文(仅 rwfx 单智能体会重新写入)。
|
||||
// 清掉上一次深链可能残留的任务上下文(单智能体分支会用本次任务重新写入)。
|
||||
clearTaskContext();
|
||||
|
||||
if (parsed.roundtable) {
|
||||
@@ -177,22 +219,15 @@ 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),
|
||||
actionId: rawId || taskId,
|
||||
});
|
||||
return { path: `${taskBase}/agent/${agentId}/chats` };
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export interface TaskContext {
|
||||
agentId: string;
|
||||
/** 自动发出的开场信息(任务名称/内容)。 */
|
||||
openingText: string;
|
||||
/** 深链原始 id(xdfx 为「行动id」,需追加行动id 的按钮用;其它业务等于 taskId)。 */
|
||||
actionId?: string;
|
||||
}
|
||||
|
||||
const TASK_CONTEXT_STORAGE_KEY = "task.rwfxContext";
|
||||
@@ -190,6 +192,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 {
|
||||
@@ -210,6 +218,7 @@ export function readTaskContext(): TaskContext | null {
|
||||
taskId: String(parsed.taskId),
|
||||
agentId: String(parsed.agentId),
|
||||
openingText: typeof parsed.openingText === "string" ? parsed.openingText : "",
|
||||
actionId: typeof parsed.actionId === "string" ? parsed.actionId : undefined,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
@@ -4,19 +4,27 @@
|
||||
* 新建对话时由 [[task-purpose]] 流程给 thread 打 ``metadata.taskId`` 标签
|
||||
* (见 useThreadStream 的 ``threadMetadata``);这里复用服务端分页搜索按该标签过滤,
|
||||
* 后端默认按 ``updated_at desc`` 排序,所以 ``threads[0]`` 即最新一条。
|
||||
*
|
||||
* 同一个 taskId 可能挂在多条业务链上(rwfx / 3qfx / xdfx 共用任务 id),每条业务用各自的
|
||||
* 智能体。传入 ``agentId`` 时额外按 ``metadata.agent_id`` 过滤(后端 metadata 过滤是 AND/子集
|
||||
* 匹配),把不同业务的对话列表隔离开——否则三种业务的对话会混在一起。
|
||||
*/
|
||||
|
||||
import { useThreadsPage } from "@/core/threads/hooks";
|
||||
import type { AgentThread } from "@/core/threads/types";
|
||||
|
||||
export function useTaskThreads(taskId: string | null | undefined) {
|
||||
export function useTaskThreads(
|
||||
taskId: string | null | undefined,
|
||||
agentId?: string | null | undefined,
|
||||
) {
|
||||
const enabled = !!taskId;
|
||||
// 任务工作区只在有 taskId 时渲染;无 taskId 时传一个不可能命中的过滤,返回空列表。
|
||||
const query = useThreadsPage({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
metadata: enabled ? { taskId } : { taskId: "__none__" },
|
||||
});
|
||||
const metadata = enabled
|
||||
? agentId
|
||||
? { taskId, agent_id: agentId }
|
||||
: { taskId }
|
||||
: { taskId: "__none__" };
|
||||
const query = useThreadsPage({ page: 1, pageSize: 100, metadata });
|
||||
const threads: AgentThread[] = enabled ? (query.data?.threads ?? []) : [];
|
||||
return {
|
||||
...query,
|
||||
|
||||
Reference in new issue
Block a user