feat(dashboard-agent): 深链大屏接入研判情报报告接口 selectSqReport + 样例假数据
- dashboard-input: 请求 {consumer}/cop/selectSqReport?taskId=,新增 extractSqReport
反序列化各条 contentJson、按 categoryType 归类成干净结构喂给智能体
- 真实接口未就绪:新增 sq-report-mock(.json/-data.ts) 样例假数据 + adapter,
request.ts 按 VITE_MOCK_SQ_REPORT 开关挂载(机制同 cop-task-detail)
- env:dev=true 走假数据,prod/network/sit=false 真实接口
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
81ca13280b
commit
2b58fe6c8b
8 files changed
+288
-21
No files matched your search
@@ -1,16 +1,19 @@
|
||||
/**
|
||||
* 大屏绘制智能体「独立页面」的**输入 JSON** 适配 seam。
|
||||
*
|
||||
* 设计意图:外部系统登录时**后续会**提供一个接口返回一份业务 JSON,本页让
|
||||
* 智能体分析这份 JSON → 产出 report-json → 绘制大屏。
|
||||
* 设计意图:深链 `?taskId=` 模式下,后端提供「研判情报报告」接口
|
||||
* GET `{VITE_AIPI_CONSUMER_URL}/cop/selectSqReport?taskId=`
|
||||
* 返回该任务的多类情报内容;本页把它**提取成干净的结构化 JSON** 交给智能体分析
|
||||
* → 产出 report-json → 绘制大屏(react / html)。
|
||||
*
|
||||
* ⚠️ 该接口现在还没开发好。本模块先做**占位适配**让页面尽快可用:
|
||||
* - 配了 `VITE_DASHBOARD_INPUT_URL` → 按 taskId 拉取真实 JSON;
|
||||
* - 没配 → 回退用登录深链already 暂存的任务详情(`login.copTaskDetail`)拼一份占位输入,
|
||||
* 让智能体至少能基于任务名/内容产出一版可看的大屏。
|
||||
* 接口返回结构:`{ state, msg, data: [{ id, taskId, ..., contentJson, categoryType }] }`,
|
||||
* 其中 `contentJson` 是**被转义的 JSON 字符串**,`categoryType` 形如「敌情关键事件 JSON」。
|
||||
* 本模块 `extractSqReport` 把每条的 `contentJson` 反序列化、按 `categoryType` 归类,
|
||||
* 拼成 `{ taskId, categories: { 敌情关键事件: [...], 决心建议: {...}, ... } }` 喂给智能体。
|
||||
*
|
||||
* 接口就绪后:把 `fetchDashboardInputJson` 换成真实拉取;再按规划迁到「后台转换格式
|
||||
* + 智能体校验」(校验仍由智能体做,不在后台硬抽)。
|
||||
* ⚠️ 真实接口现在还**请求不到**:通过 `VITE_MOCK_SQ_REPORT=true`(见 `utils/request.ts`)
|
||||
* 走后端给的样例假数据(`sq-report-mock.json`),机制与 cop-task-detail 假数据一致。
|
||||
* 联调通过后把该 env 置 false 即走真实接口;接口/数据若有变只需改 `extractSqReport`。
|
||||
*/
|
||||
|
||||
import { requestCrudService } from "@/service/request-axios-service";
|
||||
@@ -83,10 +86,52 @@ export const DEMO_DASHBOARD_INPUT = {
|
||||
],
|
||||
};
|
||||
|
||||
function inputBaseUrl(): string {
|
||||
// TODO: 接口就绪后确认环境变量名/路径。占位:未配则走 copTaskDetail 回退。
|
||||
const value = (import.meta.env as Record<string, unknown>).VITE_DASHBOARD_INPUT_URL;
|
||||
return typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||||
/** 「研判情报报告」接口完整地址:`{VITE_AIPI_CONSUMER_URL}/cop/selectSqReport`。 */
|
||||
function sqReportUrl(): string {
|
||||
const value = (import.meta.env as Record<string, unknown>).VITE_AIPI_CONSUMER_URL;
|
||||
const base = typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||||
return base ? `${base}/cop/selectSqReport` : "";
|
||||
}
|
||||
|
||||
/** 把单条 `contentJson` 反序列化为对象/数组/原值(已是对象则原样返回)。 */
|
||||
function parseContentJson(raw: unknown): unknown {
|
||||
if (typeof raw !== "string") return raw ?? null;
|
||||
const text = raw.trim();
|
||||
if (!text) return null;
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return text; // 解析失败就把原始字符串透传给智能体,让它自行理解
|
||||
}
|
||||
}
|
||||
|
||||
/** 去掉 categoryType 末尾的「 JSON」后缀,作为归类键(如「敌情关键事件 JSON」→「敌情关键事件」)。 */
|
||||
function categoryKey(categoryType: unknown, index: number): string {
|
||||
const raw = typeof categoryType === "string" ? categoryType.trim() : "";
|
||||
const cleaned = raw.replace(/\s*JSON\s*$/i, "").trim();
|
||||
return cleaned || `分类${index + 1}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 selectSqReport 的整包响应**提取**成喂给智能体的干净结构化输入:
|
||||
* `{ taskId, source, categories: { [类目]: 反序列化后的内容 } }`
|
||||
* 返回 null 表示无可用内容(调用方据此回退/不开跑)。
|
||||
*/
|
||||
export function extractSqReport(body: unknown, taskId: string): unknown | null {
|
||||
const data = (body && typeof body === "object" ? (body as { data?: unknown }).data : null) as
|
||||
| Array<Record<string, unknown>>
|
||||
| undefined;
|
||||
if (!Array.isArray(data) || data.length === 0) return null;
|
||||
const categories: Record<string, unknown> = {};
|
||||
data.forEach((item, index) => {
|
||||
const content = parseContentJson(item?.contentJson);
|
||||
if (content == null) return;
|
||||
let key = categoryKey(item?.categoryType, index);
|
||||
if (key in categories) key = `${key}(${index + 1})`; // 同名类目去重保留
|
||||
categories[key] = content;
|
||||
});
|
||||
if (Object.keys(categories).length === 0) return null;
|
||||
return { taskId, source: "cop/selectSqReport", categories };
|
||||
}
|
||||
|
||||
/** 读取暂存的输入 JSON。 */
|
||||
@@ -123,22 +168,25 @@ function placeholderFromCopTask(taskId: string): unknown | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* 取本页的输入 JSON:优先暂存 → 真实接口(配了地址)→ copTaskDetail 占位。
|
||||
* 取本页的输入 JSON:优先暂存 → selectSqReport 接口(提取 contentJson)→ copTaskDetail 占位。
|
||||
* 任一步失败都静默退化,最终可能返回 null(调用方据此提示用户/不自动开跑)。
|
||||
*
|
||||
* 真实接口未就绪时,`VITE_MOCK_SQ_REPORT=true` 让请求走本地样例假数据(见 utils/request.ts),
|
||||
* 此处逻辑对真假数据完全一致——都是「请求 selectSqReport → extractSqReport 提取内容」。
|
||||
*/
|
||||
export async function fetchDashboardInputJson(taskId: string): Promise<unknown | null> {
|
||||
const cached = readDashboardInputJson();
|
||||
if (cached) return cached;
|
||||
|
||||
const base = inputBaseUrl();
|
||||
if (base && taskId) {
|
||||
const url = sqReportUrl();
|
||||
if (url && taskId) {
|
||||
try {
|
||||
// service 拦截器统一注入鉴权并返回 response.data(整包 body)。
|
||||
const body = await requestCrudService.get(`${base}`, { taskId });
|
||||
const data = body && typeof body === "object" ? (body as { data?: unknown }).data ?? body : body;
|
||||
if (data) {
|
||||
persistDashboardInputJson(data);
|
||||
return data;
|
||||
const body = await requestCrudService.get(url, { taskId });
|
||||
const extracted = extractSqReport(body, taskId);
|
||||
if (extracted) {
|
||||
persistDashboardInputJson(extracted);
|
||||
return extracted;
|
||||
}
|
||||
} catch {
|
||||
/* 拉取失败 → 落占位 */
|
||||
@@ -147,7 +195,6 @@ export async function fetchDashboardInputJson(taskId: string): Promise<unknown |
|
||||
|
||||
const placeholder = placeholderFromCopTask(taskId);
|
||||
// 占位里有真实任务内容(来自登录详情)才用它;否则回退演示数据,保证页面能跑通看效果。
|
||||
// ⚠️ 接口就绪后删除 DEMO 兜底。
|
||||
const detail = readCopTaskDetail();
|
||||
const resolved = detail ? placeholder : { ...DEMO_DASHBOARD_INPUT, taskId: taskId || DEMO_DASHBOARD_INPUT.taskId };
|
||||
if (resolved) persistDashboardInputJson(resolved);
|
||||
|
||||
Reference in new issue
Block a user