Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Docker Publish (GHCR)
on:
release:
types: [published]
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
fail_on_unmatched_files: false
13 changes: 13 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Cloudflare
wrangler.toml
.env*

# Dependencies
node_modules/

# Build artifacts
dist/

# Misc
.DS_Store
*.log
192 changes: 103 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
<div align="center">

![ChatForge banner](docs/assets/banner.svg)
![ChatForge](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 interface — single Cloudflare Worker, Workers AI, streaming SSE, no origin server**

<a href="https://github.com/OneByJorah/ChatForge/stargazers"><img src="https://img.shields.io/github/stars/OneByJorah/ChatForge?style=flat-square" alt="Stars"></a>
<a href="https://github.com/OneByJorah/ChatForge/commits"><img src="https://img.shields.io/github/last-commit/OneByJorah/ChatForge?style=flat-square" alt="Last commit"></a>
<img src="https://img.shields.io/github/license/OneByJorah/ChatForge?style=flat-square" alt="License">
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
<img src="https://img.shields.io/badge/Cloudflare%20Workers-F38020?style=flat-square&logo=cloudflare&logoColor=white" alt="Cloudflare Workers">
<img src="https://img.shields.io/badge/streaming-SSE-6366f1?style=flat-square" alt="SSE streaming">
[![GitHub release](https://img.shields.io/github/v/release/OneByJorah/ChatForge?color=3178C6&label=release&logo=github)](https://github.com/OneByJorah/ChatForge/releases)
[![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?style=flat-square&logo=cloudflare&logoColor=white)](https://developers.cloudflare.com/workers/)
[![Workers AI](https://img.shields.io/badge/Workers%20AI-8B8CFF?style=flat-square&logo=cloudflare&logoColor=white)](https://developers.cloudflare.com/workers-ai/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?color=FFB300&logo=open-source-initiative&logoColor=FFB300)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-2496ED?style=flat-square&logo=docker&logoColor=white)](https://www.docker.com/)
[![SSE Streaming](https://img.shields.io/badge/SSE-6366f1?style=flat-square&logo=socket.io&logoColor=white)](https://html.spec.whatwg.org/multipage/networking.html#server-sent-events)

</div>

![ChatForge screenshot](docs/assets/screenshot.png)

</div>
## What This Is

ChatForge is a reference implementation of an edge-hosted chat application: a static frontend plus a streaming `/api/chat` endpoint, both running inside a single 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.

Built for developers who want a clean starting point to fork and extend for their 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.

## Quick Start

Expand All @@ -29,121 +38,126 @@ npm run dev

Open **http://localhost:8787**. Run `npm run deploy` to publish to Cloudflare Workers.

## What This Is
## Install

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.
### From npm

> [!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.
```bash
npm install chatforge
```

## Features
### From Source

- **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.
```bash
git clone https://github.com/OneByJorah/ChatForge.git
cd ChatForge
npm install
npm run dev
```

## Architecture
### Docker (local preview)

```bash
git clone https://github.com/OneByJorah/ChatForge.git
cd ChatForge
docker compose up
```
Browser ──SSE──▶ Cloudflare Worker (src/index.ts)
│
├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static files)
│
└── POST /api/chat ──▶ env.AI.run() ──▶ Workers AI (Llama 3.1 8B)
│
└── text/event-stream response

### Cloudflare Workers (production)

```bash
git clone https://github.com/OneByJorah/ChatForge.git
cd ChatForge
npm install
npx wrangler login
npm run deploy
```

Chat history is kept in browser memory only. The Worker is stateless.
## Features

## API
- **Workers AI inference** — `@cf/meta/llama-3.1-8b-instruct-fp8` at the edge, no API keys needed
- **Real-time SSE streaming** — tokens delivered as they are generated
- **Single-Worker deploy** — frontend + API with one `wrangler deploy`
- **Framework-free frontend** — vanilla HTML/CSS/JS, ~5.65 KiB gzipped
- **Security headers** — CSP, HSTS, `X-Frame-Options`, `nosniff`, referrer, permissions policy
- **Input validation** — message shape, count (max 100), length (max 32,000 chars)
- **Swappable model** — change `MODEL_ID` in `src/index.ts` to any Workers AI text-generation model
- **Docker preview** — serve static UI locally with `docker compose up`

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/chat` | `POST` | Send `{ "messages": [...] }`, receive an SSE-streamed reply |
| `/*` (non-API) | `GET` | Static frontend from `public/` |
## Tech Stack

Errors: `400` invalid JSON/messages, `405` wrong method, `404` unknown API route, `500` AI failure. Full details in [docs/API.md](docs/API.md).
- **Runtime** — Cloudflare Workers
- **AI** — Workers AI (`@cf/meta/llama-3.1-8b-instruct-fp8`)
- **Frontend** — Vanilla HTML/CSS/JavaScript, SSE
- **Deployment** — `wrangler deploy`, Docker Compose for local preview
- **Config** — `wrangler.jsonc` (compatibility date 2025-10-08, nodejs_compat, global_fetch_strictly_public)

## Configuration
## Package Badges

No runtime secrets are needed — the AI binding is declared in [`wrangler.jsonc`](wrangler.jsonc). Deployment credentials are read from the environment (never committed):
[![GitHub release](https://img.shields.io/github/v/release/OneByJorah/ChatForge?color=3178C6&label=release&logo=github)](https://github.com/OneByJorah/ChatForge/releases)
[![Cloudflare Workers](https://img.shields.io/badge/Cloudflare%20Workers-F38020?style=flat-square&logo=cloudflare&logoColor=white)](https://developers.cloudflare.com/workers/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?color=FFB300&logo=open-source-initiative&logoColor=FFB300)](https://opensource.org/licenses/MIT)

| Variable | Required | Description |
|----------|----------|-------------|
| `CLOUDFLARE_API_TOKEN` | For deploy | Cloudflare API token used by Wrangler |
| `CLOUDFLARE_ACCOUNT_ID` | For deploy | Cloudflare account ID |
## Configuration

See [.env.example](.env.example).
Edit `wrangler.jsonc` to customize:

## Supported Models
| Setting | Description |
|---------|-------------|
| `name` | Worker name (default: `chatforge`) |
| `main` | Entry point (default: `src/index.ts`) |
| `compatibility_date` | Workers compatibility date |
| `assets.directory` | Static frontend directory (default: `./public`) |
| `ai.binding` | Workers AI binding (default: `AI`) |

| 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)) |
Environment variables (set via `wrangler secret` or `.dev.vars`):

Browse the [Workers AI model catalog](https://developers.cloudflare.com/workers-ai/models/) — any text-generation model ID can be dropped in.
| Variable | Description |
|----------|-------------|
| `MODEL_ID` | Workers AI model ID (default: `@cf/meta/llama-3.1-8b-instruct-fp8`) |

## Docker Preview
## Architecture

```bash
docker compose up -d # http://localhost:8787
```
Browser ──HTTPS──> Cloudflare Worker (src/index.ts)
│
├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static files)
│
└── POST /api/chat ──> env.AI.run() ──> Workers AI (Llama 3.1 8B)
│
└── stream ──> text/event-stream response
```

> [!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.
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for full architecture details and upgrade path.

## Use Cases
## API Reference

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.
See [docs/API.md](docs/API.md) for the full API reference.

## Tech Stack
### `POST /api/chat`

Cloudflare Workers, Workers AI, TypeScript, vanilla HTML/CSS/JS, Server-Sent Events, Wrangler, Vitest, Docker (nginx:alpine preview).
Send a chat message and receive a streaming AI response.

## Screenshots
**Request:**
```json
{
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Hello!" }
]
}
```

| 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) |
**Response:** `text/event-stream` — SSE stream with `data:` lines, ends with `data: [DONE]`

## Project Structure
### `GET /` or any non-API path

```
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
```
Serves static frontend assets from the `public/` directory.

## 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.
Contributions are welcome. [Open an issue](https://github.com/OneByJorah/ChatForge/issues) to report a bug or request a feature.

## License

MIT — see [LICENSE](LICENSE).

## Connect

- [jorahone.com](https://jorahone.com)
- [GitHub Org](https://github.com/OneByJorah)
- [info@jorahone.com](mailto:info@jorahone.com)
83 changes: 45 additions & 38 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
{
"name": "ChatForge",
"description": "A simple chat application powered by Cloudflare Workers AI",
"cloudflare": {
"label": "LLM Chat App",
"products": [
"Workers",
"Workers AI",
"AI Gateway"
],
"categories": [
"ai"
],
"docs_url": "https://developers.cloudflare.com/workers-ai/",
"preview_icon_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/a974bd55-20c6-4656-fa29-1551d43ddc00/public",
"preview_image_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/65dd8441-89d3-491d-c5a1-0a000d070700/preview",
"publish": true
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.22.0",
"@cloudflare/workers-types": "^5.20260822.1",
"@types/node": "^26.2.0",
"typescript": "^7.0.2",
"vitest": "^4.1.11",
"wrangler": "^4.125.0"
},
"private": true,
"scripts": {
"cf-typegen": "wrangler types",
"check": "tsc --noEmit && wrangler deploy --dry-run",
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"typecheck": "tsc --noEmit",
"lint": "tsc --noEmit",
"format": "tsc --noEmit"
}
}
"name": "chatforge",
"description": "A simple chat application powered by Cloudflare Workers AI",
"cloudflare": {
"label": "LLM Chat App",
"products": [
"Workers",
"Workers AI",
"AI Gateway"
],
"categories": [
"ai"
],
"docs_url": "https://developers.cloudflare.com/workers-ai/",
"preview_icon_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/a974bd55-20c6-4656-fa29-1551d43ddc00/public",
"preview_image_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/65dd8441-89d3-491d-c5a1-0a000d070700/preview",
"publish": true
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.22.0",
"@cloudflare/workers-types": "^5.20260822.1",
"@types/node": "^26.2.0",
"typescript": "^7.0.2",
"vitest": "^4.1.11",
"wrangler": "^4.125.0"
},
"scripts": {
"cf-typegen": "wrangler types",
"check": "tsc --noEmit && wrangler deploy --dry-run",
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"typecheck": "tsc --noEmit",
"lint": "tsc --noEmit",
"format": "tsc --noEmit"
},
"version": "1.0.0",
"files": [
"public/",
"src/"
],
"publishConfig": {
"access": "public"
}
}
Loading