| 6 |
6 |
Mail: contact@spboucher.ai |
| 7 |
7 |
--> |
| 8 |
8 |
|
| 9 |
|
−# Zyquo Cloud Web |
| 10 |
|
− |
| 11 |
|
−**The browser edition of Zyquo Cloud** — a multi-provider AI chat that runs |
| 12 |
|
−entirely in your browser. **No account, no backend, no telemetry.** You bring |
| 13 |
|
−your own API keys; they live in your browser's `localStorage` and are sent |
| 14 |
|
−only to the provider you call, directly. Conversation history lives locally |
| 15 |
|
−in IndexedDB. |
| 16 |
|
− |
| 17 |
|
−Live at **https://www.zyquo.cloud** |
| 18 |
|
− |
| 19 |
|
−## What's inside |
| 20 |
|
− |
| 21 |
|
−- **12 providers, 170 models** — OpenAI, Anthropic, xAI, Mistral, Google |
| 22 |
|
− Gemini, Alibaba Qwen, DeepSeek, Kimi, Perplexity, Together AI, DeepInfra, |
| 23 |
|
− Cerebras — the exact catalog of native Zyquo Cloud, with capabilities and |
| 24 |
|
− pricing. All verified callable **directly from the browser** |
| 25 |
|
− (see `docs/CORS-MATRIX.md`). |
| 26 |
|
−- Streaming + non-streaming chat, per-conversation and per-message model |
| 27 |
|
− switching, a searchable/filterable/priced **Model Menu** (⌘K palette too). |
| 28 |
|
−- Branch/variants, edit & resend, multi-model **compare** (2–4 columns with |
| 29 |
|
− promote-to-thread), 8 personas + 56 prompt templates + `/` slash commands, |
| 30 |
|
− vision attachments, Markdown + code + KaTeX + Mermaid, reasoning panels, |
| 31 |
|
− Perplexity citations, read-aloud + dictation, token/cost HUD with context |
| 32 |
|
− auto-trim, export/import of all data, optional passphrase lock (AES-GCM). |
| 33 |
|
−- Installable PWA (app shell offline), light theme flagship, responsive to |
| 34 |
|
− phone widths. |
| 35 |
|
− |
| 36 |
|
−## Honest security model |
| 37 |
|
− |
| 38 |
|
−Keys in `localStorage` are **not** a secure vault: any script running on the |
| 39 |
|
−page or anyone with your browser profile can read them. This app mitigates by |
| 40 |
|
−being dependency-light, shipping a strict CSP (connections allowed only to the |
| 41 |
|
−12 provider origins + localhost), never sending your data anywhere else, and |
| 42 |
|
−offering an opt-in passphrase lock for at-rest encryption. The first-run |
| 43 |
|
−notice says exactly this. |
| 44 |
|
− |
| 45 |
|
−## Develop |
|
9 |
+<div align="center"> |
|
10 |
+ |
|
11 |
+<img src="assets/logo.png" alt="Zyquo Cloud" width="140" /> |
|
12 |
+ |
|
13 |
+# ⚡ Zyquo Cloud Web |
|
14 |
+ |
|
15 |
+### *Every cloud model. One beautiful chat. Entirely in your browser.* |
|
16 |
+ |
|
17 |
+**The browser edition of Zyquo Cloud — 12 AI providers, 170 models, zero backend. |
|
18 |
+Bring your own keys; nothing ever leaves your device.** |
|
19 |
+ |
|
20 |
+<br/> |
|
21 |
+ |
|
22 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web/releases) |
|
23 |
+[](LICENSE) |
|
24 |
+[](#-contributing) |
|
25 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web/commits/main) |
|
26 |
+ |
|
27 |
+[](https://react.dev) |
|
28 |
+[](https://www.typescriptlang.org/) |
|
29 |
+[](https://vitejs.dev) |
|
30 |
+[](https://zustand-demo.pmnd.rs/) |
|
31 |
+[](#-features) |
|
32 |
+ |
|
33 |
+[](#-the-provider-fleet) |
|
34 |
+[](#-the-provider-fleet) |
|
35 |
+[](docs/VERIFICATION.md) |
|
36 |
+[](#-privacy--security-model) |
|
37 |
+[](#-tech-stack) |
|
38 |
+[](#-features) |
|
39 |
+[](#-features) |
|
40 |
+ |
|
41 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web/actions/workflows/ci.yml) |
|
42 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web/actions/workflows/deploy-pages.yml) |
|
43 |
+[](https://www.zyquo.cloud) |
|
44 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web/commits/main) |
|
45 |
+[](https://github.com/spboucher-ai/zyquo-cloud-web) |
|
46 |
+ |
|
47 |
+<br/> |
|
48 |
+ |
|
49 |
+**[🚀 Open the app →](https://www.zyquo.cloud)** |
|
50 |
+ |
|
51 |
+</div> |
|
52 |
+ |
|
53 |
+--- |
|
54 |
+ |
|
55 |
+## 📚 Table of Contents |
|
56 |
+ |
|
57 |
+- [✨ 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) |
|
72 |
+ |
|
73 |
+--- |
|
74 |
+ |
|
75 |
+## ✨ Features |
|
76 |
+ |
|
77 |
+Everything below is implemented and verified in this codebase — no vaporware. |
|
78 |
+ |
|
79 |
+### 💬 Chat engine |
|
80 |
+- **Streaming & non-streaming chat** against 12 providers through two faithfully |
|
81 |
+ ported wire clients: one OpenAI `/chat/completions` client (11 providers + |
|
82 |
+ custom endpoints, every quirk handled) and one native **Anthropic Messages |
|
83 |
+ API** client — over `fetch` + `ReadableStream` with a hand-rolled incremental |
|
84 |
+ **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 as |
|
92 |
+ numbered, clickable chips. |
|
93 |
+- **Auto-retry with backoff** on 429/5xx (honors `Retry-After`), typed |
|
94 |
+ human-readable errors that name the provider, and a CORS-shaped network |
|
95 |
+ error that explains the proxy/Zyquo Router fallback. |
|
96 |
+- **Auto-titling** after the first exchange using the provider's cheapest |
|
97 |
+ non-reasoning model (native Zyquo Cloud behavior, ported verbatim). |
|
98 |
+ |
|
99 |
+### 🎛 The Model Menu |
|
100 |
+- Command-palette-style picker: **fuzzy search** across name / model ID / |
|
101 |
+ provider, **filter chips** (Vision · Tools · Reasoning · JSON · Long context |
|
102 |
+ · Cheapest), **Favorites ⭐ / Recents / Aliases** groups, keyed-providers |
|
103 |
+ first, full keyboard navigation (`↑↓`, `↵` use here, `⌘↵` set default). |
|
104 |
+- Every row shows capability badges (👁 🧠 🛠), **context window**, and |
|
105 |
+ **$/1M-token pricing** straight from the catalog; un-keyed providers get an |
|
106 |
+ inline **"Add key"** affordance. |
|
107 |
+ |
|
108 |
+### 🧪 Power features |
|
109 |
+- **Multi-model compare** — broadcast one prompt to **2–4 columns**, each |
|
110 |
+ streaming independently with its own stop/copy/regenerate, then **promote |
|
111 |
+ the best answer into your thread**. |
|
112 |
+- **Variants & branching** — regenerate keeps the previous answer as a |
|
113 |
+ 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), ported |
|
120 |
+ verbatim from native Zyquo Cloud. |
|
121 |
+- **Vision attachments** (drag/drop/paste images → base64 parts) and **text |
|
122 |
+ file attachments** (injected as fenced context blocks), gated by each |
|
123 |
+ model's real capabilities. |
|
124 |
+- **Read-aloud** (Web Speech TTS) and **dictation** (SpeechRecognition). |
|
125 |
+- **Token & cost HUD** — per-message and per-conversation token counts and |
|
126 |
+ 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 shows |
|
130 |
+ controls the active model actually supports; unchecked = provider default, |
|
131 |
+ omitted from the request entirely. |
|
132 |
+ |
|
133 |
+### 📝 Rendering |
|
134 |
+- GitHub-flavored **Markdown** (tables with zebra rows, task lists, |
|
135 |
+ blockquotes with accent bar), **syntax-highlighted code blocks** with |
|
136 |
+ language label, copy and wrap toggles, **KaTeX math**, and **Mermaid |
|
137 |
+ diagrams** (the ~1 MB renderer lazy-loads only when a diagram appears). |
|
138 |
+ |
|
139 |
+### 💾 Local-first persistence |
|
140 |
+- 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 a |
|
145 |
+ warning), per-conversation Markdown/JSON export, confirm-twice clear-data, |
|
146 |
+ **undo** for deletes. |
|
147 |
+- Optional **passphrase lock**: the key map encrypted at rest with |
|
148 |
+ **AES-GCM** (PBKDF2, 310k iterations) — honestly framed as at-rest |
|
149 |
+ protection, not a running-session vault. |
|
150 |
+ |
|
151 |
+### 🎨 Design |
|
152 |
+- The **ZyquoTheme** design system, ported token-for-token from the native |
|
153 |
+ macOS app — light flagship theme (`#FAFBFD` / sky-indigo `#4E6AF0`), derived |
|
154 |
+ 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. |
|
159 |
+ |
|
160 |
+--- |
|
161 |
+ |
|
162 |
+## 🛰 The Provider Fleet |
|
163 |
+ |
|
164 |
+| 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 | ✅ | ✅ | ✅ | — | |
|
178 |
+ |
|
179 |
+**All 12 call the provider APIs directly from your browser** — CORS |
|
180 |
+feasibility was empirically probed per provider (preflight + authenticated |
|
181 |
+POST + streaming), documented with official sources in |
|
182 |
+[`docs/CORS-MATRIX.md`](docs/CORS-MATRIX.md). Anthropic requires its |
|
183 |
+documented `anthropic-dangerous-direct-browser-access: true` header, which the |
|
184 |
+client sends. A **per-provider base-URL override** (Settings → Providers & |
|
185 |
+Keys) lets you point any provider at your own proxy or a locally running |
|
186 |
+**Zyquo Router** if a policy ever changes. |
|
187 |
+ |
|
188 |
+--- |
|
189 |
+ |
|
190 |
+## 🌐 Live Demo |
|
191 |
+ |
|
192 |
+| | 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 | |
|
196 |
+ |
|
197 |
+> Open it, add any provider API key in **Settings → Providers & Keys** (it |
|
198 |
+> stays in *your* browser), pick a model, chat. That's the whole onboarding. |
|
199 |
+ |
|
200 |
+--- |
|
201 |
+ |
|
202 |
+## 📸 Screenshots |
|
203 |
+ |
|
204 |
+<div align="center"> |
|
205 |
+ |
|
206 |
+| Light (flagship) | Dark | |
|
207 |
+|:-:|:-:| |
|
208 |
+|  |  | |
|
209 |
+ |
|
210 |
+| The Model Menu | ⌘K Command Palette | |
|
211 |
+|:-:|:-:| |
|
212 |
+|  |  | |
|
213 |
+ |
|
214 |
+| 📱 Mobile | |
|
215 |
+|:-:| |
|
216 |
+| <img src="assets/screenshots/mobile.png" width="320" alt="Mobile" /> | |
|
217 |
+ |
|
218 |
+</div> |
|
219 |
+ |
|
220 |
+--- |
|
221 |
+ |
|
222 |
+## 🔒 Privacy & Security Model |
|
223 |
+ |
|
224 |
+**This app has no backend, no accounts, and no telemetry — by design.** |
|
225 |
+ |
|
226 |
+- 🔑 **Bring-your-own-key:** your API keys live in `localStorage` on your |
|
227 |
+ 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) could |
|
230 |
+ read it. The app says so instead of pretending otherwise. |
|
231 |
+- 🛡 **Strict CSP:** `connect-src` is locked to exactly the 12 provider |
|
232 |
+ origins + `localhost` (for Zyquo Router). No inline scripts, no third-party |
|
233 |
+ scripts, `object-src 'none'`, `frame-ancestors 'none'`. |
|
234 |
+- 🕵️ **Audited:** during the full 169-model verification run, the page |
|
235 |
+ contacted the 12 provider origins and localhost — **nothing else**. |
|
236 |
+- 🔐 **Optional passphrase lock:** AES-GCM (WebCrypto) encryption of the key |
|
237 |
+ 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. |
|
240 |
+ |
|
241 |
+--- |
|
242 |
+ |
|
243 |
+## 🧱 Tech Stack |
|
244 |
+ |
|
245 |
+| 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 | |
|
256 |
+ |
|
257 |
+--- |
|
258 |
+ |
|
259 |
+## 🚀 Getting Started |
|
260 |
+ |
|
261 |
+### Prerequisites |
|
262 |
+- **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) |
|
265 |
+ |
|
266 |
+### Install & run |
| 46 |
267 |
|
| 47 |
268 |
```bash |
|
269 |
+git clone https://github.com/spboucher-ai/zyquo-cloud-web.git |
|
270 |
+cd zyquo-cloud-web |
| 48 |
271 |
npm install |
| 49 |
|
−npm run dev # http://localhost:5173 |
| 50 |
|
−npm run check # typecheck + lint + build |
|
272 |
+npm run dev # → http://localhost:5173 |
| 51 |
273 |
``` |
| 52 |
274 |
|
| 53 |
|
−## Self-host |
|
275 |
+Open the app, go to **Settings → Providers & Keys**, paste a key, hit |
|
276 |
+**Test**, pick a model — done. |
|
277 |
+ |
|
278 |
+### Environment variables |
|
279 |
+ |
|
280 |
+The app itself needs **none** — keys are entered in the UI and stored in the |
|
281 |
+browser. Build-time only: |
|
282 |
+ |
|
283 |
+| Variable | Purpose | Default | |
|
284 |
+|---|---|---| |
|
285 |
+| `VITE_BASE` | Base path for subpath hosting (e.g. GitHub Pages sets `/zyquo-cloud-web/`) | `/` | |
|
286 |
+ |
|
287 |
+> ⚠️ Never put API keys in `.env` files here — there is no server to hide |
|
288 |
+> them in. The `.gitignore` blocks `.env*` and the local test-key file anyway. |
|
289 |
+ |
|
290 |
+--- |
|
291 |
+ |
|
292 |
+## 📜 Available Scripts |
|
293 |
+ |
|
294 |
+| 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 | |
|
307 |
+ |
|
308 |
+--- |
| 54 |
309 |
|
| 55 |
|
−The build is 100% static: |
|
310 |
+## 🗂 Project Structure |
| 56 |
311 |
|
|
312 |
+``` |
|
313 |
+zyquo-cloud-web/ |
|
314 |
+├── index.html # CSP meta, PWA/social meta |
|
315 |
+├── vite.config.ts # Vite + PWA (app-shell precache only) |
|
316 |
+├── src/ |
|
317 |
+│ ├── App.tsx # Shell: sidebar + chat, global shortcuts |
|
318 |
+│ ├── types/ # Provider, AIModel, Conversation, Message, Settings |
|
319 |
+│ ├── providers/ # 🔌 ALL networking lives here |
|
320 |
+│ │ ├── registry.ts # 12-provider metadata + client resolution |
|
321 |
+│ │ ├── openaiCompatible.ts# one client, 11 providers + custom, every quirk |
|
322 |
+│ │ ├── anthropic.ts # native Messages API + browser CORS header |
|
323 |
+│ │ ├── sse.ts # fetch/ReadableStream SSE parser + retry |
|
324 |
+│ │ ├── catalog.ts # the complete 170-model typed catalog |
|
325 |
+│ │ └── testKey.ts # key validation (/models or min completion) |
|
326 |
+│ ├── storage/ # 💾 the ONLY place touching localStorage/IndexedDB |
|
327 |
+│ │ ├── keys.ts # namespaced key map (+ vault overlay) |
|
328 |
+│ │ ├── vault.ts # AES-GCM passphrase lock |
|
329 |
+│ │ ├── db.ts # dependency-free IndexedDB wrapper |
|
330 |
+│ │ ├── conversations.ts # CRUD + schema migration + search |
|
331 |
+│ │ ├── settings.ts # theme, default model, proxy overrides |
|
332 |
+│ │ └── backup.ts # export / import / clear-everything |
|
333 |
+│ ├── state/store.ts # Zustand: send/stream/stop/regenerate/branch/title |
|
334 |
+│ ├── features/ # personas, prompt library, compare, exporters, trim |
|
335 |
+│ ├── components/ # Sidebar, ChatView, ModelMenu, CompareView, … |
|
336 |
+│ └── design/ # ZyquoTheme tokens (CSS vars), components.css |
|
337 |
+├── scripts/ # prod server + live verification gates |
|
338 |
+├── docs/ # PLAN, PROVIDER-REUSE, CORS-MATRIX, VERIFICATION, NATIVE-UX |
|
339 |
+└── .github/workflows/ # CI + GitHub Pages deploy |
|
340 |
+``` |
|
341 |
+ |
|
342 |
+Two hard architectural invariants, enforced by sweep: **all network I/O is in |
|
343 |
+`providers/`**, **all persistence is in `storage/`** — components stay |
|
344 |
+presentational. |
|
345 |
+ |
|
346 |
+--- |
|
347 |
+ |
|
348 |
+## ☁️ Deployment |
|
349 |
+ |
|
350 |
+The build is **100% static** — host it anywhere. |
|
351 |
+ |
|
352 |
+### GitHub Pages (automated) |
|
353 |
+Every push to `main` triggers [`deploy-pages.yml`](.github/workflows/deploy-pages.yml): |
|
354 |
+build with `VITE_BASE=/zyquo-cloud-web/` → deploy to |
|
355 |
+**https://spboucher-ai.github.io/zyquo-cloud-web/**. |
|
356 |
+ |
|
357 |
+### Self-host (any box, 30 seconds) |
| 57 |
358 |
```bash |
| 58 |
359 |
npm run build |
| 59 |
|
−node scripts/server.mjs 8080 dist # zero-dep server w/ security headers |
| 60 |
|
−# …or serve dist/ with any static host (Cloudflare Pages, Netlify, nginx…) |
|
360 |
+node scripts/server.mjs 8080 dist |
|
361 |
+``` |
|
362 |
+`server.mjs` is a zero-dependency Node server that adds the full security |
|
363 |
+header set (CSP, HSTS preload, nosniff, Referrer-Policy, Permissions-Policy), |
|
364 |
+immutable caching for hashed assets, SPA fallback, and `/healthz`. |
|
365 |
+ |
|
366 |
+### Production (www.zyquo.cloud) |
|
367 |
+The primary instance runs the same `server.mjs` under **PM2** behind an |
|
368 |
+**ngrok** custom domain: |
|
369 |
+```bash |
|
370 |
+pm2 start server.mjs --name zyquo-cloud-web -- 8080 dist |
|
371 |
+pm2 start "ngrok http --url=www.zyquo.cloud 8080" --name zyquo-cloud-ngrok |
|
372 |
+pm2 save |
| 61 |
373 |
``` |
| 62 |
374 |
|
| 63 |
|
−### Use with Zyquo Router |
|
375 |
+### Static hosts |
|
376 |
+Cloudflare Pages / Netlify / Vercel: build command `npm run build`, output |
|
377 |
+`dist/`. Add the security headers from `scripts/server.mjs` via your host's |
|
378 |
+headers config for parity. |
|
379 |
+ |
|
380 |
+--- |
|
381 |
+ |
|
382 |
+## ✅ Verification |
|
383 |
+ |
|
384 |
+This app was verified **live, with real keys, from a real browser** — full |
|
385 |
+report in [`docs/VERIFICATION.md`](docs/VERIFICATION.md): |
|
386 |
+ |
|
387 |
+| 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** ✅ | |
|
399 |
+ |
|
400 |
+--- |
|
401 |
+ |
|
402 |
+## 🗺 Roadmap |
|
403 |
+ |
|
404 |
+- [ ] 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 threads |
|
408 |
+- [ ] Conversation tags & archive surfaces (data model ready) |
|
409 |
+- [ ] i18n (FR first) |
|
410 |
+ |
|
411 |
+--- |
|
412 |
+ |
|
413 |
+## 🤝 Contributing |
|
414 |
+ |
|
415 |
+Contributions are welcome! |
|
416 |
+ |
|
417 |
+1. **Fork** and create a branch: `git checkout -b feat/my-feature` |
|
418 |
+2. Keep the invariants: networking only in `providers/`, persistence only in |
|
419 |
+ `storage/`, design tokens only (no raw hex in components), and the file |
|
420 |
+ header on every code file. |
|
421 |
+3. `npm run check` must be green. |
|
422 |
+4. Open a PR with a clear description. |
|
423 |
+ |
|
424 |
+> 🔐 Never commit keys. `.keys.local.json`, `.env*` and friends are |
|
425 |
+> gitignored; CI contains a history secret-scan precedent. |
|
426 |
+ |
|
427 |
+--- |
|
428 |
+ |
|
429 |
+## 📄 License |
|
430 |
+ |
|
431 |
+Released under the [MIT License](LICENSE) — © 2026 Simon-Pierre Boucher. |
|
432 |
+ |
|
433 |
+--- |
|
434 |
+ |
|
435 |
+<div align="center"> |
|
436 |
+ |
|
437 |
+## 👤 Author |
|
438 |
+ |
|
439 |
+<img src="assets/logo.png" alt="Zyquo" width="48" /> |
|
440 |
+ |
|
441 |
+**Simon-Pierre Boucher** |
|
442 |
+ |
|
443 |
+📫 **Contact:** [contact@spboucher.ai](mailto:contact@spboucher.ai) |
|
444 |
+ |
|
445 |
+*Zyquo Cloud Web is part of the Zyquo family — native **Zyquo Cloud** (macOS), |
|
446 |
+**Zyquo Router** (local gateway), and friends.* |
| 64 |
447 |
|
| 65 |
|
−If a provider ever blocks browser CORS (all 12 currently allow it), or you |
| 66 |
|
−want to route through a local gateway: run Zyquo Router locally and set the |
| 67 |
|
−provider's **Proxy / base URL** in Settings → Providers & Keys to your Router |
| 68 |
|
−endpoint (e.g. `http://localhost:8787/v1`). `localhost` is already allowed by |
| 69 |
|
−the CSP. |
|
448 |
+<br/> |
| 70 |
449 |
|
| 71 |
|
−## Verification |
|
450 |
+⭐ **If this project is useful to you, a star makes my day.** ⭐ |
| 72 |
451 |
|
| 73 |
|
−`docs/VERIFICATION.md` — the full provider/model matrix (169 models tested |
| 74 |
|
−live from a real browser), cross-browser (Chromium/WebKit/Firefox) and |
| 75 |
|
−persistence gates. Gate scripts in `scripts/`. |
|
452 |
+</div> |