PinchChat
[](https://github.com/MarlBurroW/pinchchat/actions/workflows/ci.yml)
[](https://github.com/MarlBurroW/pinchchat/releases)
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
[](https://github.com/MarlBurroW/pinchchat/pkgs/container/pinchchat)
[](https://github.com/MarlBurroW/pinchchat)
[](https://marlburrow.github.io/pinchchat/)
[](CODE_OF_CONDUCT.md)
**A sleek, dark-themed webchat UI for [OpenClaw](https://github.com/openclaw/openclaw) โ monitor sessions, stream responses, and inspect tool calls in real-time.**
## โจ Features
- ๐ง **Tool call visualization** โ see what your agent is doing in real-time: colored badges, visible parameters, expandable results. The killer feature missing from every other chat UI.
- ๐ฌ **GPT-like interface** โ sessions in a sidebar, switch between conversations. Familiar if you've used ChatGPT or Claude.
- ๐ **Multi-session navigation** โ browse all active sessions including cron jobs, sub-agents, and background tasks
- โก **Live streaming** โ watch the agent think and write token by token
- ๐ **Token usage tracking** โ progress bars per session so you know how much context is left
- ๐ผ๏ธ **Inline images** โ generated or read images render directly in chat with lightbox preview
- ๐ฏ **Chat-focused** โ no settings menus or config panels cluttering the screen. Just the conversation.
- ๐จ **Themes** โ Dark, Light, and OLED Black modes with 6 accent colors. Persisted per-browser.
- ๐ง **Thinking/reasoning display** โ see the agent's reasoning process in collapsible blocks with elapsed time
- ๐ **Message search** โ Ctrl+F to search and navigate through conversation history
- ๐ **Split view** โ open 2 sessions side by side with a resizable divider
- โ๏ธ **Syntax-highlighted input** โ real-time markdown coloring as you type (code blocks, bold, links)
- ๐ **Drag & drop reorder** โ organize sessions in the sidebar by dragging, order persists across reloads
- ๐ **Raw JSON viewer** โ inspect full gateway message payloads for debugging
- ๐๏ธ **Channel icons** โ Discord, Telegram, cron, and other session types shown with distinct icons
- ๐ค **Export conversations** โ download any session as a formatted Markdown file
- ๐ **i18n** โ English and French built-in, easy to extend
- ๐ **Notification sounds** โ subtle audio chime for new messages (toggleable)
- โจ๏ธ **Keyboard shortcuts** โ navigate sessions, toggle sidebar, search, and more without touching the mouse
- ๐ฑ **PWA support** โ installable as a progressive web app with offline caching and auto-updates
- โฟ **Accessible** โ skip-to-chat link, focus-visible outlines, semantic HTML, `prefers-reduced-motion` support, ARIA live regions
## ๐ Quick Start
### Docker (recommended)
```bash
docker run -p 3000:80 ghcr.io/marlburrow/pinchchat:latest
```
Open `http://localhost:3000` and enter your OpenClaw gateway URL + token on the login screen.
Or use Docker Compose:
```bash
curl -O https://raw.githubusercontent.com/MarlBurroW/pinchchat/main/docker-compose.yml
docker compose up -d
```
### From source
**Prerequisites:** Node.js 20+, an OpenClaw gateway running and accessible.
```bash
git clone https://github.com/MarlBurroW/pinchchat.git
cd pinchchat
npm install
cp .env.example .env
npm run dev
```
Optionally edit `.env` to pre-fill the gateway URL:
```env
VITE_GATEWAY_WS_URL=ws://localhost:18789
VITE_LOCALE=en # or "fr" for French UI
```
### Production build
```bash
npm run build
npx vite preview
```
Or serve the `dist/` folder with nginx, Caddy, or any static file server.
## โ๏ธ Configuration
All configuration is optional โ credentials are entered at runtime via the login screen.
| Variable | Description | Default |
|---|---|---|
| `VITE_GATEWAY_WS_URL` | Pre-fill the gateway URL on the login screen | `ws://:18789` |
| `VITE_LOCALE` | UI language (`en` or `fr`) | `en` |
> **Note:** The gateway token is entered at runtime and stored in `localStorage` โ it is never baked into the build.
## ๐จ Customization
PinchChat stores all preferences in `localStorage` โ no server-side config needed.
### Themes
Click the palette icon in the header to switch between:
| Theme | Description |
|-------|-------------|
| **Dark** (default) | Zinc-based dark theme, easy on the eyes |
| **Light** | Clean light mode with white backgrounds |
| **OLED** | Pure black backgrounds for OLED screens |
| **System** | Follows your OS dark/light preference |
### Accent Colors
Six accent colors are available: **Cyan** (default), **Violet**, **Emerald**, **Amber**, **Rose**, and **Blue**. The accent tints buttons, links, progress bars, and user message bubbles.
### Other Preferences
These settings persist across sessions:
- **Sidebar width** โ drag the right edge to resize
- **Session order** โ drag & drop to reorder; pinned sessions stay on top
- **Syntax highlighting** โ toggle the `>` button in the input area
- **Split view** โ open two sessions side by side (icon in header)
- **Language** โ switch between English and French via the globe icon
- **Message drafts** โ input text is preserved per-session when switching
## ๐ Architecture
```mermaid
graph TD
subgraph Browser["๐ PinchChat (Browser)"]
Login["LoginScreen
credentials"]
App["App.tsx
router"]
UI["Chat + Sidebar
main UI"]
Hook["useGateway
WebSocket state machine
auth ยท sessions ยท messages"]
Login --> App --> UI
App & UI --> Hook
end
Hook <-->|"WebSocket (JSON frames)"| Gateway["๐ OpenClaw Gateway
ws://host:18789"]
Gateway <-->|API| LLM["๐ค LLM Provider
Anthropic, OpenAI, etc."]
```
### Key Components
| File | Role |
|---|---|
| `src/hooks/useGateway.ts` | WebSocket connection, auth, message streaming, session management |
| `src/components/LoginScreen.tsx` | Runtime credential entry (stored in `localStorage`) |
| `src/components/Chat.tsx` | Message list with auto-scroll and streaming display |
| `src/components/ChatInput.tsx` | Input with file upload, paste, drag & drop, image compression |
| `src/components/ChatMessage.tsx` | Markdown rendering, tool calls, thinking blocks |
| `src/components/Sidebar.tsx` | Session list with token usage bars and activity indicators |
| `src/components/Header.tsx` | Connection status, token progress bar, logout |
| `src/lib/i18n.ts` | Lightweight i18n (English + French) |
| `src/lib/gateway.ts` | WebSocket protocol helpers and message types |
### Data Flow
1. **Login** โ User enters gateway URL + token โ stored in `localStorage`
2. **Connect** โ `useGateway` opens a WebSocket and authenticates with the token
3. **Sessions** โ Gateway pushes session list; user selects one in the sidebar
4. **Messages** โ Messages stream in via WebSocket frames; the hook assembles partial chunks into complete messages
5. **Send** โ User input (+ optional file attachments) is sent as a JSON frame over the WebSocket
> ๐ For a deeper dive into the codebase structure, see [ARCHITECTURE.md](ARCHITECTURE.md).
## ๐ Adding a Language
PinchChat uses a zero-dependency i18n system. Adding a new language takes ~5 minutes:
1. **Open `src/lib/i18n.ts`** and duplicate the `en` object with your locale code:
```ts
const de: typeof en = {
'login.title': 'PinchChat',
'login.subtitle': 'Verbinde dich mit deinem OpenClaw-Gateway',
// ... translate all keys
};
```
2. **Register it** in the `messages` record (same file):
```ts
const messages: Record = { en, fr, de };
```
3. **Add a label** for the language selector:
```ts
export const localeLabels: Record = {
en: 'EN',
fr: 'FR',
de: 'DE',
};
```
4. **Done.** The language selector, `VITE_LOCALE`, and browser auto-detection all pick it up automatically.
> **Tip:** TypeScript enforces that your new locale object has the same keys as `en` โ missing translations won't compile.
## โจ๏ธ Keyboard Shortcuts
Press **?** anywhere to open the shortcuts panel.
| Shortcut | Action |
|---|---|
| `Enter` | Send message |
| `Shift + Enter` | New line |
| `Esc` | Stop generation / close sidebar |
| `Ctrl/โ + F` | Search messages in current session |
| `Ctrl/โ + K` | Focus session search |
| `Alt + โ` / `Alt + โ` | Switch between sessions |
| `?` | Show shortcuts help |
## โ Troubleshooting
### Connection fails / "WebSocket error"
- Make sure your OpenClaw gateway is running and reachable from the browser
- Check the gateway URL format: `ws://host:18789` (or `wss://` if behind TLS)
- If PinchChat is served over HTTPS, the gateway **must** also use `wss://` โ browsers block mixed content
- Verify the gateway token is correct (copy-paste from your OpenClaw config)
### Blank screen after login
- Open your browser's developer console (F12) and check for errors
- Ensure the gateway is running a compatible version of OpenClaw (v0.24+)
- Try clearing `localStorage` (Application tab โ Storage โ Clear site data) and logging in again
### Messages don't appear / sessions empty
- The WebSocket may have disconnected silently โ check the connection indicator in the header
- If the gateway was restarted, PinchChat reconnects automatically, but you may need to refresh once
- Verify the token has access to the sessions you expect
### Docker: "port already in use"
```bash
# Check what's using port 3000
lsof -i :3000
# Use a different port
docker run -p 8080:80 ghcr.io/marlburrow/pinchchat:latest
```
### Images not displaying
- Inline images require the gateway to send media as base64 data URLs or accessible HTTP URLs
- If images show as broken, the gateway may be behind a reverse proxy that strips large payloads โ check proxy buffer settings
### Debugging
PinchChat includes built-in debugging tools:
**WebSocket debug logging** โ open the browser console (F12) and run:
```js
localStorage.setItem('pinchchat:debug', '1');
```
Reload the page. All WebSocket frames (sent and received) will be logged to the console with a `[GW]` prefix. Set to `'0'` to disable.
**Raw JSON viewer** โ click the `{ }` toggle on any message to see the full gateway payload as formatted JSON. Useful for understanding the protocol or reporting bugs.
**Metadata inspector** โ hover over any message and click the โน๏ธ icon to see message metadata (timestamp, ID, channel, sender info).
### Build errors from source
```bash
# Ensure Node.js 20+
node --version
# Clean install
rm -rf node_modules package-lock.json
npm install
npm run build
```
## ๐ Reverse Proxy
PinchChat is a static SPA โ serve it behind any reverse proxy. The only special requirement is WebSocket support for the OpenClaw gateway connection.
> **Note:** PinchChat itself only serves static files (HTML/JS/CSS). The WebSocket connection goes directly from the **browser** to your OpenClaw gateway, not through PinchChat's server. You only need to proxy WebSocket if you're also putting the gateway behind the same reverse proxy.
### Nginx
```nginx
server {
listen 443 ssl;
server_name chat.example.com;
# PinchChat static files
location / {
proxy_pass http://127.0.0.1:3000;
# Or serve directly from the build output:
# root /path/to/pinchchat/dist;
# try_files $uri $uri/ /index.html;
}
}
# Optional: proxy the OpenClaw gateway too
server {
listen 443 ssl;
server_name gw.example.com;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # Keep WebSocket alive
}
}
```
### Caddy
```caddyfile
chat.example.com {
reverse_proxy 127.0.0.1:3000
}
# Optional: proxy the gateway
gw.example.com {
reverse_proxy 127.0.0.1:18789
}
```
Caddy handles WebSocket upgrades and TLS automatically.
### Traefik (Docker labels)
```yaml
services:
pinchchat:
image: ghcr.io/marlburrow/pinchchat:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.pinchchat.rule=Host(`chat.example.com`)"
- "traefik.http.routers.pinchchat.tls.certresolver=letsencrypt"
- "traefik.http.services.pinchchat.loadbalancer.server.port=80"
```
### Traefik (Kubernetes IngressRoute)
```yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: pinchchat
spec:
entryPoints: [websecure]
routes:
- match: Host(`chat.example.com`)
kind: Rule
services:
- name: pinchchat
port: 80
tls:
certResolver: letsencrypt
```
### Important notes
- **HTTPS + WSS**: If PinchChat is served over HTTPS, the gateway URL must use `wss://` โ browsers block mixed content (`https` page โ `ws://` connection)
- **Timeouts**: Set generous read timeouts for the gateway proxy (WebSocket connections are long-lived). Nginx defaults to 60s which will drop the connection.
- **Buffering**: If images aren't loading, increase proxy buffer sizes (`proxy_buffer_size`, `proxy_buffers` in Nginx)
- **Health checks**: The PinchChat container responds to `GET /` with the SPA โ use it as a health check endpoint
## ๐ Tech Stack
- [React](https://react.dev/) 19
- [Vite](https://vite.dev/) 7
- [Tailwind CSS](https://tailwindcss.com/) v4
- [Radix UI](https://www.radix-ui.com/) primitives
- [highlight.js](https://highlightjs.org/) via rehype-highlight
- [Lucide React](https://lucide.dev/) icons
- [react-markdown](https://github.com/remarkjs/react-markdown) with GFM
## ๐ License
[MIT](LICENSE) ยฉ Nicolas Varrot
## ๐ Changelog
See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.
## ๐ Security
See [SECURITY.md](SECURITY.md) for the security policy and how to report vulnerabilities.
## ๐ค Contributing
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## ๐ Links
- [OpenClaw](https://github.com/openclaw/openclaw) โ the AI agent platform PinchChat connects to