Initial commit: Meow TUI AI chat client
A Rust terminal chat client with OpenAI-compatible provider support, real-time SSE streaming, markdown rendering with syntax highlighting, and persistent chat history. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# Meow - Rust Terminal AI Chat Client
|
||||
|
||||
A high-performance terminal UI (TUI) AI chat client written in Rust, supporting custom OpenAI-compatible providers (DeepSeek, Kimi, etc.).
|
||||
|
||||
## Overview
|
||||
|
||||
Meow is a pure-text terminal chat application. It features:
|
||||
|
||||
- OpenAI-compatible provider abstraction layer
|
||||
- Real-time streaming (SSE) chat output
|
||||
- Markdown rendering with syntax highlighting
|
||||
- Persistent chat history in JSON Lines format
|
||||
- Mouse scroll support for message history
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Component | Crate |
|
||||
|-----------|-------|
|
||||
| Async Runtime | `tokio` |
|
||||
| HTTP Client | `reqwest` (with streaming/SSE) |
|
||||
| TUI Framework | `ratatui` + `crossterm` |
|
||||
| Markdown Parser | `pulldown-cmark` |
|
||||
| Syntax Highlighting | `syntect` (fancy-regex backend) |
|
||||
| Serialization | `serde` + `serde_json` + `toml` |
|
||||
| Configuration Path | `dirs` (cross-platform home dir) |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Layered Design
|
||||
|
||||
```
|
||||
TUI Layer (ratatui widgets)
|
||||
├── chat.rs (implicit in app.rs)
|
||||
├── model_select.rs (popup)
|
||||
├── provider_config.rs (form)
|
||||
├── input.rs (multi-line text area)
|
||||
└── markdown.rs (rendering pipeline)
|
||||
|
||||
App Layer (state machine)
|
||||
└── app.rs - coordinates all modules, event loop, async stream handling
|
||||
|
||||
Provider Layer
|
||||
├── providers/mod.rs - Provider trait
|
||||
└── providers/openai.rs - OpenAI-compatible implementation
|
||||
|
||||
Storage Layer
|
||||
├── config.rs - ~/.meow/config.toml
|
||||
├── storage.rs - ~/.meow/data/sessions/*.jsonl
|
||||
└── message.rs - core data types
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. User types message in input box
|
||||
2. `Enter` triggers `App::send_message()`
|
||||
3. User message is saved to disk via `Storage::append_message()`
|
||||
4. A tokio task spawns to call `Provider::chat_stream()`
|
||||
5. SSE chunks flow back through `tokio::sync::mpsc::UnboundedChannel`
|
||||
6. Main event loop polls channel via `App::poll_stream()` and redraws
|
||||
7. When stream ends (`__STREAM_END__`), the full assistant message is persisted
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.rs # Entry point: initializes tokio, Tui, App, event loop
|
||||
├── app.rs # App state machine: Chat / ModelSelect / ProviderConfig
|
||||
├── config.rs # Config & ProviderConfig structs, load/save to ~/.meow/config.toml
|
||||
├── message.rs # Role enum, Message, ChatSession
|
||||
├── storage.rs # JSON Lines persistence for chat sessions
|
||||
├── providers/
|
||||
│ ├── mod.rs # Provider trait, Model struct, ProviderError
|
||||
│ └── openai.rs # OpenAICompatibleProvider: /v1/models, /v1/chat/completions SSE
|
||||
└── tui/
|
||||
├── mod.rs # Tui struct: terminal init/restore, raw mode, panic hooks
|
||||
├── input.rs # InputState: multi-line editor with cursor movement
|
||||
└── markdown.rs # MarkdownRenderer: pulldown-cmark -> ratatui Lines + syntect highlighting
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Config is stored at `~/.meow/config.toml` (cross-platform via `dirs::home_dir()`).
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[[providers]]
|
||||
name = "DeepSeek"
|
||||
base_url = "https://api.deepseek.com"
|
||||
api_key = "sk-..."
|
||||
default_model = "deepseek-chat"
|
||||
```
|
||||
|
||||
Chat sessions are stored as JSON Lines at `~/.meow/data/sessions/<uuid>.jsonl`.
|
||||
|
||||
## Keybindings
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `Enter` | Send message |
|
||||
| `Shift+Enter` | Newline in input |
|
||||
| `Ctrl+P` | Open model selector |
|
||||
| `Ctrl+S` | Open provider config |
|
||||
| `Ctrl+N` | Start new chat session |
|
||||
| `Ctrl+B` | Toggle sidebar |
|
||||
| `Ctrl+R` | Reset current chat |
|
||||
| `Ctrl+C` | Stop streaming (while AI is typing) |
|
||||
| `Ctrl+Q` | Quit |
|
||||
| `Mouse Scroll` | Scroll message history |
|
||||
|
||||
## Provider Protocol
|
||||
|
||||
The `Provider` trait (`async_trait`) defines:
|
||||
|
||||
- `name() -> &str`
|
||||
- `config() -> &ProviderConfig`
|
||||
- `list_models() -> Result<Vec<Model>>`
|
||||
- `chat_stream(model, messages) -> Result<BoxStream<Result<String>>>`
|
||||
|
||||
`OpenAICompatibleProvider` implements this for any OpenAI API-compatible endpoint.
|
||||
|
||||
## Markdown Support
|
||||
|
||||
Rendered elements:
|
||||
|
||||
- **Bold** / *Italic*
|
||||
- `Inline code` (dark background)
|
||||
- Code blocks with syntax highlighting (syntect themes)
|
||||
- Bullet / numbered lists
|
||||
- Blockquotes (│ prefix)
|
||||
- Horizontal rules
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
On first run, if no providers exist, the app automatically enters the **Add Provider** form. Fill in name, base URL, and API key to get started.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The binary will be at `target/release/meow.exe` (Windows) or `target/release/meow` (Unix).
|
||||
Reference in New Issue
Block a user