#!/usr/bin/env python3 """Serve the SupplyAgent business-design visualizations locally.""" from __future__ import annotations import argparse import html import mimetypes import threading import webbrowser from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import unquote, urlparse VISUALIZATION_DIR = Path(__file__).resolve().parents[1] PAGES_DIR = VISUALIZATION_DIR / "pages" PAGES: tuple[tuple[str, str, str], ...] = ( ( "全局分类树-需求热度与关系分析.html", "00 · 平台全局需求地图", "用全量冰柱热力图呈现冷热分布,并从上游数据直接形成数百条平台需求和可追溯 Reason。", ), ( "graph-structure-options.html", "01 · 图结构方案比较", "比较单一大树、自由图和树骨架关系图。", ), ( "graph-structure-left-to-right.html", "02 · 从左到右的树图结构", "展示主题、分类树、具体节点、平台需求和证据的阅读方向。", ), ( "business-graph-model.html", "03 · 业务图节点与边", "展示分类节点、需求元素、平台需求、关系和证据。", ), ( "demand-feedback-loop.html", "04 · 需求—内容—表现闭环", "展示真实线上表现如何反向更新需求强度。", ), ( "final-output-structure.html", "05 · 最终输出结构", "展示主题方向、平台需求和证据卡的组织方式。", ), ( "node-drilldown-evidence-chain.html", "06 · 节点下钻证据链", "展示分类节点如何下挂元素、视频、短观点、长讨论并提取平台需求。", ), ( "waiting-business-scope.html", "过程页 A · 业务范围确认", "对话过程中的可视化等待页。", ), ( "waiting-demand-pipeline.html", "过程页 B · 需求流水线确认", "对话过程中的可视化等待页。", ), ( "waiting-strength-lifecycle.html", "过程页 C · 强度生命周期确认", "对话过程中的可视化等待页。", ), ) BASE_CSS = """ * { box-sizing: border-box; } html, body { min-height: 100%; } body { margin: 0; color: #0f172a; background: #f5f7fb; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.5; } a { color: inherit; } .topbar { position: sticky; top: 0; z-index: 50; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 24px; color: #e2e8f0; background: rgba(15, 23, 42, .96); border-bottom: 1px solid #334155; } .topbar a { text-decoration: none; } .topbar .brand { font-weight: 700; color: #fff; } .topbar .meta { color: #94a3b8; font-size: 13px; } .content { width: min(1480px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 48px; } h1, h2, h3 { line-height: 1.25; } h2 { margin: 0 0 8px; font-size: 26px; } .subtitle { color: #64748b; margin: 0 0 20px; } .options { display: flex; flex-direction: column; gap: 12px; } .option, .card { cursor: pointer; } .option.selected, .card.selected { border-color: #2563eb !important; box-shadow: 0 0 0 3px rgba(37, 99, 235, .14) !important; } .gallery-title { margin: 4px 0 8px; font-size: 30px; } .gallery-intro { max-width: 880px; color: #64748b; margin: 0 0 24px; } .gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 16px; } .gallery-card { display: block; padding: 18px; text-decoration: none; background: #fff; border: 1px solid #dbe3ec; border-radius: 15px; transition: .18s ease; } .gallery-card:hover { transform: translateY(-2px); border-color: #60a5fa; box-shadow: 0 12px 28px rgba(15, 23, 42, .09); } .gallery-card h3 { margin: 0 0 7px; font-size: 17px; } .gallery-card p { margin: 0; color: #64748b; font-size: 13px; } .reference { margin-top: 24px; padding: 18px; border: 1px solid #dbe3ec; border-radius: 15px; background: #fff; } .reference img { display: block; width: min(100%, 650px); height: auto; margin-top: 12px; border: 1px solid #e2e8f0; } """ def _page_metadata(filename: str) -> tuple[str, str] | None: for item_filename, title, description in PAGES: if filename == item_filename: return title, description return None def _document(title: str, body: str, *, subtitle: str = "") -> bytes: subtitle_html = ( f'
' if subtitle else "" ) document = f"""{html.escape(description)}
""" ) body = f"""本目录保存需求汇总业务设计过程中形成的全部可视化材料。 正式业务结论以 README_myself.md 为准;图稿用于解释树、图、需求、内容和反馈之间的关系。
用于理解全局分类树、分类节点下挂元素,以及元素如何扩展成明确语言和长段讨论。