diff --git a/AGENTS.md b/AGENTS.md index 60c6b2e..2f19343 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -138,6 +138,7 @@ All streaming endpoints yield `data: {json}\n\n`. Key shapes: ### Completed this session - **B7 (v0.19.0)** — Apple Silicon worker support. `gpu.py` now detects `sys.platform == "darwin"` and parses `system_profiler SPDisplaysDataType` for GPU model/VRAM instead of `rocm-smi`. `hardware.py` has darwin branch via `_get_vram_darwin()`. `node_agent/agent.py` reports VRAM on macOS via `system_profiler`. 5 new tests cover linux/darwin gpu paths, 3 new hardware tests cover darwin assessment + VRAM parsing. - **B5 (v0.19.1)** — Default model auto-pull on first start. `model_pull.py` with `ensure_model()` checks llama-server availability, falls back to Ollama pull API. Integrated into `app.py` lifespan. 11 tests cover all paths. +- **B6 (v0.19.2)** — Waterfall direction toggle. NEW/OLD topbar button toggles between newest-first and oldest-first ordering. `scrollToTop()` replaced by `scrollToLatest()` which checks direction. Preference persisted in localStorage. - **Scroll-position fighting** — `scrollToTop()` now respects `_userScrolledAway` flag (100px threshold), skips auto-scroll when user is reading older content. `resetScrollLock()` called on new messages. - **401 error cascade** — `SESSION_TIMEOUT_SECONDS` bumped 90→3600 (1 hour). All 10 unprotected `authFetch` calls wrapped in try/catch. - **Token counter** — removed localStorage persistence; resets to 0 on page refresh. @@ -157,13 +158,12 @@ All streaming endpoints yield `data: {json}\n\n`. Key shapes: ### Upcoming (backlog) - B4 — RAG Corpus Management UI -- B6 — waterfall direction toggle - B8 — **Encryption & PHI readiness** — spec out encryption at rest (SQLCipher for caic.db, Qdrant payload encryption) and in-transit (TLS for inference, AMQP, RAG). Per-user auth, audit logging, log sanitizer, data lifecycle. Document the "personal LAN HIPAA gap." - **Preferred approach: Private Chat mode** — a toggle that skips DB persistence, memory/RAG injection, content logging, and **external SearXNG searching** entirely. Zero stored data, zero external queries = zero data to protect. Simpler, more robust, less code to audit than full encryption. Design this as the primary PHI path before reaching for crypto. - **In-transit still needs TLS** — Private Chat eliminates at-rest risk, but AMQP (RabbitMQ) and inference (llama-server) traffic between coordinator and workers is still plaintext on the wire. TLS termination on each node is the lightweight fix (self-signed CA, nginx sidecar or RabbitMQ TLS). ### Key config values (current) -- `VERSION = "v0.19.1"` in `config.py` +- `VERSION = "v0.19.2"` in `config.py` - `SESSION_TIMEOUT_SECONDS = 3600` - `DEFAULT_MODEL = "qwen2.5-7b-instruct"` - `LLAMA_SERVER_BASE = "http://192.168.50.108:8081"` diff --git a/README.md b/README.md index 7e3af1a..3cb9e87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@  -# cAIc v0.19.1 +# cAIc v0.19.2 Consumer AI hardware is a wasteland of incompatibility. NVIDIA speaks CUDA, AMD speaks ROCm. Your RTX 5070 Ti lives in one machine with 16 GB VRAM; your RX 6600 XT lives in another with 12 GB. Alone, neither can run a 14B model at usable speed. Together, they could — if the software stack didn't treat heterogeneous hardware as a bug instead of a feature. @@ -42,6 +42,13 @@ At v1.0, this ships with a Docker compose stack and setup wizard that detect CPU Developer wiki: [Home](https://llgit.llamachile.tube/gramps/cAIc/wiki/Home) — includes [FAQ](https://llgit.llamachile.tube/gramps/cAIc/wiki/FAQ), [Installation Guide](https://llgit.llamachile.tube/gramps/cAIc/wiki/Installation), and [full architecture docs](https://llgit.llamachile.tube/gramps/cAIc/wiki/Developer-Architecture) +## What's New in v0.19.2 + +### Waterfall Direction Toggle (B6) +- **NEW/OLD toggle** — topbar button switches between newest-first (waterfall) and oldest-first (traditional chat) +- **Direction-aware scroll** — newest-first scrolls to top, oldest-first scrolls to bottom +- **localStorage persistence** — preference survives page reloads, default is newest-first (waterfall) + ## What's New in v0.19.1 ### Default Model Auto-Pull on First Start (B5) @@ -49,6 +56,14 @@ Developer wiki: [Home](https://llgit.llamachile.tube/gramps/cAIc/wiki/Home) — - **Startup integration** — `app.py` lifespan calls `ensure_model()` after `assess_hardware()`, pulling the missing model via Ollama's streaming pull API - **Idempotent** — skips pull if model already available on llama-server or Ollama +## What's New in v0.19.0 + +### Apple Silicon Worker Support (B7) +- **`gpu.py`** — now detects `sys.platform == "darwin"` and parses `system_profiler SPDisplaysDataType` for GPU model/VRAM instead of `rocm-smi` +- **`hardware.py`** — darwin branch via `_get_vram_darwin()` for VRAM assessment on macOS +- **`node_agent/agent.py`** — `get_load()` reports VRAM via `system_profiler SPDisplaysDataType` on macOS workers +- Hybrid detection — falls back to AMD rocm-smi on Linux, `available: False` if neither is detected + ## What's New in v0.18.0 ### Wiki — Installation Guide, Screenshots Gallery, Full Documentation diff --git a/config.py b/config.py index 487d114..84e7200 100644 --- a/config.py +++ b/config.py @@ -9,7 +9,7 @@ import logging log = logging.getLogger("caic") -VERSION = "v0.19.1" +VERSION = "v0.19.2" OLLAMA_BASE = os.environ.get("OLLAMA_BASE", "http://localhost:11434") LLAMA_SERVER_BASE = os.environ.get("LLAMA_SERVER_BASE", "http://192.168.50.108:8081") SEARXNG_BASE = "http://localhost:8888" diff --git a/templates/index.html b/templates/index.html index 0be464e..65bf57e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -401,6 +401,7 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var( + @@ -1101,7 +1102,7 @@ async function loadConversation(convId) { container.innerHTML = ''; conversationHistory = []; data.messages.forEach(msg => { appendMessage(msg.role, msg.content, false); conversationHistory.push({ role: msg.role, content: msg.content }); }); - scrollToTop(); + scrollToLatest(); await loadConversations(); } catch(e) {} } @@ -1179,7 +1180,7 @@ async function sendSearch() { if (data.error) { textEl.textContent = 'Error: ' + data.error; setStreamingState(false); return; } if (data.conversation_id && !currentConvId) { currentConvId = data.conversation_id; await loadConversations(); } if (data.search_results) { textEl.innerHTML = '