UNPKG

@sofianedjerbi/knowledge-tree-mcp

Version:

MCP server for hierarchical project knowledge management

1,488 lines (1,313 loc) 136 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Knowledge Dashboard</title> <!-- Chart.js for data visualization --> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <!-- Vis.js for network graph --> <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> <!-- Markdown parsing --> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <!-- Code highlighting --> <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-dark.min.css" rel="stylesheet" /> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-typescript.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script> <!-- Lucide Icons --> <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-bg: #0f1419; --secondary-bg: #1a1f2e; --card-bg: #252a3a; --accent-bg: #2d3348; --primary-text: #ffffff; --secondary-text: #a0a9c0; --muted-text: #6b7280; --accent-blue: #3b82f6; --accent-green: #10b981; --accent-yellow: #f59e0b; --accent-red: #ef4444; --accent-purple: #8b5cf6; --border-color: #374151; --hover-bg: #374151; --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; background: var(--primary-bg); color: var(--primary-text); overflow-x: hidden; } /* Dashboard Layout */ .dashboard { display: grid; grid-template-columns: 280px 1fr; grid-template-rows: 60px 1fr; grid-template-areas: "sidebar header" "sidebar main"; height: 100vh; } /* Header */ .header { grid-area: header; background: var(--secondary-bg); border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; padding: 0 24px; box-shadow: var(--shadow); } .header-title { font-size: 18px; font-weight: 600; color: var(--primary-text); } .connection-status { display: flex; align-items: center; gap: 8px; font-size: 14px; } .status-indicator { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-red); transition: background-color 0.3s; } .status-indicator.connected { background: var(--accent-green); } /* Sidebar */ .sidebar { grid-area: sidebar; background: var(--secondary-bg); border-right: 1px solid var(--border-color); padding: 24px 0; overflow-y: auto; } .sidebar-brand { padding: 0 24px 32px; border-bottom: 1px solid var(--border-color); margin-bottom: 24px; } .brand-title { font-size: 20px; font-weight: 700; color: var(--primary-text); } .brand-subtitle { font-size: 12px; color: var(--muted-text); margin-top: 4px; } .nav-menu { list-style: none; } .nav-item { margin-bottom: 8px; } .nav-link { display: flex; align-items: center; padding: 12px 24px; color: var(--secondary-text); text-decoration: none; transition: all 0.2s; border-left: 3px solid transparent; } .nav-link:hover, .nav-link.active { background: var(--accent-bg); color: var(--primary-text); border-left-color: var(--accent-blue); } .nav-icon { width: 20px; height: 20px; margin-right: 12px; opacity: 0.7; flex-shrink: 0; } /* Main Content */ .main-content { grid-area: main; padding: 24px; overflow-y: auto; background: var(--primary-bg); position: relative; z-index: 1; height: calc(100vh - 60px); /* Full height minus header */ } /* Page Views */ .page-view { display: none; width: 100%; min-height: 500px; /* Ensure minimum height */ } .page-view.active { display: block !important; visibility: visible !important; opacity: 1 !important; min-height: 500px !important; } /* Cards */ .card { background: var(--card-bg); border-radius: 12px; border: 1px solid var(--border-color); box-shadow: var(--shadow); overflow: hidden; } .card-header { padding: 20px 24px; border-bottom: 1px solid var(--border-color); background: var(--secondary-bg); } .card-title { font-size: 18px; font-weight: 600; color: var(--primary-text); } .card-subtitle { font-size: 14px; color: var(--muted-text); margin-top: 4px; } .card-body { padding: 24px; } /* Grid Layout */ .grid { display: grid; gap: 24px; width: 100%; min-height: 100%; } .grid-cols-1 { grid-template-columns: 1fr; } .grid-cols-2 { grid-template-columns: repeat(2, 1fr); } .grid-cols-3 { grid-template-columns: repeat(3, 1fr); } .grid-cols-4 { grid-template-columns: repeat(4, 1fr); } /* Metric Items */ .metric-item { text-align: center; padding: 20px; background: var(--accent-bg); border-radius: 8px; transition: transform 0.2s, box-shadow 0.2s; } .metric-item:hover { transform: translateY(-1px); box-shadow: var(--shadow); } .metric-value { font-size: 32px; font-weight: 700; margin-bottom: 8px; } .metric-label { font-size: 14px; color: var(--muted-text); margin-bottom: 8px; } .metric-change { font-size: 12px; padding: 4px 8px; border-radius: 6px; display: inline-block; } .metric-change.positive { background: rgba(16, 185, 129, 0.1); color: var(--accent-green); } .metric-change.negative { background: rgba(239, 68, 68, 0.1); color: var(--accent-red); } /* Tag Cloud Panel */ #tagCloudPanel { min-height: 180px; padding: 20px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: flex-start; } .tag-cloud-item { display: inline-block; padding: 6px 12px; background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 8px; font-size: 13px; font-weight: 500; transition: all 0.2s; cursor: pointer; position: relative; } .tag-cloud-item:hover { background: rgba(59, 130, 246, 0.2); border-color: rgba(59, 130, 246, 0.4); transform: translateY(-1px); box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2); } .tag-count { font-size: 11px; color: var(--muted-text); margin-left: 6px; font-weight: 600; } /* Different tag sizes based on frequency */ .tag-size-1 { font-size: 11px; } .tag-size-2 { font-size: 12px; } .tag-size-3 { font-size: 13px; } .tag-size-4 { font-size: 14px; } .tag-size-5 { font-size: 15px; font-weight: 600; } /* Chart Container */ .chart-container { position: relative; height: 300px; margin: 16px 0; } .chart-container.large { height: 400px; } /* Network Graph */ .network-container { height: calc(100vh - 240px); border-radius: 8px; overflow: hidden; background: var(--accent-bg); margin-top: 12px; } /* Tab group styling */ .tab-group { display: flex; background: var(--secondary-bg); border-radius: 8px; padding: 4px; gap: 4px; } .tab-button { padding: 8px 16px; border: none; background: transparent; color: var(--secondary-text); border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center; white-space: nowrap; } .tab-button:hover { background: var(--accent-bg); color: var(--primary-text); } .tab-button.active { background: var(--accent-blue); color: white; } /* Graph and Tree page specific adjustments */ #graph-page .network-container, #tree-page .network-container { height: calc(100vh - 264px); /* Account for the 12px top margin + 12px bottom space */ } .network-fullscreen { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; z-index: 9999 !important; border-radius: 0 !important; background: var(--primary-bg) !important; } .network-fullscreen-controls { position: absolute; top: 20px; left: 20px; right: 20px; z-index: 10000; display: flex; gap: 12px; align-items: center; background: rgba(30, 41, 59, 0.95); padding: 12px 16px; border-radius: 8px; backdrop-filter: blur(10px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); } .network-fullscreen-controls button { background: var(--card-bg); color: var(--primary-text); border: 1px solid var(--border-color); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; transition: background 0.2s; } .network-fullscreen-controls button:hover { background: var(--accent-bg); } /* Search */ .search-container { margin-bottom: 24px; } .search-input { width: 100%; padding: 16px 20px; background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 12px; color: var(--primary-text); font-size: 16px; transition: border-color 0.2s, box-shadow 0.2s; } .search-input:focus { outline: none; border-color: var(--accent-blue); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .search-input::placeholder { color: var(--muted-text); } /* Priority Colors */ .priority-critical { color: var(--accent-red); } .priority-required { color: var(--accent-yellow); } .priority-common { color: var(--accent-green); } .priority-edge-case { color: var(--accent-purple); } /* Priority Filter */ .priority-filter { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; width: 100%; justify-content: space-between; } .priority-filter label { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; font-weight: 500; padding: 8px 16px; border-radius: 8px; transition: background 0.2s; white-space: nowrap; flex: 1; justify-content: center; background: rgba(255, 255, 255, 0.05); border: 1px solid transparent; } .priority-filter label:hover { background: rgba(255, 255, 255, 0.1); } .priority-filter label:has(input:checked) { background: rgba(59, 130, 246, 0.2); border-color: rgba(59, 130, 246, 0.3); } .priority-filter input[type="checkbox"] { cursor: pointer; transform: scale(1.2); } /* Tag Cloud Enhanced */ .tag-cloud { margin: 15px 0; } .tag-cloud-item { display: inline-block; padding: 6px 12px; margin: 4px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 20px; font-size: 12px; color: #ccc; transition: all 0.2s; cursor: pointer; position: relative; } .tag-cloud-item:hover { background: rgba(59, 130, 246, 0.2); border-color: rgba(59, 130, 246, 0.4); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); color: var(--accent-blue); } .tag-count { font-size: 10px; color: #888; margin-left: 4px; font-weight: 600; } /* Different tag sizes based on frequency */ .tag-size-1 { font-size: 11px; opacity: 0.7; } .tag-size-2 { font-size: 12px; opacity: 0.8; } .tag-size-3 { font-size: 13px; opacity: 0.9; } .tag-size-4 { font-size: 14px; opacity: 1; } .tag-size-5 { font-size: 15px; opacity: 1; font-weight: 500; } /* Tree view styles - CLI-like */ .tree-view { font-family: 'Monaco', 'Consolas', 'Courier New', monospace; font-size: 13px; line-height: 1; color: var(--primary-text); background: var(--secondary-bg); padding: 16px; border-radius: 8px; overflow-x: auto; white-space: pre; } .tree-line { font-family: 'Monaco', 'Consolas', 'Courier New', monospace; display: block; height: 18px; line-height: 18px; white-space: nowrap; position: relative; padding: 0; margin: 0; border-radius: 3px; transition: all 0.2s ease; } .tree-line.clickable { cursor: pointer; } .tree-line:hover { background: rgba(59, 130, 246, 0.15); box-shadow: inset 3px 0 0 var(--accent-blue); } .tree-line.clickable:hover .tree-label { color: var(--accent-blue); font-weight: 500; } .tree-line.clickable:hover .tree-icon { transform: scale(1.1); } /* Highlight child items on hover */ .tree-child-highlight { background: rgba(16, 185, 129, 0.08); box-shadow: inset 2px 0 0 rgba(16, 185, 129, 0.5); } .tree-child-highlight .tree-branch, .tree-child-highlight .tree-indent { color: var(--accent-green); opacity: 0.7; } .tree-child-highlight .tree-label { color: var(--accent-green); } .tree-indent { color: var(--secondary-text); opacity: 0.4; user-select: none; white-space: pre; } .tree-branch { color: var(--secondary-text); opacity: 0.4; user-select: none; white-space: pre; } .tree-icon { width: 14px; height: 14px; margin: 0 2px 0 1px; flex-shrink: 0; vertical-align: text-bottom; transition: transform 0.2s ease; display: inline-block; } .tree-folder-icon { color: var(--accent-yellow); } .tree-file-icon { color: var(--secondary-text); } .tree-label { color: var(--primary-text); } .tree-folder-label { font-weight: 600; color: var(--accent-blue); } .tree-count { font-size: 10px; color: var(--secondary-text); opacity: 0.6; margin-left: 2px; padding: 0 4px; border-radius: 8px; background: var(--accent-bg); display: inline-block; } /* Highlight folders with many items */ .tree-count-high { background: rgba(245, 158, 11, 0.2); color: var(--accent-yellow); font-weight: 600; } .tree-priority { font-size: 10px; padding: 0 4px; border-radius: 3px; margin-left: 2px; display: inline-block; } .tree-node { margin: 0; padding: 0; } .tree-children { margin: 0; padding: 0; } .tree-node.collapsed .tree-children { display: none; } .tree-toggle { display: inline-block; width: 10px; text-align: center; color: var(--secondary-text); opacity: 0.6; margin: 0; padding: 0; font-size: 11px; cursor: pointer; user-select: none; vertical-align: baseline; } /* Simple tree view styles */ .simple-tree { font-family: 'Monaco', 'Consolas', 'Courier New', monospace; font-size: 13px; background: var(--secondary-bg); padding: 16px; border-radius: 8px; color: var(--primary-text); height: 100%; overflow: auto; } .tree-folder, .tree-file { display: block; padding: 4px 0; cursor: pointer; white-space: nowrap; } .tree-folder:hover, .tree-file:hover { background: rgba(59, 130, 246, 0.15); } .tree-toggle { display: inline-block; width: 12px; color: var(--secondary-text); cursor: pointer; user-select: none; } .tree-icon { width: 16px; height: 16px; margin: 0 4px; vertical-align: middle; } .tree-label { color: var(--primary-text); } .tree-folder .tree-label { color: var(--accent-blue); font-weight: 600; } .tree-count { font-size: 11px; color: var(--secondary-text); opacity: 0.6; margin-left: 4px; padding: 1px 6px; border-radius: 10px; background: var(--accent-bg); } .tree-priority { font-size: 11px; padding: 1px 6px; border-radius: 3px; margin-left: 8px; } .tree-children { margin-left: 0; } .tree-item { margin: 0; } /* Priority Legend */ .priority-legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 16px 0; } .priority-item { display: flex; align-items: center; gap: 8px; font-size: 12px; } .priority-dot { width: 12px; height: 12px; border-radius: 50%; } /* Table */ .table { width: 100%; border-collapse: collapse; } .table th, .table td { padding: 12px 16px; text-align: left; border-bottom: 1px solid var(--border-color); } .table th { background: var(--secondary-bg); font-weight: 600; color: var(--primary-text); font-size: 14px; } .table td { color: var(--secondary-text); font-size: 14px; } .table tbody tr:hover { background: var(--accent-bg); } /* Loading */ .loading { display: flex; align-items: center; justify-content: center; padding: 40px; color: var(--muted-text); } .spinner { width: 20px; height: 20px; border: 2px solid var(--border-color); border-top: 2px solid var(--accent-blue); border-radius: 50%; animation: spin 1s linear infinite; margin-right: 12px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Responsive */ @media (max-width: 1024px) { .dashboard { grid-template-columns: 1fr; grid-template-areas: "header" "main"; } .sidebar { display: none; } } @media (max-width: 768px) { .grid-cols-2, .grid-cols-3, .grid-cols-4 { grid-template-columns: 1fr; } .main-content { padding: 16px; } .metric-item { padding: 16px; } .metric-value { font-size: 24px; } } /* Entry Details Modal */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; } .modal.active { display: flex; } .modal-content { background: var(--card-bg); border-radius: 12px; max-width: 800px; max-height: 90vh; overflow-y: auto; margin: 20px; border: 1px solid var(--border-color); } .modal-header { padding: 20px 24px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; } .modal-close { background: none; border: none; color: var(--muted-text); font-size: 24px; cursor: pointer; padding: 4px; border-radius: 4px; transition: background-color 0.2s; } .modal-close:hover { background: var(--hover-bg); } .modal-body { padding: 24px; } /* Tags */ .tag { display: inline-block; padding: 4px 8px; background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); border-radius: 6px; font-size: 12px; margin: 2px 4px 2px 0; } /* Empty State */ .empty-state { text-align: center; padding: 60px 20px; color: var(--muted-text); } .empty-state-icon { margin-bottom: 16px; color: var(--muted-text); display: flex; justify-content: center; align-items: center; } .empty-state-title { font-size: 18px; font-weight: 600; margin-bottom: 8px; } .empty-state-description { font-size: 14px; } /* Tag Cloud */ .tag-cloud { padding: 20px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: flex-start; } .tag-cloud-item { display: inline-flex; align-items: center; gap: 4px; padding: 6px 14px; background: var(--secondary-bg); color: var(--secondary-text); border: 1px solid var(--border-color); border-radius: 16px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; text-decoration: none; white-space: nowrap; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; } .tag-cloud-item:hover { background: var(--accent-bg); color: var(--accent-blue); border-color: var(--accent-blue); transform: translateY(-1px); } .tag-cloud-item .tag-count { font-size: 0.8em; opacity: 0.6; font-weight: 400; padding: 2px 6px; background: rgba(0, 0, 0, 0.1); border-radius: 10px; min-width: 24px; text-align: center; } /* Tag size variations - clean and minimal */ .tag-cloud-item.tag-size-1 { font-size: 12px; opacity: 0.7; } .tag-cloud-item.tag-size-2 { font-size: 13px; opacity: 0.8; } .tag-cloud-item.tag-size-3 { font-size: 14px; opacity: 0.9; } .tag-cloud-item.tag-size-4 { font-size: 15px; font-weight: 600; opacity: 1; } .tag-cloud-item.tag-size-5 { font-size: 16px; font-weight: 600; background: var(--accent-bg); color: var(--accent-blue); border-color: var(--accent-blue); } .tag-count { display: inline-block; margin-left: 6px; padding: 2px 6px; background: rgba(59, 130, 246, 0.2); border-radius: 10px; font-size: 10px; font-weight: 600; color: var(--accent-blue); } .tag-cloud-empty { color: var(--muted-text); font-style: italic; padding: 40px 20px; } </style> </head> <body> <div class="dashboard"> <!-- Header --> <div class="header"> <h1 class="header-title">Knowledge Dashboard</h1> <div class="connection-status"> <div class="status-indicator" id="statusIndicator"></div> <span id="statusText">Connecting...</span> </div> </div> <!-- Sidebar --> <div class="sidebar"> <div class="sidebar-brand"> <div class="brand-title"> <i data-lucide="book-open" style="width: 24px; height: 24px; margin-right: 8px; vertical-align: middle;"></i> Knowledge </div> <div class="brand-subtitle">MCP Dashboard</div> </div> <nav> <ul class="nav-menu"> <li class="nav-item"> <a href="#" class="nav-link active" data-page="overview"> <i data-lucide="layout-dashboard" class="nav-icon"></i> Overview </a> </li> <li class="nav-item"> <a href="#" class="nav-link" data-page="graph"> <i data-lucide="network" class="nav-icon"></i> Connections </a> </li> <li class="nav-item"> <a href="#" class="nav-link" data-page="tree"> <i data-lucide="folder-tree" class="nav-icon"></i> Explorer </a> </li> <li class="nav-item"> <a href="#" class="nav-link" data-page="analytics"> <i data-lucide="bar-chart-3" class="nav-icon"></i> Analytics </a> </li> <li class="nav-item"> <a href="#" class="nav-link" data-page="recent"> <i data-lucide="clock" class="nav-icon"></i> Recent Activity </a> </li> <li class="nav-item"> <a href="#" class="nav-link" data-page="search"> <i data-lucide="search" class="nav-icon"></i> Search </a> </li> </ul> </nav> </div> <!-- Main Content --> <div class="main-content"> <!-- Overview Page --> <div class="page-view active" id="overview-page"> <!-- Top Row: Activity Trend + Tag Cloud Panel --> <div class="grid grid-cols-2" style="margin-bottom: 32px; gap: 24px;"> <!-- Activity Trend Panel --> <div class="card"> <div class="card-header"> <div class="card-title">Activity Trend</div> <div class="card-subtitle">Usage over the last 30 days</div> </div> <div class="card-body"> <div class="chart-container"> <canvas id="activityChart"></canvas> </div> </div> </div> <!-- Tag Cloud Panel --> <div class="card"> <div class="card-header"> <div class="card-title">Popular Tags</div> <div class="card-subtitle">Most frequently used tags</div> </div> <div class="card-body"> <div id="tagCloudPanel"> <div class="loading"> <div class="spinner"></div> Loading tags... </div> </div> </div> </div> </div> <!-- Charts Row with Key Metrics --> <div class="grid grid-cols-3" style="margin-bottom: 32px;"> <div class="card"> <div class="card-header"> <div class="card-title">Priority Distribution</div> <div class="card-subtitle">Knowledge entries by priority level</div> </div> <div class="card-body"> <div class="chart-container"> <canvas id="priorityChart"></canvas> </div> </div> </div> <div class="card"> <div class="card-header"> <div class="card-title">Key Metrics</div> <div class="card-subtitle">Knowledge base statistics</div> </div> <div class="card-body"> <div class="grid grid-cols-2" style="gap: 16px;"> <div class="metric-item"> <div class="metric-value priority-critical" id="totalEntries">-</div> <div class="metric-label">Total Entries</div> <div class="metric-change positive" id="entriesChange">+0 today</div> </div> <div class="metric-item"> <div class="metric-value priority-required" id="totalConnections">-</div> <div class="metric-label">Connected Entries</div> <div class="metric-change positive" id="connectionsChange">+0 today</div> </div> <div class="metric-item"> <div class="metric-value priority-common" id="totalSearches">-</div> <div class="metric-label">Searches (30d)</div> <div class="metric-change positive" id="searchesChange">+0 today</div> </div> <div class="metric-item"> <div class="metric-value priority-edge-case" id="totalActivity">-</div> <div class="metric-label">Activity (30d)</div> <div class="metric-change positive" id="activityChange">+0 today</div> </div> </div> </div> </div> <div class="card"> <div class="card-header"> <div class="card-title">Activity Types</div> <div class="card-subtitle">Breakdown by activity type</div> </div> <div class="card-body"> <div class="chart-container"> <canvas id="activityRingChart"></canvas> </div> </div> </div> </div> <!-- Recent Activity Table --> <div class="card"> <div class="card-header"> <div class="card-title">Recent Activity</div> <div class="card-subtitle">Latest knowledge base changes</div> </div> <div class="card-body"> <div id="recentActivityTable"> <div class="loading"> <div class="spinner"></div> Loading recent activity... </div> </div> </div> </div> </div> <!-- Visualizer Page --> <div class="page-view" id="graph-page"> <div class="card"> <div class="card-header"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div> <div class="card-title">Knowledge Connections</div> <div class="card-subtitle">Interactive network graph showing relationships between entries</div> </div> <div style="display: flex; gap: 8px;"> <button id="physicsToggle" onclick="togglePhysics()" style="background: var(--accent-red); color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 4px;" title="Toggle continuous physics simulation"> <i data-lucide="zap" style="width: 16px; height: 16px;"></i> <span id="physicsToggleText">Disable Physics</span> </button> <button onclick="relayoutGraph()" style="background: var(--accent-green); color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 4px;"> <i data-lucide="refresh-cw" style="width: 16px; height: 16px;"></i> Re-layout </button> <button onclick="toggleFullscreen(event)" style="background: var(--accent-blue); color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 4px;"> <i data-lucide="maximize" style="width: 16px; height: 16px;"></i> Fullscreen </button> </div> </div> </div> <div class="card-body" style="padding: 12px;"> <div class="priority-legend"> <div class="priority-item"> <div class="priority-dot" style="background: var(--accent-red);"></div> <span>Critical</span> </div> <div class="priority-item"> <div class="priority-dot" style="background: var(--accent-yellow);"></div> <span>Required</span> </div> <div class="priority-item"> <div class="priority-dot" style="background: var(--accent-green);"></div> <span>Common</span> </div> <div class="priority-item"> <div class="priority-dot" style="background: var(--accent-purple);"></div> <span>Edge Case</span> </div> </div> <div class="network-container" id="networkContainer"></div> </div> </div> </div> <!-- Tree Page --> <div class="page-view" id="tree-page"> <div class="card"> <div class="card-header"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div> <div class="card-title">Knowledge Explorer</div> <div class="card-subtitle">Browse knowledge entries in a hierarchical folder structure</div> </div> <button onclick="toggleFullscreen(event)" style="background: var(--accent-blue); color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 12px; display: flex; align-items: center; gap: 4px;"> <i data-lucide="maximize" style="width: 16px; height: 16px;"></i> Fullscreen </button> </div> </div> <div class="card-body" style="padding: 12px;"> <div class="network-container"> <div id="fancytree" style="height: 100%; overflow: auto;"></div> </div> </div> </div> </div> <!-- Analytics Page --> <div class="page-view" id="analytics-page"> <div class="grid grid-cols-2"> <div class="card"> <div class="card-header"> <div class="card-title">Search Analytics</div> <div class="card-subtitle">Search patterns and popular queries</div> </div> <div class="card-body" id="searchAnalytics"> <div class="loading"> <div class="spinner"></div> Loading search analytics... </div> </div> </div> <div class="card"> <div class="card-header"> <div class="card-title">Tool Usage</div> <div class="card-subtitle">MCP tool call statistics</div> </div> <div class="card-body" id="toolAnalytics"> <div class="loading"> <div class="spinner"></div> Loading tool analytics... </div> </div> </div> </div> <div class="card" style="margin-top: 24px;"> <div class="card-header"> <div class="card-title">Usage Patterns</div> <div class="card-subtitle">Hourly activity distribution</div> </div> <div class="card-body"> <div class="chart-container large"> <canvas id="hourlyActivityChart"></canvas> </div> </div> </div> </div> <!-- Recent Activity Page --> <div class="page-view" id="recent-page"> <div class="card"> <div class="card-header"> <div class="card-title">Recent Changes</div> <div class="card-subtitle">Latest updates to the knowledge base</div> </div> <div class="card-body" id="recentChanges"> <div class="loading"> <div class="spinner"></div> Loading recent changes... </div> </div> </div> </div> <!-- Search Page --> <div class="page-view" id="search-page"> <div class="search-container"> <input type="text" class="search-input" id="searchInput" placeholder="Search knowledge entries... (* = wildcard, ? = single char)"> <!-- Priority Filters --> <div class="priority-filter" style="margin-top: 16px;"> <label class="priority-critical"> <input type="checkbox" name="priority" value="CRITICAL" checked> CRITICAL </label> <label class="priority-required"> <input type="checkbox" name="priority" value="REQUIRED" checked> REQUIRED </label> <label class="priority-common"> <input type="checkbox" name="priority" value="COMMON" checked> COMMON </label> <label class="priority-edge-case"> <input type="checkbox" name="priority" value="EDGE-CASE" checked> EDGE-CASE </label> </div> </div> <div class="card"> <div class="card-header"> <div class="card-title">Search Results</div> <div class="card-subtitle" id="searchResultsSubtitle">Enter a search query to find knowledge entries</div> </div> <div class="card-body" id="searchResults"> <div class="empty-state"> <div class="empty-state-icon"> <i data-lucide="search" style="width: 48px; height: 48px;"></i> </div> <div class="empty-state-title">Search Knowledge Base</div> <div class="empty-state-description">Use the search box above to find specific knowledge entries</div> </div> </div> </div> </div> </div> <!-- Closing main-content --> </div> <!-- Closing dashboard --> </div> <!-- Entry Details Modal --> <div class="modal" id="entryModal"> <div class="modal-content"> <div class="modal-header"> <h3 id="modalTitle">Entry Details</h3> <button class="modal-close" onclick="closeModal()">&times;</button> </div> <div class="modal-body" id="modalContent"> <!-- Entry details will be loaded here --> </div> </div> </div> <script> // Global variables let ws; let knowledgeMap = new Map(); let currentStats = null; let currentAnalytics = null; let network = null; let charts = {}; // Priority colors const priorityColors = { 'CRITICAL': '#ef4444', // red 'REQUIRED': '#f59e0b', // yellow 'COMMON': '#10b981', // green 'EDGE-CASE': '#8b5cf6' // purple }; // Initialize dashboard document.addEventListener('DOMContentLoaded', function() { // Initialize Lucide icons lucide.createIcons(); setupNavigation(); setupSearch(); connectWebSocket(); // Load initial data after WebSocket connects setTimeout(() => { loadOverviewData(); }, 500); }); // Navigation function setupNavigation() { const navLinks = document.querySelectorAll('.nav-link'); const pageViews = document.querySelectorAll('.page-view'); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); // Update active nav link navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); // Show corresponding page const targetPage = link.dataset.page; // Remove active from all pages pageViews.forEach(page => { page.classList.remove('active'); page.style.display = 'none'; // Force hide }); const targetPageElement = document.getElementById(`${targetPage}-page`); if (targetPageElement) { targetPageElement.classList.add('active'); targetPageElement.style.display = 'block'; // Force show // Load page data after ensuring the page is visible setTimeo