feat: 智能体详情新增「最近对话」列表,完善推荐问题与任务深链配置

- 新增 AgentRecentChats 组件,在智能体详情面板底部按 agent_id 展示最近对话
- 调整问答/智能体侧边栏与最近对话列表展示逻辑
- 补充 dashboard-input、task-deeplink 相关配置与线程工具方法
- 后端推荐问题接口标签处理及 position_sync 微调,新增相关测试

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-22 14:47:43 +08:00
1 parent 16ba5deb0e
commit 32476c7ad4
18 files changed
+457 -60

No files matched your search

@@ -18,6 +18,7 @@
import { requestCrudService } from "@/service/request-axios-service";
import { readCopTaskDetail } from "@/core/auth/task-deeplink";
import { fetchTaskDeeplinkConfig } from "@/core/auth/task-deeplink-config";
/** 本页输入 JSON 暂存的 sessionStorage key(拉取一次后复用,避免重复请求)。 */
export const DASHBOARD_INPUT_STORAGE_KEY = "dashboard.inputJson";
@@ -221,3 +222,43 @@ export async function fetchDashboardInputJson(
if (resolved) persistDashboardInputJson(resolved);
return resolved;
}
/* ---------------------- rwfx 进入预检:3q 是否完成 ---------------------- */
/**
* 「3q(研判情报报告)是否完成」状态:
* - ``complete`` = selectSqReport 数组里有有效内容;
* - ``incomplete`` = 数组为空 / 每条 contentJson 都是 null/空(3q 未完成);
* - ``unknown`` = 缺 consumer 地址 / 请求异常(无法判定,调用方应静默放行)。
*/
export type SqReportStatus = "complete" | "incomplete" | "unknown";
/**
* rwfx(任务分析)进入时的「3q 是否完成」预检:调 selectSqReport,复用 ``extractSqReport``
* 判定该任务是否已有有效研判情报。判定口径与大屏绘制取数完全一致(空/全 null → 无内容)。
* - 有可用内容 → ``complete``(正常);
* - 空 / 全 null → ``incomplete``(rwfx 据此弹框引导去 3qfx);
* - 缺地址 / 请求失败 → ``unknown``(不打扰用户)。
*
* **数据源由后端配置 ``task_deeplink.test`` 决定**(线上 selectSqReport 外部不可用时):
* - ``test=true`` → 直接用 ``config.yaml`` 的 ``mock.sq_report`` 假数据判定,**不发请求**
* (与目的树查询/行动解析的 mock 同一套机制,见 [[task-deeplink-config]]);
* - ``test=false`` → 调真实接口 ``GET {VITE_AIPI_CONSUMER_URL}/cop/selectSqReport?taskId=``。
*/
export async function fetchSqReportStatus(taskId: string): Promise<SqReportStatus> {
if (!taskId) return "unknown";
const cfg = await fetchTaskDeeplinkConfig();
// test=true:外部接口不可用,直接用后端 config 的假数据判定(不打真实接口)。
if (cfg.test) {
return extractSqReport(cfg.mock.sqReport, taskId) ? "complete" : "incomplete";
}
// 真实模式:打真实接口。service 拦截器统一注入鉴权并返回整包 body;空/全 null → null。
const url = sqReportUrl();
if (!url) return "unknown";
try {
const body = await requestCrudService.get(url, { taskId });
return extractSqReport(body, taskId) ? "complete" : "incomplete";
} catch {
return "unknown";
}
}
@@ -34,11 +34,13 @@ export interface TaskDeeplinkConfig {
/** 「3q详情」详情页 URL(rwfx 目的树面板链接,跳转追加 ?id= 任务id);留空则前端提示未配置。 */
detail3qUrl: string;
/** 测试假数据:purposeDetail = 原始 {state,msg,data};delete = {message,success};
* actionDetail = 原始 {state,msg,data:{taskId,...}}。 */
* actionDetail = 原始 {state,msg,data:{taskId,...}};sqReport = 原始 {state,msg,data:[...]}
* (rwfx 3q 预检用,test 为真时生效)。 */
mock: {
purposeDetail?: unknown;
delete?: { message?: string; success?: boolean };
actionDetail?: unknown;
sqReport?: unknown;
};
}
@@ -81,6 +83,7 @@ function normalize(body: unknown): TaskDeeplinkConfig {
? (mock.delete as { message?: string; success?: boolean })
: undefined,
actionDetail: mock.action_detail,
sqReport: mock.sq_report,
},
};
}
+26
View File
@@ -81,6 +81,32 @@ export function titleOfThread(thread: AgentThread, fallback = "Untitled") {
return fallback;
}
/**
* 智能体创建过程产生的「引导会话」(bootstrap)。这类会话用 metadata.thread_type
* === "bootstrap" 标记,点击后应回到「创建智能体」页继续,而不是普通问答页。
*/
export function isBootstrapThread(thread: {
metadata?: Record<string, unknown> | null;
}): boolean {
return thread.metadata?.thread_type === "bootstrap";
}
/**
* bootstrap 会话点击后回到「创建智能体」页并恢复该会话。该页只挂在
* /page/workspace 下(见 WorkspaceRoutes 的 agents/new/:thread_id),故路径固定,
* 不随当前 route base 变化。
*/
export function bootstrapResumePath(threadId: string): string {
return `/page/workspace/agents/new/${threadId}`;
}
/** 列表项点击目标:bootstrap 会话回创建页恢复,其余走普通问答路由。 */
export function listPathOfThread(thread: AgentThread): string {
return isBootstrapThread(thread)
? bootstrapResumePath(thread.thread_id)
: pathOfThread(thread);
}
export function isThreadPinned(thread: AgentThread): boolean {
return (thread.metadata as Record<string, unknown> | null)?.is_pinned === true;
}