feat(web): improve Mermaid diagram styling

This commit is contained in:
caoqianming 2026-09-02 10:13:02 +08:00
parent a1068f0ec1
commit aa8ccec738
6 changed files with 78 additions and 7 deletions

View File

@ -8,6 +8,8 @@
## Unreleased ## Unreleased
- Mermaid 图表采用更清晰的科研配色与更精致的图框,新生成的流程图还会按数据、处理、判断、结果等角色使用协调的语义色,减少单调的灰白图。
- 管理后台总览按“当前运行”和“运营资源”重新组织,执行容量改为精简摘要并可从右侧详情面板查看队列、容器、用户占用与宿主资源,异常状态和近期指标也更容易识别。 - 管理后台总览按“当前运行”和“运营资源”重新组织,执行容量改为精简摘要并可从右侧详情面板查看队列、容器、用户占用与宿主资源,异常状态和近期指标也更容易识别。
## 0.70.0 — 2026-09-02 ## 0.70.0 — 2026-09-02
@ -20,8 +22,6 @@
- 专业软件节点启用完整的新管理界面节点状态、首次注册、软件环境、本机任务、ANSYS 验收和数据目录迁移均可直接管理;应用图标、导航、状态提示和窄窗口布局也更清晰,关闭窗口后仍会继续在托盘运行。 - 专业软件节点启用完整的新管理界面节点状态、首次注册、软件环境、本机任务、ANSYS 验收和数据目录迁移均可直接管理;应用图标、导航、状态提示和窄窗口布局也更清晰,关闭窗口后仍会继续在托盘运行。
- 删除已发布文件后会释放个人存储配额;管理员可分别查看用户当前文件、隐藏回收站及物理总占用。
## 0.69.1 — 2026-08-27 ## 0.69.1 — 2026-08-27
- 专业软件任务的进度与结果改为在当前对话左侧集中堆叠,不再用多条结果消息打断正文;可直接打开 Blender 等软件的多张预览,手机端从“软件结果”入口统一查看。 - 专业软件任务的进度与结果改为在当前对话左侧集中堆叠,不再用多条结果消息打断正文;可直接打开 Blender 等软件的多张预览,手机端从“软件结果”入口统一查看。

View File

