From 8a5e9fbf1037a6ef4e0406936b9f430327f62e5c Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 13 Sep 2026 04:37:15 -0400 Subject: [PATCH 1/2] docs: rewrite README to v2 quality bar, J1 branding, real screenshot Co-Authored-By: opencode --- README.md | 136 ++++++++++-------------------------------------------- 1 file changed, 25 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index b4ae095..ddb42ea 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,63 @@ -
- -![ChatForge banner](docs/assets/banner.svg) - # ChatForge -**Edge-native AI chat interface** — a single Cloudflare Worker serving a streaming chat UI powered by Workers AI, with no origin server. +> Edge-native AI chat reference implementation — a single Cloudflare Worker serving a streaming SSE chat UI powered by Workers AI, with no origin server and no API key to manage. -Stars -Last commit -License -TypeScript -Cloudflare Workers -SSE streaming +[![License](https://img.shields.io/github/license/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge) +[![Top Language](https://img.shields.io/github/languages/top/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge) +[![Stars](https://img.shields.io/github/stars/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge/stargazers) +[![Last Commit](https://img.shields.io/github/last-commit/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge/commits) +[![CI](https://img.shields.io/github/actions/workflow/status/OneByJorah/ChatForge/ci.yml?style=for-the-badge&color=FFB300&labelColor=0a0a09&label=ci)](https://github.com/OneByJorah/ChatForge/actions/workflows/ci.yml) -![ChatForge screenshot](docs/assets/screenshot.png) +![ChatForge UI](docs/screenshots/chatforge-ui.png) -
+## What This Is + +ChatForge is a reference implementation of an edge-hosted chat app: a static frontend plus a streaming `/api/chat` endpoint, both running inside one Cloudflare Worker and calling Workers AI through a binding. There is no backend server, no API key, and no regional infrastructure — just a clean starting point to fork and extend. It is intentionally minimal: no authentication, no persistence, no rate limiting; chat history lives in browser memory and resets on refresh. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the production upgrade path. ## Quick Start ```bash git clone https://github.com/OneByJorah/ChatForge.git cd ChatForge -npm install -npx wrangler login +npm install && npx wrangler login npm run dev ``` Open **http://localhost:8787**. Run `npm run deploy` to publish to Cloudflare Workers. -## What This Is - -ChatForge is a reference implementation of an edge-hosted chat app: a static frontend plus a streaming `/api/chat` endpoint, both running inside one Cloudflare Worker and calling Workers AI through a binding. There is no backend server, no API key to manage, and no cold-start regional infrastructure. It exists as a clean starting point you can fork and extend for your own product. - -> [!NOTE] -> ChatForge is intentionally minimal: **no authentication, no persistence, and no rate limiting**. Chat history lives in browser memory and resets on refresh. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the production upgrade path. - ## Features -- **Workers AI inference** — runs `@cf/meta/llama-3.1-8b-instruct-fp8` at the edge with no API keys. -- **Real-time streaming** — Server-Sent Events (SSE) deliver tokens as they are generated. -- **Single-Worker deploy** — frontend and API ship together with one `wrangler deploy`. -- **Framework-free frontend** — vanilla HTML/CSS/JS keeps the whole UI small. -- **Security headers** — CSP, HSTS, `X-Frame-Options`, `nosniff`, referrer, and permissions policies on every response. -- **Input validation** — server-side limits on message shape, count (max 100), and length (max 32,000 chars). -- **Swappable model** — change `MODEL_ID` in `src/index.ts` to any Workers AI text-generation model. -- **Docker preview** — serve the static UI locally with `docker compose up` for a quick look. +- Workers AI inference with `@cf/meta/llama-3.1-8b-instruct-fp8` at the edge. +- Server-Sent Events streaming — tokens arrive as they are generated. +- Single-Worker deploy: frontend and API ship together with one `wrangler deploy`. +- Framework-free vanilla HTML/CSS/JS frontend. +- Security headers on every response: CSP, HSTS, `X-Frame-Options`, `nosniff`, referrer and permissions policies. +- Server-side input limits on message shape, count (max 100), and length (max 32,000 chars). +- Swappable model via `MODEL_ID` in `src/index.ts`. +- Docker preview of the static UI with `docker compose up` (the `/api/chat` endpoint requires `wrangler dev` or a deployed Worker for the AI binding). ## Architecture ``` Browser ──SSE──▶ Cloudflare Worker (src/index.ts) │ - ├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static files) + ├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static) │ - └── POST /api/chat ──▶ env.AI.run() ──▶ Workers AI (Llama 3.1 8B) + └── POST /api/chat ──▶ env.AI.run() ──▶ Workers AI │ └── text/event-stream response ``` -Chat history is kept in browser memory only. The Worker is stateless. - -## API - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/chat` | `POST` | Send `{ "messages": [...] }`, receive an SSE-streamed reply | -| `/*` (non-API) | `GET` | Static frontend from `public/` | - -Errors: `400` invalid JSON/messages, `405` wrong method, `404` unknown API route, `500` AI failure. Full details in [docs/API.md](docs/API.md). - -## Configuration - -No runtime secrets are needed — the AI binding is declared in [`wrangler.jsonc`](wrangler.jsonc). Deployment credentials are read from the environment (never committed): - -| Variable | Required | Description | -|----------|----------|-------------| -| `CLOUDFLARE_API_TOKEN` | For deploy | Cloudflare API token used by Wrangler | -| `CLOUDFLARE_ACCOUNT_ID` | For deploy | Cloudflare account ID | - -See [.env.example](.env.example). - -## Supported Models - -| Provider | Model | -|----------|-------| -| **Cloudflare Workers AI** | `@cf/meta/llama-3.1-8b-instruct-fp8` (default, swappable via `MODEL_ID` in [src/index.ts](src/index.ts)) | +Chat history is kept in browser memory only; the Worker is stateless. -Browse the [Workers AI model catalog](https://developers.cloudflare.com/workers-ai/models/) — any text-generation model ID can be dropped in. +## Stack -## Docker Preview - -```bash -docker compose up -d # http://localhost:8787 -``` - -> [!WARNING] -> The Docker image previews only the static frontend. The `/api/chat` endpoint requires `wrangler dev` or a deployed Worker because it depends on the `env.AI` binding. - -## Use Cases - -1. **Starter template** — fork it as the base for a custom edge chat product. -2. **Learning Workers AI** — see streaming, bindings, and security headers in ~one file. -3. **Internal tools** — deploy a no-backend assistant for quick Q&A. -4. **Prototyping** — test prompts against Llama 3.1 at the edge with zero infrastructure. - -## Tech Stack - -Cloudflare Workers, Workers AI, TypeScript, vanilla HTML/CSS/JS, Server-Sent Events, Wrangler, Vitest, Docker (nginx:alpine preview). - -## Screenshots - -| View | Preview | -|------|---------| -| Chat UI | ![ChatForge UI](docs/screenshots/chatforge-ui.png) | -| Conversation | ![ChatForge conversation](docs/screenshots/chat-conversation.png) | -| Mobile | ![ChatForge mobile view](docs/screenshots/main.mobile.png) | -| Main viewport | ![ChatForge main view](docs/screenshots/main.viewport.png) | - -## Project Structure - -``` -ChatForge/ -├── src/ # Worker source -│ ├── index.ts # Routing + chat API -│ ├── types.ts # Type definitions -│ └── __tests__/ # Vitest suite -├── public/ # index.html + chat.js -├── wrangler.jsonc # Cloudflare config -├── Dockerfile # Static UI preview image -├── docker-compose.yml # Local static preview -├── docs/ # API.md, ARCHITECTURE.md, assets -└── package.json -``` +Cloudflare Workers · Workers AI · TypeScript · vanilla HTML/CSS/JS · Server-Sent Events · Wrangler · Vitest · Docker (nginx:alpine preview). ## Contributing -Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), then [open an issue](https://github.com/OneByJorah/ChatForge/issues) or a pull request. +See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). [Open an issue](https://github.com/OneByJorah/ChatForge/issues) or a pull request. ## License MIT — see [LICENSE](LICENSE). - -## Connect - -- [jorahone.com](https://jorahone.com) -- [GitHub Org](https://github.com/OneByJorah) -- [info@jorahone.com](mailto:info@jorahone.com) From e843903339a8f97111815652cd19aed032d36b43 Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 13 Sep 2026 05:00:27 -0400 Subject: [PATCH 2/2] docs: add real screenshot of running app (captured on :8787) Co-Authored-By: opencode --- .github/screenshots/main.png | Bin 0 -> 31952 bytes README.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 .github/screenshots/main.png diff --git a/.github/screenshots/main.png b/.github/screenshots/main.png new file mode 100644 index 0000000000000000000000000000000000000000..c646ef710d22edf9543d4c2869fd9c1799df3bd2 GIT binary patch literal 31952 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%0An^^tw`Y{2`0zn^AKFV$rmA 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 w{fGbhUpQySz;up}4Kc2Ff&V=H+j%$%k`YSy literal 0 HcmV?d00001 diff --git a/README.md b/README.md index ddb42ea..721c4cf 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ [![Top Language](https://img.shields.io/github/languages/top/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge) [![Stars](https://img.shields.io/github/stars/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge/stargazers) [![Last Commit](https://img.shields.io/github/last-commit/OneByJorah/ChatForge?style=for-the-badge&color=FFB300&labelColor=0a0a09)](https://github.com/OneByJorah/ChatForge/commits) + +![ChatForge chat UI](.github/screenshots/main.png) [![CI](https://img.shields.io/github/actions/workflow/status/OneByJorah/ChatForge/ci.yml?style=for-the-badge&color=FFB300&labelColor=0a0a09&label=ci)](https://github.com/OneByJorah/ChatForge/actions/workflows/ci.yml) ![ChatForge UI](docs/screenshots/chatforge-ui.png)