GitHub Actions
feat: WebLLM browser agent with PeerJS mesh, HybridRAG, news signals, and easter-egg ticker
78cc96f
Raw
History Blame
7.11 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HearthNet — Browser Agent + Mesh</title>
<style>
:root {
--bg:#0b1020; --card:#111827; --line:#334155; --fg:#eef2ff; --muted:#94a3b8;
--accent:#6366f1; --accent-soft:#1e1b4b; --ok:#34d399; --warn:#fbbf24; --err:#f87171;
}
* { box-sizing:border-box; }
body { margin:0; font-family:system-ui,-apple-system,Segoe UI,sans-serif; background:var(--bg); color:var(--fg); }
a { color:#a5b4fc; }
.shell { max-width:1400px; margin:0 auto; padding:16px; }
header { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:12px; }
header h1 { font-size:18px; margin:0; font-weight:600; }
header .spacer { flex:1; }
select, input, button { font-family:inherit; font-size:14px; }
select, input { background:var(--card); color:var(--fg); border:1px solid var(--line); border-radius:8px; padding:8px 10px; }
button { background:#1f2937; color:var(--fg); border:1px solid var(--line); border-radius:8px; padding:8px 14px; cursor:pointer; }
button:hover:not(:disabled) { border-color:var(--accent); }
button.primary { background:var(--accent); border-color:var(--accent); color:#fff; }
button:disabled { opacity:.5; cursor:not-allowed; }
#model-progress { font-size:12px; color:var(--muted); }
#model-progress.ok { color:var(--ok); } #model-progress.warn { color:var(--warn); }
.tabs { display:flex; gap:6px; margin-bottom:12px; flex-wrap:wrap; }
.tab { padding:8px 14px; border-radius:10px; background:var(--card); border:1px solid var(--line); cursor:pointer; }
.tab.active { background:var(--accent-soft); border-color:var(--accent); }
.pane { display:none; }
.pane.active { display:block; }
.panel { border:1px solid var(--line); background:var(--card); border-radius:14px; padding:14px; margin-bottom:12px; }
.panel-title { font-weight:700; margin-bottom:10px; color:#a5b4fc; }
.muted { color:var(--muted); }
.mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
pre { white-space:pre-wrap; word-break:break-word; margin:0; }
.pill { display:inline-block; padding:2px 8px; border-radius:10px; font-size:12px; background:var(--accent-soft); color:#c7d2fe; }
.pill.ok { background:#064e3b; color:var(--ok); } .pill.warn { background:#451a03; color:var(--warn); } .pill.err { background:#450a0a; color:var(--err); }
.row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.row input { flex:1; min-width:120px; }
/* agent */
.agent-top { display:flex; gap:8px; margin-bottom:12px; }
.agent-top input { flex:1; }
.agent-grid { display:grid; grid-template-columns:1fr 320px; gap:12px; }
.agent-main, .agent-side .panel { min-height:60vh; }
.agent-main { border:1px solid var(--line); background:var(--card); border-radius:14px; padding:14px; }
/* news */
.news-ticker { overflow:hidden; white-space:nowrap; border:1px solid var(--line); background:var(--card); border-radius:10px; padding:8px 0; margin-bottom:12px; }
.news-ticker-track { display:inline-block; padding-left:100%; animation:scroll 60s linear infinite; }
.news-ticker-track:hover { animation-play-state:paused; }
@keyframes scroll { to { transform:translateX(-100%); } }
.tick { padding:0 14px; } .tick-sep { color:var(--muted); }
.news-grid { display:grid; grid-template-columns:1fr 340px; gap:12px; }
.news-item { padding:10px 0; border-bottom:1px solid var(--line); }
.news-item a { font-weight:600; text-decoration:none; }
.news-meta { font-size:12px; color:var(--muted); margin:2px 0; }
.news-sum { font-size:13px; color:#cbd5e1; }
.signal { padding:6px 0; border-bottom:1px solid var(--line); }
.signal.on { } .signal-hits { font-size:11px; color:var(--muted); margin-top:4px; }
.alert-row, .peer-row { padding:6px 0; border-bottom:1px solid var(--line); font-size:13px; }
/* mesh */
.mesh-grid { display:grid; grid-template-columns:240px 1fr; gap:12px; }
.mesh-chat { height:300px; overflow:auto; border:1px solid var(--line); border-radius:8px; padding:8px; background:#0f1729; font-size:13px; }
.msg { padding:2px 0; } .msg.self b { color:var(--ok); } .msg.sys { color:var(--muted); font-style:italic; }
/* easter-egg live ticker (press "e") */
.egg-ticker { position:fixed; left:0; right:0; bottom:0; z-index:9999; display:flex; align-items:center;
background:linear-gradient(90deg,#111827,#1e1b4b); border-top:2px solid var(--accent);
box-shadow:0 -8px 24px rgba(0,0,0,.5); overflow:hidden; transform:translateY(0); transition:transform .25s ease; }
.egg-ticker.hidden { transform:translateY(110%); }
.egg-label { flex:0 0 auto; padding:8px 14px; font-weight:800; font-size:12px; letter-spacing:.08em;
color:#fff; background:var(--accent); }
.egg-ticker-track { white-space:nowrap; padding:8px 0; animation:scroll 50s linear infinite; }
.egg-ticker:hover .egg-ticker-track { animation-play-state:paused; }
.etk { padding:0 16px; } .etk b { color:#a5b4fc; } .esep { color:var(--muted); }
@media (max-width:900px) { .agent-grid,.news-grid,.mesh-grid { grid-template-columns:1fr; } }
</style>
</head>
<body>
<div class="shell">
<header>
<h1>🔥 HearthNet</h1>
<span class="muted">browser-local agent · news · WebRTC mesh</span>
<span class="spacer"></span>
<span class="muted" style="opacity:.55;font-size:11px">press <kbd>e</kbd> for live ticker</span>
<label class="muted">Model</label>
<select id="model"></select>
<span id="model-progress">select a model — it loads on first chat</span>
</header>
<div class="tabs">
<div class="tab active" data-tab="agent">Agent</div>
<div class="tab" data-tab="news">News</div>
<div class="tab" data-tab="mesh">Mesh</div>
</div>
<div id="pane-agent" class="pane active"></div>
<div id="pane-news" class="pane"></div>
<div id="pane-mesh" class="pane">
<div class="row" style="margin-bottom:12px">
<button id="share-signals">Share active signals to mesh</button>
<span class="muted">broadcasts News-tab signals to all connected browsers</span>
</div>
<div id="mesh-mount"></div>
</div>
</div>
<!-- easter egg: press "e" to toggle a global live news ticker -->
<div id="egg-ticker" class="egg-ticker hidden">
<span class="egg-label">⚡ LIVE</span>
<div class="egg-ticker-track" id="egg-track">press “e” again to hide…</div>
</div>
<!-- PeerJS (WebRTC signaling broker) — JavaScript only, no server of ours -->
<script src="https://unpkg.com/peerjs@1.5.4/dist/peerjs.min.js"></script>
<script type="module" src="./index.js"></script>
</body>
</html>