SPB Git

spb/zyquo-cloud-web Public MIT

Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.

TypeScript 81.9% CSS 8.9% JavaScript 7.5% Shell 1.1% HTML 0.6%
20.6 KB · 453 lines markdown
Rendered Raw Blame History
1<!--2  README.md3  Zyquo Cloud Web45  Author: Simon-Pierre Boucher6  Mail: contact@spboucher.ai7-->89<div align="center">1011<img src="assets/logo.png" alt="Zyquo Cloud" width="140" />1213# ⚡ Zyquo Cloud Web1415### *Every cloud model. One beautiful chat. Entirely in your browser.*1617**The browser edition of Zyquo Cloud — 12 AI providers, 170 models, zero backend.18Bring your own keys; nothing ever leaves your device.**1920<br/>2122[![Version](https://img.shields.io/badge/version-1.0.0-4E6AF0?style=for-the-badge)](https://github.com/spboucher-ai/zyquo-cloud-web/releases)23[![License: MIT](https://img.shields.io/badge/license-MIT-2FA36B?style=for-the-badge)](LICENSE)24[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-D9822B?style=for-the-badge)](#-contributing)25[![Maintained](https://img.shields.io/badge/maintained-yes-C94F7C?style=for-the-badge)](https://github.com/spboucher-ai/zyquo-cloud-web/commits/main)2627[![React 19](https://img.shields.io/badge/React-19-61DAFB?style=for-the-badge&logo=react&logoColor=black)](https://react.dev)28[![TypeScript strict](https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)29[![Vite 7](https://img.shields.io/badge/Vite-7-646CFF?style=for-the-badge&logo=vite&logoColor=white)](https://vitejs.dev)30[![Zustand](https://img.shields.io/badge/Zustand-state-593D88?style=for-the-badge&logo=react&logoColor=white)](https://zustand-demo.pmnd.rs/)31[![PWA](https://img.shields.io/badge/PWA-installable-5A0FC8?style=for-the-badge&logo=pwa&logoColor=white)](#-features)3233[![Providers](https://img.shields.io/badge/AI_providers-12-FF6B6B?style=for-the-badge)](#-the-provider-fleet)34[![Models](https://img.shields.io/badge/models-170-FFD93D?style=for-the-badge)](#-the-provider-fleet)35[![Verified live](https://img.shields.io/badge/verified_live-169%2F169-6BCB77?style=for-the-badge)](docs/VERIFICATION.md)36[![Backend](https://img.shields.io/badge/backend-none_🎉-4D96FF?style=for-the-badge)](#-privacy--security-model)37[![Core bundle](https://img.shields.io/badge/core_bundle-288_kB_gzip-9B5DE5?style=for-the-badge)](#-tech-stack)38[![Personas](https://img.shields.io/badge/personas-8-F15BB5?style=for-the-badge)](#-features)39[![Templates](https://img.shields.io/badge/prompt_templates-56-00BBF9?style=for-the-badge)](#-features)4041[![CI](https://img.shields.io/github/actions/workflow/status/spboucher-ai/zyquo-cloud-web/ci.yml?branch=main&style=for-the-badge&label=CI&color=2FA36B)](https://github.com/spboucher-ai/zyquo-cloud-web/actions/workflows/ci.yml)42[![Pages](https://img.shields.io/github/actions/workflow/status/spboucher-ai/zyquo-cloud-web/deploy-pages.yml?branch=main&style=for-the-badge&label=Pages%20deploy&color=6D84F5)](https://github.com/spboucher-ai/zyquo-cloud-web/actions/workflows/deploy-pages.yml)43[![Live](https://img.shields.io/badge/🌐_live-www.zyquo.cloud-4E6AF0?style=for-the-badge)](https://www.zyquo.cloud)44[![Last commit](https://img.shields.io/github/last-commit/spboucher-ai/zyquo-cloud-web?style=for-the-badge&color=E59A4D)](https://github.com/spboucher-ai/zyquo-cloud-web/commits/main)45[![Repo size](https://img.shields.io/github/repo-size/spboucher-ai/zyquo-cloud-web?style=for-the-badge&color=43BD83)](https://github.com/spboucher-ai/zyquo-cloud-web)4647<br/>4849**[🚀 Open the app →](https://www.zyquo.cloud)**5051</div>5253---5455## 📚 Table of Contents5657- [✨ Features](#-features)58- [🛰 The Provider Fleet](#-the-provider-fleet)59- [🌐 Live Demo](#-live-demo)60- [📸 Screenshots](#-screenshots)61- [🔒 Privacy & Security Model](#-privacy--security-model)62- [🧱 Tech Stack](#-tech-stack)63- [🚀 Getting Started](#-getting-started)64- [📜 Available Scripts](#-available-scripts)65- [🗂 Project Structure](#-project-structure)66- [☁️ Deployment](#️-deployment)67- [✅ Verification](#-verification)68- [🗺 Roadmap](#-roadmap)69- [🤝 Contributing](#-contributing)70- [📄 License](#-license)71- [👤 Author](#-author)7273---7475## ✨ Features7677Everything below is implemented and verified in this codebase — no vaporware.7879### 💬 Chat engine80- **Streaming & non-streaming chat** against 12 providers through two faithfully81  ported wire clients: one OpenAI `/chat/completions` client (11 providers +82  custom endpoints, every quirk handled) and one native **Anthropic Messages83  API** client — over `fetch` + `ReadableStream` with a hand-rolled incremental84  **SSE parser** (`event:`/`data:` blocks, keep-alive comments, `[DONE]`,85  trailing-event flush).86- **Token-by-token rendering** with a blinking caret, live **elapsed time +87  tok/s**, **Stop** (`AbortController` teardown) and **Continue**.88- **Reasoning streams** normalized from every dialect: `reasoning_content`,89  `reasoning`, Mistral's array-of-thinking-chunks, DashScope `enable_thinking`,90  Anthropic `thinking` blocks — shown in a collapsible *Thought process* panel.91- **Perplexity citations** (`citations` + `search_results` merged) rendered as92  numbered, clickable chips.93- **Auto-retry with backoff** on 429/5xx (honors `Retry-After`), typed94  human-readable errors that name the provider, and a CORS-shaped network95  error that explains the proxy/Zyquo Router fallback.96- **Auto-titling** after the first exchange using the provider's cheapest97  non-reasoning model (native Zyquo Cloud behavior, ported verbatim).9899### 🎛 The Model Menu100- Command-palette-style picker: **fuzzy search** across name / model ID /101  provider, **filter chips** (Vision · Tools · Reasoning · JSON · Long context102  · Cheapest), **Favorites ⭐ / Recents / Aliases** groups, keyed-providers103  first, full keyboard navigation (`↑↓`, `↵` use here, `⌘↵` set default).104- Every row shows capability badges (👁 🧠 🛠), **context window**, and105  **$/1M-token pricing** straight from the catalog; un-keyed providers get an106  inline **"Add key"** affordance.107108### 🧪 Power features109- **Multi-model compare** — broadcast one prompt to **2–4 columns**, each110  streaming independently with its own stop/copy/regenerate, then **promote111  the best answer into your thread**.112- **Variants & branching** — regenerate keeps the previous answer as a113  swipeable variant (`‹ 2/3 ›`); branch a whole conversation from any message;114  **edit & resend** forks from an earlier user turn.115- **⌘K command palette** — actions (new chat, compare, focus mode, summarize,116  duplicate), jump-to-conversation, model switch, personas, templates.117- **`/` slash commands** — quick tools (`/summarize`, `/translate`,118  `/rewrite`, `/explain`) + the full library with `{{input}}` substitution.119- **8 personas** and **56 prompt templates** (8 categories × 7), ported120  verbatim from native Zyquo Cloud.121- **Vision attachments** (drag/drop/paste images → base64 parts) and **text122  file attachments** (injected as fenced context blocks), gated by each123  model's real capabilities.124- **Read-aloud** (Web Speech TTS) and **dictation** (SpeechRecognition).125- **Token & cost HUD** — per-message and per-conversation token counts and126  cost estimates from catalog pricing, plus a **context-window usage bar**127  with **auto-trim** of the oldest turns at 90% capacity (system prompt kept).128- **Per-conversation parameters** — temperature, top-p, max tokens,129  penalties, reasoning effort, extended thinking — the panel only shows130  controls the active model actually supports; unchecked = provider default,131  omitted from the request entirely.132133### 📝 Rendering134- GitHub-flavored **Markdown** (tables with zebra rows, task lists,135  blockquotes with accent bar), **syntax-highlighted code blocks** with136  language label, copy and wrap toggles, **KaTeX math**, and **Mermaid137  diagrams** (the ~1 MB renderer lazy-loads only when a diagram appears).138139### 💾 Local-first persistence140- Conversations in **IndexedDB** (schema-versioned, dependency-free wrapper),141  keys and settings in namespaced `localStorage`142  (`zyquo.cloud.web.*`) — **surviving reloads and full browser restarts**143  (gate-tested).144- **Export / import everything** as JSON (keys strictly opt-in with a145  warning), per-conversation Markdown/JSON export, confirm-twice clear-data,146  **undo** for deletes.147- Optional **passphrase lock**: the key map encrypted at rest with148  **AES-GCM** (PBKDF2, 310k iterations) — honestly framed as at-rest149  protection, not a running-session vault.150151### 🎨 Design152- The **ZyquoTheme** design system, ported token-for-token from the native153  macOS app — light flagship theme (`#FAFBFD` / sky-indigo `#4E6AF0`), derived154  dark theme, **5 accent choices**, adjustable chat font size and density,155  focus mode, `prefers-reduced-motion` respected.156- **Installable PWA** (app-shell precache, 1.15 MB) and fully **responsive**157  the sidebar becomes a drawer on phones, code blocks scroll internally,158  44 pt touch targets.159160---161162## 🛰 The Provider Fleet163164| Provider | Models | Wire format | Streaming | Vision | Reasoning | Citations |165|---|:-:|---|:-:|:-:|:-:|:-:|166| 🟢 **OpenAI** | 27 | OpenAI compat | ✅ | ✅ | ✅ | — |167| 🟠 **Anthropic** | 11 | **Messages API** | ✅ | ✅ | ✅ | — |168| ⚫ **xAI** | 5 | OpenAI compat | ✅ | ✅ | ✅ | — |169| 🔵 **Mistral** | 10 | OpenAI compat | ✅ | ✅ | ✅ | — |170| 🔴 **Google Gemini** | 14 | OpenAI compat | ✅ | ✅ | ✅ | — |171| 🟣 **Alibaba Qwen** | 32 | OpenAI compat | ✅ | ✅ | ✅ | — |172| 🌀 **DeepSeek** | 2 | OpenAI compat | ✅ | — | ✅ | — |173| 🌙 **Kimi (Moonshot)** | 12 | OpenAI compat | ✅ | ✅ | ✅ | — |174| 🔍 **Perplexity** | 4 | OpenAI compat | ✅ | — | ✅ | ✅ |175| 🧩 **Together AI** | 16 | OpenAI compat | ✅ | — | ✅ | — |176| 📦 **DeepInfra** | 34 | OpenAI compat | ✅ | ✅ | ✅ | — |177| ⚡ **Cerebras** | 3 | OpenAI compat | ✅ | ✅ | ✅ | — |178179**All 12 call the provider APIs directly from your browser** — CORS180feasibility was empirically probed per provider (preflight + authenticated181POST + streaming), documented with official sources in182[`docs/CORS-MATRIX.md`](docs/CORS-MATRIX.md). Anthropic requires its183documented `anthropic-dangerous-direct-browser-access: true` header, which the184client sends. A **per-provider base-URL override** (Settings → Providers &185Keys) lets you point any provider at your own proxy or a locally running186**Zyquo Router** if a policy ever changes.187188---189190## 🌐 Live Demo191192| | URL | Notes |193|---|---|---|194| 🚀 **Production** | **https://www.zyquo.cloud** | Primary deployment — HTTPS, security headers, installable PWA |195| 🪞 **GitHub Pages mirror** | https://spboucher-ai.github.io/zyquo-cloud-web/ | Auto-deployed from `main` by CI |196197> Open it, add any provider API key in **Settings → Providers & Keys** (it198> stays in *your* browser), pick a model, chat. That's the whole onboarding.199200---201202## 📸 Screenshots203204<div align="center">205206| Light (flagship) | Dark |207|:-:|:-:|208| ![Chat — light](assets/screenshots/chat-light.png) | ![Chat — dark](assets/screenshots/chat-dark.png) |209210| The Model Menu | ⌘K Command Palette |211|:-:|:-:|212| ![Model menu](assets/screenshots/model-menu.png) | ![Command palette](assets/screenshots/command-palette.png) |213214| 📱 Mobile |215|:-:|216| <img src="assets/screenshots/mobile.png" width="320" alt="Mobile" /> |217218</div>219220---221222## 🔒 Privacy & Security Model223224**This app has no backend, no accounts, and no telemetry — by design.**225226- 🔑 **Bring-your-own-key:** your API keys live in `localStorage` on your227  device and are sent **only** to the provider you call, directly.228- 🧾 **Honest first-run notice:** browser storage is *not* a secure vault —229  anyone with your browser profile (or a script running on the page) could230  read it. The app says so instead of pretending otherwise.231- 🛡 **Strict CSP:** `connect-src` is locked to exactly the 12 provider232  origins + `localhost` (for Zyquo Router). No inline scripts, no third-party233  scripts, `object-src 'none'`, `frame-ancestors 'none'`.234- 🕵️ **Audited:** during the full 169-model verification run, the page235  contacted the 12 provider origins and localhost — **nothing else**.236- 🔐 **Optional passphrase lock:** AES-GCM (WebCrypto) encryption of the key237  map at rest, PBKDF2-derived key, wrong-passphrase rejection tested.238- 📦 **Dependency-light on purpose:** every dependency is key-theft surface —239  no icon packs, no CSS framework, no IndexedDB wrapper lib, no auth SDKs.240241---242243## 🧱 Tech Stack244245| Layer | Choice | Why |246|---|---|---|247| ⚛️ UI | **React 19** + **TypeScript (strict)** | `noUncheckedIndexedAccess`, project references, zero `any` leaks |248| ⚡ Build | **Vite 7** | code-split (Mermaid/KaTeX lazy), hashed assets, **288 kB gzip** core bundle |249| 🗃 State | **Zustand 5** | one store: conversations, streaming, settings, UI |250| 🎨 Styling | **CSS custom properties** (ZyquoTheme tokens) | no Tailwind/framework — tokens ported 1:1 from the native macOS app |251| 📝 Markdown | react-markdown + remark-gfm + rehype-highlight + **KaTeX** + **Mermaid** (lazy) | GFM, math, diagrams, Zyquo-mapped syntax theme |252| 🌊 Networking | hand-rolled **SSE parser** over `fetch`/`ReadableStream` | one parser for OpenAI-style and Anthropic named events |253| 💾 Storage | **IndexedDB** (own 80-line wrapper) + namespaced `localStorage` | schema versioning, no dependency |254| 📲 PWA | vite-plugin-pwa (Workbox) | app-shell precache only — **never** API responses or keys |255| 🧪 Testing | **Playwright** (Chromium/WebKit/Firefox) | live gate scripts: streaming, persistence, full provider matrix |256257---258259## 🚀 Getting Started260261### Prerequisites262- **Node.js ≥ 20** and **npm**263- At least one provider API key (OpenAI, Anthropic, xAI, Mistral, Gemini,264  DashScope, DeepSeek, Moonshot, Perplexity, Together, DeepInfra or Cerebras)265266### Install & run267268```bash269git clone https://github.com/spboucher-ai/zyquo-cloud-web.git270cd zyquo-cloud-web271npm install272npm run dev          # → http://localhost:5173273```274275Open the app, go to **Settings → Providers & Keys**, paste a key, hit276**Test**, pick a model — done.277278### Environment variables279280The app itself needs **none** — keys are entered in the UI and stored in the281browser. Build-time only:282283| Variable | Purpose | Default |284|---|---|---|285| `VITE_BASE` | Base path for subpath hosting (e.g. GitHub Pages sets `/zyquo-cloud-web/`) | `/` |286287> ⚠️ Never put API keys in `.env` files here — there is no server to hide288> them in. The `.gitignore` blocks `.env*` and the local test-key file anyway.289290---291292## 📜 Available Scripts293294| Script | What it does |295|---|---|296| `npm run dev` | Vite dev server on `:5173` |297| `npm run build` | Type-check (project refs) + production build + PWA assets |298| `npm run preview` | Serve the production build locally |299| `npm run typecheck` | `tsc -b --noEmit` |300| `npm run lint` | ESLint (flat config) over `src/` |301| `npm run check` | typecheck + lint + build — the CI gate |302| `node scripts/server.mjs 8080 dist` | Zero-dependency production server (CSP/HSTS headers, SPA fallback, `/healthz`) |303| `node scripts/gate-phase2.mjs` | Live streaming gate in headless Chromium |304| `node scripts/gate-phase3.mjs` | 13-check persistence gate (reload + browser restart + vault) |305| `node scripts/verify-matrix.mjs` | Full provider/model verification matrix (needs real keys in `.keys.local.json`) |306| `node scripts/verify-browsers.mjs` | Chromium/WebKit/Firefox responsive smoke |307308---309310## 🗂 Project Structure311312```313zyquo-cloud-web/314├── index.html                 # CSP meta, PWA/social meta315├── vite.config.ts             # Vite + PWA (app-shell precache only)316├── src/317│   ├── App.tsx                # Shell: sidebar + chat, global shortcuts318│   ├── types/                 # Provider, AIModel, Conversation, Message, Settings319│   ├── providers/             # 🔌 ALL networking lives here320│   │   ├── registry.ts        #   12-provider metadata + client resolution321│   │   ├── openaiCompatible.ts#   one client, 11 providers + custom, every quirk322│   │   ├── anthropic.ts       #   native Messages API + browser CORS header323│   │   ├── sse.ts             #   fetch/ReadableStream SSE parser + retry324│   │   ├── catalog.ts         #   the complete 170-model typed catalog325│   │   └── testKey.ts         #   key validation (/models or min completion)326│   ├── storage/               # 💾 the ONLY place touching localStorage/IndexedDB327│   │   ├── keys.ts            #   namespaced key map (+ vault overlay)328│   │   ├── vault.ts           #   AES-GCM passphrase lock329│   │   ├── db.ts              #   dependency-free IndexedDB wrapper330│   │   ├── conversations.ts   #   CRUD + schema migration + search331│   │   ├── settings.ts        #   theme, default model, proxy overrides332│   │   └── backup.ts          #   export / import / clear-everything333│   ├── state/store.ts         # Zustand: send/stream/stop/regenerate/branch/title334│   ├── features/              # personas, prompt library, compare, exporters, trim335│   ├── components/            # Sidebar, ChatView, ModelMenu, CompareView, …336│   └── design/                # ZyquoTheme tokens (CSS vars), components.css337├── scripts/                   # prod server + live verification gates338├── docs/                      # PLAN, PROVIDER-REUSE, CORS-MATRIX, VERIFICATION, NATIVE-UX339└── .github/workflows/         # CI + GitHub Pages deploy340```341342Two hard architectural invariants, enforced by sweep: **all network I/O is in343`providers/`**, **all persistence is in `storage/`** — components stay344presentational.345346---347348## ☁️ Deployment349350The build is **100% static** — host it anywhere.351352### GitHub Pages (automated)353Every push to `main` triggers [`deploy-pages.yml`](.github/workflows/deploy-pages.yml):354build with `VITE_BASE=/zyquo-cloud-web/` → deploy to355**https://spboucher-ai.github.io/zyquo-cloud-web/**.356357### Self-host (any box, 30 seconds)358```bash359npm run build360node scripts/server.mjs 8080 dist361```362`server.mjs` is a zero-dependency Node server that adds the full security363header set (CSP, HSTS preload, nosniff, Referrer-Policy, Permissions-Policy),364immutable caching for hashed assets, SPA fallback, and `/healthz`.365366### Production (www.zyquo.cloud)367The primary instance runs the same `server.mjs` under **PM2** behind an368**ngrok** custom domain:369```bash370pm2 start server.mjs --name zyquo-cloud-web -- 8080 dist371pm2 start "ngrok http --url=www.zyquo.cloud 8080" --name zyquo-cloud-ngrok372pm2 save373```374375### Static hosts376Cloudflare Pages / Netlify / Vercel: build command `npm run build`, output377`dist/`. Add the security headers from `scripts/server.mjs` via your host's378headers config for parity.379380---381382## ✅ Verification383384This app was verified **live, with real keys, from a real browser** — full385report in [`docs/VERIFICATION.md`](docs/VERIFICATION.md):386387| Check | Result |388|---|---|389| 🌊 Streaming, token-by-token | **169 / 169 tested models** ✅ |390| 📬 Non-streaming completion | 12 / 12 providers ✅ |391| ⏹ Stop / `AbortController` | 12 / 12 providers ✅ |392| 👁 Vision (base64 image parts) | 9 / 9 vision-capable providers ✅ |393| 🧠 Reasoning deltas | all thinking models ✅ |394| 🔗 Perplexity citations | ✅ (19 chips on one query) |395| 🌍 CORS direct-from-browser | 12 / 12 ✅ |396| 💾 Persistence (reload + restart + vault) | 13 / 13 checks ✅ |397| 🧭 Cross-browser (Chromium/WebKit/Firefox) | 15 / 15 checks ✅ |398| 🕵️ Network audit | provider origins + localhost, **nothing else** ✅ |399400---401402## 🗺 Roadmap403404- [ ] Function calling / tools UI (the catalog already tracks `tools` capability)405- [ ] Model aliases editor in Settings (aliases already surface in the Model Menu)406- [ ] Client-side PDF export (Markdown/JSON exports ship today)407- [ ] Transcript virtualization for 1,000+ message threads408- [ ] Conversation tags & archive surfaces (data model ready)409- [ ] i18n (FR first)410411---412413## 🤝 Contributing414415Contributions are welcome!4164171. **Fork** and create a branch: `git checkout -b feat/my-feature`4182. Keep the invariants: networking only in `providers/`, persistence only in419   `storage/`, design tokens only (no raw hex in components), and the file420   header on every code file.4213. `npm run check` must be green.4224. Open a PR with a clear description.423424> 🔐 Never commit keys. `.keys.local.json`, `.env*` and friends are425> gitignored; CI contains a history secret-scan precedent.426427---428429## 📄 License430431Released under the [MIT License](LICENSE) — © 2026 Simon-Pierre Boucher.432433---434435<div align="center">436437## 👤 Author438439<img src="assets/logo.png" alt="Zyquo" width="48" />440441**Simon-Pierre Boucher**442443📫 **Contact:** [contact@spboucher.ai](mailto:contact@spboucher.ai)444445*Zyquo Cloud Web is part of the Zyquo family — native **Zyquo Cloud** (macOS),446**Zyquo Router** (local gateway), and friends.*447448<br/>449450**If this project is useful to you, a star makes my day.**451452</div>453