- 新增带导航+侧边栏的独立页面 DashboardAgentPage:按 taskId 取/存会话, 有有效 report-json 直显大屏(DashboardApp),否则进生成过程(不自动跳)+输入框可改 - 后端 dashboard_sessions 存储(model/sql/__init__)+路由/by-task(GET/PUT 不分权) + deps/app/models 接线;新表靠 create_all 建,无迁移 - 扩展 useStep3Dashboard:buildInitialPrompt(输入任务JSON) + hydrateTranscript(恢复聊天记录) - 抽出共享 DashboardChatPanel;report-template 增 buildDashboardPromptFromJson - 输入 seam core/auth/dashboard-input:JSON 接口未就绪先用占位/演示数据兜底 - 路由 StrategyRoutes dashboard-agent + 侧边栏「问答管理→大屏绘制」 - 圆桌业务链卡片「关系图」改横向布局 - 顺带提交:AIWritingContext 登录切换处理、thread-access-error helper Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
156 lines
6.6 KiB
TypeScript
156 lines
6.6 KiB
TypeScript
/**
|
||
* 大屏绘制智能体「独立页面」的**输入 JSON** 适配 seam。
|
||
*
|
||
* 设计意图:外部系统登录时**后续会**提供一个接口返回一份业务 JSON,本页让
|
||
* 智能体分析这份 JSON → 产出 report-json → 绘制大屏。
|
||
*
|
||
* ⚠️ 该接口现在还没开发好。本模块先做**占位适配**让页面尽快可用:
|
||
* - 配了 `VITE_DASHBOARD_INPUT_URL` → 按 taskId 拉取真实 JSON;
|
||
* - 没配 → 回退用登录深链already 暂存的任务详情(`login.copTaskDetail`)拼一份占位输入,
|
||
* 让智能体至少能基于任务名/内容产出一版可看的大屏。
|
||
*
|
||
* 接口就绪后:把 `fetchDashboardInputJson` 换成真实拉取;再按规划迁到「后台转换格式
|
||
* + 智能体校验」(校验仍由智能体做,不在后台硬抽)。
|
||
*/
|
||
|
||
import { requestCrudService } from "@/service/request-axios-service";
|
||
import { readCopTaskDetail } from "@/core/auth/task-deeplink";
|
||
|
||
/** 本页输入 JSON 暂存的 sessionStorage key(拉取一次后复用,避免重复请求)。 */
|
||
export const DASHBOARD_INPUT_STORAGE_KEY = "dashboard.inputJson";
|
||
|
||
/**
|
||
* 🧪 演示用模拟业务 JSON —— JSON 接口未就绪、也没有登录任务详情时的兜底输入。
|
||
* 让大屏页可立即跑通:智能体分析这份数据 → report-json → 渲染大屏。
|
||
* ⚠️ 接口就绪后删除本常量及其兜底引用。
|
||
*/
|
||
export const DEMO_DASHBOARD_INPUT = {
|
||
taskId: "DEMO-涉台舆情",
|
||
taskName: "2026 第一季度涉台舆情态势研判",
|
||
taskContent:
|
||
"汇总境内外多平台涉台舆情数据,研判热点叙事走势、关键行为体立场与传播渠道分布,识别风险并给出处置建议。",
|
||
overview:
|
||
"本季涉台舆情整体平稳但局部波动,'两岸交流''和平发展'类正向叙事占比上升,少数账号借军事议题制造紧张;境外智库与媒体联动放大特定框架。",
|
||
period: "2026-01-01 ~ 2026-03-31",
|
||
kpis: {
|
||
总信息量: 1284000,
|
||
日均增速: "12%",
|
||
正向占比: "58%",
|
||
负向占比: "19%",
|
||
识别风险点: 7,
|
||
覆盖平台: 12,
|
||
},
|
||
channels: [
|
||
{ platform: "微博", share: 34 },
|
||
{ platform: "境外社媒(X/FB)", share: 27 },
|
||
{ platform: "短视频", share: 18 },
|
||
{ platform: "新闻门户", share: 13 },
|
||
{ platform: "论坛/贴吧", share: 8 },
|
||
],
|
||
narratives: [
|
||
{ theme: "两岸交流与融合发展", sentiment: "正向", volume: 42, trend: "up" },
|
||
{ theme: "和平稳定·避战", sentiment: "正向", volume: 26, trend: "up" },
|
||
{ theme: "军事演训与安全", sentiment: "中性偏负", volume: 21, trend: "flat" },
|
||
{ theme: "外部势力介入", sentiment: "负向", volume: 11, trend: "down" },
|
||
],
|
||
actors: [
|
||
{ name: "主流官媒", stance: "正面引导", influence: 88, note: "议程设置与权威发布" },
|
||
{ name: "岛内民间团体", stance: "中性多元", influence: 64, note: "交流合作类内容活跃" },
|
||
{ name: "境外智库", stance: "负面框架", influence: 57, note: "联动媒体放大紧张叙事" },
|
||
{ name: "自媒体大V", stance: "立场分化", influence: 49, note: "情绪化传播、易被借势" },
|
||
],
|
||
risks: [
|
||
{ label: "军事议题被恶意剪辑放大", level: "high" },
|
||
{ label: "境外智库联动制造紧张框架", level: "high" },
|
||
{ label: "个别大V情绪化带节奏", level: "mid" },
|
||
{ label: "短视频谣言二次传播", level: "mid" },
|
||
{ label: "节假日期间信息量骤增", level: "low" },
|
||
],
|
||
timeline: [
|
||
{ phase: "数据汇聚", desc: "12 平台多源采集与清洗去重" },
|
||
{ phase: "叙事识别", desc: "聚类提炼核心叙事与情感倾向" },
|
||
{ phase: "行为体画像", desc: "关键账号立场与影响力建模" },
|
||
{ phase: "风险研判", desc: "识别高危议题与传播链路" },
|
||
{ phase: "处置建议", desc: "分级响应与正向引导预案" },
|
||
],
|
||
weeklyVolume: [
|
||
{ week: "W1", value: 86 },
|
||
{ week: "W2", value: 92 },
|
||
{ week: "W3", value: 110 },
|
||
{ week: "W4", value: 134 },
|
||
{ week: "W5", value: 121 },
|
||
{ week: "W6", value: 158 },
|
||
],
|
||
};
|
||
|
||
function inputBaseUrl(): string {
|
||
// TODO: 接口就绪后确认环境变量名/路径。占位:未配则走 copTaskDetail 回退。
|
||
const value = (import.meta.env as Record<string, unknown>).VITE_DASHBOARD_INPUT_URL;
|
||
return typeof value === "string" ? value.replace(/\/+$/, "") : "";
|
||
}
|
||
|
||
/** 读取暂存的输入 JSON。 */
|
||
export function readDashboardInputJson(): unknown | null {
|
||
try {
|
||
const raw = sessionStorage.getItem(DASHBOARD_INPUT_STORAGE_KEY);
|
||
return raw ? JSON.parse(raw) : null;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/** 暂存输入 JSON(隐私模式/quota 异常静默失败)。 */
|
||
export function persistDashboardInputJson(data: unknown): void {
|
||
try {
|
||
sessionStorage.setItem(DASHBOARD_INPUT_STORAGE_KEY, JSON.stringify(data));
|
||
} catch {
|
||
/* noop */
|
||
}
|
||
}
|
||
|
||
/** 用登录暂存的任务详情拼一份占位输入(接口未就绪时的回退)。 */
|
||
function placeholderFromCopTask(taskId: string): unknown | null {
|
||
const detail = readCopTaskDetail();
|
||
if (!detail) return taskId ? { taskId } : null;
|
||
return {
|
||
taskId,
|
||
taskName: detail.taskName,
|
||
taskContent: detail.taskContent,
|
||
overview: detail.overview,
|
||
taskDirection: detail.taskDirection,
|
||
_note: "占位输入(真实业务 JSON 接口未就绪)",
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 取本页的输入 JSON:优先暂存 → 真实接口(配了地址)→ copTaskDetail 占位。
|
||
* 任一步失败都静默退化,最终可能返回 null(调用方据此提示用户/不自动开跑)。
|
||
*/
|
||
export async function fetchDashboardInputJson(taskId: string): Promise<unknown | null> {
|
||
const cached = readDashboardInputJson();
|
||
if (cached) return cached;
|
||
|
||
const base = inputBaseUrl();
|
||
if (base && 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;
|
||
}
|
||
} catch {
|
||
/* 拉取失败 → 落占位 */
|
||
}
|
||
}
|
||
|
||
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);
|
||
return resolved;
|
||
}
|