fix: SSE line buffering, provider trait plumbing, streaming safety, session resume, code block boxes, markdown improvements
Bug fixes: - Fix SSE chunk parsing: buffer partial lines across network boundaries (openai.rs) - Use Arc<dyn Provider> so send_message calls through the trait and reuses the HTTP connection pool, instead of hardcoding OpenAICompatibleProvider per request (app.rs) - Fix Ctrl+Q during streaming: persist partial response before quitting (app.rs) - Fix Ctrl+N duplicate JSONL messages: remove redundant save_current_session call and guard against new session while streaming (app.rs) - Fix code block content never rendered: text was accumulated into the wrong variable (markdown.rs) - Fix status bar stuck on 'Loading models...' after closing model selector (app.rs) - Fix CJK character panic in session title truncation: use char-level slicing (storage.rs) New features: - Session resume with Ctrl+O: list, browse, and load past chat sessions (app.rs, storage.rs) - Code block boxes: full-border rendering (top/left/right/bottom) with solid background for acrylic/transparent terminals (markdown.rs) - Code blocks fill chat area width with CJK-aware display width padding (markdown.rs, app.rs) - Markdown: add heading support (H1-H6), strikethrough, fix bold+italic by combining style stack layers (markdown.rs) Docs: - Replace CLAUDE.md with AGENTS.md containing repo-specific agent guidance
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# Meow - Agent Notes
|
||||
|
||||
Rust terminal AI chat client (TUI). Single crate, no workspace.
|
||||
|
||||
## Build & Run
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
# binary: target/release/meow
|
||||
```
|
||||
|
||||
No tests, no lint/typecheck tooling configured. Just `cargo build` / `cargo run`.
|
||||
|
||||
## Architecture (Single Crate)
|
||||
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `src/main.rs` | `tokio::main`, event loop (100ms tick), dispatches to `App` |
|
||||
| `src/app.rs` | State machine: `Chat` / `ModelSelect` / `ProviderConfig`; draws all UI; spawns stream task |
|
||||
| `src/config.rs` | `Config` / `ProviderConfig`; loads/saves `~/.meow/config.toml` via `dirs` |
|
||||
| `src/message.rs` | `Role`, `Message`, `ChatSession` with `uuid` + `chrono` |
|
||||
| `src/storage.rs` | JSON Lines persistence at `~/.meow/data/sessions/<uuid>.jsonl` |
|
||||
| `src/providers/mod.rs` | `Provider` trait (`async_trait`), `Model`, `ProviderError` |
|
||||
| `src/providers/openai.rs` | `OpenAICompatibleProvider`: `/v1/models`, `/v1/chat/completions` SSE |
|
||||
| `src/tui/mod.rs` | `Tui`: raw mode, alternate screen, mouse capture, panic hook restore |
|
||||
| `src/tui/input.rs` | Multi-line `InputState` with cursor movement; `Shift+Enter` newline, `Enter` submit |
|
||||
| `src/tui/markdown.rs` | `pulldown-cmark` → `ratatui::Line` + `syntect` highlighting (`base16-ocean.dark`) |
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **Streaming protocol**: `Provider::chat_stream` returns `BoxStream<Result<String>>`. The spawned tokio task sends chunks through an `mpsc::unbounded_channel`. Sentinel `__STREAM_END__` marks completion; `App::poll_stream()` drains the channel on each tick.
|
||||
- **Provider rebuild**: After saving config, `App::rebuild_providers()` reconstructs the `Vec<Box<dyn Provider>>` from `config.providers`. Always call this after mutating providers.
|
||||
- **First-run behavior**: If `config.providers` is empty, app boots into `ProviderConfig` state. `Esc` in that state quits the app instead of returning to chat.
|
||||
- **Ctrl+C while streaming**: Stops stream and calls `finish_stream_message()` to persist partial response. Do NOT treat as app quit during streaming.
|
||||
- **Session persistence**: `Storage::append_message` is append-only JSON Lines. `save_current_session()` iterates messages and appends each (safe to call multiple times; duplicates are harmless in current design).
|
||||
- **Syntax highlighting**: `syntect` uses `regex-fancy` backend (not `onig`). Theme hardcoded to `base16-ocean.dark`.
|
||||
|
||||
## Runtime Data Paths
|
||||
|
||||
- Config: `~/.meow/config.toml`
|
||||
- Sessions: `~/.meow/data/sessions/<uuid>.jsonl`
|
||||
|
||||
## Dependencies to Know
|
||||
|
||||
- `reqwest` with `rustls-tls`, `stream` — SSE via `bytes_stream()`
|
||||
- `ratatui` 0.29 + `crossterm` 0.28 — all TUI rendering
|
||||
- `syntect` with `default-themes`, `default-syntaxes`, `regex-fancy`
|
||||
- `pulldown-cmark` 0.12 — markdown parser
|
||||
|
||||
## Adding a New Provider
|
||||
|
||||
Only `OpenAICompatibleProvider` exists. To add a non-OpenAI provider:
|
||||
|
||||
1. Implement `Provider` trait in `src/providers/<name>.rs`
|
||||
2. Register in `src/providers/mod.rs`
|
||||
3. Wire instantiation in `App::new()` and `App::rebuild_providers()`
|
||||
Reference in New Issue
Block a user