Spaces:
Running on Zero
Running on Zero
File size: 5,397 Bytes
78cc96f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | // index.js β browser-only wiring. No backend, no API keys.
import { mountBrowserAgent } from "./src/ui/browser-agent.js";
import { mountNewsPage } from "./src/ui/news-page.js";
import { mountMeshPanel } from "./src/ui/mesh-panel.js";
import { createWebLLM, MODELS, hasWebGPU } from "./src/llm/webllm.js";
import { scrapeUrl, webSearchNews } from "./src/news/ingest.js";
import { ragIndex, ragSearch } from "./src/rag/rag.js";
// ββ model picker + progress βββββββββββββββββββββββββββββββββββββββββββββββ
const modelSel = document.getElementById("model");
const progressEl = document.getElementById("model-progress");
MODELS.forEach((m) => {
const o = document.createElement("option");
o.value = m.id;
o.textContent = m.label;
modelSel.appendChild(o);
});
if (!hasWebGPU()) {
progressEl.textContent = "β WebGPU unavailable β use Chrome/Edge to run the local model. News + mesh still work.";
progressEl.className = "warn";
}
const webllm = createWebLLM({
onProgress: (p) => {
progressEl.textContent = p.text || "";
progressEl.className = p.stage === "ready" ? "ok" : "";
},
});
// LLM adapter the agent runtime expects, pinned to the selected model.
const llm = {
chat: (args) => webllm.chat({ ...args, model: modelSel.value }),
};
// ββ browser-local tools βββββββββββββββββββββββββββββββββββββββββββββββββββββ
const deps = {
webSearch: (q) => webSearchNews(q),
scrape: (url) => scrapeUrl(url),
summarize: async (text, focus) => {
const sys = "Summarize the text concisely. " + (focus ? `Focus on: ${focus}.` : "");
const { text: out } = await webllm.chat({
messages: [
{ role: "system", content: sys },
{ role: "user", content: String(text).slice(0, 8000) },
],
stream: false,
model: modelSel.value,
});
return out;
},
remember: (content) => {
const mem = JSON.parse(localStorage.getItem("hearthnet_memory") || "[]");
mem.push({ content, ts: Date.now() });
localStorage.setItem("hearthnet_memory", JSON.stringify(mem));
return "saved";
},
schedule: (delaySec, message) => {
const ms = Math.max(1, Number(delaySec) || 1) * 1000;
setTimeout(() => {
if (Notification?.permission === "granted") new Notification("HearthNet", { body: message });
else alert(`HearthNet: ${message}`);
}, ms);
return `scheduled in ${Math.round(ms / 1000)}s`;
},
ragindex: (text, source) => ragIndex(text, source),
ragsearch: (query, topK) => ragSearch(query, topK || 4),
};
// ββ tabs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const tabs = document.querySelectorAll(".tab");
const panes = document.querySelectorAll(".pane");
tabs.forEach((t) => {
t.onclick = () => {
tabs.forEach((x) => x.classList.remove("active"));
panes.forEach((x) => x.classList.remove("active"));
t.classList.add("active");
document.getElementById(`pane-${t.dataset.tab}`).classList.add("active");
};
});
// ββ mount UIs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
mountBrowserAgent(document.getElementById("pane-agent"), llm, deps);
let lastSignals = [];
const news = mountNewsPage(document.getElementById("pane-news"), {
onSignals: (active) => { lastSignals = active; },
});
const meshUI = mountMeshPanel(document.getElementById("mesh-mount"), {
onShareSignals: (signals, from) => {
console.log("received signals from", from, signals);
},
});
// share current active signals to the mesh
document.getElementById("share-signals").onclick = () => {
meshUI.shareSignals(news.signals.filter((s) => s.active));
};
// ββ easter egg: press "e" to toggle a global live news ticker βββββββββββββββ
let eggOn = false;
function isTyping() {
const t = document.activeElement;
return t && /^(input|textarea|select)$/i.test(t.tagName);
}
function escHtml(s) {
return String(s || "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
}
function populateEgg() {
const track = document.getElementById("egg-track");
const items = news.items || [];
if (!items.length) {
track.textContent = "fetching live newsβ¦";
news.refresh().then(() => { if (eggOn) populateEgg(); });
return;
}
track.innerHTML = items
.slice(0, 40)
.map((it) => `<span class="etk"><b>${escHtml(it.source)}</b> ${escHtml(it.title)}</span>`)
.join('<span class="esep">β’</span>');
}
document.addEventListener("keydown", (e) => {
if (e.key.toLowerCase() !== "e" || isTyping() || e.ctrlKey || e.metaKey || e.altKey) return;
eggOn = !eggOn;
document.getElementById("egg-ticker").classList.toggle("hidden", !eggOn);
if (eggOn) populateEgg();
});
// ask notification permission early (used by schedule tool)
if (typeof Notification !== "undefined" && Notification.permission === "default") {
Notification.requestPermission().catch(() => {});
}
|