The panel is a no-build web app (native ES modules) served by your own node at http://localhost:8545/app/. It talks only to that node's control API (API), which is loopback-only unless you opt in to remote admin (a login on one hostname).
Try it without installing anything: app.elyrainference.com is a live node with a real model; signed out you get a chat page that shows which machine answered (Live app and the try page). The views below are the full panel, which is what you run on your own node.
The top bar shows the node status chip (Online · :<port>), the wallet balance chip, Demo market · N simulated peers while the simulated market is on, and the theme toggle (sun / moon). On phones the sidebar becomes a drawer opened from the menu button. If the node is not running an overlay says so and reconnects by itself (status is polled every 2 s).
Views#
The sidebar follows the order a new user needs: Start (Home, Live), Set up (Connect, Network), Use AI (Playground, Models, Routing), Earn & pay (Provide, Payments). Home opens with a guided checklist (Use AI or Sell AI, progress bar, one highlighted next step with its button); Provide opens with a Go-live checklist (source, price, reachable URL, gas, first payment). Steps are derived from live state, nothing is stored. Numbers in the tables below are historical; use the sidebar order.
| # | View | What it is for |
|---|---|---|
| - | Overview | Your endpoint URL, requests/tokens/spend/earnings for 24 h, a 14-day traffic chart, the active policy, recent routes, and the I buy / I sell checklists |
| - | Live | A private realtime view of what the node is doing: a streaming activity feed and a presence card (Live view) |
| 01 | Playground | Chat through your own router. Pick auto, an auto:* shorthand or a model. Under each answer a collapsible route footer always shows SIMULATED PEER or LIVE, the privacy label (CLEAR / TEE) and the peer; open it for model, price, tokens, cost and latency. Next to it a route preview shows the top 6 candidates and why the others lost |
| 02 | Models | Every listing the router can reach (live peers, your own upstreams, simulated ones), filterable All / Live / TEE / Cheapest / Fastest / Top rated, with price per MTok, speed, reputation, privacy and source. Try sends a test request to that listing |
| 03 | Routing | The policy: four weights (price, speed, reputation, privacy), presets (Balanced, Cheapest, Fastest, Private, Top rated), "Require private peers" (teeOnly) and a max price. Live preview of the ranking as you change it |
| 04 | Network | Peer counts, topology graph, this node's identity (name, node id, wallet, peer URL, relay status), the demo-network switch, add a peer by URL, peers found through the on-chain registry (badge "from registry"), and the TEE trust list (image digests you accept) |
| 05 | Payments | Alerts, the Fund this node stepper (chain mode), wallet balance, spent/earned, the Daily spend cap, the receipt ledger, settlement batches. With an escrow configured: network settings, mint (mock token only), approve + deposit (or Fund from your wallet), the per-wallet deposit cap ($20 USDG default, shown next to every deposit), withdraw, vouchers both ways, explorer links |
| 06 | Provide | Switch providing on, pick a single source, several sources or All healthy local sources, select models to offer, default and per-model prices per MTok, credit limit, per-wallet deposit cap (maxDepositUsd), privacy claim, a copyable peer URL with live relay status, earnings (all-time, 24 h, unsettled, per-day bars, per buyer wallet) and what peers see |
| 07 | Connect | Base URL with curl / Python / Node / env snippets, Detect local (finds Ollama on 11434, LM Studio on 1234, 8000, 8080), add a model source (Ollama, LM Studio, OpenAI, OpenRouter, Groq, Together, or any /v1 server), and Back up this node |
What the panel tells you to do next#
- Overview shows one banner at the top of the page, derived from live state. With no real source: "Connect a real model" with
Detect Ollama(opens Connect and runs detection) andAdd a source. With a real source while the simulated market is still on: "Your real model is connected" withTry itandHide simulated market(turns the demo network off). Otherwise no banner. - Connect marks a source Offline (with the reason on hover) after it fails two health checks in a row, and Remote when you told it the source is another machine. Offline sources are left out of routing until they answer again.
- Connect says so when a source was added ("Ollama is ready", models,
Open playground), and explains what to do when detection finds nothing. - Playground lists Your models (live) above the simulated market, says in the empty state who will answer, and while a reply is pending shows the elapsed seconds and, after 6 s, that a first request after idle can take a minute (a cold model load). Result badges read
LIVE/SIMULATED PEER, the peer, model, price,CLEAR/TEE, tokens, cost and time (seconds above 1 s). - A live source always wins over simulated peers for
auto(USAGE), so the route preview shows only real options once you have one.
Alerts, guided funding, spend cap and backup#
All four are derived from live state (/api/status, and the /api/chain snapshot the view already loads); nothing is stored except the backup tick, and no extra backend polling was added. Code: views/alerts.js, payments-steps.js, payments-cap.js, backup.js.
- Alerts (top of Home and Payments, non-blocking, each with its one fix): No ETH for gas (chain mode, wallet below 0.0005 ETH; Payments: faucet link + copy address, Home: opens Payments at the stepper), Escrow below $1 (spendable balance, buyers and anyone who has deposited or paid; Add funds), You used N% of today's spend cap (from 80%) and Daily spend cap reached (Raise the cap, which focuses the cap field). They disappear by themselves when the cause is gone. A button with
data-focus="<id>"scrolls to and focuses that element (opening any fold around it); withdata-goit carries the target to the next view asS.pre.focus. - Fund this node (Payments, chain mode only; hidden in simulated mode, a note while the RPC is unreachable): 1 Get gas (ETH) (faucet link, copy the node address), 2 Get USDG (a MOCK TEST TOKEN badge and Mint 100 on the testnet mock; otherwise copy the address, plus Fund from your wallet when wallet connect is enabled), 3 Deposit into the escrow (amount + Approve + deposit, enabled once there is gas and USDG). Each step is done or Next with its button; when all three are done the card collapses to a one-line "Funded" summary you can open. It is redrawn only when the facts it shows change and never while you type in it. The advanced escrow form below is unchanged.
- Daily spend cap (Payments): the control for
spendCapUsdDay(Configuration). A usage bar shows the last 24 hours against the cap; empty field = no cap. The form is drawn once per visit; only the bar refreshes on the poll. - Back up this node (Connect): explains that identity and wallet live in the data directory and what losing it costs (the wallet and unredeemed earnings, reputation), shows the directory path and whether secrets are encrypted at rest (
ELYRA_PASSPHRASE, a boolean from/api/status), a checklist (passphrase set, derived; offline copy, your own tick kept in this browser'slocalStorage: Elyra cannot see a copy), and Download public facts / Copy as JSON: node id, name, wallet address, network, escrow and version, built in the browser from/api/statusand/api/chain. The file has no keys and no passphrase; no API returns a private key or passphrase and none must be added.
Verified at 390 px wide (all eight views, no element wider than the screen): one column, tables and code scroll inside their own box, the topology graph scrolls inside its box.
Live view#
The Live page (sidebar, Start) is the owner's window on the node, fed by GET /api/events (API). It is private: only loopback or an admin session can open it, and it shows metadata only (never a prompt, an answer or a key). Code: views/live.js, css/live.css.
- Presence card (left; below the feed on narrow screens): peers online or offline with their round-trip time, Serving you (which peers answered you in the last 5 minutes, with request counts; simulated peers carry SIM), and Buying from you (buyers in the last 5 minutes as short wallet ids, or
peer abc123when you sell without an escrow; says "You are not selling" with a link to Provide when providing is off). - Activity feed (right): one row per event, newest on top, with a kind badge (Buying, Selling, Peer, Money, Alert), model and peer, tokens, cost, latency and a coloured status (green ok, amber refused with the reason such as Spend cap or Not funded, red failed or alert). Events from the demo market carry the SIM badge. At most 200 rows stay in the DOM; each event adds one row, the list is never re-rendered.
- Controls: filter pills (All, Buying, Selling, Peers, Money, Alerts; they only flip an attribute CSS reads), Pause (events keep arriving and are held; Resume · N new flushes them) and a connection chip (Live, Connecting…, Reconnecting in 4s… with a 1 s to 30 s backoff, resuming from the last event id so nothing is shown twice). The empty state reads "Nothing yet" with a button to the Playground.
- The stream is opened when you enter the page and closed when you leave it (
leave()hook inrouter.js); the node allows 8 streams at once. All event text goes throughesc().
Design system and theme#
All colours, radii and fonts are CSS variables in panel/css/base.css :root; nothing else hard-codes a colour (the topology SVG is painted by classes in network.css).
| Token | Role |
|---|---|
--bg --surface --raised --input |
Page, card, raised/hover and field backgrounds (dark: #0b0d12 / #12151c / #181c26) |
--border --border2 |
1 px hairline and stronger control borders |
--text --muted --faint |
Body, secondary and placeholder text (all AA on every surface in both themes) |
--accent --accent-text --accent-bg --accent-bd --grad |
Indigo/violet brand colour; the gradient is only for primary buttons, progress bars, chart bars and the logo mark |
--ok --warn --bad --sim (+ -bg, -bd) |
Semantic colours: success / caution / danger / simulated (purple). SIM, DEMO and CLAIMED badges keep their own colour so they never look verified |
--font --mono |
System UI stack; monospace only for addresses, URLs, hashes, code |
--r-card --r-ctl --side --top |
12 px cards, 8 px controls, 232 px sidebar, 56 px top bar |
Theme toggle. Dark is the default. The top-bar button switches to the light theme ([data-theme="light"] overrides the colour tokens only) and stores the choice in localStorage["elyra-theme"]. With nothing stored the panel follows prefers-color-scheme and falls back to dark. panel/theme-init.js is a tiny classic script in <head> that sets the attribute before first paint (no flash); lib/theme.js wires the button.
Public pages share it. The signed-out try/market page (panel/try/, Live app and the try page) uses the same tokens, radii, system fonts, buttons, pills and dark/light theme (same localStorage["elyra-theme"] key, so one choice follows the visitor between the panel and the try page). The sign-in page (router/http/login-page.mjs) uses the same look, dark by default and light by OS preference (its CSP allows no script). /app/* is behind the admin login, so panel/try/try.css carries its own copy of the token block (colours, fonts, radii): when you change a token in base.css, change it there too. The theme script is panel/try/theme.js (classic script, CSP script-src 'self'). The old commercial typefaces are gone from panel/.
Components. Buttons: .btn (secondary), .btn.primary, .btn.ghost, .btn.danger, sizes .sm / .lg. Badges are pills (.badge + tee / sim / live / warn / bad / count). Forms use components/form.js (field() = label, control, helper text, inline error; watchField() = validation). Polled regions are written with setHtml() so a refresh never re-renders unchanged markup; forms are rendered once per visit and never on the 2 s poll. Motion respects prefers-reduced-motion.
Typical flows#
Use a real local model: Overview → Detect Ollama (or Connect → Detect local, or add http://127.0.0.1:11434/v1) → Add → Open playground → send. See OLLAMA.
Tune routing: Routing → drag weights or choose a preset → watch the live preview → the new weights apply to every request sent to auto. A specific model name ignores the weights for model choice and just picks the best peer for that model.
Buy privately: Routing → tick "Require private peers". Requests with no private route fail instead of leaking. Private means your own machine/LAN or a VERIFIED TEE; a seller's own TEE claim shows as CLAIMED and does not count (TEE).
Pay on-chain (testnet): Payments → network robinhood-testnet + escrow address from STATUS → Mint test USDG (mock only) → Deposit → route a request to a live paid peer → the receipt appears in the ledger. Simulated peers are never settled.
Sell: PROVIDER.
Labels you will see#
| Label | Meaning |
|---|---|
SIM / SIMULATED PEER |
Comes from the built-in demo market. Never real inference, never settled |
DEMO |
Demo engine or demo claim (e.g. TEE · DEMO) |
LIVE |
A real peer or your own upstream |
CLEAR |
The operator can read prompts |
LOCAL |
Your own machine or LAN |
CLAIMED |
Seller says it runs in a TEE; unverified, does not count for private routing |
VERIFIED |
A Google-signed Confidential Space attestation passed your trust list. Proven only against a simulated Google so far (STATUS) |
MOCK |
The testnet token is our open-mint MockUSDG, worthless |
Live app and the try page#
The project's own node runs at app.elyrainference.com. Signed out you get the public app: a real product with navigation, not a single chat box. The full panel (everything above) is behind a login and is for the node's owner.
It is a small, separate surface, off unless the node sets ELYRA_PUBLIC_TRY=1 (Remote access). The chat only ever uses a local model on that machine: never a paid peer, never a hosted API key, never simulated answers. To use the full panel on your own node, run it (Quickstart).
Pages and navigation#
Header: logo, Market, Playground, Sell, Docs (new tab), theme toggle and the wallet button. Navigation is hash routing (#/market default, #/playground, #/sell; the old #sell and #m=<model> links still work, #m= opens that model's sellers). On phones the nav sits in a second row of the sticky header. Moving between pages moves keyboard focus to the page heading; there is a skip link; the current page has aria-current. The footer says it is an early build and shows the node's real facts (peers, settlement, free chats left today).
| Page | What it is |
|---|---|
| Market | Hero with the install snippet, stat strip, searchable table of models and sellers (price, measured or claimed speed, privacy label; unmeasured values say so), per-seller detail, and a "Paid models need your own node" card. Try on a row opens the playground on that model |
| Playground | One-screen chat: model picker (live models only), LIVE / LOCAL or REMOTE / CLEAR badges (SIM if the server ever says the answer was simulated), a quota meter from GET /pub/quota with the tier (Visitor or Wallet), streaming with a Stop button (a stopped answer is kept and marked), starter prompts, and under each answer a collapsible Route and cost (served by, model, where it runs, simulated yes/no, price, cost, tokens in/out, first word, time, speed, "paid peers used: none"). Errors say what to do next: out of free requests (sign in with a wallet, or run your own node), daily capacity used up, no model online (check again, see the market), model busy or not answering (try again; the text returns to the composer), cannot reach the node |
| Sell | Three steps (run a model, start your node, get paid) with the one command to run and links to the provider, relay, wallet and Ollama docs |
History lives only in the visitor's localStorage["elyra-try-chat"] (last 60 turns, one conversation). The page says so under the composer, Clear history (two clicks) wipes it, and nothing about it is stored on the node; the last few messages of the conversation are sent with each request, as before. Choosing another model keeps the conversation.
Wallet, sign-in and tiers#
The header button is Connect wallet. It opens a menu with two separate steps. Connect reads the address from a browser wallet (EIP-6963 or window.ethereum: MetaMask, Rabby, Coinbase Wallet; the existing flow, no AppKit, because the strict CSP forbids the WalletConnect hosts; phone wallets by QR are not enabled). Sign in asks that wallet for one off-chain signature through signInWithWallet in /try/auth.js (wallet sign-in): it proves you own the address; no transaction, no gas, nothing is approved, no funds move. After it the chip shows the short address and a Wallet badge (Owner when the address is on ELYRA_ADMIN_ADDRESSES, with an Open control panel link), the playground quota switches to the larger Wallet tier, and the menu has Sign out (POST /auth/logout). A wallet session can never reach /api, /v1 or /metrics. Balances, the network switch and Get 100 test USDG (testnet mock token only) stay in the same menu once connected. The session is the node's HttpOnly cookie; the page only learns who it is from GET /auth/session.
Username (opt-in ELYRA_GATEWAY=1): with the gateway on, the signed-in wallet also picks a name once (GET/PUT /auth/username) and the free chat needs it: without a session the chat says to sign in (401 signin_required), without a name to choose one (403 profile_required); an admin session is exempt. It is the same wallet session, not a second sign-in.
Paying (opt-in ELYRA_PUBLIC_PAY=1): the sign-in session itself cannot pay, because a wallet cannot sign a voucher per request. When the node enables pay-per-use, pay.js creates a throwaway session key in the browser, you deposit into the escrow for that address from your own wallet (capped at $20 per wallet by default), and the page signs each voucher only for an amount within the price you accepted (/pub/paid-chat). The node relays and never holds your funds or key. Without it the Paid models need your own node card says so, with the one-line install and the docs. Nothing on the page simulates a payment.
Code#
panel/try/: index.html (shell and the three pages), try.css (tokens + styles), theme.js (classic script in <head>), and ES modules loaded from try.js: lib.js (helpers, shared state, event bus, badge vocabulary), market.js, chat.js, wallet.js (connect, sign-in, menu), auth.js (sign-in and username helper, shared with the login page; see AUTH), pay.js and session.js (the opt-in session-key pay-per-use flow). No inline script or style (CSP script-src 'self'; style-src 'self'), all server text via textContent. router/test/try-page.test.mjs checks that on disk. Verified in a browser at 1440, 1024, 768 and 390 px in both themes: no horizontal overflow, AA contrast on every text node (script-checked), no console errors.