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", ` +
+

Related

+
没有更多相关检索结果
+
+ `); return; } - relatedSearch.innerHTML = ` -
-
-

Related

-

相关检索

-
+ detailPanel.insertAdjacentHTML("beforeend", ` +
+

Related

+

相关检索

+ ${results.map((item) => ` +
+ ${h(item.title || item.kind || "结果")} +

${h(item.text || "")}

+
+ `).join("")}
- ${results.map((item) => ` -
- ${h(item.title || item.kind || "结果")} -

${h(item.text || "")}

-
- `).join("")} - `; + `); } function renderGraph(payload) { @@ -125,7 +124,6 @@ function renderGraph(payload) { if (!nodes.length) { graphSvg.innerHTML = ""; renderInspector(empty("当前没有可显示的图谱数据")); - relatedSearch.innerHTML = ""; return; } @@ -409,19 +407,29 @@ function renderGraph(payload) {
`; } renderInspector(` -
+

${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", ` +
+

Related

+
相关检索加载失败
+
+ `); + }); }); }); @@ -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", ` +
+

Related

+
相关检索加载失败
+
+ `); + }); }); }); @@ -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 +
+ +
-
+ +
- - + +
@@ -47,18 +56,18 @@
+
+
-
-
-
点击节点或关系查看详情
-
- + +
+
点击节点或关系查看详情
@@ -66,4 +75,4 @@ - + \ No newline at end of file diff --git a/meeting_memory/web_demo/static/index.html b/meeting_memory/web_demo/static_v2/index.html similarity index 87% rename from meeting_memory/web_demo/static/index.html rename to meeting_memory/web_demo/static_v2/index.html index d89a24f..8f5dfda 100644 --- a/meeting_memory/web_demo/static/index.html +++ b/meeting_memory/web_demo/static_v2/index.html @@ -3,11 +3,12 @@ - Meeting Memory Console - + 会议记忆中枢 — Meeting Memory +
+ +
+

Dashboard

@@ -40,8 +43,10 @@
+
+
@@ -49,6 +54,7 @@
+
@@ -73,6 +79,7 @@
+
+ +
@@ -124,6 +133,7 @@
+
@@ -138,4 +148,4 @@ - + \ No newline at end of file diff --git a/meeting_memory/web_demo/static_v2/styles.css b/meeting_memory/web_demo/static_v2/styles.css new file mode 100644 index 0000000..a0a0822 --- /dev/null +++ b/meeting_memory/web_demo/static_v2/styles.css @@ -0,0 +1,1259 @@ +/* ============================================================ + Meeting Memory V2 — 蓝白配色主题 + ============================================================ */ + +/* ── Design Tokens ── */ +:root { + /* 蓝色主色系 */ + --blue-50: #eff6ff; + --blue-100: #dbeafe; + --blue-200: #bfdbfe; + --blue-300: #93c5fd; + --blue-400: #60a5fa; + --blue-500: #3b82f6; + --blue-600: #2563eb; + --blue-700: #1d4ed8; + --blue-800: #1e40af; + + /* 中性色 */ + --gray-50: #f8fafc; + --gray-100: #f1f5f9; + --gray-200: #e2e8f0; + --gray-300: #cbd5e1; + --gray-400: #94a3b8; + --gray-500: #64748b; + --gray-600: #475569; + --gray-700: #334155; + --gray-800: #1e293b; + + /* 语义色 */ + --green: #059669; + --green-bg: #ecfdf5; + --amber: #d97706; + --amber-bg: #fffbeb; + --red: #dc2626; + --red-bg: #fef2f2; + --blue: #2563eb; + --blue-bg: #eff6ff; + + /* 功能变量 */ + --primary: var(--blue-600); + --primary-hover: var(--blue-700); + --primary-light: var(--blue-100); + --primary-soft: var(--blue-50); + --accent: var(--blue-500); + --bg: var(--gray-50); + --surface: #ffffff; + --surface-hover: var(--gray-50); + --border: var(--gray-200); + --border-light: var(--gray-100); + --text: var(--gray-800); + --text-secondary: var(--gray-500); + --text-muted: var(--gray-400); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -1px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -2px rgba(0, 0, 0, 0.04); + --radius-sm: 6px; + --radius: 10px; + --radius-lg: 14px; + --radius-xl: 18px; +} + +/* ── Reset ── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 14px; + line-height: 1.5; + color: var(--text); + background: var(--bg); + min-height: 100vh; +} + +a { color: inherit; text-decoration: none; } +button { cursor: pointer; font: inherit; border: none; background: none; } +input, textarea, select { font: inherit; color: inherit; } + +/* ── Layout Shell ── */ +.shell { + display: grid; + grid-template-columns: 200px minmax(0, 1fr); + gap: 0; + min-height: 100vh; +} + +/* ── Sidebar ── */ +.sidebar { + display: flex; + flex-direction: column; + background: var(--surface); + border-right: 1px solid var(--border); + padding: 20px 16px; + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; +} + +.brand { + display: flex; + align-items: center; + gap: 10px; + padding-bottom: 20px; + border-bottom: 1px solid var(--border-light); + margin-bottom: 16px; +} + +.brand-mark { + width: 36px; + height: 36px; + display: grid; + place-items: center; + border-radius: var(--radius); + background: var(--primary); + color: #fff; + font-size: 16px; + font-weight: 700; + flex-shrink: 0; +} + +.brand-kicker { + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + letter-spacing: 0.04em; + text-transform: uppercase; + margin-bottom: 1px; +} + +.brand h1 { + font-size: 15px; + font-weight: 700; + color: var(--text); + line-height: 1.2; +} + +/* ── Navigation ── */ +.nav { + display: flex; + flex-direction: column; + gap: 2px; + margin-bottom: 16px; +} + +.nav-link { + display: flex; + align-items: center; + padding: 8px 12px; + border-radius: var(--radius); + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + transition: all 0.15s ease; +} + +.nav-link:hover { + background: var(--blue-50); + color: var(--primary); +} + +.nav-link.active { + background: var(--blue-50); + color: var(--primary); + font-weight: 600; +} + +/* ── Sidebar Shortcuts ── */ +.sidebar-shortcuts { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: auto; + padding-top: 16px; + border-top: 1px solid var(--border-light); +} + +.pill-link { + display: inline-flex; + align-items: center; + padding: 5px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + background: var(--gray-50); + border: 1px solid var(--border); + transition: all 0.15s ease; +} + +.pill-link:hover { + background: var(--blue-50); + border-color: var(--blue-200); + color: var(--primary); +} + +/* ── Legend (graph page) ── */ +.legend { + margin-top: auto; + padding-top: 12px; +} + +.legend .eyebrow { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 8px; +} + +.legend > span { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--text-secondary); + padding: 3px 0; +} + +.legend-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +.legend-dot.meeting { background: #3b82f6; } +.legend-dot.episode { background: #10b981; } +.legend-dot.entity { background: #06b6d4; } +.legend-dot.fact { background: #f59e0b; } + +/* ── Main Area ── */ +.main { + display: flex; + flex-direction: column; + gap: 16px; + padding: 24px; + min-height: 100vh; +} + +/* ── Toolbar ── */ +.main-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; +} + +.main-toolbar .eyebrow { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 2px; +} + +.main-toolbar h2 { + font-size: 22px; + font-weight: 700; + color: var(--text); +} + +.main-toolbar-actions { + display: flex; + gap: 8px; +} + +/* ── Buttons ── */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + height: 34px; + padding: 0 14px; + border-radius: var(--radius); + font-size: 13px; + font-weight: 600; + color: #fff; + background: var(--primary); + transition: all 0.15s ease; + white-space: nowrap; +} + +.btn:hover { background: var(--primary-hover); } +.btn:active { transform: scale(0.97); } +.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } + +.btn.ghost { + background: var(--surface); + color: var(--text-secondary); + border: 1px solid var(--border); +} + +.btn.ghost:hover { + background: var(--gray-50); + border-color: var(--gray-300); + color: var(--text); +} + +.icon-btn { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: var(--radius); + color: var(--text-secondary); + font-size: 18px; + transition: all 0.15s ease; +} + +.icon-btn:hover { + background: var(--gray-100); + color: var(--text); +} + +/* ── Stats Grid ── */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.highlight-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 16px; + box-shadow: var(--shadow-sm); +} + +.highlight-card .eyebrow { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 4px; +} + +.highlight-card strong { + display: block; + font-size: 15px; + font-weight: 700; + color: var(--text); + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.highlight-card p:last-child { + font-size: 12px; + color: var(--text-muted); + margin: 0; +} + +/* ── Panel (Cards & Sections) ── */ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 16px; + box-shadow: var(--shadow-sm); +} + +.panel-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 8px; + margin-bottom: 12px; +} + +.panel-head .eyebrow { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 2px; +} + +.panel-head h3 { + font-size: 15px; + font-weight: 700; + color: var(--text); +} + +/* ── Unified Panel (Import / Search / Stats tabs) ── */ +.unified-panel { + display: flex; + flex-direction: column; +} + +.unified-tabs { + display: flex; + gap: 2px; + margin-bottom: 14px; + padding: 3px; + border-radius: var(--radius); + background: var(--gray-100); +} + +.unified-tab { + flex: 1; + padding: 7px 12px; + border-radius: 7px; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + background: transparent; + transition: all 0.15s ease; +} + +.unified-tab.active { + background: var(--surface); + color: var(--primary); + box-shadow: var(--shadow-sm); +} + +.unified-tab:hover:not(.active) { + color: var(--text); +} + +.unified-pane.hidden { display: none; } + +/* ── Import Form ── */ +.import-form, +.import-fieldset { + display: flex; + flex-direction: column; + gap: 10px; +} + +.import-fieldset { + border: none; + min-width: 0; +} + +.import-fieldset:disabled { opacity: 0.5; } + +.field-label { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); +} + +input[type="text"], +input[type="number"], +input[type="file"], +textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--surface); + color: var(--text); + transition: border-color 0.15s ease; + outline: none; +} + +input:focus, +textarea:focus { + border-color: var(--blue-400); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +textarea { + min-height: 120px; + resize: vertical; +} + +.check-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--text-secondary); + cursor: pointer; +} + +.check-row input { + accent-color: var(--primary); +} + +/* ── Status Box ── */ +.status-box { + margin-top: 6px; + padding: 10px 12px; + border-radius: var(--radius); + background: var(--gray-50); + border: 1px solid var(--border); + font-size: 12px; + color: var(--text-secondary); +} + +.status-box[data-kind="error"] { + background: var(--red-bg); + border-color: #fecaca; + color: var(--red); +} + +.status-box[data-kind="success"] { + background: var(--green-bg); + border-color: #a7f3d0; + color: var(--green); +} + +/* ── Progress List ── */ +.progress-list, +.search-results, +.mini-stats, +.card-list, +.list-stack { + display: flex; + flex-direction: column; + gap: 6px; +} + +.empty-state { + padding: 20px 16px; + text-align: center; + color: var(--text-muted); + font-size: 13px; + border: 1px dashed var(--border); + border-radius: var(--radius); +} + +/* ── Progress Item ── */ +.progress-item { + display: grid; + grid-template-columns: 24px 1fr; + gap: 8px; + align-items: start; + padding: 10px 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--gray-50); + font-size: 13px; +} + +.progress-index { + width: 24px; + height: 24px; + display: grid; + place-items: center; + border-radius: 999px; + background: var(--blue-100); + color: var(--primary); + font-size: 11px; + font-weight: 700; +} + +/* ── Mini Stat ── */ +.mini-stat { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--gray-50); +} + +.ms-icon { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: var(--radius); + font-size: 14px; + background: var(--blue-50); + color: var(--primary); + flex-shrink: 0; +} + +.ms-body strong { + display: block; + font-size: 15px; + font-weight: 700; + line-height: 1.2; +} + +.ms-body p { + margin: 0; + font-size: 11px; + color: var(--text-muted); +} + +/* ── Meeting Card ── */ +.meeting-card { + display: flex; + gap: 12px; + padding: 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--surface); + cursor: pointer; + transition: all 0.15s ease; +} + +.meeting-card:hover { + border-color: var(--blue-200); + box-shadow: var(--shadow-sm); +} + +.mc-date { + flex-shrink: 0; + width: 44px; + height: 44px; + display: grid; + place-items: center; + border-radius: var(--radius-sm); + background: var(--blue-50); + color: var(--primary); + font-size: 10px; + font-weight: 700; + text-align: center; + line-height: 1.2; +} + +.mc-body { + flex: 1; + min-width: 0; +} + +.mc-body h4 { + font-size: 13px; + font-weight: 600; + margin-bottom: 4px; + color: var(--text); +} + +.mc-body p { + font-size: 12px; + color: var(--text-secondary); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: 1.4; +} + +/* ── List Item (action items) ── */ +.list-item { + display: flex; + gap: 10px; + padding: 10px 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--surface); +} + +.li-priority { + flex-shrink: 0; + width: 4px; + border-radius: 2px; +} + +.li-body { + flex: 1; + min-width: 0; +} + +.li-body strong { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 2px; +} + +.li-body p { + font-size: 12px; + color: var(--text-secondary); + margin: 0 0 4px; +} + +/* ── Metric Card ── */ +.metric-card { + padding: 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--surface); +} + +.mc-head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.mc-head strong { + font-size: 13px; + font-weight: 600; +} + +.mc-value { + font-size: 16px; + font-weight: 700; + color: var(--primary); +} + +.metric-card p { + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 8px; +} + +.mc-bar-track { + height: 4px; + border-radius: 2px; + background: var(--gray-100); + overflow: hidden; +} + +.mc-bar-fill { + height: 100%; + border-radius: 2px; + background: var(--primary); + transition: width 0.3s ease; +} + +/* ── Series Card ── */ +.series-card { + display: flex; + gap: 10px; + align-items: center; + padding: 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--surface); +} + +.sc-count { + flex-shrink: 0; + width: 36px; + height: 36px; + display: grid; + place-items: center; + border-radius: var(--radius-sm); + background: var(--blue-50); + color: var(--primary); + font-size: 13px; + font-weight: 700; +} + +.sc-body strong { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 2px; +} + +.sc-body p { + font-size: 12px; + color: var(--text-secondary); + margin: 0; +} + +/* ── Search Box ── */ +.search-box { + display: flex; + gap: 8px; +} + +.search-box input { + flex: 1; +} + +/* ── Search Results ── */ +.result-card { + padding: 12px; + border: 1px solid var(--border-light); + border-radius: var(--radius); + background: var(--surface); +} + +.result-card:hover { + border-color: var(--blue-200); +} + +.rc-kind { + display: inline-block; + padding: 1px 7px; + border-radius: 4px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + background: var(--blue-50); + color: var(--primary); + margin-bottom: 4px; +} + +.result-card strong { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 2px; +} + +.result-card p { + font-size: 12px; + color: var(--text-secondary); + margin: 0; +} + +/* ── Chips ── */ +.chip { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + background: var(--blue-50); + color: var(--primary); +} + +.chip.status-done, +.chip.status-completed { + background: var(--green-bg); + color: var(--green); +} + +.chip.status-pending, +.chip.status-todo { + background: var(--amber-bg); + color: var(--amber); +} + +.chip.status-in_progress, +.chip.status-active { + background: var(--blue-bg); + color: var(--blue); +} + +.chip.status-blocked { + background: var(--red-bg); + color: var(--red); +} + +/* ── Content Grid ── */ +.content-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} + +/* ── Dialog / Modal ── */ +.detail-modal { + width: min(700px, calc(100vw - 32px)); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 0; + background: var(--surface); + box-shadow: var(--shadow-lg); +} + +.detail-modal::backdrop { + background: rgba(30, 41, 59, 0.3); +} + +.dialog-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + padding: 20px 20px 8px; +} + +.dialog-head .eyebrow { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 2px; +} + +.dialog-head h3 { + font-size: 17px; + font-weight: 700; +} + +.dialog-meta { + padding: 0 20px 8px; + font-size: 12px; + color: var(--text-secondary); +} + +.dialog-content { + margin: 0; + padding: 20px; + white-space: pre-wrap; + font-family: "SF Mono", "Consolas", "Courier New", monospace; + font-size: 13px; + line-height: 1.6; + color: var(--text-secondary); + max-height: 55vh; + overflow-y: auto; +} + +/* ── Graph Page ── */ + +.graph-shell { + height: 100vh; + overflow: hidden; +} + +.graph-shell .main { + padding: 12px; + gap: 10px; +} + +.graph-shell .sidebar { + padding: 16px 14px; +} + +/* ── Graph Toolbar ── */ +.graph-toolbar { + padding: 12px; +} + +.graph-controls { + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; +} + +.graph-controls .search-input { + flex: 1; + min-width: 180px; + height: 34px; + padding: 6px 10px; + font-size: 13px; +} + +.graph-controls label.field-label { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + white-space: nowrap; +} + +.graph-controls label.field-label input { + width: 48px; + height: 30px; + padding: 4px 6px; + font-size: 12px; +} + +.graph-controls .btn { + height: 34px; + font-size: 12px; +} + +.graph-toolbar-row { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; +} + +.graph-type-filter { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 12px; +} + +.graph-type-filter label { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-secondary); + cursor: pointer; + user-select: none; +} + +.graph-type-filter label input { + accent-color: var(--primary); +} + +.graph-actions { + display: flex; + align-items: center; + gap: 10px; + font-size: 12px; + color: var(--text-secondary); +} + +.graph-meta { + font-size: 12px; + color: var(--text-muted); +} + +/* ── Graph Layout ── */ +.graph-layout { + display: grid; + grid-template-columns: 1fr 280px; + gap: 10px; + 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: var(--gray-50); + border-radius: var(--radius); +} + +#graphSvg { + width: 100%; + height: 100%; + display: block; +} + +/* ── Detail Panel (graph page) ── */ +.detail-panel { + display: flex; + flex-direction: column; + gap: 0; + overflow-y: auto; + padding: 0; +} + +/* ── Detail Section (sub-container inside detail panel) ── */ +.detail-section { + padding: 14px 16px; + border-bottom: 1px solid var(--border-light); + word-break: break-all; +} + +.detail-section:last-child { + border-bottom: none; +} + +.detail-section .eyebrow { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 3px; +} + +.detail-section h3 { + font-size: 15px; + font-weight: 700; + color: var(--text); + margin-bottom: 6px; +} + +.detail-section strong { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 4px; + word-break: break-word; +} + +.detail-section p { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.5; +} + +.detail-section .chip-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} + +.detail-section .result-card { + margin-bottom: 8px; +} + +.detail-section .result-card:last-child { + margin-bottom: 0; +} + +.detail-section .empty-state { + margin: 4px 0; +} + +/* ── Graph SVG elements ── */ + +.graph-node { cursor: pointer; } + +.graph-node circle { + stroke: #fff; + stroke-width: 2; + transition: opacity 0.15s; +} + +.graph-node--meeting circle { fill: #3b82f6; } +.graph-node--episode circle { fill: #10b981; } +.graph-node--entity circle { fill: #06b6d4; } +.graph-node--fact circle { fill: #f59e0b; } + +.graph-node:hover circle { opacity: 0.8; } + +.graph-node text { + font-size: 11px; + fill: var(--text); + pointer-events: none; + user-select: none; +} + +.graph-edge { + stroke: var(--gray-300); + stroke-width: 1.5; + cursor: pointer; + transition: stroke 0.15s; +} + +.edge-wrap:hover .graph-edge { + stroke: var(--gray-400); +} + +.graph-edge.active { + stroke: var(--primary); + stroke-width: 2; +} + +.edge-wrap text { + font-size: 10px; + fill: var(--text-muted); + pointer-events: none; + user-select: none; +} + +/* ── Zoom controls ── */ +.zoom-reset-btn, +.pause-btn { + font-size: 12px; + height: 28px; + padding: 0 10px; +} + +.zoom-hint { + font-size: 11px; + color: var(--text-muted); + padding: 4px 0; +} + +/* ── Responsive ── */ + +@media (max-width: 1100px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .content-grid { + grid-template-columns: 1fr; + } + + .graph-layout { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .shell { + grid-template-columns: 1fr; + } + + .sidebar { + position: static; + height: auto; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 14px 16px; + flex-direction: row; + align-items: center; + gap: 12px; + } + + .sidebar .brand { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; + flex-shrink: 0; + } + + .sidebar .nav { + flex-direction: row; + margin-bottom: 0; + flex: 1; + } + + .sidebar .sidebar-shortcuts, + .sidebar .legend { + display: none; + } + + .main { + padding: 14px; + } + + .stats-grid { + grid-template-columns: 1fr 1fr; + } + + .graph-shell { + height: auto; + } + + .graph-shell .sidebar { + flex-wrap: wrap; + } + + .graph-controls { + flex-direction: column; + align-items: stretch; + } + + .graph-type-filter { + gap: 6px; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .main-toolbar { + flex-direction: column; + align-items: flex-start; + } + + .search-box { + flex-direction: column; + } +} \ No newline at end of file