add intelligence features; implement signals and predictions pages in admin panel

This commit is contained in:
ImBenji
2026-04-23 22:58:19 +01:00
parent 4ffd31c2ab
commit bec6763191
26 changed files with 3265 additions and 3128 deletions
+136
View File
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Articles</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles" class="active">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<div class="stats-bar" id="statsBar">
<div class="stat"><span class="label">Total articles</span><span class="value" id="s-total"></span></div>
<div class="stat"><span class="label">With content</span><span class="value" id="s-content"></span></div>
<div class="stat"><span class="label">With embedding</span><span class="value" id="s-embed"></span></div>
<div class="stat"><span class="label">Events</span><span class="value" id="s-events"></span></div>
</div>
<main class="content">
<div class="filters">
<label>Keyword <input type="text" id="f-keyword" placeholder="search..." /></label>
<label>Source <select id="f-source"><option value="">All sources</option></select></label>
<label>Status
<select id="f-status">
<option value="">All</option>
<option value="ok">ok</option>
<option value="error">error</option>
<option value="pending">pending</option>
<option value="null">no status</option>
</select>
</label>
<label>From <input type="date" id="f-from" /></label>
<label>To <input type="date" id="f-to" /></label>
<button class="primary" id="searchBtn" style="align-self:flex-end">Search</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:44px">ID</th>
<th>Title</th>
<th>Source</th>
<th>Status</th>
<th>Ingested</th>
<th style="width:80px"></th>
</tr>
</thead>
<tbody id="articleTable"></tbody>
</table>
</div>
<div class="pagination">
<button id="prevBtn">← Prev</button>
<span id="pageInfo"></span>
<button id="nextBtn">Next →</button>
</div>
</main>
<!-- article edit modal -->
<div class="overlay" id="articleOverlay">
<div class="modal">
<h2 id="modalTitle">Article</h2>
<div id="modalMeta" style="font-size:12px; color:var(--muted-dark); margin-top:4px; display:flex; gap:14px; flex-wrap:wrap"></div>
<div class="modal-divider"></div>
<div class="field">
<label>Title</label>
<input type="text" id="m-title" />
</div>
<div class="field">
<label>Description</label>
<textarea id="m-desc" style="min-height:70px"></textarea>
</div>
<div class="field">
<label>Content</label>
<textarea id="m-content" style="min-height:200px"></textarea>
</div>
<div style="display:flex; gap:12px; flex-wrap:wrap">
<div class="field" style="flex:1; min-width:140px">
<label>Content status</label>
<select id="m-status">
<option value="">— none —</option>
<option value="ok">ok</option>
<option value="error">error</option>
<option value="pending">pending</option>
</select>
</div>
<div class="field" style="flex:1; min-width:140px">
<label>Language</label>
<input type="text" id="m-lang" placeholder="en" />
</div>
<div class="field" style="flex:1; min-width:140px">
<label>Pub date</label>
<input type="text" id="m-pubdate" />
</div>
<div class="field" style="flex:1; min-width:140px">
<label>Is index page</label>
<select id="m-indexpage">
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
</div>
<div class="modal-footer">
<button class="danger" id="deleteBtn">Delete</button>
<div style="flex:1"></div>
<button id="cancelBtn">Cancel</button>
<button class="primary" id="saveBtn">Save</button>
</div>
</div>
</div>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/articles.js"></script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Events</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events" class="active">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<div class="stats-bar" id="statsBar">
<div class="stat"><span class="label">Total articles</span><span class="value" id="s-total"></span></div>
<div class="stat"><span class="label">With content</span><span class="value" id="s-content"></span></div>
<div class="stat"><span class="label">With embedding</span><span class="value" id="s-embed"></span></div>
<div class="stat"><span class="label">Events</span><span class="value" id="s-events"></span></div>
</div>
<main class="content">
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:44px">ID</th>
<th>Title</th>
<th style="width:100px">Articles</th>
<th>Created</th>
<th style="width:80px"></th>
</tr>
</thead>
<tbody id="eventTable"></tbody>
</table>
</div>
<div class="pagination">
<button id="ePrevBtn">← Prev</button>
<span id="ePageInfo"></span>
<button id="eNextBtn">Next →</button>
</div>
</main>
<!-- event edit modal -->
<div class="overlay" id="eventOverlay">
<div class="modal" style="width:480px">
<h2>Edit Event</h2>
<div class="modal-divider"></div>
<div class="field">
<label>Title</label>
<input type="text" id="em-title" />
</div>
<div class="modal-footer">
<button class="danger" id="eDeleteBtn">Delete event</button>
<div style="flex:1"></div>
<button id="eCancelBtn">Cancel</button>
<button class="primary" id="eSaveBtn">Save</button>
</div>
</div>
</div>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/events.js"></script>
</body>
</html>
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Intelligence / Graph</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
<link rel="stylesheet" href="/admin/assets/css/intel.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence" class="active">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<nav class="subnav">
<a href="/admin/intelligence/knowledge">Knowledge</a>
<a href="/admin/intelligence/predictions">Predictions</a>
<a href="/admin/intelligence/signals">Signals</a>
<a href="/admin/intelligence/graph" class="active">Graph</a>
</nav>
<main class="content">
<div id="intel-unavailable" class="intel-unavailable" style="display:none">
intelligence.sqlite not found — is the intelligence worker running?
</div>
<div id="intel-content">
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px" id="intel-stats-row"></div>
<div id="intel-graph-layout">
<div id="intel-graph-svg-wrap">
<svg id="intel-graph-svg"></svg>
<div id="graph-empty" style="display:none; position:absolute; inset:0; color:var(--muted); font-size:13px; text-align:center; padding-top:120px">No relationship data yet</div>
<div id="graph-controls">
<input id="graph-search" placeholder="Search companies..." autocomplete="off" spellcheck="false" />
<div id="graph-chips">
<button class="graph-chip active" data-type="all">All</button>
<button class="graph-chip" data-type="competitor">Competitor</button>
<button class="graph-chip" data-type="customer">Customer</button>
<button class="graph-chip" data-type="supplier">Supplier</button>
<button class="graph-chip" data-type="investor">Investor</button>
</div>
</div>
<div id="graph-legend">
<span><span class="graph-legend-dot" style="background:#E24B4A"></span>Competitor</span>
<span><span class="graph-legend-dot" style="background:#639922"></span>Customer</span>
<span><span class="graph-legend-dot" style="background:#BA7517"></span>Supplier</span>
<span><span class="graph-legend-dot" style="background:#378ADD"></span>Investor</span>
</div>
</div>
<aside id="graph-info">
<p class="graph-empty-msg">Click a node to see its connections.</p>
</aside>
</div>
</div>
</main>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/intel-shared.js"></script>
<script src="/admin/assets/js/intel-graph.js"></script>
</body>
</html>
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Intelligence / Knowledge</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
<link rel="stylesheet" href="/admin/assets/css/intel.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence" class="active">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<nav class="subnav">
<a href="/admin/intelligence/knowledge" class="active">Knowledge</a>
<a href="/admin/intelligence/predictions">Predictions</a>
<a href="/admin/intelligence/signals">Signals</a>
<a href="/admin/intelligence/graph">Graph</a>
</nav>
<main class="content">
<div id="intel-unavailable" class="intel-unavailable" style="display:none">
intelligence.sqlite not found — is the intelligence worker running?
</div>
<div id="intel-content">
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px" id="intel-stats-row"></div>
<div class="filters">
<label>Company
<select id="i-company"><option value="">All companies</option></select>
</label>
<label>Type
<select id="i-type">
<option value="">All types</option>
<option value="relationship">Relationship</option>
<option value="theme">Theme</option>
<option value="factor">Factor</option>
</select>
</label>
<label>Sort
<select id="i-sort">
<option value="id">Recent first</option>
<option value="event_date">By event date</option>
</select>
</label>
<button class="primary" id="i-filter-btn" style="align-self:flex-end">Filter</button>
</div>
<div class="table-wrap">
<table>
<thead id="intel-thead"></thead>
<tbody id="intel-tbody"></tbody>
</table>
</div>
<div class="pagination">
<button id="iPrevBtn">← Prev</button>
<span id="iPageInfo"></span>
<button id="iNextBtn">Next →</button>
</div>
</div>
</main>
<!-- intelligence detail modal -->
<div class="overlay" id="intelOverlay">
<div class="modal" style="width:740px">
<h2 id="intel-modal-title">Detail</h2>
<div id="intel-modal-meta" style="font-size:12px; color:var(--muted-dark); margin-top:4px; display:flex; gap:12px; flex-wrap:wrap"></div>
<div class="modal-divider"></div>
<div id="intel-modal-body" class="intel-body"></div>
<div class="modal-footer">
<button id="intelCloseBtn">Close</button>
</div>
</div>
</div>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/intel-shared.js"></script>
<script src="/admin/assets/js/intel-knowledge.js"></script>
</body>
</html>
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Intelligence / Predictions</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
<link rel="stylesheet" href="/admin/assets/css/intel.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence" class="active">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<nav class="subnav">
<a href="/admin/intelligence/knowledge">Knowledge</a>
<a href="/admin/intelligence/predictions" class="active">Predictions</a>
<a href="/admin/intelligence/signals">Signals</a>
<a href="/admin/intelligence/graph">Graph</a>
</nav>
<main class="content">
<div id="intel-unavailable" class="intel-unavailable" style="display:none">
intelligence.sqlite not found — is the intelligence worker running?
</div>
<div id="intel-content">
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px" id="intel-stats-row"></div>
<div class="filters">
<label>Company
<select id="i-company"><option value="">All companies</option></select>
</label>
<label>Sort
<select id="i-sort">
<option value="id">Recent first</option>
<option value="event_date">By event date</option>
</select>
</label>
<button class="primary" id="i-filter-btn" style="align-self:flex-end">Filter</button>
</div>
<div class="table-wrap">
<table>
<thead id="intel-thead"></thead>
<tbody id="intel-tbody"></tbody>
</table>
</div>
<div class="pagination">
<button id="iPrevBtn">← Prev</button>
<span id="iPageInfo"></span>
<button id="iNextBtn">Next →</button>
</div>
</div>
</main>
<div class="overlay" id="intelOverlay">
<div class="modal" style="width:740px">
<h2 id="intel-modal-title">Detail</h2>
<div id="intel-modal-meta" style="font-size:12px; color:var(--muted-dark); margin-top:4px; display:flex; gap:12px; flex-wrap:wrap"></div>
<div class="modal-divider"></div>
<div id="intel-modal-body" class="intel-body"></div>
<div class="modal-footer">
<button id="intelCloseBtn">Close</button>
</div>
</div>
</div>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/intel-shared.js"></script>
<script src="/admin/assets/js/intel-predictions.js"></script>
</body>
</html>
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Intelligence / Signals</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
<link rel="stylesheet" href="/admin/assets/css/intel.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence" class="active">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<nav class="subnav">
<a href="/admin/intelligence/knowledge">Knowledge</a>
<a href="/admin/intelligence/predictions">Predictions</a>
<a href="/admin/intelligence/signals" class="active">Signals</a>
<a href="/admin/intelligence/graph">Graph</a>
</nav>
<main class="content">
<div id="intel-unavailable" class="intel-unavailable" style="display:none">
intelligence.sqlite not found — is the intelligence worker running?
</div>
<div id="intel-content">
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px" id="intel-stats-row"></div>
<div id="intel-signals-grid" class="signal-grid"></div>
<div id="intel-signals-empty" class="signal-empty" style="display:none">No signals generated yet — waiting for the signal worker to run.</div>
</div>
</main>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/intel-shared.js"></script>
<script src="/admin/assets/js/intel-signals.js"></script>
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — SQL</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats">Stats</a>
<a href="/admin/intelligence">Intelligence</a>
<a href="/admin/sql" class="active">SQL</a>
</nav>
</header>
<div class="stats-bar" id="statsBar">
<div class="stat"><span class="label">Total articles</span><span class="value" id="s-total"></span></div>
<div class="stat"><span class="label">With content</span><span class="value" id="s-content"></span></div>
<div class="stat"><span class="label">With embedding</span><span class="value" id="s-embed"></span></div>
<div class="stat"><span class="label">Events</span><span class="value" id="s-events"></span></div>
</div>
<main class="content">
<div style="display:flex; gap:10px; margin-bottom:12px; align-items:center">
<select id="sql-db" style="min-width:160px">
<option value="archive">archive.sqlite</option>
<option value="intelligence">intelligence.sqlite</option>
</select>
<button class="primary" id="sql-run-btn">Run</button>
<span id="sql-elapsed" style="color:var(--muted-dark); font-size:12px"></span>
</div>
<textarea id="sql-input" style="width:100%; min-height:120px; font-family:'SF Mono','Fira Code',monospace; font-size:13px; margin-bottom:12px" placeholder="SELECT ..."></textarea>
<div id="sql-error" style="color:#fca5a5; font-size:13px; margin-bottom:10px; display:none"></div>
<div id="sql-results" style="overflow-x:auto"></div>
</main>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/sql.js"></script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Duriin Admin — Stats</title>
<link rel="stylesheet" href="/admin/assets/css/base.css">
<link rel="stylesheet" href="/admin/assets/css/layout.css">
<link rel="stylesheet" href="/admin/assets/css/components.css">
<link rel="stylesheet" href="/admin/assets/css/intel.css">
</head>
<body>
<header class="app-header">
<h1>Duriin <span>Admin</span></h1>
<nav class="tabs">
<a href="/admin/articles">Articles</a>
<a href="/admin/events">Events</a>
<a href="/admin/stats" class="active">Stats</a>
<a href="/admin/intelligence">Intelligence</a>
<a href="/admin/sql">SQL</a>
</nav>
</header>
<div class="stats-bar" id="statsBar">
<div class="stat"><span class="label">Total articles</span><span class="value" id="s-total"></span></div>
<div class="stat"><span class="label">With content</span><span class="value" id="s-content"></span></div>
<div class="stat"><span class="label">With embedding</span><span class="value" id="s-embed"></span></div>
<div class="stat"><span class="label">Events</span><span class="value" id="s-events"></span></div>
</div>
<main class="content">
<div style="margin-bottom:28px">
<div class="section-heading">Pipeline throughput — last 1 hour</div>
<div style="display:flex; gap:12px; flex-wrap:wrap; margin-top:10px">
<div class="intel-stat-card" style="min-width:180px">
<span class="label">Articles ingested</span>
<span class="value" id="rate-ingested"></span>
</div>
<div class="intel-stat-card" style="min-width:180px">
<span class="label">Content fetched</span>
<span class="value" id="rate-content"></span>
</div>
<div class="intel-stat-card" style="min-width:180px">
<span class="label">Embeddings generated</span>
<span class="value" id="rate-embeddings"></span>
</div>
</div>
</div>
<div style="display:flex; gap:32px; flex-wrap:wrap; padding-top:4px">
<div>
<div class="section-heading">By source</div>
<div class="table-wrap" style="width:auto; min-width:220px">
<table style="width:auto">
<thead><tr><th>Source</th><th style="text-align:right">Count</th></tr></thead>
<tbody id="sourceTable"></tbody>
</table>
</div>
</div>
<div>
<div class="section-heading">By content status</div>
<div class="table-wrap" style="width:auto; min-width:180px">
<table style="width:auto">
<thead><tr><th>Status</th><th style="text-align:right">Count</th></tr></thead>
<tbody id="statusTable"></tbody>
</table>
</div>
</div>
</div>
</main>
<div id="toast"><span class="toast-dot"></span><span id="toast-msg"></span></div>
<script src="/admin/assets/js/app.js"></script>
<script src="/admin/assets/js/stats.js"></script>
</body>
</html>