6a87496a4b
- PineAgents provider base_url 改为动态使用 DEMO_API_BASE_URL(远程服务器) - list_provider_info 支持从请求头透传 console 端 JWT,优先用于 /v1/models - forward/forward_upload 未显式传 auth_header 时自动使用本地持久化 token - auth_token_store 添加日志,便于排查 token 保存/加载问题 - fetch_models 超时增加到 15s,添加获取结果日志
173 lines
4.4 KiB
TypeScript
173 lines
4.4 KiB
TypeScript
import { Modal } from "@agentscope-ai/design";
|
|
import React from "react";
|
|
import type {
|
|
SecurityScanErrorResponse,
|
|
BlockedSkillFinding,
|
|
BlockedSkillRecord,
|
|
SkillScannerConfig,
|
|
} from "../api/modules/security";
|
|
import type { TFunction } from "i18next";
|
|
|
|
export function tryParseScanError(
|
|
error: unknown,
|
|
): SecurityScanErrorResponse | null {
|
|
if (!(error instanceof Error)) return null;
|
|
const msg = error.message || "";
|
|
const jsonStart = msg.indexOf("{");
|
|
if (jsonStart === -1) return null;
|
|
try {
|
|
const parsed = JSON.parse(msg.substring(jsonStart));
|
|
if (parsed?.type === "security_scan_failed") {
|
|
return parsed as SecurityScanErrorResponse;
|
|
}
|
|
} catch {
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Cap long finding lists so modals stay readable; full history remains in alerts. */
|
|
const MAX_FINDINGS_IN_MODAL = 5;
|
|
|
|
function renderFindings(findings: BlockedSkillFinding[], t: TFunction) {
|
|
const total = findings.length;
|
|
const shown = findings.slice(0, MAX_FINDINGS_IN_MODAL);
|
|
const moreCount = total - shown.length;
|
|
return React.createElement(
|
|
"div",
|
|
{ style: { maxHeight: 300, overflow: "auto", marginTop: 8 } },
|
|
shown.map((f, i) =>
|
|
React.createElement(
|
|
"div",
|
|
{
|
|
key: i,
|
|
style: {
|
|
padding: "8px 12px",
|
|
marginBottom: 4,
|
|
background: "var(--bg)",
|
|
borderRadius: 6,
|
|
border: "1px solid var(--color-surface-strong)",
|
|
},
|
|
},
|
|
React.createElement(
|
|
"strong",
|
|
{ style: { marginBottom: 4, display: "block" } },
|
|
f.title,
|
|
),
|
|
React.createElement(
|
|
"div",
|
|
{ style: { fontSize: 12, color: "#666" } },
|
|
f.file_path + (f.line_number ? `:${f.line_number}` : ""),
|
|
),
|
|
f.description &&
|
|
React.createElement(
|
|
"div",
|
|
{ style: { fontSize: 12, color: "#999", marginTop: 2 } },
|
|
f.description,
|
|
),
|
|
),
|
|
),
|
|
moreCount > 0 &&
|
|
React.createElement(
|
|
"div",
|
|
{
|
|
key: "more",
|
|
style: {
|
|
fontSize: 12,
|
|
color: "#888",
|
|
marginTop: 8,
|
|
padding: "8px 12px",
|
|
},
|
|
},
|
|
t("security.skillScanner.scanError.moreFindings", { count: moreCount }),
|
|
),
|
|
);
|
|
}
|
|
|
|
export function showScanErrorModal(
|
|
scanError: SecurityScanErrorResponse,
|
|
t: TFunction,
|
|
) {
|
|
const findings = scanError.findings || [];
|
|
Modal.error({
|
|
title: t("security.skillScanner.scanError.title"),
|
|
width: 640,
|
|
content: React.createElement(
|
|
"div",
|
|
null,
|
|
React.createElement(
|
|
"p",
|
|
null,
|
|
t("security.skillScanner.scanError.description"),
|
|
),
|
|
renderFindings(findings, t),
|
|
),
|
|
});
|
|
}
|
|
|
|
export function showScanWarnModal(
|
|
findings: BlockedSkillFinding[],
|
|
t: TFunction,
|
|
) {
|
|
Modal.warning({
|
|
title: t("security.skillScanner.scanError.title"),
|
|
width: 640,
|
|
content: React.createElement(
|
|
"div",
|
|
null,
|
|
React.createElement(
|
|
"p",
|
|
null,
|
|
t("security.skillScanner.scanError.warnDescription"),
|
|
),
|
|
renderFindings(findings, t),
|
|
),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check an error for a scan failure, show the modal if found, and return
|
|
* whether it was handled.
|
|
*/
|
|
export function handleScanError(error: unknown, t: TFunction): boolean {
|
|
const scanError = tryParseScanError(error);
|
|
if (scanError) {
|
|
showScanErrorModal(scanError, t);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* After a successful operation, check if the scanner recorded any
|
|
* warn-mode findings and show a warning modal if so.
|
|
*/
|
|
export async function checkScanWarnings(
|
|
skillName: string,
|
|
fetchAlerts: () => Promise<BlockedSkillRecord[]>,
|
|
fetchScannerCfg: () => Promise<SkillScannerConfig>,
|
|
t: TFunction,
|
|
): Promise<void> {
|
|
try {
|
|
const [alerts, scannerCfg] = await Promise.all([
|
|
fetchAlerts(),
|
|
fetchScannerCfg(),
|
|
]);
|
|
if (!alerts.length) return;
|
|
if (
|
|
scannerCfg?.whitelist?.some(
|
|
(w: { skill_name: string }) => w.skill_name === skillName,
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
const latestForSkill = alerts
|
|
.filter((a) => a.skill_name === skillName && a.action === "warned")
|
|
.pop();
|
|
if (!latestForSkill) return;
|
|
showScanWarnModal(latestForSkill.findings || [], t);
|
|
} catch {
|
|
// best-effort; don't break the caller on failure
|
|
}
|
|
}
|