}
+ */
+async function query(userMessage) {
+ if (!_isReady) return null;
+
+ try {
+ if (_mode === 'webllm' && _engine) {
+ const result = await _engine.chat.completions.create({
+ messages: [
+ { role: 'system', content: SYSTEM_PROMPT },
+ { role: 'user', content: userMessage },
+ ],
+ max_tokens: 120,
+ temperature: 0.7,
+ stream: false,
+ });
+ return result.choices[0].message.content.trim();
+ }
+
+ if (_mode === 'transformers' && _engine) {
+ const out = await _engine(userMessage, {
+ max_new_tokens: 80,
+ do_sample: true,
+ temperature: 0.7,
+ });
+ const raw = out[0]?.generated_text ?? '';
+ // Strip the input echo that some seq2seq models return
+ return raw.replace(userMessage, '').trim() || raw.trim();
+ }
+ } catch (err) {
+ console.warn('[EdgeAI] Inference error:', err);
+ }
+
+ return null; // caller should fall back to Ollama
+}
+
+/** True once a model is loaded and ready. */
+function isReady() { return _isReady; }
+
+/** Current inference mode string. */
+function getMode() { return _mode; }
+
+// ─── Private helpers ───────────────────────
+
+async function _tryWebLLM() {
+ if (!navigator.gpu) return false;
+ try {
+ _setStatus('loading', 'Initializing WebLLM (WebGPU)…');
+ const { CreateMLCEngine } = await import('https://esm.run/@mlc-ai/web-llm');
+ _engine = await CreateMLCEngine(WEBLLM_MODEL, {
+ initProgressCallback: (p) => {
+ const pct = Math.round((p.progress ?? 0) * 100);
+ _setStatus('loading', `Loading SmolLM2: ${pct}%`);
+ },
+ });
+ _mode = 'webllm';
+ _setStatus('ready', 'Edge AI: WebGPU ⚡');
+ return true;
+ } catch (err) {
+ console.warn('[EdgeAI] WebLLM unavailable:', err.message ?? err);
+ return false;
+ }
+}
+
+async function _tryTransformers() {
+ try {
+ _setStatus('loading', 'Initializing edge model (CPU)…');
+ const { pipeline, env } = await import(
+ 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2'
+ );
+ // Use local cache; allow remote model hub
+ env.allowLocalModels = false;
+ env.allowRemoteModels = true;
+
+ _engine = await pipeline('text2text-generation', TRANSFORMERS_MODEL, {
+ progress_callback: (info) => {
+ if (info.status === 'downloading') {
+ const pct = info.total ? Math.round((info.loaded / info.total) * 100) : 0;
+ _setStatus('loading', `Downloading model: ${pct}%`);
+ }
+ },
+ });
+ _mode = 'transformers';
+ _setStatus('ready', 'Edge AI: CPU ◈');
+ return true;
+ } catch (err) {
+ console.warn('[EdgeAI] Transformers.js unavailable:', err.message ?? err);
+ return false;
+ }
+}
+
+function _setStatus(state, text) {
+ _statusCb(state, text);
+}
+
+export const EdgeIntelligence = { init, query, isReady, getMode };
diff --git a/index.html b/index.html
index 3a2c6ea..284a649 100644
--- a/index.html
+++ b/index.html
@@ -95,6 +95,20 @@
+
+
+ Edge AI: offline
+
+
+
+
+
+ ⚡
+ loading…
+
+
WASD move Mouse look Enter chat
diff --git a/nostr-identity.js b/nostr-identity.js
new file mode 100644
index 0000000..7280193
--- /dev/null
+++ b/nostr-identity.js
@@ -0,0 +1,163 @@
+// ═══════════════════════════════════════════
+// NOSTR IDENTITY — Silent signing, no popup
+// ═══════════════════════════════════════════
+// Manages a local Nostr keypair stored in localStorage.
+// Events are signed client-side without triggering any extension popup.
+//
+// Priority:
+// 1. Existing key from localStorage (persisted identity)
+// 2. Freshly generated key (new sovereign identity)
+//
+// Users can optionally delegate signing to window.nostr (NIP-07) by
+// calling useExtension(true), but the default is always silent/local.
+
+const STORAGE_KEY = 'nexus_nostr_identity_v1';
+const NOSTR_TOOLS = 'https://esm.sh/nostr-tools@1.17.0';
+
+// ─── State ─────────────────────────────────
+let _identity = null; // { privkey: hex, pubkey: hex, npub: string }
+let _useExtension = false;
+let _nt = null; // nostr-tools module reference
+
+// ─── Public API ────────────────────────────
+
+/**
+ * Load or generate a local Nostr identity.
+ * Returns { pubkey, npub } — private key is never exposed externally.
+ */
+async function init() {
+ _nt = await _loadTools();
+ _identity = _loadStored() ?? _generate();
+ return { pubkey: _identity.pubkey, npub: _identity.npub };
+}
+
+/**
+ * Sign a Nostr event silently (no extension popup).
+ * @param {number} kind NIP event kind
+ * @param {string} content Event content
+ * @param {Array} tags Optional tags array
+ * @returns {Promise