From f857a9097739c282d0b7f5f108997b0cfcec7bd2 Mon Sep 17 00:00:00 2001
From: Bifang <915779419@qq.com>
Date: Mon, 15 Jun 2026 09:17:21 +0800
Subject: [PATCH] =?UTF-8?q?web=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
meeting_memory/web_demo/server.py | 31 +-
meeting_memory/web_demo/static/graph.js | 81 +-
meeting_memory/web_demo/static/styles.css | 977 -------------
.../web_demo/{static => static_v2}/graph.html | 37 +-
.../web_demo/{static => static_v2}/index.html | 24 +-
meeting_memory/web_demo/static_v2/styles.css | 1259 +++++++++++++++++
6 files changed, 1373 insertions(+), 1036 deletions(-)
delete mode 100644 meeting_memory/web_demo/static/styles.css
rename meeting_memory/web_demo/{static => static_v2}/graph.html (62%)
rename meeting_memory/web_demo/{static => static_v2}/index.html (87%)
create mode 100644 meeting_memory/web_demo/static_v2/styles.css
diff --git a/meeting_memory/web_demo/server.py b/meeting_memory/web_demo/server.py
index 40e19da..a63f498 100644
--- a/meeting_memory/web_demo/server.py
+++ b/meeting_memory/web_demo/server.py
@@ -20,6 +20,7 @@ from meeting_memory.meeting_processor import meeting_processor, state_store
logger = logging.getLogger(__name__)
STATIC_DIR = Path(__file__).resolve().parent / "static"
+STATIC_V2_DIR = Path(__file__).resolve().parent / "static_v2"
RAW_DIR = Path(config.storage.raw_dir)
IMPORT_JOBS = {}
IMPORT_JOBS_LOCK = threading.Lock()
@@ -29,8 +30,22 @@ class GraphDemoHandler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=str(STATIC_DIR), **kwargs)
+ # ── Route: serve /static_v2/* from the v2 directory ──
+ def translate_path(self, path):
+ parsed = urlparse(path)
+ raw = parsed.path
+
+ # Serve /static_v2/* from static_v2 directory
+ if raw.startswith("/static_v2/"):
+ rel = raw[len("/static_v2/"):]
+ return str(STATIC_V2_DIR / rel)
+
+ return super().translate_path(path)
+
def do_GET(self):
parsed = urlparse(self.path)
+
+ # API endpoints
if parsed.path == "/api/dashboard":
self._handle_dashboard()
return
@@ -55,10 +70,16 @@ class GraphDemoHandler(SimpleHTTPRequestHandler):
if parsed.path == "/api/import-status":
self._handle_import_status(parsed.query)
return
+
+ # Page routing — serve v2 HTML as default
if parsed.path in ("/", "/index.html"):
- self.path = "/index.html"
+ self.path = "/static_v2/index.html"
elif parsed.path == "/graph":
- self.path = "/graph.html"
+ self.path = "/static_v2/graph.html"
+ elif parsed.path == "/graph.html":
+ self.path = "/static_v2/graph.html"
+
+ # JS files (/app.js, /graph.js) resolve to STATIC_DIR via default translate_path
super().do_GET()
def do_POST(self):
@@ -311,9 +332,9 @@ def _serialize_meeting(path: Path, include_content: bool = False):
lines = raw_text.splitlines()
for line in lines[:12]:
if line.startswith('title: "'):
- title = line[len('title: "') : -1]
+ title = line[len('title: "'):-1]
elif line.startswith('date: "'):
- date = line[len('date: "') : -1]
+ date = line[len('date: "'):-1]
content_start = 0
for idx, line in enumerate(lines):
@@ -397,4 +418,4 @@ if __name__ == "__main__":
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
- run_demo_server()
+ run_demo_server()
\ No newline at end of file
diff --git a/meeting_memory/web_demo/static/graph.js b/meeting_memory/web_demo/static/graph.js
index c8ce419..47e025c 100644
--- a/meeting_memory/web_demo/static/graph.js
+++ b/meeting_memory/web_demo/static/graph.js
@@ -4,8 +4,7 @@ const graphNodeLimit = document.getElementById("graphNodeLimit");
const graphEdgeLimit = document.getElementById("graphEdgeLimit");
const graphSvg = document.getElementById("graphSvg");
const graphMeta = document.getElementById("graphMeta");
-const graphDetail = document.getElementById("graphDetail");
-const relatedSearch = document.getElementById("relatedSearch");
+const detailPanel = document.getElementById("detailPanel");
const graphTypeFilter = document.getElementById("graphTypeFilter");
let selectedEntityTypes = null;
@@ -76,35 +75,35 @@ async function loadGraphKinds() {
}
function renderInspector(content) {
- graphDetail.innerHTML = content;
+ detailPanel.innerHTML = content;
}
async function loadRelated(query) {
- if (!query) {
- relatedSearch.innerHTML = "";
- return;
- }
+ if (!query) return;
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=4`);
const payload = await response.json();
const results = payload.results || [];
if (!results.length) {
- relatedSearch.innerHTML = empty("没有更多相关检索结果");
+ detailPanel.insertAdjacentHTML("beforeend", `
+
+
${h(node.kind)}
${h(node.label)}
${body}
- ${related.map((edge) => `
-
- ${h(edge.source)} → ${h(edge.target)}
- ${h(edge.fact || edge.description || edge.predicate || "")}
-
- `).join("")}
+
+
Relations
+ ${related.length ? related.map((edge) => `
+
+ ${h(edge.source)} → ${h(edge.target)}
+ ${h(edge.fact || edge.description || edge.predicate || "")}
+
+ `).join("") : `
没有关联关系
`}
+
`);
- loadRelated(node.label).catch(() => relatedSearch.innerHTML = empty("相关检索加载失败"));
+ loadRelated(node.label).catch(() => {
+ detailPanel.insertAdjacentHTML("beforeend", `
+
+ `);
+ });
});
});
@@ -432,7 +440,7 @@ function renderGraph(payload) {
line?.classList.add("active");
const edge = edges.find((item) => item.id === el.dataset.edgeId);
renderInspector(`
-
+
Edge
${h(edge.source)} → ${h(edge.target)}
${h(edge.fact || edge.description || "暂无补充描述")}
@@ -444,7 +452,14 @@ function renderGraph(payload) {
`);
- loadRelated(`${edge.source} ${edge.predicate} ${edge.target}`).catch(() => relatedSearch.innerHTML = empty("相关检索加载失败"));
+ loadRelated(`${edge.source} ${edge.predicate} ${edge.target}`).catch(() => {
+ detailPanel.insertAdjacentHTML("beforeend", `
+
+ `);
+ });
});
});
@@ -514,4 +529,4 @@ graphForm?.addEventListener("submit", (event) => {
});
loadGraphKinds().catch(() => {});
-fetchGraph().catch((error) => renderInspector(empty(`图谱加载失败: ${error}`)));
+fetchGraph().catch((error) => renderInspector(empty(`图谱加载失败: ${error}`)));
\ No newline at end of file
diff --git a/meeting_memory/web_demo/static/styles.css b/meeting_memory/web_demo/static/styles.css
deleted file mode 100644
index 2f545ab..0000000
--- a/meeting_memory/web_demo/static/styles.css
+++ /dev/null
@@ -1,977 +0,0 @@
-:root {
- --primary: #5d67f5;
- --primary-2: #7f8bff;
- --primary-soft: #edf1ff;
- --accent: #53c2da;
- --bg: #f5f7ff;
- --bg-2: #fbfcff;
- --panel: rgba(255, 255, 255, 0.9);
- --panel-strong: rgba(255, 255, 255, 0.96);
- --border: rgba(212, 221, 247, 0.95);
- --text: #22264d;
- --muted: #68709d;
- --danger: #b3261e;
- --success: #11693c;
- --shadow: 0 12px 28px rgba(73, 81, 141, 0.08);
- --shadow-sm: 0 6px 16px rgba(73, 81, 141, 0.06);
- --radius-xl: 20px;
- --radius-lg: 16px;
- --radius-md: 12px;
- --radius-sm: 10px;
-}
-
-* { box-sizing: border-box; }
-
-html, body {
- margin: 0;
- min-height: 100%;
-}
-
-body {
- font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
- font-size: 13px;
- color: var(--text);
- background:
- radial-gradient(circle at 10% 10%, rgba(126, 186, 255, 0.16), transparent 24%),
- radial-gradient(circle at 88% 14%, rgba(132, 121, 255, 0.12), transparent 22%),
- linear-gradient(135deg, #f8faff 0%, var(--bg) 55%, var(--bg-2) 100%);
-}
-
-a { color: inherit; text-decoration: none; }
-
-button, input, textarea { font: inherit; }
-
-.shell {
- display: grid;
- grid-template-columns: 220px minmax(0, 1fr);
- gap: 14px;
- min-height: 100vh;
- padding: 14px;
-}
-
-.sidebar, .panel, .detail-modal::backdrop {
- backdrop-filter: blur(12px);
-}
-
-.sidebar {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 14px;
- border: 1px solid var(--border);
- border-radius: 22px;
- background: linear-gradient(180deg, rgba(236, 243, 255, 0.92), rgba(255, 255, 255, 0.8));
- box-shadow: var(--shadow);
-}
-
-.brand {
- display: flex;
- gap: 10px;
- align-items: center;
-}
-
-.brand-mark {
- width: 40px;
- height: 40px;
- display: grid;
- place-items: center;
- border-radius: 14px;
- color: #fff;
- font-size: 17px;
- font-weight: 800;
- background: linear-gradient(135deg, var(--primary), var(--primary-2));
-}
-
-.brand-kicker, .eyebrow {
- margin: 0 0 3px;
- color: var(--primary);
- font-size: 10px;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
-}
-
-.brand h1, .panel h3, .dialog-head h3 {
- margin: 0;
-}
-
-.brand h1 { font-size: 18px; }
-
-.nav {
- display: grid;
- gap: 6px;
-}
-
-.nav-link {
- padding: 10px 12px;
- border: 1px solid transparent;
- border-radius: var(--radius-md);
- color: var(--muted);
- font-size: 13px;
- font-weight: 700;
- transition: 0.2s ease;
-}
-
-.nav-link:hover, .nav-link.active {
- color: var(--primary);
- border-color: rgba(109, 123, 255, 0.16);
- background: rgba(255, 255, 255, 0.78);
-}
-
-.side-card, .panel {
- border: 1px solid var(--border);
- border-radius: var(--radius-xl);
- background: var(--panel);
- box-shadow: var(--shadow-sm);
-}
-
-.panel { padding: 14px; }
-
-.panel-head {
- display: flex;
- justify-content: space-between;
- align-items: start;
- gap: 10px;
- margin-bottom: 10px;
-}
-
-.panel h3 { font-size: 17px; }
-
-.sidebar-shortcuts {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- padding: 10px;
- margin-top: auto;
-}
-
-.pill-link, .chip {
- display: inline-flex;
- align-items: center;
- min-height: 24px;
- padding: 0 9px;
- border-radius: 999px;
- font-size: 11px;
- font-weight: 700;
-}
-
-.pill-link {
- background: rgba(255, 255, 255, 0.9);
- border: 1px solid var(--border);
-}
-
-.chip {
- background: var(--primary-soft);
- color: var(--primary);
-}
-
-.chip.status-done, .chip.status-completed { background: #edfdf4; color: var(--success); }
-.chip.status-pending, .chip.status-todo { background: #fff8e7; color: #b8860b; }
-.chip.status-in_progress, .chip.status-active { background: #e8f4fd; color: #4a90d9; }
-.chip.status-blocked { background: #fff4f2; color: var(--danger); }
-
-.main {
- display: flex;
- flex-direction: column;
- gap: 12px;
- min-height: 0;
-}
-
-.main-toolbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 16px;
- padding: 16px 18px;
- border: 1px solid var(--border);
- border-radius: 22px;
- background:
- radial-gradient(circle at top right, rgba(134, 144, 255, 0.12), transparent 28%),
- linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(244, 248, 255, 0.96));
- box-shadow: var(--shadow);
-}
-
-.main-toolbar h2 {
- margin: 0;
- font-size: 22px;
-}
-
-.main-toolbar-actions {
- display: flex;
- gap: 8px;
-}
-
-.btn, .icon-btn {
- border: none;
- cursor: pointer;
- transition: 0.2s ease;
-}
-
-.btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 36px;
- padding: 0 14px;
- border-radius: 11px;
- font-size: 12px;
- font-weight: 700;
- color: #fff;
- background: linear-gradient(135deg, var(--primary), var(--primary-2));
- box-shadow: 0 8px 18px rgba(93, 103, 245, 0.18);
-}
-
-.btn:hover, .icon-btn:hover { transform: translateY(-1px); }
-
-.btn:disabled {
- opacity: 0.68;
- cursor: not-allowed;
- transform: none;
-}
-
-.btn.ghost {
- color: var(--primary);
- background: rgba(255, 255, 255, 0.94);
- box-shadow: none;
- border: 1px solid var(--border);
-}
-
-.stats-grid, .content-grid, .workspace-grid {
- display: grid;
- gap: 12px;
-}
-
-.stats-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
-
-.highlight-card {
- padding: 0;
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- background: var(--panel-strong);
- box-shadow: var(--shadow-sm);
- overflow: hidden;
-}
-
-.highlight-card .hc-bar {
- height: 4px;
- background: var(--card-accent);
-}
-
-.highlight-card .eyebrow {
- padding: 12px 14px 0;
-}
-
-.highlight-card strong {
- display: block;
- margin: 4px 0 2px;
- padding: 0 14px;
- font-size: 26px;
- color: var(--card-accent);
-}
-
-.highlight-card p:last-child {
- padding: 0 14px 14px;
- margin: 0;
- color: var(--muted);
-}
-
-.dashboard-grid {
- grid-template-columns: minmax(330px, 1.1fr) minmax(340px, 1fr) minmax(220px, 0.72fr);
- align-items: start;
-}
-
-.search-box, .import-form, .import-fieldset {
- display: grid;
- gap: 8px;
-}
-
-.import-fieldset {
- margin: 0;
- padding: 0;
- border: 0;
- min-width: 0;
-}
-
-.import-fieldset:disabled { opacity: 0.6; }
-
-.search-box input, .graph-controls input, textarea, input[type="file"] {
- width: 100%;
- min-height: 38px;
- padding: 9px 12px;
- border: 1px solid var(--border);
- border-radius: 11px;
- background: rgba(255, 255, 255, 0.94);
- color: var(--text);
-}
-
-textarea {
- min-height: 138px;
- resize: vertical;
-}
-
-.field-label {
- font-size: 11px;
- font-weight: 700;
- color: var(--muted);
-}
-
-.check-row {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 12px;
- color: var(--muted);
-}
-
-.status-box {
- margin-top: 10px;
- padding: 10px 12px;
- border-radius: 12px;
- border: 1px solid var(--border);
- background: rgba(255, 255, 255, 0.76);
- font-size: 12px;
- color: var(--muted);
-}
-
-.status-box[data-kind="error"] {
- color: var(--danger);
- background: #fff4f2;
-}
-
-.status-box[data-kind="success"] {
- color: var(--success);
- background: #edfdf4;
-}
-
-.progress-list, .search-results, .mini-stats, .card-list, .list-stack, .related-search {
- display: grid;
- gap: 8px;
-}
-
-.progress-item, .mini-stat, .card, .list-item, .result-card, .detail-card {
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.88);
-}
-
-.progress-item {
- display: grid;
- grid-template-columns: 24px 1fr;
- gap: 8px;
- align-items: start;
-}
-
-.progress-index {
- width: 24px;
- height: 24px;
- display: grid;
- place-items: center;
- border-radius: 999px;
- background: var(--primary-soft);
- color: var(--primary);
- font-size: 11px;
- font-weight: 700;
-}
-
-.mini-stat {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 12px;
-}
-
-.ms-icon {
- width: 32px;
- height: 32px;
- display: grid;
- place-items: center;
- border-radius: 10px;
- font-size: 15px;
- background: color-mix(in srgb, var(--stat-color) 14%, transparent);
- color: var(--stat-color);
- flex-shrink: 0;
-}
-
-.ms-body strong {
- display: block;
- font-size: 16px;
- line-height: 1.2;
-}
-
-.ms-body p {
- margin: 0;
- font-size: 11px;
- color: var(--muted);
-}
-
-.mini-stat strong, .card h4, .list-item strong, .result-card strong {
- display: block;
- margin-bottom: 4px;
-}
-
-.card { cursor: pointer; }
-
-.card:hover, .result-card:hover, .list-item:hover {
- border-color: rgba(120, 132, 255, 0.34);
-}
-
-.content-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
-
-/* ── Meeting card ── */
-
-.meeting-card {
- display: flex;
- gap: 10px;
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.88);
- cursor: pointer;
- transition: 0.2s ease;
-}
-
-.meeting-card:hover {
- border-color: rgba(120, 132, 255, 0.34);
-}
-
-.mc-date {
- flex-shrink: 0;
- width: 44px;
- height: 44px;
- display: grid;
- place-items: center;
- border-radius: 10px;
- background: var(--primary-soft);
- color: var(--primary);
- font-size: 11px;
- font-weight: 700;
- text-align: center;
- line-height: 1.2;
-}
-
-.mc-body h4 {
- margin: 0 0 4px;
- font-size: 13px;
-}
-
-.mc-body p {
- margin: 0;
- font-size: 12px;
- color: var(--muted);
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
-}
-
-/* ── List item with priority dot ── */
-
-.list-item {
- display: flex;
- gap: 10px;
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.88);
-}
-
-.li-priority {
- flex-shrink: 0;
- width: 4px;
- border-radius: 2px;
- background: var(--pri-color);
-}
-
-.li-body {
- flex: 1;
- min-width: 0;
-}
-
-.li-body strong {
- display: block;
- margin-bottom: 2px;
-}
-
-.li-body p {
- margin: 0 0 6px;
- font-size: 12px;
- color: var(--muted);
-}
-
-/* ── Metric card ── */
-
-.metric-card {
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.88);
-}
-
-.mc-head {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 2px;
-}
-
-.mc-head strong {
- display: block;
-}
-
-.mc-value {
- font-size: 16px;
- font-weight: 700;
- color: var(--primary);
-}
-
-.metric-card p {
- margin: 0 0 8px;
- font-size: 12px;
- color: var(--muted);
-}
-
-.mc-bar-track {
- height: 4px;
- border-radius: 2px;
- background: rgba(212, 221, 247, 0.5);
- margin-bottom: 8px;
- overflow: hidden;
-}
-
-.mc-bar-fill {
- height: 100%;
- border-radius: 2px;
- background: linear-gradient(90deg, var(--primary), var(--primary-2));
- transition: width 0.4s ease;
-}
-
-/* ── Series card ── */
-
-.series-card {
- display: flex;
- gap: 10px;
- align-items: center;
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.88);
-}
-
-.sc-count {
- flex-shrink: 0;
- width: 36px;
- height: 36px;
- display: grid;
- place-items: center;
- border-radius: 10px;
- font-size: 14px;
- font-weight: 700;
- background: var(--primary-soft);
- color: var(--primary);
-}
-
-.sc-body strong {
- display: block;
- margin-bottom: 2px;
-}
-
-.sc-body p {
- margin: 0;
- font-size: 12px;
- color: var(--muted);
-}
-
-/* ── Unified Import / Search panel ── */
-
-.unified-panel {
- display: flex;
- flex-direction: column;
-}
-
-.unified-tabs {
- display: flex;
- gap: 4px;
- margin-bottom: 12px;
- padding: 3px;
- border-radius: 11px;
- background: rgba(212, 221, 247, 0.3);
-}
-
-.unified-tab {
- flex: 1;
- padding: 7px 12px;
- border: none;
- border-radius: 8px;
- font-size: 12px;
- font-weight: 700;
- cursor: pointer;
- background: transparent;
- color: var(--muted);
- transition: 0.2s ease;
-}
-
-.unified-tab.active {
- background: #fff;
- color: var(--primary);
- box-shadow: 0 2px 6px rgba(73, 81, 141, 0.1);
-}
-
-.unified-tab:hover:not(.active) {
- color: var(--text);
-}
-
-.unified-pane.hidden {
- display: none;
-}
-
-/* ── Result card with kind badge ── */
-
-.result-card {
- position: relative;
-}
-
-.rc-kind {
- display: inline-block;
- padding: 1px 7px;
- border-radius: 4px;
- font-size: 10px;
- font-weight: 700;
- text-transform: uppercase;
- background: var(--primary-soft);
- color: var(--primary);
- margin-bottom: 4px;
-}
-
-.empty-state {
- padding: 16px 14px;
- text-align: center;
- border: 1px dashed var(--border);
- border-radius: 14px;
- color: var(--muted);
-}
-
-.detail-modal {
- width: min(820px, calc(100vw - 24px));
- border: 1px solid var(--border);
- border-radius: 20px;
- padding: 0;
- background: rgba(255, 255, 255, 0.97);
- box-shadow: var(--shadow);
-}
-
-.detail-modal::backdrop {
- background: rgba(37, 44, 78, 0.28);
-}
-
-.dialog-head {
- display: flex;
- justify-content: space-between;
- gap: 10px;
- padding: 16px 16px 6px;
-}
-
-.dialog-meta { padding: 0 16px 6px; color: var(--muted); }
-
-.dialog-content {
- margin: 0;
- padding: 0 16px 16px;
- white-space: pre-wrap;
- font-family: "Consolas", "Courier New", monospace;
- max-height: 60vh;
- overflow: auto;
- color: var(--muted);
-}
-
-.icon-btn {
- width: 30px;
- height: 30px;
- border-radius: 10px;
- background: rgba(242, 245, 255, 0.92);
- color: var(--primary);
- font-size: 20px;
-}
-
-/* ── Graph page ── */
-
-.graph-shell {
- height: 100vh;
- overflow: hidden;
- gap: 10px;
- padding: 10px;
-}
-
-.graph-shell .sidebar {
- flex-shrink: 0;
-}
-
-.graph-shell .main {
- gap: 8px;
-}
-
-.graph-shell .graph-layout {
- gap: 8px;
-}
-
-.graph-shell .graph-layout .panel {
- padding: 10px;
-}
-
-.graph-layout {
- display: grid;
- grid-template-columns: 1fr 300px;
- gap: 12px;
- flex: 1;
- min-height: 0;
-}
-
-.graph-stage-panel {
- display: flex;
- flex-direction: column;
- padding: 0;
- overflow: hidden;
-}
-
-.graph-stage {
- flex: 1;
- min-height: 0;
- position: relative;
- background:
- linear-gradient(180deg, rgba(251, 253, 255, 0.96), rgba(241, 246, 255, 0.94)),
- radial-gradient(circle at center, rgba(133, 196, 255, 0.08), transparent 36%);
-}
-
-#graphSvg {
- width: 100%;
- height: 100%;
- display: block;
-}
-
-.detail-panel {
- display: flex;
- flex-direction: column;
- gap: 8px;
- overflow: hidden;
-}
-
-.detail-panel .detail-card,
-.detail-panel .related-search {
- overflow-y: auto;
-}
-
-.detail-card {
- flex-shrink: 0;
- word-break: break-all;
-}
-
-.detail-card strong {
- word-break: break-word;
-}
-
-.related-search {
- flex-shrink: 0;
-}
-
-.related-search .result-card {
- word-break: break-all;
-}
-
-/* ── Graph toolbar ── */
-
-.graph-toolbar { padding: 8px 12px; }
-
-.graph-controls {
- display: flex;
- gap: 6px;
- align-items: center;
-}
-
-.graph-controls .search-input {
- flex: 1;
- min-height: 30px;
- padding: 6px 10px;
-}
-
-.graph-controls label.field-label {
- display: flex;
- align-items: center;
- gap: 2px;
- white-space: nowrap;
- font-size: 10px;
-}
-
-.graph-controls label.field-label input {
- width: 44px;
- min-height: 26px;
- padding: 4px 6px;
-}
-
-.graph-controls .btn {
- min-height: 30px;
- padding: 0 12px;
- font-size: 11px;
-}
-
-.graph-toolbar-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 6px;
- margin-top: 6px;
-}
-
-.graph-actions {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 11px;
- color: var(--muted);
-}
-
-.graph-type-filter {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 4px 10px;
-}
-
-.graph-type-filter label {
- display: inline-flex;
- align-items: center;
- gap: 3px;
- font-size: 11px;
- color: var(--muted);
- cursor: pointer;
- user-select: none;
-}
-
-.graph-type-filter label input {
- margin: 0;
- accent-color: var(--primary);
-}
-
-.graph-meta { font-size: 11px; color: var(--muted); }
-
-/* ── Graph nodes & edges ── */
-
-.graph-node { cursor: pointer; }
-
-.graph-node circle {
- stroke: rgba(255, 255, 255, 0.85);
- stroke-width: 2;
- transition: filter 0.15s;
-}
-
-.graph-node--meeting circle { fill: #4a90d9; }
-.graph-node--episode circle { fill: #34c759; }
-.graph-node--entity circle { fill: var(--accent); }
-.graph-node--fact circle { fill: #ff9500; }
-
-.graph-node:hover circle { filter: brightness(1.2); }
-
-.graph-node text {
- font-size: 11px;
- fill: var(--text);
- pointer-events: none;
- user-select: none;
-}
-
-.graph-edge {
- stroke: rgba(120, 136, 194, 0.42);
- stroke-width: 1.6;
- cursor: pointer;
- transition: stroke 0.15s, stroke-width 0.15s;
-}
-
-.edge-wrap:hover .graph-edge {
- stroke: rgba(120, 136, 194, 0.7);
- stroke-width: 2;
-}
-
-.graph-edge.active {
- stroke: var(--primary);
- stroke-width: 2.4;
-}
-
-.edge-wrap text {
- pointer-events: none;
- user-select: none;
-}
-
-/* ── Legend ── */
-
-.legend { font-size: 11px; color: var(--muted); }
-
-.legend-dot {
- display: inline-block;
- width: 9px;
- height: 9px;
- border-radius: 50%;
- margin-right: 6px;
-}
-
-.legend-dot.meeting { background: #4a90d9; }
-.legend-dot.episode { background: #34c759; }
-.legend-dot.entity { background: var(--accent); }
-.legend-dot.fact { background: #ff9500; }
-
-.graph-shell .sidebar {
- gap: 8px;
- padding: 10px;
-}
-
-.graph-shell .sidebar .legend {
- display: flex;
- flex-direction: column;
- gap: 3px;
- font-size: 11px;
- padding: 0 4px;
-}
-
-.graph-shell .sidebar .legend .eyebrow {
- margin-bottom: 4px;
-}
-
-/* ── Graph controls overlay ── */
-
-.zoom-reset-btn, .pause-btn {
- font-size: 11px;
- min-height: 28px;
- padding: 0 10px;
-}
-
-.zoom-hint {
- font-size: 11px;
- color: var(--muted);
- padding: 4px 0;
-}
-
-/* ── Responsive ── */
-
-@media (max-width: 1240px) {
- .shell, .graph-shell, .dashboard-grid, .content-grid, .graph-layout, .stats-grid {
- grid-template-columns: 1fr;
- }
-
- .sidebar { order: 2; }
-
- .graph-shell { height: auto; overflow: auto; }
-}
-
-@media (max-width: 720px) {
- .shell, .graph-shell {
- padding: 10px;
- gap: 10px;
- }
-
- .sidebar, .panel { border-radius: 18px; }
-
- .search-box { grid-template-columns: 1fr; }
-
- .graph-stage { min-height: 250px; }
-
- .graph-controls { flex-wrap: wrap; }
-
- .graph-controls .search-input { min-width: 100%; }
-}
diff --git a/meeting_memory/web_demo/static/graph.html b/meeting_memory/web_demo/static_v2/graph.html
similarity index 62%
rename from meeting_memory/web_demo/static/graph.html
rename to meeting_memory/web_demo/static_v2/graph.html
index f04b537..48dae9a 100644
--- a/meeting_memory/web_demo/static/graph.html
+++ b/meeting_memory/web_demo/static_v2/graph.html
@@ -3,11 +3,12 @@
-
Neo4j Graph Explorer
-
+
图谱浏览 — Meeting Memory
+