Merge branch 'ji' into master
合并 ji 的功能:开放问答接口(/api/open/chat[/stream] 回传生成文件 + 临时不留存)、 技能地址管理、智能体详情「最近对话」等。 冲突 frontend StrategyRoutes.tsx 已解决:同时保留 admin/roundtable-diagnostics(master) 与 admin/skill-addresses(ji) 两个管理员路由。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
fbec222eb4
38 files changed
+3365
-94
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
import { isEmbedActive } from "@/core/embed/embed-session";
|
||||
|
||||
const AUTH_STORAGE_KEY = "deerflow.auth";
|
||||
|
||||
export interface LiveAccount {
|
||||
/** 实时登录用户 id(随 storage/focus 刷新)。null = 未登录。 */
|
||||
userId: string | null;
|
||||
/** 实时显示名(邮箱 @ 前缀,否则 user_id)。 */
|
||||
displayName: string;
|
||||
/** 组件首次挂载时捕获的用户 id。 */
|
||||
mountUserId: string | null;
|
||||
/** 组件首次挂载时捕获的显示名。 */
|
||||
mountDisplayName: string;
|
||||
/**
|
||||
* 当前登录账号与挂载时不一致 —— 即本视图加载后登录被切换过(通常是在另一个
|
||||
* 标签页登录了别的账号)。归属/发布类操作必须据此提醒,避免误归属。
|
||||
*/
|
||||
switched: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪「实时」登录账号,而不是挂载时缓存的值。
|
||||
*
|
||||
* 鉴权 token 存在 localStorage(非嵌入态下被同浏览器所有标签页共享),所以在
|
||||
* 另一个标签页登录会**静默替换**本标签页 API 调用所用的 token。任何会决定归属的
|
||||
* 界面(创建/发布智能体)都必须显示请求实际将以哪个账号执行,并在会话中途被切换
|
||||
* 账号时给出提示 —— 否则会出现「用 A 账号创建却归到 B 账号名下」。
|
||||
*/
|
||||
export function useLiveAccount(): LiveAccount {
|
||||
const mountRef = useRef<{ id: string | null; name: string } | null>(null);
|
||||
if (mountRef.current === null) {
|
||||
const auth = getStoredAuth();
|
||||
mountRef.current = {
|
||||
id: auth?.user_id ?? null,
|
||||
name: getAccountDisplayName(auth),
|
||||
};
|
||||
}
|
||||
const [live, setLive] = useState(() => mountRef.current!);
|
||||
|
||||
useEffect(() => {
|
||||
function refresh() {
|
||||
const auth = getStoredAuth();
|
||||
const id = auth?.user_id ?? null;
|
||||
const name = getAccountDisplayName(auth);
|
||||
setLive((prev) =>
|
||||
prev.id === id && prev.name === name ? prev : { id, name },
|
||||
);
|
||||
}
|
||||
// 嵌入(iframe)态鉴权走按标签页的 sessionStorage、不跨标签,收不到 storage 事件
|
||||
// —— 只挂在正常态;focus/visibilitychange 兜底刷新两种形态都有效。
|
||||
function onStorage(e: StorageEvent) {
|
||||
if (e.key === AUTH_STORAGE_KEY) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
if (!isEmbedActive()) {
|
||||
window.addEventListener("storage", onStorage);
|
||||
}
|
||||
window.addEventListener("focus", refresh);
|
||||
document.addEventListener("visibilitychange", refresh);
|
||||
refresh();
|
||||
return () => {
|
||||
window.removeEventListener("storage", onStorage);
|
||||
window.removeEventListener("focus", refresh);
|
||||
document.removeEventListener("visibilitychange", refresh);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const mount = mountRef.current!;
|
||||
return {
|
||||
userId: live.id,
|
||||
displayName: live.name,
|
||||
mountUserId: mount.id,
|
||||
mountDisplayName: mount.name,
|
||||
switched: live.id !== null && mount.id !== null && live.id !== mount.id,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user