feat(dashboard): 大屏绘制智能体独立页面(按 taskId 存取,不分权)

- 新增带导航+侧边栏的独立页面 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>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-18 14:26:27 +08:00
1 parent 500a562bb6
commit 32772b7448
20 files changed
+1396 -271

No files matched your search

@@ -0,0 +1,155 @@
/**
* 大屏绘制智能体「独立页面」的**输入 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;
}