# chat-zig **Repository Path**: peter9975/chat-zig ## Basic Information - **Project Name**: chat-zig - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-08 - **Last Updated**: 2026-09-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ChatZig An AI chat application built with [Gooey](https://github.com/duanebester/gooey) that connects to both Anthropic's Claude and OpenAI's GPT models. Inspired by [chat-ai](https://github.com/duanebester/chat-ai) (GPUI/Rust version). > **Status:** Zig 0.16 / Gooey v0.2.9. Uses the new `std.Io` stack end-to-end โ€” > `std.Io.Group` for fiber lifecycle, `std.Io.Queue(T)` for cross-thread result > delivery, `std.Io.Dir` / `std.Io.File` for attachment and session-log reads, > and the `io`-aware `std.http.Client` for both the Anthropic and OpenAI > request paths.
## Features - ๐Ÿ’ฌ Chat with Claude and GPT models, switchable per-conversation from the settings panel - ๐ŸŽ™๏ธ Voice dictation โ€” record a microphone clip and transcribe it via OpenAI (`gpt-4o-mini-transcribe`) - ๐Ÿงน Automatic chat compaction โ€” long conversations are summarized so requests stay bounded (see [`docs/CHAT_COMPACTION.md`](docs/CHAT_COMPACTION.md)) - ๐Ÿ’พ Session persistence โ€” conversations are logged to `sessions/*.jsonl` and browsable/restorable from a history panel - ๐ŸŽจ Light/dark theme, glass window effect - ๐Ÿ“œ Virtual list for efficient message rendering - ๐Ÿ”„ Async HTTP requests via `std.Io.Group` + `std.Io.Queue` (non-blocking UI, zero heap per result), with retry/backoff shared across providers - โœ‚๏ธ Structured cancellation โ€” window close cleanly unwinds in-flight fetches - โŒจ๏ธ Text input with send button, file attachments, and a live microphone level meter while recording ## Prerequisites 1. **Zig 0.16.0** โ€” Install from [ziglang.org](https://ziglang.org/download/). Older versions will not build; the code depends on `std.Io`, `std.process.Init`, and the new `std.http.Client.io` field. 2. **Anthropic API Key** โ€” Get one from [Anthropic Console](https://console.anthropic.com/). Required for Claude models. 3. **OpenAI API Key** (optional) โ€” Get one from [OpenAI Platform](https://platform.openai.com/). Enables GPT chat models and voice dictation transcription; the app runs fine without it, just with those features disabled. Gooey is pinned to [v0.2.9](https://github.com/duanebester/gooey/releases/tag/v0.2.9) in `build.zig.zon` (tarball URL + hash), so no local checkout of Gooey is needed. macOS only for now โ€” audio capture uses CoreAudio/AudioToolbox directly. ## Setup ```bash # Clone git clone https://github.com/yourusername/chat-zig cd chat-zig # Set your API key(s) export ANTHROPIC_API_KEY="your-key-here" export OPENAI_API_KEY="your-key-here" # optional: GPT models + voice dictation # Build and run zig build run # Run the (offline) unit tests โ€” JSON escaping, response parsing, base64, # compaction cut arithmetic, session log framing, date formatting, etc. zig build test ``` ## Architecture This follows the Gooey pattern for stateful apps, using Zig 0.16's `std.Io` primitives for all async work: 1. **`main(init: std.process.Init)`** โ€” pulls `io` + `environ` from the runtime-provided `Init` struct and publishes them on module-level globals so `AppState.init` can reach them without extra plumbing. 2. **AppState** โ€” single source of truth. Owns a `std.Io.Group` (for fiber lifecycle), a `std.Io.Queue(WorkerResult)` (for result delivery), and the staging buffers that workers fill. 3. **`layout.zig` + `src/components/`** โ€” render UI based on current state. The top of `render` calls `s.drainResults(cx)` to apply any completed worker outputs before anything else reads `is_loading` or `messages`. `layout.zig` composes the root from the components in `src/components/`; each component reaches for its siblings directly rather than through a shared registry, so the dependency edges stay visible in each file's imports. 4. **Command handlers** โ€” update state in response to user actions. `sendMessage` launches an HTTP worker via `io_group.async(...)` instead of spawning a raw `std.Thread` + dispatcher trampoline. The worker picks the Anthropic or OpenAI client based on `AppState.selected_model`'s provider; both clients share the retry/backoff policy in `src/http/http.zig`. ### Async result flow ```zig // sendMessage launches a fiber on the shared Io instance. // The Io.Group owns the task โ€” cancellation on window close unwinds it. self.io_group.async(io, httpWorker, .{ io, &self.http_client.?, self, &self.result_queue, }); // httpWorker runs off the main thread, blocks on the HTTP call, writes // the response text into the staging buffer, and signals completion. fn httpWorker( io: std.Io, client: *http.AnthropicClient, app: *AppState, queue: *std.Io.Queue(WorkerResult), ) void { var result = client.sendBlocking(request); defer result.deinit(client.allocator); // Write staging buffer BEFORE putOne โ€” `putOne` is the happens-before // edge that makes these writes visible to the render thread. const outcome: WorkerResult = ...; queue.putOne(io, outcome) catch {}; app.requestRenderFromWorker(); // nudge the event loop } // The render loop drains the queue each frame โ€” non-blocking, bounded. pub fn drainResults(self: *AppState, cx: *gooey.Cx) void { var buf: [RESULT_QUEUE_CAPACITY]WorkerResult = undefined; for (cx.drainQueue(WorkerResult, &self.result_queue, &buf)) |r| { switch (r) { .chat_success => |ok| self.applyChatSuccess(ok), .chat_error => |err| self.applyChatError(err), } } } ``` No per-request heap allocation, no dispatcher trampoline, and the `std.Io.Group` registration with Gooey guarantees that window close cancels any in-flight fetch before `AppState` is torn down. ### Compaction and session persistence Long conversations are summarized rather than truncated: `AppState.messages` is never mutated, and a small summary + cut index is layered on top only when assembling the next request. See [`docs/CHAT_COMPACTION.md`](docs/CHAT_COMPACTION.md) for the full design, including how the cut is computed across ring-buffer overflow and how sessions are framed and restored from `sessions/*.jsonl`. ## License MIT