@ -161,6 +161,14 @@ _HOST_ENV_BLOCK = """\
## 运行环境(本地 host) ## 运行环境(本地 host)
`shell` 走的是 **Windows cmd.exe**( bash):避免 unix-only flag,`mkdir -p` 不识别 `run_python` `os.makedirs(..., exist_ok=True)` 建目录;复杂管道/重定向用 run_python 更稳""" `shell` 走的是 **Windows cmd.exe**( bash):避免 unix-only flag,`mkdir -p` 不识别 `run_python` `os.makedirs(..., exist_ok=True)` 建目录;复杂管道/重定向用 run_python 更稳"""
_MERMAID_STYLE_BLOCK = """\
## Mermaid 视觉规范
生成 Mermaid 图时默认采用**克制的语义配色**,不要输出全灰单色图,也不要随机彩虹色:
- 流程图 / 架构图用 `classDef` 按角色分组,通常 35 色足够:输入/数据用蓝色,处理/模块用青绿色,判断/风险用橙色,结果/成功用绿色,异常用红色;同类节点保持同色
- 使用浅色填充较深描边与高对比深色文字,线条颜色统一;分组框比节点更淡,避免大面积高饱和背景
- 颜色服务于信息层级;若用户指定品牌色黑白打印或已有 `classDef` / `style` 规则,优先遵循用户设置,不要覆盖"""
def load_config() -> dict: def load_config() -> dict:
return yaml.safe_load((ROOT / "config" / "agent.yaml").read_text(encoding="utf-8")) or {} return yaml.safe_load((ROOT / "config" / "agent.yaml").read_text(encoding="utf-8")) or {}
@ -363,6 +371,7 @@ def _build_system_prompt(
is_docker = os.getenv("ZCBOT_SANDBOX_BACKEND", "host").lower() == "docker" is_docker = os.getenv("ZCBOT_SANDBOX_BACKEND", "host").lower() == "docker"
# 运行环境段紧跟模板(平台/网络是基础事实,放前面);general_v1 的「平台」段指向这里。 # 运行环境段紧跟模板(平台/网络是基础事实,放前面);general_v1 的「平台」段指向这里。
prompt += _CONTAINER_ENV_BLOCK if is_docker else _HOST_ENV_BLOCK prompt += _CONTAINER_ENV_BLOCK if is_docker else _HOST_ENV_BLOCK
prompt += _MERMAID_STYLE_BLOCK
if skills.skills: if skills.skills:
prompt += f"\n\n## 可用 skill (用 load_skill 加载完整指引)\n{skills.discovery_block()}" prompt += f"\n\n## 可用 skill (用 load_skill 加载完整指引)\n{skills.discovery_block()}"
# .memory/ 在 agent 视角下的可写路径:docker 给容器路径,host 给宿主绝对路径。 # .memory/ 在 agent 视角下的可写路径:docker 给容器路径,host 给宿主绝对路径。

View File

@ -78,6 +78,11 @@ test("renders completed mermaid blocks with strict local configuration", async (
assert.equal(calls.initialize.length, 1); assert.equal(calls.initialize.length, 1);
assert.equal(calls.initialize[0].securityLevel, "strict"); assert.equal(calls.initialize[0].securityLevel, "strict");
assert.equal(calls.initialize[0].startOnLoad, false); assert.equal(calls.initialize[0].startOnLoad, false);
assert.equal(calls.initialize[0].theme, "base");
assert.equal(calls.initialize[0].themeVariables.primaryColor, "#eaf2ff");
assert.equal(calls.initialize[0].themeVariables.secondaryColor, "#e8f7f3");
assert.equal(calls.initialize[0].themeVariables.tertiaryColor, "#fff4df");
assert.equal(calls.initialize[0].themeVariables.pie5, "#d96c75");
assert.equal(calls.render[0].source, "flowchart LR\nA --> B"); assert.equal(calls.render[0].source, "flowchart LR\nA --> B");
assert.equal(fixture.pre.replacement.className, "mermaid-diagram"); assert.equal(fixture.pre.replacement.className, "mermaid-diagram");
assert.equal(fixture.pre.replacement.children[0].innerHTML, "<svg viewBox='0 0 10 10'></svg>"); assert.equal(fixture.pre.replacement.children[0].innerHTML, "<svg viewBox='0 0 10 10'></svg>");

View File

@ -75,6 +75,15 @@ class TestSystemPromptPaths(unittest.TestCase):
self.assertIn(str(wd), prompt) self.assertIn(str(wd), prompt)
self.assertNotIn("/workspace/数据资源展示", prompt) self.assertNotIn("/workspace/数据资源展示", prompt)
def test_mermaid_visual_guidance_is_backend_independent(self):
for backend in ("docker", "host"):
with self.subTest(backend=backend):
prompt, _, _ = _build(backend)
self.assertIn("## Mermaid 视觉规范", prompt)
self.assertIn("`classDef` 按角色分组", prompt)
self.assertIn("不要输出全灰单色图", prompt)
self.assertIn("用户指定品牌色", prompt)
def test_rename_guard_only_when_tool_is_available(self): def test_rename_guard_only_when_tool_is_available(self):
interactive, _, _ = _build("docker", allow_rename=True) interactive, _, _ = _build("docker", allow_rename=True)
scheduled, _, _ = _build("docker", allow_rename=False) scheduled, _, _ = _build("docker", allow_rename=False)

View File

@ -11,7 +11,53 @@ const MERMAID_CONFIG = Object.freeze({
startOnLoad: false, startOnLoad: false,
securityLevel: "strict", securityLevel: "strict",
suppressErrorRendering: true, suppressErrorRendering: true,
theme: "neutral", // A restrained scientific palette. Author-defined classDef/style rules still
// take precedence, while unstyled diagrams get a consistent visual baseline.
theme: "base",
themeVariables: Object.freeze({
background: "#ffffff",
primaryColor: "#eaf2ff",
primaryTextColor: "#17324d",
primaryBorderColor: "#5b8fc9",
secondaryColor: "#e8f7f3",
secondaryTextColor: "#163c35",
secondaryBorderColor: "#4f9f8d",
tertiaryColor: "#fff4df",
tertiaryTextColor: "#594018",
tertiaryBorderColor: "#d79a3b",
lineColor: "#60758a",
textColor: "#25384a",
mainBkg: "#eaf2ff",
secondBkg: "#e8f7f3",
border1: "#5b8fc9",
border2: "#4f9f8d",
arrowheadColor: "#60758a",
clusterBkg: "#f7f9fc",
clusterBorder: "#b9c8d8",
actorBkg: "#eaf2ff",
actorBorder: "#5b8fc9",
actorTextColor: "#17324d",
actorLineColor: "#8ca0b3",
signalColor: "#536b82",
signalTextColor: "#25384a",
labelBoxBkgColor: "#f7f9fc",
labelBoxBorderColor: "#b9c8d8",
labelTextColor: "#25384a",
loopTextColor: "#25384a",
noteBkgColor: "#fff4df",
noteBorderColor: "#d79a3b",
noteTextColor: "#594018",
activationBkgColor: "#d9eee9",
activationBorderColor: "#4f9f8d",
pie1: "#4f7cac",
pie2: "#5ca89a",
pie3: "#e4a64a",
pie4: "#a879b2",
pie5: "#d96c75",
pie6: "#6d9dc5",
pie7: "#84b59f",
pie8: "#d8b365",
}),
maxTextSize: 50000, maxTextSize: 50000,
maxEdges: 500, maxEdges: 500,
fontFamily: '-apple-system, "Segoe UI", "Microsoft YaHei", sans-serif', fontFamily: '-apple-system, "Segoe UI", "Microsoft YaHei", sans-serif',

View File

@ -442,11 +442,12 @@ header .who {
.mermaid-diagram { .mermaid-diagram {
margin: 14px 0; margin: 14px 0;
padding: 14px; padding: 18px;
overflow-x: auto; overflow-x: auto;
border: 1px solid var(--border-soft); border: 1px solid #dce6f2;
border-radius: var(--r-lg); border-radius: var(--r-xl);
background: #ffffff; background: linear-gradient(145deg, #ffffff 0%, #f8fbff 100%);
box-shadow: 0 6px 22px rgba(50, 79, 108, 0.08);
} }
.mermaid-viewport { .mermaid-viewport {
@ -458,6 +459,7 @@ header .who {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
margin: 0 auto; margin: 0 auto;
filter: drop-shadow(0 2px 3px rgba(38, 61, 82, 0.06));
} }
.mermaid-notice { .mermaid-notice {