# HFAI Cloud — connecting the website to the API

The website ships **`cloud.html`**, a dedicated HFAI Cloud application page
(OpenRouter-style sidebar: Chat, Models, API keys, Usage, Credits, API reference).
The homepage "HFAI Cloud · API" section is a teaser that links to it, and the header
"API" link goes straight there.

The app has **two modes** selected by one global:

| `window.HFAI_API_BASE` | Mode | Behaviour |
| --- | --- | --- |
| empty | **demo** | Fully interactive simulation. No network calls. A `demo` badge shows in the sidebar. |
| set (worker URL) | **live** | Real SIWS login, real streaming chat, real models/keys/usage/Solana-Pay top-ups. A `live` badge shows. |

Every action in `cloud.html` is gated: the app shell renders, and the wallet login
appears on the first interaction.

The backend lives in a separate repository: **`Sobraniex/HFAI-API`**
(Cloudflare Workers + D1 + KV). Its README is the deploy runbook; this file is the
short version for whoever is wiring the two together.

---

## 1. What the front end expects

The chat, wallet gate, and key table read two globals:

| Global | Purpose | Default |
| --- | --- | --- |
| `window.HFAI_API_BASE` | Base URL of the deployed worker, e.g. `https://hfai-api.<account>.workers.dev` | empty → **preview mode** |

When `HFAI_API_BASE` is empty, the UI stays in preview mode and says so in the
composer hint. When it is set, the API panel's base URL, the `curl`/Python/JS
snippets, and the connection flow use the live host.

Openings still work without any wallet: the gate offers **Preview the demo**.

## 2. Turn it on (one line)

The value lives in `dist/assets/config.js`; just fill in the URL:

```html
<!-- assets/config.js -->
window.HFAI_API_BASE = "https://hfai-api.YOUR-ACCOUNT.workers.dev";
```

That is the only required change. The app is already wired in `dist/cloud.html`
and `dist/cloud.js`; the homepage section (`<section id="cloud">`) just links to it.
Also add your site's origin to the worker's `ALLOWED_ORIGINS`.

## 3. Wallet sign-in

`cloud.js` looks for a Solana provider in this order:

1. `window.phantom?.solana`
2. `window.solana` (when `isPhantom`)
3. `window.heartflow` (the HeartFlow Wallet extension, also Phantom-compatible)
4. `window.solana` (any Solana Standard wallet)

**Live flow:** `connect()` → `POST /auth/nonce` → `signMessage(message)` →
base58-encode the signature → `POST /auth/verify` → the API sets a session
cookie, and the app sends every request with `credentials: 'include'`. On
load, the app calls `GET /me` to restore the session. The session is **never**
stored in `localStorage` or JS-readable storage; the cookie must be
`HttpOnly; Secure; SameSite=Lax` (or `Strict`) so an XSS cannot read it.

**Demo flow:** no server; `connect()` + local signature unlock the UI only.

If no extension is installed, the gate explains this and offers the demo — it does
not fake a connection.

## 4. Endpoints the front end is built to use

| Method | Path | Use |
| --- | --- | --- |
| `POST` | `/auth/nonce` | Get the SIWS message to sign |
| `POST` | `/auth/verify` | Exchange signature + pubkey for a session cookie |
| `GET`  | `/me` | Wallet, credit balance, tier |
| `GET`  | `/v1/models` | Model list + prices shown in the picker |
| `POST` | `/v1/chat/completions` | OpenAI-compatible streaming chat |
| `GET`/`POST` | `/conversations` | List / create saved conversations |
| `GET` | `/conversations/:id` | Load a conversation with its messages |
| `PATCH` | `/conversations/:id` | Rename a conversation / change persona |
| `DELETE` | `/conversations/:id` | Delete a conversation |
| `GET`/`POST` | `/keys` | List / create API keys |
| `DELETE` | `/keys/:id` | Revoke a key |
| `GET` | `/usage` | Metered usage for the chart |
| `GET` | `/tiers` | Holder tier thresholds and bonuses |
| `POST` | `/billing/topup` | Create a Solana Pay intent |
| `POST` | `/billing/verify` | Confirm on-chain payment → credits |

The app creates a conversation on the first message, sends `conversationId` with
each chat request (so the API persists the turn and auto-titles the conversation),
lists conversations in the sidebar, and reloads them from `GET /conversations/:id`.
`GET /me` returns `tier`/`tierLabel`/`hfaiBalance`, and `/v1/models` returns
per-user discounted prices, so a holder sees their reduced rate reflected in the UI.

`POST /v1/chat/completions` accepts the **session cookie** (browser app) or
**`Authorization: Bearer hfai_live_…`** (external customers), so the same gateway
serves both. The browser app sends `persona`
(`aurora` / `gigi` / `nova` / `clara`) and the API injects the matching system
prompt. Free models (`aurora-ai*`, `gigi-ai` on Workers AI, and `aurora-local`
via Ollama) run without credits; paid providers require a positive balance.
Requests default to OpenAI's non-streaming shape and stream when `stream:true`.

## 5. Deploying the backend

Follow `Sobraniex/HFAI-API/README.md`. In short:

```bash
cd HFAI-API
npm install
npx wrangler login                       # or set CLOUDFLARE_API_TOKEN
npx wrangler d1 create hfai              # paste database_id into wrangler.toml
npx wrangler kv namespace create HFAI_KV # paste id into wrangler.toml
npx wrangler d1 execute hfai --remote --file=./src/db/schema.sql
npx wrangler secret put OPENROUTER_API_KEY   # optional; Workers AI & Ollama need no key
npx wrangler deploy
```

Workers AI is wired through the `[ai]` binding in `wrangler.toml`, so the free
`aurora-ai*` / `gigi-ai` models work with no key at all.

Then copy the printed `https://…workers.dev` URL into `window.HFAI_API_BASE`.

## 6. Local testing without any paid keys

```bash
# in HFAI-API
npm run dev        # wrangler dev on http://127.0.0.1:8787
# with a local Ollama running on :11434, chat works with zero upstream keys
```

Point the website at it while testing (edit `assets/config.js`):

```js
window.HFAI_API_BASE = "http://127.0.0.1:8787";
```

## 7. Security notes

- The browser session is a **cookie only** (`HttpOnly; Secure; SameSite`); the
  front end never keeps a session token in `localStorage` or any JS-readable
  storage. `POST /auth/logout` must clear the cookie server-side.
- API keys are stored hashed (SHA-256) and shown once; the site only ever renders
  masked keys. Generate key material with a CSPRNG server-side, never `Math.random`.
- Prefer prepaid credits + per-key spend caps; never let a public browser session
  spend without a balance.
- Keep upstream provider keys as `wrangler secret`s, never in the repo.
- The site is static and stores no secrets. Do not put upstream keys in
  `index.html` or `assets/config.js`.
- `assets/config.js` only sets `window.HFAI_API_BASE` (the public worker URL) and
  is the sole inline-free bootstrap, so the CSP can forbid `'unsafe-inline'` scripts.
- Wallet: transaction signing always requires explicit approval (there is no
  auto-approve bypass); the wallet locks on tab hide and after a period of
  inactivity; the recovery-phrase confirmation pool is shuffled.

## 8. Honest-labelling rules

The rest of this site is careful to separate shipped features from plans. Keep
HFAI Cloud the same way: while in preview mode the composer hint and the section
footnote say the chat is illustrative. Remove or update that wording only once the
API is genuinely live.
