{"id":50403117,"url":"https://github.com/alainbrown/crystal","last_synced_at":"2026-05-31T00:04:40.409Z","repository":{"id":361300960,"uuid":"1253907664","full_name":"alainbrown/crystal","owner":"alainbrown","description":"A private, on-device AI chat panel for Chrome. Runs Qwen3.5 entirely in the browser via WebGPU — no server, no API key.","archived":false,"fork":false,"pushed_at":"2026-05-30T01:16:09.000Z","size":5232,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T03:11:08.223Z","etag":null,"topics":["ai-chat","chrome-extension","local-first","local-llm","manifest-v3","on-device-ai","onnxruntime-web","privacy","qwen","react","side-panel","transformers-js","typescript","webgpu"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alainbrown.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-29T23:49:58.000Z","updated_at":"2026-05-30T01:13:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alainbrown/crystal","commit_stats":null,"previous_names":["alainbrown/crystal"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/alainbrown/crystal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alainbrown%2Fcrystal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alainbrown%2Fcrystal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alainbrown%2Fcrystal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alainbrown%2Fcrystal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alainbrown","download_url":"https://codeload.github.com/alainbrown/crystal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alainbrown%2Fcrystal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33714036,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-chat","chrome-extension","local-first","local-llm","manifest-v3","on-device-ai","onnxruntime-web","privacy","qwen","react","side-panel","transformers-js","typescript","webgpu"],"created_at":"2026-05-31T00:04:36.548Z","updated_at":"2026-05-31T00:04:40.404Z","avatar_url":"https://github.com/alainbrown.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Crystal\n\n[![CI](https://github.com/alainbrown/crystal/actions/workflows/ci.yml/badge.svg)](https://github.com/alainbrown/crystal/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-6b72f0.svg)](LICENSE)\n![Chrome Manifest V3](https://img.shields.io/badge/Chrome-Manifest%20V3-5258d8?logo=googlechrome\u0026logoColor=white)\n![WebGPU](https://img.shields.io/badge/WebGPU-on--device-6b72f0)\n![React 19](https://img.shields.io/badge/React-19-9aa0ff?logo=react\u0026logoColor=white)\n![TypeScript](https://img.shields.io/badge/TypeScript-3178c6?logo=typescript\u0026logoColor=white)\n![Vite](https://img.shields.io/badge/Vite-8-646cff?logo=vite\u0026logoColor=white)\n\nA private, on-device AI chat panel for Chrome. A multimodal **Qwen3.5** model runs\nentirely in your browser via **WebGPU** — no server, no API key, no data leaving your\nmachine. Model weights download once from the Hugging Face Hub and are cached by the\nbrowser.\n\nCrystal is a Chrome **Manifest V3** extension with two surfaces: a chat **side panel**\nand a full-tab **settings** page.\n\n![Crystal demo — typing a message, the model reasoning then streaming a reply, and switching models](demo/store-assets/crystal-demo.gif)\n\n## How it works\n\nInference runs in the extension's **background service worker**, which owns a single\n`LLMEngine` backed by [transformers.js](https://github.com/huggingface/transformers.js)\nand onnxruntime-web. The UI surfaces are thin React apps that never touch the model —\nthey talk to the service worker over a `chrome.runtime` port and exchange typed\nmessages (`load` / `generate` / `interrupt` / `dispose` → `status` / `progress` /\n`ready` / `token` / `complete` / `error`).\n\n```\nside panel  ─┐\n             ├─ chrome.runtime port ─→  service worker  ─→  LLMEngine (WebGPU)\nsettings    ─┘                                              transformers.js + ORT\n```\n\nRunning inference in the service worker (rather than a page or Web Worker) is what lets\nonnxruntime-web resolve its WebGPU backend as same-origin assets — the only arrangement\nthat satisfies the strict MV3 extension CSP.\n\n## Models\n\nSelectable in settings; the weights are fetched at runtime from the HF Hub.\n\n| Model | Download | Notes |\n| ----- | -------- | ----- |\n| Qwen3.5 **0.8B** (default) | ~480 MB | fastest · smallest |\n| Qwen3.5 **2B** | ~1.3 GB | balanced |\n| Qwen3.5 **4B** | ~2.6 GB | sharpest · heavy |\n\nSettings (precision, temperature, max tokens, reasoning, CPU fallback, theme, and more)\npersist to `chrome.storage.local` and stay synced across both surfaces.\n\n## Requirements\n\n- A WebGPU-capable Chromium browser (Chrome / Edge, recent version).\n- [pnpm](https://pnpm.io/).\n\n## Getting started\n\n```bash\npnpm install\npnpm dev          # Vite + CRXJS dev server with HMR\n```\n\nThen load the extension:\n\n1. Open `chrome://extensions`.\n2. Enable **Developer mode**.\n3. Click **Load unpacked** and select the `dist/` directory.\n4. Open the side panel from the Crystal toolbar icon.\n\nThe first chat triggers a one-time model download; subsequent loads use the browser\ncache.\n\n## Commands\n\n| Command | What it does |\n| ------- | ------------ |\n| `pnpm dev` | Vite + CRXJS dev server with HMR. |\n| `pnpm build` | `tsc -b \u0026\u0026 vite build` → unpacked extension in `dist/`. |\n| `pnpm typecheck` | `tsc -b --noEmit` across all tsconfig projects. |\n| `pnpm test` | Vitest unit tests (jsdom; no network or GPU). |\n| `pnpm test:e2e` | Builds, then runs the Playwright journey against the built `dist/`. |\n\nRun a single unit test:\n\n```bash\npnpm exec vitest run tests/unit/store.test.ts\npnpm exec vitest run -t \"partial name\"\n```\n\n## Testing\n\n- **Unit** (`tests/unit/`, Vitest + jsdom) — pure logic and store wiring exercised\n  through an injected `MockEngine`. No network or GPU. The store is transport-agnostic,\n  so the client is faked via `__setClientFactory`.\n- **E2E** (`e2e/journey.spec.ts`, Playwright) — loads the built extension and drives the\n  real WebGPU engine end to end: model download → streamed reply → token stats, plus the\n  settings page. Needs a real GPU and downloads ~480 MB of weights on first run.\n\n## Project layout\n\n```\nsrc/\n  background/service-worker.ts   single LLMEngine; owns inference\n  worker/\n    engine.ts                    LLMEngine interface\n    transformers-engine.ts       real WebGPU implementation\n    mock-engine.ts               deterministic test double\n    protocol.ts                  typed port messages\n  lib/\n    worker-client.ts             port client used by the UI\n    models.ts                    model catalog\n    settings.ts                  settings schema + chrome.storage persistence\n    chat.ts                      chat message helpers\n  store/chat-store.ts            Zustand store; tokens → chat messages\n  sidepanel/                     chat side-panel React UI\n  options/                       settings-page React UI\ne2e/                             Playwright journey\ntests/unit/                      Vitest unit tests\n```\n\nTypeScript uses project references: `tsconfig.app.json` (src + unit tests),\n`tsconfig.node.json` (build configs), and `tsconfig.e2e.json` (e2e). `pnpm typecheck`\nbuilds all three.\n\n## Privacy\n\nEverything runs locally. The only network traffic is the one-time model-weight download\nfrom the Hugging Face Hub (allowed via the extension's `host_permissions`). Your\nconversations never leave the browser.\n\n## License\n\n[MIT](LICENSE) © 2026 Alain Brown\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falainbrown%2Fcrystal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falainbrown%2Fcrystal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falainbrown%2Fcrystal/lists"}