1341 lines
75 KiB
Python
1341 lines
75 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
为用户交流技术实现方案.md中的架构图生成SVG并转为JPG
|
||
"""
|
||
import os
|
||
import subprocess
|
||
|
||
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "images")
|
||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||
|
||
# ─── 颜色方案 ───
|
||
C_BG = "#ffffff"
|
||
C_TITLE = "#1a1a2e"
|
||
C_LAYER1 = "#e3f2fd" # 浅蓝
|
||
C_LAYER1_B = "#1565c0"
|
||
C_LAYER2 = "#e8f5e9" # 浅绿
|
||
C_LAYER2_B = "#2e7d32"
|
||
C_LAYER3 = "#fff3e0" # 浅橙
|
||
C_LAYER3_B = "#e65100"
|
||
C_LAYER4 = "#f3e5f5" # 浅紫
|
||
C_LAYER4_B = "#6a1b9a"
|
||
C_LAYER5 = "#fce4ec" # 浅粉
|
||
C_LAYER5_B = "#c62828"
|
||
C_LAYER6 = "#e0f7fa" # 浅青
|
||
C_LAYER6_B = "#00838f"
|
||
C_BOX = "#f5f5f5"
|
||
C_BOX_B = "#616161"
|
||
C_ARROW = "#424242"
|
||
C_HIGHLIGHT = "#ffd54f"
|
||
C_AI = "#e8eaf6"
|
||
C_AI_B = "#283593"
|
||
C_VR = "#ede7f6"
|
||
C_VR_B = "#4527a0"
|
||
C_SECURITY = "#ffebee"
|
||
C_SECURITY_B = "#b71c1c"
|
||
C_DEPLOY = "#e0f2f1"
|
||
C_DEPLOY_B = "#00695c"
|
||
|
||
FONT = "Microsoft YaHei, PingFang SC, Heiti SC, SimHei, sans-serif"
|
||
|
||
def svg_header(w, h):
|
||
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">'
|
||
|
||
def svg_text(x, y, text, size=14, color=C_TITLE, bold=False, anchor="middle", font=FONT):
|
||
weight = "bold" if bold else "normal"
|
||
return f'<text x="{x}" y="{y}" font-family="{font}" font-size="{size}" fill="{color}" font-weight="{weight}" text-anchor="{anchor}" dominant-baseline="middle">{text}</text>'
|
||
|
||
def svg_rect(x, y, w, h, fill=C_BOX, stroke=C_BOX_B, rx=8, sw=1.5, opacity=1.0):
|
||
return f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{rx}" ry="{rx}" fill="{fill}" stroke="{stroke}" stroke-width="{sw}" opacity="{opacity}"/>'
|
||
|
||
def svg_box(x, y, w, h, text, fill=C_BOX, stroke=C_BOX_B, ts=13, tc=C_TITLE, bold=False, rx=8):
|
||
cx = x + w/2
|
||
cy = y + h/2
|
||
return svg_rect(x, y, w, h, fill, stroke, rx) + svg_text(cx, cy, text, ts, tc, bold)
|
||
|
||
import math as _math
|
||
|
||
def svg_arrow(x1, y1, x2, y2, color=C_ARROW, sw=2, al=14, aw=0.55):
|
||
"""绘制带箭头的线段。箭头在(x2,y2)端。
|
||
al: 箭头长度, aw: 箭头半角(弧度)
|
||
"""
|
||
angle = _math.atan2(y2-y1, x2-x1)
|
||
# 箭头三角形的两个底点
|
||
bx = x2 - al * _math.cos(angle)
|
||
by = y2 - al * _math.sin(angle)
|
||
ax1 = bx + al * _math.sin(angle) * _math.tan(aw)
|
||
ay1 = by - al * _math.cos(angle) * _math.tan(aw)
|
||
ax2 = bx - al * _math.sin(angle) * _math.tan(aw)
|
||
ay2 = by + al * _math.cos(angle) * _math.tan(aw)
|
||
# 线段缩短到箭头底部
|
||
return (f'<line x1="{x1}" y1="{y1}" x2="{bx:.1f}" y2="{by:.1f}" stroke="{color}" stroke-width="{sw}" stroke-linecap="round"/>'
|
||
f'<polygon points="{x2},{y2} {ax1:.1f},{ay1:.1f} {ax2:.1f},{ay2:.1f}" fill="{color}"/>')
|
||
|
||
def svg_line(x1, y1, x2, y2, color=C_ARROW, sw=1.5, dash=None):
|
||
d = f' stroke-dasharray="{dash}"' if dash else ''
|
||
return f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{color}" stroke-width="{sw}"{d} stroke-linecap="round"/>'
|
||
|
||
def svg_arrow_dashed(x1, y1, x2, y2, color=C_ARROW, sw=1.5, dash='6,4', al=12, aw=0.5):
|
||
"""绘制带箭头的虚线段。"""
|
||
angle = _math.atan2(y2-y1, x2-x1)
|
||
bx = x2 - al * _math.cos(angle)
|
||
by = y2 - al * _math.sin(angle)
|
||
ax1 = bx + al * _math.sin(angle) * _math.tan(aw)
|
||
ay1 = by - al * _math.cos(angle) * _math.tan(aw)
|
||
ax2 = bx - al * _math.sin(angle) * _math.tan(aw)
|
||
ay2 = by + al * _math.cos(angle) * _math.tan(aw)
|
||
return (f'<line x1="{x1}" y1="{y1}" x2="{bx:.1f}" y2="{by:.1f}" stroke="{color}" stroke-width="{sw}" stroke-dasharray="{dash}" stroke-linecap="round"/>'
|
||
f'<polygon points="{x2},{y2} {ax1:.1f},{ay1:.1f} {ax2:.1f},{ay2:.1f}" fill="{color}"/>')
|
||
|
||
def svg_arrow_down(cx, y1, y2, color=C_ARROW, sw=2, al=14):
|
||
"""绘制垂直向下的箭头。"""
|
||
return svg_arrow(cx, y1, cx, y2, color, sw, al)
|
||
|
||
def svg_h_branch(x1, y1, x2_left, x2_right, y2, color=C_ARROW, sw=2):
|
||
"""从(x1,y1)向下到y2,然后水平分叉到x2_left和x2_right,各带箭头。"""
|
||
s = f'<line x1="{x1}" y1="{y1}" x2="{x1}" y2="{y2}" stroke="{color}" stroke-width="{sw}" stroke-linecap="round"/>'
|
||
s += f'<line x1="{x2_left}" y1="{y2}" x2="{x2_right}" y2="{y2}" stroke="{color}" stroke-width="{sw}" stroke-linecap="round"/>'
|
||
s += svg_arrow(x2_left, y2, x2_left, y2+18, color, sw)
|
||
s += svg_arrow(x2_right, y2, x2_right, y2+18, color, sw)
|
||
return s
|
||
|
||
def save_svg_and_jpg(svg_content, name):
|
||
svg_path = os.path.join(OUTPUT_DIR, f"{name}.svg")
|
||
png_path = os.path.join(OUTPUT_DIR, f"{name}.png")
|
||
jpg_path = os.path.join(OUTPUT_DIR, f"{name}.jpg")
|
||
if not svg_content.rstrip().endswith("</svg>"):
|
||
svg_content += "\n</svg>"
|
||
with open(svg_path, "w", encoding="utf-8") as f:
|
||
f.write(svg_content)
|
||
# SVG → PNG (rsvg-convert支持中文字体)
|
||
result = subprocess.run(
|
||
["/usr/local/bin/rsvg-convert", "-d", "200", "-p", "200",
|
||
"-f", "png", "-o", png_path, svg_path],
|
||
capture_output=True, text=True
|
||
)
|
||
if result.returncode != 0:
|
||
print(f" ✗ {name} rsvg-convert失败: {result.stderr[:200]}")
|
||
return
|
||
# PNG → JPG (sips是macOS自带工具)
|
||
result2 = subprocess.run(
|
||
["sips", "-s", "format", "jpeg", "-s", "formatOptions", "95",
|
||
png_path, "--out", jpg_path],
|
||
capture_output=True, text=True
|
||
)
|
||
if result2.returncode != 0:
|
||
print(f" ✗ {name} sips失败: {result2.stderr[:200]}")
|
||
return
|
||
os.remove(png_path)
|
||
print(f" ✓ {name}.jpg")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图1: 六层技术架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_01_six_layer():
|
||
W, H = 900, 620
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "六层技术架构", 22, C_TITLE, True)
|
||
layers = [
|
||
("用户触达层", "线下终端(触屏/VR/环幕/UWB/RFID) | 线上(Web/小程序/H5) | 节点终端", C_LAYER1, C_LAYER1_B),
|
||
("应用服务层", "F1自适应叙事 F2情境任务 F3文物数字生命 F4成长护照 F5云上共同体 F6青年共创 F7行动实验室 F8星火节点", C_LAYER2, C_LAYER2_B),
|
||
("公共平台层", "X1体验编排引擎 X2成长护照档案 X3内容治理中心 S9节点协同", C_LAYER3, C_LAYER3_B),
|
||
("AI能力层", "可信叙事引擎 学习成长引擎 编审助手引擎 策展助手引擎", C_LAYER4, C_LAYER4_B),
|
||
("数据资产层", "知识图谱 向量检索库 内容资源仓 业务数据库 时序分析库 实时缓存 消息总线", C_LAYER5, C_LAYER5_B),
|
||
("基础设施层", "私有云容器编排 GPU推理集群 5G专网 IoT网关 CDN 安防网络", C_LAYER6, C_LAYER6_B),
|
||
]
|
||
y = 60
|
||
lh = 80
|
||
for i, (title, content, fill, stroke) in enumerate(layers):
|
||
s += svg_rect(40, y, W-80, lh-10, fill, stroke, 10, 2)
|
||
s += svg_text(130, y+22, title, 15, stroke, True)
|
||
s += svg_text(W/2, y+50, content, 12, "#333")
|
||
if i < len(layers) - 1:
|
||
s += svg_arrow_down(W/2, y+lh-12, y+lh, "#bbb", 1.5, 8)
|
||
y += lh
|
||
save_svg_and_jpg(s, "01_six_layer_architecture")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图2: 系统部署拓扑
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_02_deployment():
|
||
W, H = 880, 560
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "系统部署拓扑", 22, C_TITLE, True)
|
||
# 公网入口
|
||
s += svg_box(300, 60, 280, 50, "公网入口 WAF + 负载均衡", C_SECURITY, C_SECURITY_B, 13, C_SECURITY_B, True)
|
||
# 分叉到两个区域 — 用h_branch
|
||
s += svg_h_branch(W/2, 110, 180, 700, 155, C_ARROW, 2)
|
||
# 线上云区
|
||
s += svg_box(60, 175, 240, 160, "", C_LAYER1, C_LAYER1_B, 13, C_LAYER1_B, True, 10)
|
||
s += svg_text(180, 200, "线上云区 (公有云)", 14, C_LAYER1_B, True)
|
||
s += svg_text(180, 230, "Web/小程序", 12, "#333")
|
||
s += svg_text(180, 255, "API网关", 12, "#333")
|
||
s += svg_text(180, 280, "CDN节点", 12, "#333")
|
||
s += svg_text(180, 305, "港澳加速", 12, "#333")
|
||
# 线下场馆区
|
||
s += svg_box(580, 175, 240, 160, "", C_LAYER2, C_LAYER2_B, 13, C_LAYER2_B, True, 10)
|
||
s += svg_text(700, 200, "线下场馆区 (私有云)", 14, C_LAYER2_B, True)
|
||
s += svg_text(700, 230, "AI推理GPU集群", 12, "#333")
|
||
s += svg_text(700, 255, "容器集群", 12, "#333")
|
||
s += svg_text(700, 280, "数据库集群", 12, "#333")
|
||
s += svg_text(700, 305, "知识图谱+向量库", 12, "#333")
|
||
# VPN连线 — 双向虚线箭头
|
||
s += svg_arrow_dashed(300, 255, 580, 255, "#888", 1.5, "6,4")
|
||
s += svg_arrow_dashed(580, 255, 300, 255, "#888", 1.5, "6,4")
|
||
s += svg_text(440, 245, "VPN专线互联", 11, "#888")
|
||
# 设备区 — 从线下场馆区底部出发
|
||
s += svg_box(350, 380, 200, 60, "IoT/安防/VR\n设备网络 (VLAN隔离)", C_LAYER6, C_LAYER6_B, 12, C_LAYER6_B, True)
|
||
s += svg_arrow(700, 335, 550, 380, C_ARROW, 2)
|
||
# 全国节点 — 从线上云区底部出发
|
||
s += svg_box(60, 380, 240, 60, "全国星火节点\n(远程组件包分发)", C_LAYER3, C_LAYER3_B, 12, C_LAYER3_B, True)
|
||
s += svg_arrow(180, 335, 180, 380, C_ARROW, 2)
|
||
s += svg_text(W/2, 490, "AI推理+核心数据部署在场馆私有云,保证安全与低延迟", 12, "#666")
|
||
s += svg_text(W/2, 515, "Web/小程序/云展馆部署在公有云,CDN加速港澳访问", 12, "#666")
|
||
s += svg_text(W/2, 540, "全国节点通过S9节点协同系统远程部署,断网不失能", 12, "#666")
|
||
save_svg_and_jpg(s, "02_deployment_topology")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图3: AI可信知识引擎架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_03_ai_engine():
|
||
W, H = 860, 580
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "AI可信知识引擎架构", 22, C_TITLE, True)
|
||
# 外框
|
||
s += svg_rect(30, 55, W-60, 490, "#fafafa", C_AI_B, 12, 2)
|
||
# 三列
|
||
cols = [
|
||
("知识图谱", "事件/人物/文物/\n概念/地点及关系\n\n史实锚点\n\n交叉验证", C_LAYER4, C_LAYER4_B),
|
||
("RAG检索增强", "用户提问\n → 语义检索\n → 召回史料\n → 生成回答\n\n读史料后回答\n非凭记忆回答", C_LAYER1, C_LAYER1_B),
|
||
("大模型微调", "延安精神\n专属语料\n → LoRA微调\n → 人群适配\n\n懂延安·懂党史·懂教育", C_LAYER3, C_LAYER3_B),
|
||
]
|
||
cw = 230
|
||
gap = 20
|
||
x0 = 50
|
||
for i, (title, content, fill, stroke) in enumerate(cols):
|
||
x = x0 + i * (cw + gap)
|
||
s += svg_rect(x, 80, cw, 280, fill, stroke, 10, 2)
|
||
s += svg_text(x + cw/2, 110, title, 16, stroke, True)
|
||
for j, line in enumerate(content.split("\n")):
|
||
s += svg_text(x + cw/2, 145 + j*22, line, 12, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + cw, 220, x + cw + gap, 220, "#999", 2, 12)
|
||
# 汇聚箭头
|
||
s += svg_arrow_down(W/2, 360, 380, "#999", 2)
|
||
# 安全护栏
|
||
s += svg_rect(150, 380, W-300, 140, C_SECURITY, C_SECURITY_B, 10, 2)
|
||
s += svg_text(W/2, 410, "安全护栏层", 15, C_SECURITY_B, True)
|
||
steps = ["输入过滤", "事实校验", "输出过滤", "审计追溯"]
|
||
for i, step in enumerate(steps):
|
||
sx = 200 + i * 130
|
||
s += svg_rect(sx, 430, 110, 35, "#fff", C_SECURITY_B, 6, 1.5)
|
||
s += svg_text(sx + 55, 448, step, 12, C_SECURITY_B, True)
|
||
if i < 3:
|
||
s += svg_arrow(sx + 110, 448, sx + 130, 448, C_SECURITY_B, 2, 10)
|
||
save_svg_and_jpg(s, "03_ai_knowledge_engine")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图4: 可信叙事引擎
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_04_narrative_engine():
|
||
W, H = 860, 480
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "可信叙事引擎", 22, C_TITLE, True)
|
||
s += svg_rect(30, 55, W-60, 400, "#fafafa", C_LAYER3_B, 12, 2)
|
||
# 内容组件仓库
|
||
s += svg_rect(50, 80, 320, 220, C_LAYER3, C_LAYER3_B, 10, 2)
|
||
s += svg_text(210, 110, "内容组件仓库", 15, C_LAYER3_B, True)
|
||
items = ["已审核的文字片段", "影像素材", "3D模型", "互动任务", "AI讲解词", "每个组件标注: 适用人群/时长/审核状态"]
|
||
for i, item in enumerate(items):
|
||
s += svg_text(210, 140 + i*25, item, 12, "#333")
|
||
# 箭头 — 从内容仓库到叙事算法
|
||
s += svg_arrow(370, 190, 440, 190, C_ARROW, 2, 14)
|
||
# 叙事路线算法
|
||
s += svg_rect(440, 80, 360, 220, C_LAYER1, C_LAYER1_B, 10, 2)
|
||
s += svg_text(620, 110, "叙事路线算法", 15, C_LAYER1_B, True)
|
||
inputs = ["输入: 人群类型 + 学习问题", " 可用时长 + 语言偏好", " 实时客流状态"]
|
||
s += svg_text(620, 145, inputs[0], 12, "#333")
|
||
s += svg_text(620, 170, inputs[1], 12, "#333")
|
||
s += svg_text(620, 195, inputs[2], 12, "#333")
|
||
s += svg_text(620, 225, "输出: 节点序列 + 每节点内容", 12, C_LAYER1_B, True)
|
||
s += svg_text(620, 250, " 适配方案 + 时长估算", 12, C_LAYER1_B, True)
|
||
# 底部原则
|
||
s += svg_rect(50, 330, W-100, 50, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 355, "关键原则: 不生成新内容,只编排已审核组件 — 像乐高积木一样编排", 13, "#e65100", True)
|
||
s += svg_text(W/2, 420, "同一空间千人千面: 小学生看动画+游戏 | 党员干部看文献+分析 | 港澳青年看繁体+湾区视角", 12, "#666")
|
||
save_svg_and_jpg(s, "04_narrative_engine")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图5: 学习成长引擎
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_05_growth_engine():
|
||
W, H = 860, 420
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "学习成长引擎", 22, C_TITLE, True)
|
||
# 三个阶段
|
||
stages = [
|
||
("参观前导学", ["人群选择", "问题选择", "路线生成", "导学推送"], C_LAYER1, C_LAYER1_B),
|
||
("参观中记录", ["UWB自动记录位置", "任务完成记录", "AI问答记录", "理解表达记录"], C_LAYER2, C_LAYER2_B),
|
||
("离馆后回访", ["第7天回顾推送", "第30天进展反馈", "第90天成果提交", "行动评估+案例入库"], C_LAYER3, C_LAYER3_B),
|
||
]
|
||
sw = 240
|
||
gap = 20
|
||
x0 = 40
|
||
for i, (title, items, fill, stroke) in enumerate(stages):
|
||
x = x0 + i * (sw + gap)
|
||
s += svg_rect(x, 60, sw, 220, fill, stroke, 10, 2)
|
||
s += svg_text(x + sw/2, 90, title, 15, stroke, True)
|
||
for j, item in enumerate(items):
|
||
s += svg_text(x + sw/2, 125 + j*30, item, 12, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + sw, 170, x + sw + gap, 170, "#999", 2, 14)
|
||
# 统一档案
|
||
s += svg_arrow_down(W/2, 280, 310, "#999", 2)
|
||
s += svg_rect(120, 310, W-240, 60, C_LAYER4, C_LAYER4_B, 10, 2)
|
||
s += svg_text(W/2, 332, "统一成长档案 (X2护照)", 15, C_LAYER4_B, True)
|
||
s += svg_text(W/2, 352, "跨系统同步 · 全程陪伴 · 闭环追踪 · 北极星指标可计算", 12, "#333")
|
||
save_svg_and_jpg(s, "05_growth_engine")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图6: 复制运营引擎
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_06_replication_engine():
|
||
W, H = 860, 520
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "复制运营引擎", 22, C_TITLE, True)
|
||
# 旗舰中心
|
||
s += svg_rect(180, 60, 500, 100, C_LAYER5, C_LAYER5_B, 10, 2)
|
||
s += svg_text(W/2, 90, "L4 旗舰中心 (13000㎡)", 15, C_LAYER5_B, True)
|
||
s += svg_text(W/2, 115, "权威内容生产 → 组件包打包 → 版本发布", 12, "#333")
|
||
s += svg_text(W/2, 138, "全国节点调度 → 远程监控 → 数据回收", 12, "#333")
|
||
# 分发线 — 从旗舰中心向下到水平分发线
|
||
s += svg_line(W/2, 160, W/2, 180, C_ARROW, 2)
|
||
s += svg_text(W/2 + 55, 175, "组件包分发", 10, "#999")
|
||
s += svg_line(130, 180, 730, 180, C_ARROW, 2)
|
||
# 四级节点
|
||
nodes = [
|
||
("L1 微节点", "一体机\n即开即用\n定期WiFi同步", C_LAYER6, C_LAYER6_B),
|
||
("L2 标准节点", "7天部署\n本地AI推理\nVR串流", C_LAYER1, C_LAYER1_B),
|
||
("L3 城市节点", "独立运营\n本地服务器\n内容本地化", C_LAYER2, C_LAYER2_B),
|
||
("L4 旗舰中心", "全量系统\n内容生产\n全国调度", C_LAYER5, C_LAYER5_B),
|
||
]
|
||
nw = 180
|
||
gap = 20
|
||
x0 = 40
|
||
for i, (title, desc, fill, stroke) in enumerate(nodes):
|
||
x = x0 + i * (nw + gap)
|
||
s += svg_arrow(x + nw/2, 180, x + nw/2, 200)
|
||
s += svg_rect(x, 200, nw, 130, fill, stroke, 10, 2)
|
||
s += svg_text(x + nw/2, 225, title, 13, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + nw/2, 250 + j*22, line, 11, "#333")
|
||
# 组件包内容
|
||
s += svg_rect(50, 370, W-100, 50, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 395, "组件包: 知识图谱子集 + 史料向量 + 课程 + 任务剧本 + VR场景 + 运营SOP", 12, "#e65100", True)
|
||
s += svg_text(W/2, 460, "断网不失能: 各节点本地有AI推理和内容缓存,网络恢复后自动同步", 12, "#666")
|
||
s += svg_text(W/2, 485, "内容可回流: 各节点优秀作品可提交旗舰中心审核后全国共享", 12, "#666")
|
||
save_svg_and_jpg(s, "06_replication_engine")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图7: F1自适应叙事空间 + X1编排引擎
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_07_f1_x1():
|
||
W, H = 900, 620
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F1自适应叙事空间 + X1体验编排引擎", 20, C_TITLE, True)
|
||
# F1三模块
|
||
s += svg_rect(30, 55, W-60, 180, "#fafafa", C_LAYER1_B, 10, 2)
|
||
s += svg_text(W/2, 80, "F1 自适应叙事空间系统", 14, C_LAYER1_B, True)
|
||
modules = [
|
||
("入馆选择终端", "人群类型/学习问题\n可用时长/语言"),
|
||
("空间联动控制", "屏幕/灯光/音频\nAI/互动设备"),
|
||
("学习脉络生成", "路线节点/文物\n任务/AI问答/报告"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 50 + i * (mw + 15)
|
||
s += svg_rect(x, 95, mw, 120, C_LAYER1, C_LAYER1_B, 8, 1.5)
|
||
s += svg_text(x + mw/2, 120, title, 13, C_LAYER1_B, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 150 + j*22, line, 11, "#333")
|
||
# X1编排引擎
|
||
s += svg_arrow_down(W/2, 240, 275, C_LAYER3_B, 2, 14)
|
||
s += svg_text(W/2, 258, "X1全场景体验编排引擎", 14, C_LAYER3_B, True)
|
||
s += svg_rect(30, 275, W-60, 280, "#fafafa", C_LAYER3_B, 10, 2)
|
||
# 输入
|
||
s += svg_rect(50, 295, 200, 120, C_LAYER3, C_LAYER3_B, 8, 1.5)
|
||
s += svg_text(150, 320, "输入", 13, C_LAYER3_B, True)
|
||
inputs = ["用户路线", "UWB实时位置(10Hz)", "实时客流", "设备状态"]
|
||
for i, inp in enumerate(inputs):
|
||
s += svg_text(150, 345 + i*22, inp, 11, "#333")
|
||
# 箭头 — 输入到处理
|
||
s += svg_arrow(250, 355, 320, 355, C_ARROW, 2, 14)
|
||
# 处理
|
||
s += svg_rect(320, 295, 260, 120, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(450, 320, "实时流式计算 (CEP)", 13, "#e65100", True)
|
||
s += svg_text(450, 350, "复杂事件处理引擎", 12, "#333")
|
||
s += svg_text(450, 375, "毫秒级复合条件判断", 12, "#333")
|
||
s += svg_text(450, 400, "规则 + AI编排决策", 12, "#333")
|
||
# 箭头 — 处理到输出
|
||
s += svg_arrow(580, 355, 650, 355, C_ARROW, 2, 14)
|
||
# 输出
|
||
s += svg_rect(650, 295, 200, 120, C_LAYER2, C_LAYER2_B, 8, 1.5)
|
||
s += svg_text(750, 320, "输出", 13, C_LAYER2_B, True)
|
||
outputs = ["设备控制指令", "用户学习记录", "客流调度信号"]
|
||
for i, out in enumerate(outputs):
|
||
s += svg_text(750, 345 + i*22, out, 11, "#333")
|
||
# 设备控制
|
||
s += svg_text(W/2, 445, "六类设备统一编排", 13, C_TITLE, True)
|
||
devices = ["LED屏幕", "灯光系统", "音频系统", "VR设备", "互动终端", "AI数字人"]
|
||
dw = 120
|
||
for i, dev in enumerate(devices):
|
||
x = 60 + i * (dw + 10)
|
||
s += svg_rect(x, 460, dw, 35, C_LAYER6, C_LAYER6_B, 6, 1.5)
|
||
s += svg_text(x + dw/2, 478, dev, 11, C_LAYER6_B, True)
|
||
s += svg_text(W/2, 530, "UWB厘米级室内定位 + 复杂事件处理 + 像指挥家指挥交响乐", 12, "#666")
|
||
s += svg_text(W/2, 555, "同一物理空间,数字内容随人而变", 12, C_LAYER3_B, True)
|
||
save_svg_and_jpg(s, "07_f1_x1_orchestration")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图8: F2情境任务剧场
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_08_f2_task():
|
||
W, H = 860, 400
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F2 情境任务剧场系统", 22, C_TITLE, True)
|
||
modules = [
|
||
("任务引擎", "任务定义: 历史问题\n角色分配/资源约束\n证据集/历史事实\n评价维度\n状态机驱动(9状态)"),
|
||
("团队协作终端", "主屏: 情境展示\n分屏: 证据查看\n个人终端: 决策\n团队屏: 方案汇总\nVR模式: 虚拟窑洞讨论"),
|
||
("AI复盘引擎", "团队方案 vs 历史\nAI追问(史料引用)\n证据使用分析\n现实连接问题\n像历史导师一对一"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 30 + i * (mw + 15)
|
||
fill = [C_LAYER2, C_LAYER1, C_LAYER4][i]
|
||
stroke = [C_LAYER2_B, C_LAYER1_B, C_LAYER4_B][i]
|
||
s += svg_rect(x, 55, mw, 240, fill, stroke, 10, 2)
|
||
s += svg_text(x + mw/2, 82, title, 14, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 110 + j*22, line, 11, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + mw, 175, x + mw + 15, 175, "#999", 2, 12)
|
||
# 底部
|
||
s += svg_rect(30, 320, W-60, 50, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 345, "VR与非VR双模式共享同一剧本: 同一任务定义/证据集/历史揭示/AI复盘/护照记录", 12, "#e65100", True)
|
||
save_svg_and_jpg(s, "08_f2_task_theater")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图9: F3文物数字生命
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_09_f3_artifact():
|
||
W, H = 860, 400
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F3 文物数字生命系统", 22, C_TITLE, True)
|
||
modules = [
|
||
("数字采集管理", "3D扫描任务\n摄影测量任务\n质量检测\n元数据录入"),
|
||
("五层内容管理", "L1看见: 图像/3D\nL2知道: 元数据\nL3理解: 关联网络\nL4研究: 档案\nL5连接: 课程/任务"),
|
||
("关系星图引擎", "力导向图渲染\n交互式星图\n时间轴\n点击节点展开"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 30 + i * (mw + 15)
|
||
fill = [C_LAYER3, C_LAYER4, C_LAYER1][i]
|
||
stroke = [C_LAYER3_B, C_LAYER4_B, C_LAYER1_B][i]
|
||
s += svg_rect(x, 55, mw, 200, fill, stroke, 10, 2)
|
||
s += svg_text(x + mw/2, 82, title, 14, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 110 + j*22, line, 11, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + mw, 155, x + mw + 15, 155, "#999", 2, 12)
|
||
# 五层递进
|
||
s += svg_text(W/2, 290, "一件文物 = 一个知识宇宙", 14, C_TITLE, True)
|
||
levels = ["看见", "知道", "理解", "研究", "连接"]
|
||
lw = 140
|
||
for i, lv in enumerate(levels):
|
||
x = 50 + i * (lw + 10)
|
||
fill = [C_LAYER6, C_LAYER1, C_LAYER2, C_LAYER3, C_LAYER4][i]
|
||
stroke = [C_LAYER6_B, C_LAYER1_B, C_LAYER2_B, C_LAYER3_B, C_LAYER4_B][i]
|
||
s += svg_rect(x, 305, lw, 35, fill, stroke, 6, 1.5)
|
||
s += svg_text(x + lw/2, 323, lv, 12, stroke, True)
|
||
if i < 4:
|
||
s += svg_arrow(x + lw, 323, x + lw + 10, 323, "#999", 1.5, 10)
|
||
s += svg_text(W/2, 370, "推测性复原标识: 有争议部分用不同颜色边框区分「史实确认」和「推测复原」", 11, "#666")
|
||
save_svg_and_jpg(s, "09_f3_artifact_life")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图10: F4成长护照
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_10_f4_passport():
|
||
W, H = 860, 380
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F4 成长护照系统", 22, C_TITLE, True)
|
||
stages = [
|
||
("参观前导学", ["人群/问题选择", "团队目标创建", "参观建议生成", "导学材料推送"]),
|
||
("参观中记录", ["文物查看记录", "任务完成记录", "AI问答记录", "理解表达记录", "行动选择记录"]),
|
||
("离馆后回访", ["第7天推送回顾", "第30天进展反馈", "第90天成果提交", "后续课程推荐"]),
|
||
]
|
||
sw = 250
|
||
for i, (title, items) in enumerate(stages):
|
||
x = 30 + i * (sw + 10)
|
||
fill = [C_LAYER1, C_LAYER2, C_LAYER3][i]
|
||
stroke = [C_LAYER1_B, C_LAYER2_B, C_LAYER3_B][i]
|
||
s += svg_rect(x, 55, sw, 200, fill, stroke, 10, 2)
|
||
s += svg_text(x + sw/2, 82, title, 14, stroke, True)
|
||
for j, item in enumerate(items):
|
||
s += svg_text(x + sw/2, 110 + j*25, item, 11, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + sw, 155, x + sw + 10, 155, "#999", 2, 12)
|
||
# X2统一档案
|
||
s += svg_arrow_down(W/2, 255, 275, "#999", 2)
|
||
s += svg_rect(100, 275, W-200, 50, C_LAYER4, C_LAYER4_B, 10, 2)
|
||
s += svg_text(W/2, 300, "X2 统一成长档案 — 跨系统数据同步 · GraphQL统一查询", 13, C_LAYER4_B, True)
|
||
s += svg_text(W/2, 350, "隐私保护是设计底线: 禁止采集人脸特征/情绪识别/政治倾向推断", 11, C_SECURITY_B)
|
||
save_svg_and_jpg(s, "10_f4_growth_passport")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图11: F5云上学习共同体
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_11_f5_cloud():
|
||
W, H = 860, 400
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F5 云上学习共同体", 22, C_TITLE, True)
|
||
modules = [
|
||
("Web3D云展馆", "3D渲染引擎\n虚拟漫游\nAI导览\n文物3D展示"),
|
||
("云端课程平台", "视频点播/直播\n课程管理\n学时记录\n证书发放"),
|
||
("协作任务平台", "实时音视频协作\n多人同堂任务\n粤港澳三地同步\n在线研讨室"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 30 + i * (mw + 15)
|
||
fill = [C_LAYER1, C_LAYER2, C_LAYER3][i]
|
||
stroke = [C_LAYER1_B, C_LAYER2_B, C_LAYER3_B][i]
|
||
s += svg_rect(x, 55, mw, 160, fill, stroke, 10, 2)
|
||
s += svg_text(x + mw/2, 82, title, 14, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 110 + j*22, line, 11, "#333")
|
||
# 四种模式
|
||
s += svg_text(W/2, 245, "四种访问模式", 13, C_TITLE, True)
|
||
modes = [("8分钟精华\n轻量H5", C_LAYER6, C_LAYER6_B), ("一节课模式\n视频+互动", C_LAYER1, C_LAYER1_B),
|
||
("协作任务模式\n实时协作+白板", C_LAYER3, C_LAYER3_B), ("深度研究模式\n文献检索+图谱", C_LAYER4, C_LAYER4_B)]
|
||
mw2 = 190
|
||
for i, (title, fill, stroke) in enumerate(modes):
|
||
x = 30 + i * (mw2 + 10)
|
||
s += svg_rect(x, 265, mw2, 55, fill, stroke, 8, 1.5)
|
||
for j, line in enumerate(title.split("\n")):
|
||
s += svg_text(x + mw2/2, 283 + j*18, line, 11, stroke, j==0)
|
||
s += svg_text(W/2, 365, "三档渲染自适应: 高端(WebGPU+PBR) → 轻量(WebGL+减面) → 极简(全景图片热点)", 11, "#666")
|
||
save_svg_and_jpg(s, "11_f5_cloud_community")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图12: F6湾区青年共创
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_12_f6_cocreation():
|
||
W, H = 860, 350
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F6 湾区青年共创平台", 22, C_TITLE, True)
|
||
modules = [
|
||
("共创项目管理", "年度主题发布\n混合小组组建\n专家工作坊预约\n进度跟踪\n资源库访问"),
|
||
("AI辅助创作工具集", "AI辅助写作(标注)\nAI辅助绘画\n短视频编辑\n声音档案录制\n互动网页制作"),
|
||
("三级审核与展示", "AI编审自动检查\n编辑复审(人工)\n专家终审\n线上专区展示\n优秀作品入临展"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 30 + i * (mw + 15)
|
||
fill = [C_LAYER2, C_LAYER4, C_LAYER3][i]
|
||
stroke = [C_LAYER2_B, C_LAYER4_B, C_LAYER3_B][i]
|
||
s += svg_rect(x, 55, mw, 200, fill, stroke, 10, 2)
|
||
s += svg_text(x + mw/2, 82, title, 14, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 110 + j*22, line, 11, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + mw, 155, x + mw + 15, 155, "#999", 2, 12)
|
||
s += svg_rect(30, 280, W-60, 40, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 300, "AI编审四项自动检查: 史实校验 + 来源完整性 + 版权合规 + 政治合规", 12, "#e65100", True)
|
||
s += svg_text(W/2, 335, "AI是工具不是作者,人的创造力是核心 — 所有AI生成内容强制标注", 11, "#666")
|
||
save_svg_and_jpg(s, "12_f6_cocreation")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图13: F7行动实验室
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_13_f7_action():
|
||
W, H = 860, 350
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F7 现实行动实验室", 22, C_TITLE, True)
|
||
modules = [
|
||
("行动选择终端", "行动方向展示\n智能推荐(基于参观记录)\n行动模板选择\n自定义行动\n团队行动创建"),
|
||
("行动跟踪系统", "行动卡片生成\n7/30/90天跟踪\n进展提醒推送\n反馈表单"),
|
||
("成果回流系统", "成果提交入口\n成果墙展示\n课程案例转化\n年度优秀评选"),
|
||
]
|
||
mw = 250
|
||
for i, (title, desc) in enumerate(modules):
|
||
x = 30 + i * (mw + 15)
|
||
fill = [C_LAYER3, C_LAYER1, C_LAYER2][i]
|
||
stroke = [C_LAYER3_B, C_LAYER1_B, C_LAYER2_B][i]
|
||
s += svg_rect(x, 55, mw, 200, fill, stroke, 10, 2)
|
||
s += svg_text(x + mw/2, 82, title, 14, stroke, True)
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(x + mw/2, 110 + j*22, line, 11, "#333")
|
||
if i < 2:
|
||
s += svg_arrow(x + mw, 155, x + mw + 15, 155, "#999", 2, 12)
|
||
s += svg_rect(30, 280, W-60, 40, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 300, "把「一次性参观」变成「持续性行动」 — 优秀成果成为系统内容资产", 12, "#e65100", True)
|
||
s += svg_text(W/2, 335, "三类行动: 个人7天(实事求是调查员) | 团队30天(南泥湾式创新) | 湾区90天(红色故事传播者)", 11, "#666")
|
||
save_svg_and_jpg(s, "13_f7_action_lab")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图14: F8星火节点网络
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_14_f8_nodes():
|
||
W, H = 860, 520
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F8 星火节点全国复制网络", 22, C_TITLE, True)
|
||
nodes = [
|
||
("L4 旗舰中心", "13000㎡", "完整私有云 + GPU集群\n完整知识图谱+向量库+内容仓\n全国节点调度中心\n权威内容生产与发布", C_LAYER5, C_LAYER5_B),
|
||
("L3 城市节点", "院校/区县基地", "本地服务器 + 完整展陈\n知识图谱子集 + 向量库子集\n独立运营 + 数据上报\n云端连线能力", C_LAYER2, C_LAYER2_B),
|
||
("L2 标准节点", "高校/企事业单位", "触屏终端 + 边缘AI + VR\n本地内容缓存 + 云端AI增强\n成长护照接入\n7天标准化部署", C_LAYER1, C_LAYER1_B),
|
||
("L1 微节点", "一体机", "一块触屏交互一体机\n离线内容包 + AI轻量版\n定期WiFi/4G同步更新", C_LAYER6, C_LAYER6_B),
|
||
]
|
||
nh = 90
|
||
for i, (title, scale, desc, fill, stroke) in enumerate(nodes):
|
||
y = 55 + i * (nh + 10)
|
||
s += svg_rect(40, y, W-80, nh, fill, stroke, 10, 2)
|
||
s += svg_text(160, y + 25, title, 14, stroke, True)
|
||
s += svg_text(160, y + 48, scale, 11, "#666")
|
||
for j, line in enumerate(desc.split("\n")):
|
||
s += svg_text(450, y + 20 + j*18, line, 11, "#333")
|
||
if i < 3:
|
||
s += svg_arrow_down(W/2, y + nh, y + nh + 10, "#999", 2, 10)
|
||
s += svg_text(W/2, 490, "组件包标准化分发 — 像App Store更新一样简单", 12, C_LAYER3_B, True)
|
||
save_svg_and_jpg(s, "14_f8_spark_nodes")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图15: VR/XR整体架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_15_vr_architecture():
|
||
W, H = 900, 420
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "VR/XR 沉浸体验系统架构", 22, C_TITLE, True)
|
||
layers = [
|
||
("内容生产层", ["Unity场景工程", "3D资产管线", "交互逻辑脚本", "AI NPC驱动", "场景烘焙渲染"], C_VR, C_VR_B),
|
||
("运行时引擎", ["场景管理器", "多人同步引擎", "编排对接层", "安全监控守护", "性能监控"], C_LAYER4, C_LAYER4_B),
|
||
("设备与体感层", ["VR头显(Pico/Meta)", "6DOF动态座椅", "手部追踪", "触觉反馈背心", "风效/热效/气味"], C_LAYER3, C_LAYER3_B),
|
||
]
|
||
lh = 100
|
||
for i, (title, items, fill, stroke) in enumerate(layers):
|
||
y = 55 + i * (lh + 10)
|
||
s += svg_rect(30, y, W-60, lh, fill, stroke, 10, 2)
|
||
s += svg_text(130, y + 25, title, 14, stroke, True)
|
||
iw = 140
|
||
for j, item in enumerate(items):
|
||
x = 240 + j * (iw + 10)
|
||
s += svg_rect(x, y + 45, iw, 35, "#fff", stroke, 6, 1)
|
||
s += svg_text(x + iw/2, y + 63, item, 10, "#333")
|
||
if i < 2:
|
||
s += svg_arrow_down(W/2, y + lh, y + lh + 10, "#999", 2, 10)
|
||
s += svg_text(W/2, 390, "Unity + XR Interaction Toolkit — 一次开发多设备运行 | 6DOF体感+五感同步沉浸", 12, C_VR_B, True)
|
||
save_svg_and_jpg(s, "15_vr_xr_architecture")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图16: 多人协同VR架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_16_vr_multiplayer():
|
||
W, H = 700, 520
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "多人协同VR架构", 22, C_TITLE, True)
|
||
# 4个VR座位
|
||
seats = ["VR座位1\n(队长)", "VR座位2\n(队员)", "VR座位3\n(队员)", "VR座位4\n(队员)"]
|
||
for i, label in enumerate(seats):
|
||
x = 60 + i * 150
|
||
s += svg_rect(x, 60, 130, 60, C_VR, C_VR_B, 8, 2)
|
||
for j, line in enumerate(label.split("\n")):
|
||
s += svg_text(x + 65, 78 + j*18, line, 11, C_VR_B, j==0)
|
||
# 从座位底部到状态同步服务器顶部,用箭头
|
||
s += svg_arrow(x + 65, 120, 350, 170, C_ARROW, 1.5, 10)
|
||
# 状态同步服务器
|
||
s += svg_rect(200, 170, 300, 60, C_LAYER4, C_LAYER4_B, 10, 2)
|
||
s += svg_text(W/2, 195, "状态同步服务器", 14, C_LAYER4_B, True)
|
||
s += svg_text(W/2, 215, "Photon Fusion — 头像/手部/任务进度实时同步", 11, "#333")
|
||
s += svg_arrow_down(W/2, 230, 250, C_ARROW, 2, 12)
|
||
# 房间管理
|
||
s += svg_rect(200, 250, 300, 50, C_LAYER3, C_LAYER3_B, 10, 2)
|
||
s += svg_text(W/2, 275, "房间管理服务 — 4人同场,场次调度", 12, C_LAYER3_B, True)
|
||
s += svg_arrow_down(W/2, 300, 320, C_ARROW, 2, 12)
|
||
# X1编排
|
||
s += svg_rect(200, 320, 300, 50, C_LAYER1, C_LAYER1_B, 10, 2)
|
||
s += svg_text(W/2, 345, "X1编排引擎 — 任务剧本推送", 12, C_LAYER1_B, True)
|
||
# 底部说明
|
||
s += svg_rect(50, 400, W-100, 90, C_HIGHLIGHT, "#f57f17", 8, 2)
|
||
s += svg_text(W/2, 425, "技术亮点", 13, "#e65100", True)
|
||
s += svg_text(W/2, 448, "• Photon Fusion状态同步: 4人虚拟化身和手势实时可见", 11, "#333")
|
||
s += svg_text(W/2, 468, "• 角色与F2任务对应: 各有专属资源和约束,真正的团队协作", 11, "#333")
|
||
s += svg_text(W/2, 488, "• VR内AI NPC: 由AI可信知识引擎驱动,可对话的AI角色", 11, "#333")
|
||
save_svg_and_jpg(s, "16_vr_multiplayer")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图17: 云端VR串流
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_17_vr_cloud_streaming():
|
||
W, H = 860, 400
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "云端VR串流方案", 22, C_TITLE, True)
|
||
# 左侧: 节点
|
||
s += svg_rect(40, 70, 300, 200, C_LAYER1, C_LAYER1_B, 10, 2)
|
||
s += svg_text(190, 100, "L2/L3节点 (轻量硬件)", 14, C_LAYER1_B, True)
|
||
items_l = ["VR头显", "WiFi/5G传输", "解码显示", "手柄/头显数据"]
|
||
for i, item in enumerate(items_l):
|
||
s += svg_text(190, 130 + i*30, item, 12, "#333")
|
||
if i < 3:
|
||
s += svg_arrow_down(190, 140 + i*30, 150 + i*30, "#bbb", 1.5, 8)
|
||
# 中间: 数据流 — 双向箭头
|
||
s += svg_arrow(340, 130, 460, 130, C_VR_B, 2, 12)
|
||
s += svg_arrow(460, 130, 340, 130, C_VR_B, 2, 12)
|
||
s += svg_text(400, 118, "视频流", 10, C_VR_B, True)
|
||
s += svg_arrow(340, 160, 460, 160, C_VR_B, 2, 12)
|
||
s += svg_arrow(460, 160, 340, 160, C_VR_B, 2, 12)
|
||
s += svg_text(400, 148, "音频流", 10, C_VR_B, True)
|
||
s += svg_arrow(340, 190, 460, 190, C_VR_B, 2, 12)
|
||
s += svg_text(400, 178, "输入流", 10, C_VR_B, True)
|
||
s += svg_text(400, 220, "H.265超低延迟传输", 11, "#999")
|
||
# 右侧: 云端
|
||
s += svg_rect(520, 70, 300, 200, C_LAYER5, C_LAYER5_B, 10, 2)
|
||
s += svg_text(670, 100, "旗舰中心/区域云端 (GPU集群)", 14, C_LAYER5_B, True)
|
||
items_r = ["云端渲染服务器", "Unity应用实例", "串流编码H.265", "超低延迟传输"]
|
||
for i, item in enumerate(items_r):
|
||
s += svg_text(670, 130 + i*30, item, 12, "#333")
|
||
# 方案表
|
||
s += svg_text(W/2, 310, "四种方案对比", 13, C_TITLE, True)
|
||
schemes = [("CloudXR\n15-30ms\nL3城市节点", C_LAYER2, C_LAYER2_B),
|
||
("ALVR开源\n20-40ms\nL2标准节点", C_LAYER1, C_LAYER1_B),
|
||
("本地渲染\n<10ms\nL4旗舰中心", C_LAYER5, C_LAYER5_B),
|
||
("WebXR\n30-80ms\nF5云展馆", C_LAYER6, C_LAYER6_B)]
|
||
sw = 190
|
||
for i, (label, fill, stroke) in enumerate(schemes):
|
||
x = 30 + i * (sw + 10)
|
||
s += svg_rect(x, 325, sw, 55, fill, stroke, 8, 1.5)
|
||
for j, line in enumerate(label.split("\n")):
|
||
s += svg_text(x + sw/2, 343 + j*16, line, 10, stroke, j==0)
|
||
save_svg_and_jpg(s, "17_vr_cloud_streaming")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图18: X1编排数据流
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_18_x1_dataflow():
|
||
W, H = 860, 320
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "X1体验编排引擎 — 数据流", 22, C_TITLE, True)
|
||
# 事件源
|
||
sources = ["UWB位置流\n(10Hz)", "客流计数\n(实时)", "设备状态\n(实时)", "用户路线\n(API)", "任务状态\n(API)"]
|
||
s += svg_text(120, 75, "事件源", 13, C_TITLE, True)
|
||
for i, src in enumerate(sources):
|
||
y = 90 + i * 38
|
||
s += svg_rect(40, y, 160, 32, C_LAYER1, C_LAYER1_B, 6, 1.5)
|
||
s += svg_text(120, y + 16, src.replace("\n", " "), 10, C_LAYER1_B, True)
|
||
# 从每个事件源到处理引擎的箭头
|
||
s += svg_arrow(200, y + 16, 280, 175, C_ARROW, 1.5, 10)
|
||
# 处理引擎
|
||
s += svg_rect(280, 130, 260, 90, C_HIGHLIGHT, "#f57f17", 10, 2)
|
||
s += svg_text(410, 160, "实时流式计算", 14, "#e65100", True)
|
||
s += svg_text(410, 185, "复杂事件处理(CEP)", 12, "#333")
|
||
s += svg_text(410, 205, "规则 + AI编排决策", 12, "#333")
|
||
s += svg_arrow(540, 175, 620, 175)
|
||
# 输出
|
||
s += svg_text(700, 75, "输出目标", 13, C_TITLE, True)
|
||
outputs = ["设备控制指令\n(实时消息推送)", "用户学习记录\n(写入成长护照)", "客流调度信号\n(反馈智慧场馆)"]
|
||
for i, out in enumerate(outputs):
|
||
y = 90 + i * 55
|
||
s += svg_rect(620, y, 200, 42, C_LAYER2, C_LAYER2_B, 6, 1.5)
|
||
for j, line in enumerate(out.split("\n")):
|
||
s += svg_text(720, y + 14 + j*16, line, 10, C_LAYER2_B, True)
|
||
# 从处理引擎到每个输出目标的箭头
|
||
s += svg_arrow(540, 175, 620, y + 21, C_ARROW, 1.5, 10)
|
||
s += svg_text(W/2, 290, "五路数据流实时汇入 — 系统对全馆状态有「上帝视角」", 12, "#666")
|
||
save_svg_and_jpg(s, "18_x1_dataflow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图19: X2跨系统同步
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_19_x2_sync():
|
||
W, H = 700, 380
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "X2 成长护照 — 跨系统同步", 22, C_TITLE, True)
|
||
# 数据源
|
||
sources = ["票务系统", "教研系统", "港澳专项", "数据中台", "线下终端"]
|
||
for i, src in enumerate(sources):
|
||
y = 70 + i * 45
|
||
s += svg_rect(40, y, 160, 35, C_LAYER1, C_LAYER1_B, 8, 1.5)
|
||
s += svg_text(120, y + 18, src, 12, C_LAYER1_B, True)
|
||
# 从数据源到中心API的箭头
|
||
s += svg_arrow(200, y + 17, 280, 200, C_ARROW, 1.5, 10)
|
||
# 中心
|
||
s += svg_rect(280, 165, 200, 70, C_LAYER4, C_LAYER4_B, 10, 2)
|
||
s += svg_text(380, 190, "X2成长护照统一API", 13, C_LAYER4_B, True)
|
||
s += svg_text(380, 215, "GraphQL统一查询", 12, "#333")
|
||
# 输出
|
||
s += svg_rect(520, 165, 140, 70, C_LAYER2, C_LAYER2_B, 10, 2)
|
||
s += svg_text(590, 190, "统一学习档案", 13, C_LAYER2_B, True)
|
||
s += svg_text(590, 215, "全生命周期", 12, "#333")
|
||
s += svg_arrow(480, 200, 520, 200, C_ARROW, 2, 14)
|
||
# 底部
|
||
s += svg_text(W/2, 300, "从第一次预约到第90天行动成果,一个人的完整学习旅程在一本护照里", 12, "#666")
|
||
s += svg_text(W/2, 325, "对前端来说,所有数据来自一个「虚拟数据库」", 12, C_LAYER4_B, True)
|
||
s += svg_text(W/2, 355, "北极星指标可计算: 完成核心学习路径+理解验证+形成行动+产生反馈的完整闭环人数", 11, "#e65100")
|
||
save_svg_and_jpg(s, "19_x2_sync")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图20: X3审核工作流
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_20_x3_review():
|
||
W, H = 700, 480
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "X3 权威内容治理中心 — 审核工作流", 20, C_TITLE, True)
|
||
steps = [
|
||
("内容创建/修改", C_LAYER6, C_LAYER6_B),
|
||
("AI编审助手自动检查\n事实校验·来源检查·合规检查·版权检查", C_LAYER4, C_LAYER4_B),
|
||
("编辑复审 (人工)", C_LAYER3, C_LAYER3_B),
|
||
("专家终审 (关键内容)", C_SECURITY, C_SECURITY_B),
|
||
("版本发布 (带版本号,可回滚)", C_LAYER2, C_LAYER2_B),
|
||
("同步到: 展陈/云展馆/课程平台/全国节点", C_LAYER1, C_LAYER1_B),
|
||
]
|
||
for i, (step, fill, stroke) in enumerate(steps):
|
||
y = 65 + i * 60
|
||
s += svg_rect(100, y, W-200, 45, fill, stroke, 8, 2)
|
||
s += svg_text(W/2, y + 23, step, 12, stroke, True)
|
||
if i < len(steps) - 1:
|
||
s += svg_arrow_down(W/2, y + 45, y + 60, "#999", 2, 12)
|
||
# 底部
|
||
s += svg_rect(50, 440, W-100, 30, C_HIGHLIGHT, "#f57f17", 6, 1.5)
|
||
s += svg_text(W/2, 456, "AI初审自动化把审核效率提升10倍,人工只做最关键的终审", 11, "#e65100", True)
|
||
save_svg_and_jpg(s, "20_x3_review_workflow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图21: 网络安全架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_21_security():
|
||
W, H = 700, 420
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "网络安全架构", 22, C_TITLE, True)
|
||
zones = [
|
||
("互联网", "WAF (Web应用防火墙)", C_SECURITY, C_SECURITY_B),
|
||
("DMZ区", "负载均衡 + API网关", C_LAYER3, C_LAYER3_B),
|
||
("应用区 (容器集群)", "微服务集群 (服务网格 + 自动扩缩容)", C_LAYER1, C_LAYER1_B),
|
||
("数据区 (隔离)", "业务数据库 | 知识图谱 | 向量库 | 内容仓 | GPU推理集群", C_LAYER4, C_LAYER4_B),
|
||
("设备区 (VLAN隔离)", "IoT/安防/VR/灯光音频 (独立VLAN)", C_LAYER6, C_LAYER6_B),
|
||
]
|
||
for i, (zone, content, fill, stroke) in enumerate(zones):
|
||
y = 60 + i * 65
|
||
s += svg_rect(50, y, W-100, 55, fill, stroke, 10, 2)
|
||
s += svg_text(180, y + 20, zone, 13, stroke, True)
|
||
s += svg_text(W/2, y + 40, content, 11, "#333")
|
||
if i < len(zones) - 1:
|
||
s += svg_arrow_down(W/2, y + 55, y + 65, "#999", 2, 12)
|
||
save_svg_and_jpg(s, "21_security_architecture")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图22: AI安全三层护栏
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_22_ai_guardrail():
|
||
W, H = 600, 480
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "AI安全三层护栏", 22, C_TITLE, True)
|
||
# 用户输入
|
||
s += svg_rect(200, 60, 200, 40, C_LAYER6, C_LAYER6_B, 8, 2)
|
||
s += svg_text(W/2, 80, "用户输入", 14, C_LAYER6_B, True)
|
||
s += svg_arrow_down(W/2, 100, 125, "#999", 2, 12)
|
||
# 第一层
|
||
s += svg_rect(150, 125, 300, 50, C_SECURITY, C_SECURITY_B, 10, 2)
|
||
s += svg_text(W/2, 145, "第一层: 输入过滤", 13, C_SECURITY_B, True)
|
||
s += svg_text(W/2, 162, "拦截不当提问", 11, "#333")
|
||
s += svg_arrow_down(W/2, 175, 195, "#999", 2, 12)
|
||
# 第二层
|
||
s += svg_rect(150, 195, 300, 50, C_SECURITY, C_SECURITY_B, 10, 2)
|
||
s += svg_text(W/2, 215, "第二层: 事实校验", 13, C_SECURITY_B, True)
|
||
s += svg_text(W/2, 232, "AI回答与知识图谱比对,发现错误则拦截", 11, "#333")
|
||
s += svg_arrow_down(W/2, 245, 265, "#999", 2, 12)
|
||
# 第三层
|
||
s += svg_rect(150, 265, 300, 50, C_SECURITY, C_SECURITY_B, 10, 2)
|
||
s += svg_text(W/2, 285, "第三层: 输出过滤", 13, C_SECURITY_B, True)
|
||
s += svg_text(W/2, 302, "拦截不当表述", 11, "#333")
|
||
s += svg_arrow_down(W/2, 315, 335, "#999", 2, 12)
|
||
# 输出
|
||
s += svg_rect(100, 335, 400, 50, C_LAYER2, C_LAYER2_B, 10, 2)
|
||
s += svg_text(W/2, 355, "安全回答 + 史料来源 + 审计日志", 13, C_LAYER2_B, True)
|
||
s += svg_text(W/2, 372, "100%不当内容拦截率", 11, "#333")
|
||
# 底部
|
||
s += svg_text(W/2, 420, "NPC禁止模拟革命领袖发表新言论,仅复述已审核史料", 11, "#666")
|
||
s += svg_text(W/2, 445, "全部对话可审计追溯,支持质量回查", 11, "#666")
|
||
save_svg_and_jpg(s, "22_ai_guardrail")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图23: 多语言与多人群适配流程
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_23_multilang_flow():
|
||
W, H = 520, 620
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "多语言与多人群适配流程", 20, C_TITLE, True)
|
||
steps = [
|
||
("用户提问(繁体中文)", C_LAYER6, C_LAYER6_B),
|
||
("意图识别 + 人群类型判断(港澳中学生)", C_LAYER1, C_LAYER1_B),
|
||
("RAG检索(史料库语义检索,召回相关文献)", C_LAYER4, C_LAYER4_B),
|
||
("大模型生成(适配港澳中学生理解水平,繁体中文输出)", C_LAYER3, C_LAYER3_B),
|
||
("安全护栏(事实校验 + 输出过滤)", C_SECURITY, C_SECURITY_B),
|
||
("TTS语音合成(粤语发音可选)", C_LAYER2, C_LAYER2_B),
|
||
("回答 + 史料来源 + 追问建议", C_LAYER5, C_LAYER5_B),
|
||
]
|
||
bw = 420
|
||
bh = 50
|
||
x0 = (W - bw) // 2
|
||
y0 = 55
|
||
gap = 22
|
||
for i, (step, fill, stroke) in enumerate(steps):
|
||
y = y0 + i * (bh + gap)
|
||
s += svg_rect(x0, y, bw, bh, fill, stroke, 10, 2)
|
||
s += svg_text(W/2, y + bh/2 + 5, step, 12, stroke, True)
|
||
if i < len(steps) - 1:
|
||
s += svg_arrow_down(W/2, y + bh, y + bh + gap, "#999", 2, 14)
|
||
s += svg_text(W/2, H - 20, "同一问题 · 不同人群 · 不同语言 · 不同深度", 11, "#666")
|
||
save_svg_and_jpg(s, "23_multilang_flow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图24: X1体验编排引擎详细架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_24_x1_detail():
|
||
W, H = 860, 680
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "X1 体验编排引擎 — 详细架构", 20, C_TITLE, True)
|
||
# 外框
|
||
s += svg_rect(30, 50, W-60, H-70, "#fafafa", C_LAYER3_B, 12, 2)
|
||
# ── 输入区 ──
|
||
s += svg_text(60, 78, "输入", 14, C_LAYER3_B, True)
|
||
inputs = [
|
||
("用户路线", "来自可信叙事引擎"),
|
||
("实时位置", "UWB定位 10Hz更新"),
|
||
("实时客流", "摄像头+IoT传感器"),
|
||
("设备状态", "监控系统"),
|
||
]
|
||
iw = 180
|
||
ih = 55
|
||
ix0 = 50
|
||
for i, (title, desc) in enumerate(inputs):
|
||
x = ix0 + i * (iw + 15)
|
||
s += svg_rect(x, 88, iw, ih, C_LAYER3, C_LAYER3_B, 8, 1.5)
|
||
s += svg_text(x + iw/2, 108, title, 12, C_LAYER3_B, True)
|
||
s += svg_text(x + iw/2, 128, desc, 10, "#666")
|
||
# 汇聚箭头 → 编排逻辑
|
||
for i in range(4):
|
||
x = ix0 + i * (iw + 15) + iw/2
|
||
s += svg_arrow(x, 143, W/2, 175, C_ARROW, 1.5, 10)
|
||
# ── 编排逻辑区 ──
|
||
s += svg_text(W/2, 168, "编排逻辑 (实时流式计算)", 14, "#e65100", True)
|
||
s += svg_rect(50, 175, W-100, 250, C_HIGHLIGHT, "#f57f17", 10, 2)
|
||
steps = [
|
||
("1. 用户进入区域", "匹配路线节点"),
|
||
("2. 查询内容组件", "该节点对应的内容组件"),
|
||
("3. 下发指令到设备", "屏幕/灯光/音频/AI终端/互动设备"),
|
||
("4. 客流拥挤", "切换备选节点 → 重新下发指令"),
|
||
]
|
||
sy = 190
|
||
for i, (title, desc) in enumerate(steps):
|
||
y = sy + i * 55
|
||
s += svg_rect(70, y, W-140, 42, "#fff", "#f57f17", 8, 1.5)
|
||
s += svg_text(90, y + 26, title, 12, "#e65100", True)
|
||
s += svg_text(280, y + 26, desc, 11, "#333")
|
||
if i < 3:
|
||
s += svg_arrow_down(W/2, y + 42, y + 55, "#f57f17", 1.5, 10)
|
||
# 设备指令细分 (第3步展开)
|
||
dev_cmds = ["屏幕: 播放对应内容", "灯光: 切换场景灯光", "音频: 播放对应导览", "AI终端: 加载讲解上下文", "互动设备: 激活对应任务"]
|
||
s += svg_text(W/2, 415, "第3步设备指令明细", 11, "#e65100", True)
|
||
dcw = 150
|
||
for i, cmd in enumerate(dev_cmds):
|
||
x = 55 + i * (dcw + 5)
|
||
s += svg_rect(x, 425, dcw, 30, "#fff", "#f57f17", 6, 1)
|
||
s += svg_text(x + dcw/2, 444, cmd, 9, "#333")
|
||
# ── 输出区 ──
|
||
s += svg_arrow_down(W/2, 460, 475, C_ARROW, 2, 14)
|
||
s += svg_text(W/2, 470, "输出", 14, C_LAYER2_B, True)
|
||
outputs = [
|
||
("设备控制指令", "实时消息推送"),
|
||
("用户学习记录", "写入成长护照"),
|
||
("客流调度信号", "反馈智慧场馆"),
|
||
]
|
||
ow = 220
|
||
ox0 = (W - 3*ow - 2*20) // 2
|
||
for i, (title, desc) in enumerate(outputs):
|
||
x = ox0 + i * (ow + 20)
|
||
s += svg_rect(x, 480, ow, 55, C_LAYER2, C_LAYER2_B, 8, 2)
|
||
s += svg_text(x + ow/2, 500, title, 12, C_LAYER2_B, True)
|
||
s += svg_text(x + ow/2, 520, desc, 10, "#666")
|
||
s += svg_text(W/2, H - 25, "实时事件驱动 · 毫秒级响应 · 同一空间千人千面", 12, C_LAYER3_B, True)
|
||
save_svg_and_jpg(s, "24_x1_detail")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图25: F3文物扫码体验流程
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_25_f3_scan_flow():
|
||
W, H = 540, 660
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F3 文物扫码体验流程", 20, C_TITLE, True)
|
||
steps = [
|
||
("展柜前扫描二维码", C_LAYER6, C_LAYER6_B),
|
||
("手机加载文物星图H5", C_LAYER1, C_LAYER1_B),
|
||
("L1 看见: 3D模型 + 基本信息", C_LAYER6, C_LAYER6_B),
|
||
("上滑 → L2 知道: 元数据卡片", C_LAYER1, C_LAYER1_B),
|
||
("点击星图节点 → L3 理解: 关联网络可视化", C_LAYER2, C_LAYER2_B),
|
||
('点击"深度研究" → L4 研究: 档案文献', C_LAYER3, C_LAYER3_B),
|
||
('点击"相关课程" → L5 连接: 课程推荐', C_LAYER4, C_LAYER4_B),
|
||
('点击"做一做" → 跳转F7行动选择', C_LAYER5, C_LAYER5_B),
|
||
]
|
||
bw = 440
|
||
bh = 48
|
||
x0 = (W - bw) // 2
|
||
y0 = 55
|
||
gap = 26
|
||
for i, (step, fill, stroke) in enumerate(steps):
|
||
y = y0 + i * (bh + gap)
|
||
s += svg_rect(x0, y, bw, bh, fill, stroke, 10, 2)
|
||
s += svg_text(W/2, y + bh/2 + 5, step, 12, stroke, True)
|
||
if i < len(steps) - 1:
|
||
s += svg_arrow_down(W/2, y + bh, y + bh + gap, "#999", 2, 14)
|
||
s += svg_text(W/2, H - 20, "一件文物 = 一个知识宇宙 · 从看见到行动", 11, "#666")
|
||
save_svg_and_jpg(s, "25_f3_scan_flow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图26: F2情境任务剧场 VR/非VR双模式共享架构
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_26_f2_vr_share():
|
||
W, H = 720, 560
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "F2 情境任务剧场 — VR/非VR双模式共享架构", 18, C_TITLE, True)
|
||
# 顶部: F2
|
||
s += svg_rect(W/2-130, 50, 260, 45, C_LAYER3, C_LAYER3_B, 10, 2)
|
||
s += svg_text(W/2, 78, "F2 情境任务剧场", 15, C_LAYER3_B, True)
|
||
# 分叉箭头到两个模式
|
||
s += svg_arrow(W/2, 95, W/2-160, 130, C_ARROW, 2, 12)
|
||
s += svg_arrow(W/2, 95, W/2+160, 130, C_ARROW, 2, 12)
|
||
# 左: 非VR模式
|
||
s += svg_rect(60, 130, 280, 160, C_LAYER1, C_LAYER1_B, 10, 2)
|
||
s += svg_text(200, 155, "非VR模式", 14, C_LAYER1_B, True)
|
||
non_vr = ["多屏互动终端", "AI复盘"]
|
||
for i, item in enumerate(non_vr):
|
||
s += svg_rect(90, 170+i*50, 220, 38, "#fff", C_LAYER1_B, 8, 1.5)
|
||
s += svg_text(200, 193+i*50, item, 12, "#333")
|
||
# 右: VR模式
|
||
s += svg_rect(380, 130, 280, 160, C_VR, C_VR_B, 10, 2)
|
||
s += svg_text(520, 155, "VR模式", 14, C_VR_B, True)
|
||
vr_items = ["沉浸场景", "体感设备", "多人协同", "AI复盘"]
|
||
for i, item in enumerate(vr_items):
|
||
s += svg_rect(410, 170+i*32, 220, 26, "#fff", C_VR_B, 6, 1.2)
|
||
s += svg_text(520, 188+i*32, item, 11, "#333")
|
||
# 汇聚箭头到共享层
|
||
s += svg_arrow(200, 290, W/2, 330, C_ARROW, 2, 12)
|
||
s += svg_arrow(520, 290, W/2, 330, C_ARROW, 2, 12)
|
||
# 共享层
|
||
s += svg_rect(80, 330, W-160, 200, C_LAYER5, C_LAYER5_B, 10, 2)
|
||
s += svg_text(W/2, 358, "共享层", 14, C_LAYER5_B, True)
|
||
shared = [
|
||
"同一任务定义",
|
||
"同一证据集(从知识图谱检索)",
|
||
"同一历史事实揭示",
|
||
"同一AI学习教练复盘",
|
||
"同一成长护照记录",
|
||
]
|
||
for i, item in enumerate(shared):
|
||
y = 375 + i * 28
|
||
s += svg_rect(120, y, W-240, 22, "#fff", C_LAYER5_B, 6, 1)
|
||
s += svg_text(W/2, y+15, item, 11, "#333")
|
||
s += svg_text(W/2, H - 20, "不同体验通道 · 同一学习内核", 12, C_LAYER5_B, True)
|
||
save_svg_and_jpg(s, "26_f2_vr_share")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图27: VR体验全流程
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_27_vr_experience_flow():
|
||
W, H = 720, 780
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "VR 体验全流程", 20, C_TITLE, True)
|
||
phases = [
|
||
("排队区(3分钟)", C_LAYER6, C_LAYER6_B, [
|
||
"观看历史背景视频",
|
||
"查看分配到的角色和资源约束",
|
||
"学习VR手柄操作和交互方式",
|
||
"了解安全须知和紧急退出方法",
|
||
"查看排队位置和预计等待时间",
|
||
]),
|
||
("进入VR工位", C_LAYER1, C_LAYER1_B, [
|
||
"佩戴VR头显 + 触觉背心",
|
||
"系统加载场景(按人群类型适配内容)",
|
||
"4人同步进入同一历史场景",
|
||
]),
|
||
("沉浸体验(8-15分钟)", C_VR, C_VR_B, [
|
||
"在虚拟场景中执行任务",
|
||
"抓取文物、查看证据",
|
||
"与AI NPC对话(可提问,NPC用史料回答)",
|
||
"4人语音讨论 + 手势交互",
|
||
"体感设备同步场景事件",
|
||
"团队做出决策",
|
||
]),
|
||
("退出VR", C_LAYER5, C_LAYER5_B, [
|
||
"AI复盘(学习教练分析方案vs历史)",
|
||
"现实连接问题",
|
||
"学习记录写入成长护照",
|
||
"设备复位 + 消毒(3分钟)",
|
||
]),
|
||
]
|
||
pw = 560
|
||
x0 = (W - pw) // 2
|
||
y = 55
|
||
for pi, (title, fill, stroke, items) in enumerate(phases):
|
||
ph = 40 + len(items) * 26 + 10
|
||
s += svg_rect(x0, y, pw, ph, fill, stroke, 10, 2)
|
||
s += svg_rect(x0, y, pw, 32, fill, stroke, 10, 2)
|
||
s += svg_text(W/2, y + 21, title, 14, stroke, True)
|
||
for i, item in enumerate(items):
|
||
iy = y + 42 + i * 26
|
||
s += svg_text(x0 + 20, iy + 16, "• " + item, 11, "#333")
|
||
y += ph
|
||
if pi < len(phases) - 1:
|
||
s += svg_arrow_down(W/2, y, y + 20, C_ARROW, 2, 14)
|
||
y += 20
|
||
s += svg_text(W/2, H - 18, "从排队到离场 · 全流程数据记录 · 无缝衔接成长护照", 11, "#666")
|
||
save_svg_and_jpg(s, "27_vr_experience_flow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图28: 最小可行原型组成
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_28_mvp():
|
||
W, H = 860, 420
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "最小可行原型 (MVP)", 20, C_TITLE, True)
|
||
# 左侧: 最小原型标签
|
||
s += svg_rect(40, 140, 130, 140, C_LAYER3, C_LAYER3_B, 10, 2)
|
||
s += svg_text(105, 195, "最小原型", 16, C_LAYER3_B, True)
|
||
s += svg_text(105, 220, "MVP", 13, C_LAYER3_B)
|
||
s += svg_text(105, 250, "6个核心模块", 10, "#666")
|
||
# 等号
|
||
s += svg_text(195, 210, "=", 28, C_LAYER3_B, True)
|
||
# 6个组件 (2行×3列)
|
||
comps = [
|
||
("文物3D展示", "1件", C_LAYER6, C_LAYER6_B),
|
||
("情境任务", "1个", C_LAYER1, C_LAYER1_B),
|
||
("AI问答入口", "1个", C_LAYER4, C_LAYER4_B),
|
||
("成长护照H5", "1个", C_LAYER2, C_LAYER2_B),
|
||
("行动选择页", "1个", C_LAYER5, C_LAYER5_B),
|
||
("30天回访推送", "1个", C_VR, C_VR_B),
|
||
]
|
||
cw = 175
|
||
ch = 80
|
||
cx0 = 235
|
||
cy0 = 100
|
||
gap_x = 20
|
||
gap_y = 30
|
||
for i, (title, qty, fill, stroke) in enumerate(comps):
|
||
col = i % 3
|
||
row = i // 3
|
||
x = cx0 + col * (cw + gap_x)
|
||
y = cy0 + row * (ch + gap_y)
|
||
s += svg_rect(x, y, cw, ch, fill, stroke, 10, 2)
|
||
s += svg_text(x + cw/2, y + 28, qty, 12, stroke, True)
|
||
s += svg_text(x + cw/2, y + 55, title, 14, stroke, True)
|
||
# + 号连接 (同行内)
|
||
if col < 2:
|
||
s += svg_text(x + cw + gap_x/2 - 5, y + ch/2 + 5, "+", 20, "#999", True)
|
||
# 第一行末到第二行首的连接箭头
|
||
if i == 2:
|
||
s += svg_arrow(x + cw/2, y + ch, x + cw/2, y + ch + 10, "#999", 1.5, 10)
|
||
s += svg_arrow(x + cw/2, y + ch + 10, cx0 + cw/2, y + ch + gap_y, "#999", 1.5, 10)
|
||
s += svg_text(x + cw/2 + 15, y + ch + 12, "+", 16, "#999", True)
|
||
# 底部说明
|
||
s += svg_text(W/2, H - 25, "6个模块 = 1条完整体验链路 · 从看到文物到行动反馈", 12, "#666")
|
||
save_svg_and_jpg(s, "28_mvp")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图29: MVP用户体验流程
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_29_mvp_flow():
|
||
W, H = 860, 580
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "MVP 用户体验流程", 20, C_TITLE, True)
|
||
steps = [
|
||
("触屏终端开始", "用户入场", C_LAYER6, C_LAYER6_B),
|
||
("查看文物3D模型", "可旋转缩放", C_LAYER1, C_LAYER1_B),
|
||
("参与情境任务", "阅读证据 → 团队讨论 → 提交方案 → 历史揭示 → AI复盘", C_LAYER4, C_LAYER4_B),
|
||
("向AI提问", "获得带史料引用的回答", C_LAYER3, C_LAYER3_B),
|
||
("打开手机H5查看成长护照", "离馆后继续", C_LAYER2, C_LAYER2_B),
|
||
("选择一个行动方向", "F7现实行动", C_LAYER5, C_LAYER5_B),
|
||
("30天后收到微信推送", "提交行动反馈", C_VR, C_VR_B),
|
||
]
|
||
bw = 680
|
||
bh = 52
|
||
x0 = (W - bw) // 2
|
||
y0 = 55
|
||
gap = 22
|
||
for i, (title, desc, fill, stroke) in enumerate(steps):
|
||
y = y0 + i * (bh + gap)
|
||
s += svg_rect(x0, y, bw, bh, fill, stroke, 10, 2)
|
||
s += svg_text(x0 + 30, y + bh/2 + 5, title, 13, stroke, True)
|
||
s += svg_text(x0 + bw - 20, y + bh/2 + 5, desc, 11, "#666")
|
||
if i < len(steps) - 1:
|
||
s += svg_arrow_down(W/2, y + bh, y + bh + gap, C_ARROW, 2, 14)
|
||
s += svg_text(W/2, H - 18, "从入场到离馆后30天 · 一条完整的体验闭环", 11, "#666")
|
||
save_svg_and_jpg(s, "29_mvp_flow")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图30: MVP原型部署方案
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_30_mvp_deploy():
|
||
W, H = 760, 460
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "MVP 原型部署方案", 20, C_TITLE, True)
|
||
s += svg_text(W/2, 55, "单服务器部署(原型阶段)", 13, "#666")
|
||
# 外框
|
||
s += svg_rect(40, 75, W-80, 340, "#fafafa", C_LAYER6_B, 12, 2)
|
||
servers = [
|
||
("GPU服务器", "×1台", "AI推理 + 向量检索", C_LAYER3, C_LAYER3_B),
|
||
("应用服务器", "×1台", "业务服务 + 缓存", C_LAYER1, C_LAYER1_B),
|
||
("前端服务器", "×1台", "3D展示 + 小程序后端", C_LAYER4, C_LAYER4_B),
|
||
("互动终端", "×1台", "情境任务 + AI问答界面", C_LAYER2, C_LAYER2_B),
|
||
]
|
||
bw = 300
|
||
bh = 60
|
||
x0 = (W - bw) // 2
|
||
y0 = 95
|
||
gap = 20
|
||
for i, (name, qty, desc, fill, stroke) in enumerate(servers):
|
||
y = y0 + i * (bh + gap)
|
||
s += svg_rect(x0, y, bw, bh, fill, stroke, 10, 2)
|
||
s += svg_text(x0 + 20, y + 25, name, 14, stroke, True)
|
||
s += svg_text(x0 + bw - 20, y + 25, qty, 12, stroke, True)
|
||
s += svg_text(x0 + 20, y + 48, desc, 11, "#333")
|
||
if i < len(servers) - 1:
|
||
s += svg_arrow_down(W/2, y + bh, y + bh + gap, "#bbb", 1.5, 10)
|
||
s += svg_text(W/2, H - 20, "4台设备 · 最小成本验证完整体验闭环", 12, C_LAYER6_B, True)
|
||
save_svg_and_jpg(s, "30_mvp_deploy")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 图31: 核心体验闭环
|
||
# ═══════════════════════════════════════════════════════════════
|
||
def gen_31_core_loop():
|
||
import math as _m
|
||
W, H = 760, 560
|
||
s = svg_header(W, H)
|
||
s += f'<rect width="{W}" height="{H}" fill="{C_BG}"/>'
|
||
s += svg_text(W/2, 30, "核心体验闭环", 20, C_TITLE, True)
|
||
stages = [
|
||
("提问", "参观前选择学习问题\nAI全程可问答", C_LAYER3, C_LAYER3_B),
|
||
("体验", "自适应路线 + 沉浸VR\n情境任务 + 文物星图", C_VR, C_VR_B),
|
||
("理解", "AI复盘 + 理解表达\n离馆报告", C_LAYER4, C_LAYER4_B),
|
||
("行动", "行动选择\n7/30/90天跟踪", C_LAYER2, C_LAYER2_B),
|
||
("反馈", "进展提交 + 成果回流\n优秀案例入库", C_LAYER5, C_LAYER5_B),
|
||
("再传播", "青年共创 + 云展馆\n全国节点复制", C_LAYER1, C_LAYER1_B),
|
||
]
|
||
n = len(stages)
|
||
cx, cy = W/2, 310
|
||
r = 180
|
||
bw, bh = 130, 60
|
||
# 计算每个节点的位置
|
||
pts = []
|
||
for i in range(n):
|
||
angle = -_m.pi/2 + i * 2 * _m.pi / n # 从顶部开始顺时针
|
||
x = cx + r * _m.cos(angle)
|
||
y = cy + r * _m.sin(angle)
|
||
pts.append((x, y, angle))
|
||
# 先画弧形箭头连接 (相邻节点之间)
|
||
for i in range(n):
|
||
x1, y1, a1 = pts[i]
|
||
x2, y2, a2 = pts[(i+1) % n]
|
||
# 在圆弧上取中点偏移,画弧线
|
||
mid_angle = (a1 + a2) / 2 if i < n-1 else (a1 + a2 + 2*_m.pi) / 2
|
||
arc_r = r - 5
|
||
mx = cx + arc_r * _m.cos(mid_angle)
|
||
my = cy + arc_r * _m.sin(mid_angle)
|
||
# 画曲线箭头
|
||
s += f'<path d="M {x1:.0f} {y1:.0f} Q {mx:.0f} {my:.0f} {x2:.0f} {y2:.0f}" fill="none" stroke="{C_ARROW}" stroke-width="2" stroke-linecap="round"/>'
|
||
# 箭头头在终点
|
||
da = mid_angle - a2 if i < n-1 else mid_angle - a2 + 2*_m.pi
|
||
bx = x2 - 12 * _m.cos(a2 + _m.pi/2)
|
||
by = y2 - 12 * _m.sin(a2 + _m.pi/2)
|
||
s += svg_arrow(bx, by, x2, y2, C_ARROW, 2, 12)
|
||
# 画节点框和文字 (覆盖在线之上)
|
||
for i, (title, desc, fill, stroke) in enumerate(stages):
|
||
x, y, angle = pts[i]
|
||
rx = x - bw/2
|
||
ry = y - bh/2
|
||
s += svg_rect(rx, ry, bw, bh, fill, stroke, 10, 2)
|
||
s += svg_text(x, y - 5, title, 15, stroke, True)
|
||
# desc 多行
|
||
lines = desc.split("\\n")
|
||
for j, line in enumerate(lines):
|
||
s += svg_text(x, y + 15 + j*14, line, 9, "#333")
|
||
# 中心文字
|
||
s += svg_text(cx, cy - 5, "核心体验", 16, C_TITLE, True)
|
||
s += svg_text(cx, cy + 15, "闭环", 16, C_TITLE, True)
|
||
s += svg_text(cx, cy + 35, "一件文物 → 一次行动", 10, "#999")
|
||
save_svg_and_jpg(s, "31_core_loop")
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 主程序
|
||
# ═══════════════════════════════════════════════════════════════
|
||
if __name__ == "__main__":
|
||
print("开始生成SVG → JPG...")
|
||
gen_01_six_layer()
|
||
gen_02_deployment()
|
||
gen_03_ai_engine()
|
||
gen_04_narrative_engine()
|
||
gen_05_growth_engine()
|
||
gen_06_replication_engine()
|
||
gen_07_f1_x1()
|
||
gen_08_f2_task()
|
||
gen_09_f3_artifact()
|
||
gen_10_f4_passport()
|
||
gen_11_f5_cloud()
|
||
gen_12_f6_cocreation()
|
||
gen_13_f7_action()
|
||
gen_14_f8_nodes()
|
||
gen_15_vr_architecture()
|
||
gen_16_vr_multiplayer()
|
||
gen_17_vr_cloud_streaming()
|
||
gen_18_x1_dataflow()
|
||
gen_19_x2_sync()
|
||
gen_20_x3_review()
|
||
gen_21_security()
|
||
gen_22_ai_guardrail()
|
||
gen_23_multilang_flow()
|
||
gen_24_x1_detail()
|
||
gen_25_f3_scan_flow()
|
||
gen_26_f2_vr_share()
|
||
gen_27_vr_experience_flow()
|
||
gen_28_mvp()
|
||
gen_29_mvp_flow()
|
||
gen_30_mvp_deploy()
|
||
gen_31_core_loop()
|
||
print(f"\n全部完成! 共生成31张JPG,保存在: {OUTPUT_DIR}")
|