From 1659a1055c18474d2196695089e8adc70f93cabe Mon Sep 17 00:00:00 2001 From: Dodothereal <129273127+Dodothereal@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:15:44 +0200 Subject: [PATCH 01/67] docs(cli): update README for v0.0.4 multi-provider behavior The cli/README.md was stale against v0.0.4: it described a single-provider (Anthropic + Modal) setup, claimed the wizard downloaded the compose file from raw.githubusercontent.com, and omitted `trainable reconfigure`. Update it to match the current implementation: - multi-provider selection (Claude API key / subscription OAuth, OpenAI, Gemini, LiteLLM-routed backends) as a flat picker, with a Providers table - compose file bundled inside the wheel (offline installs) - version-pinned images on `trainable up` - `trainable reconfigure` command and the existing-config merge flow Assisted-by: Claude Code --- cli/README.md | 51 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/cli/README.md b/cli/README.md index 892a2f0..c24ab46 100644 --- a/cli/README.md +++ b/cli/README.md @@ -18,22 +18,57 @@ Config lives at `~/.trainable/`, so `trainable up` / `trainable down` work from any directory. Override the location with `TRAINABLE_HOME=...`. The wizard will: -1. Check that Docker is installed -2. Download the production Docker Compose file to `~/.trainable/` -3. Prompt for your API keys (Anthropic + Modal) +1. Check that Docker and the Compose plugin are installed +2. Write the production Docker Compose file to `~/.trainable/` (bundled inside + the wheel, so installs work offline) +3. Prompt for your LLM provider keys — pick any of Claude, OpenAI, Gemini, or + LiteLLM-routed backends (see Providers below) 4. Write a `.env` file 5. Start the full stack +## Providers + +The backend treats all four LLM providers as equal peers. Collect **at least +one**; you can add more any time with `trainable reconfigure`. + +| Provider | Env var(s) | How to get it | +|----------|------------|---------------| +| Claude (API key) | `ANTHROPIC_API_KEY` | [console.anthropic.com](https://console.anthropic.com) | +| Claude (subscription) | `CLAUDE_CODE_OAUTH_TOKEN` | run `claude setup-token` | +| OpenAI | `OPENAI_API_KEY` | [platform.openai.com/api-keys](https://platform.openai.com/api-keys) | +| Gemini | `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) | [aistudio.google.com/apikey](https://aistudio.google.com/apikey) | +| LiteLLM | free-form, e.g. `GROQ_API_KEY`, `MISTRAL_API_KEY` | your backend's dashboard | + +All setups also need Modal credentials for sandboxed code execution: +`MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` from +[modal.com/settings](https://modal.com/settings). + ## Commands | Command | Description | |---------|-------------| -| `trainable init` | Setup wizard — downloads compose file, configures secrets, launches | -| `trainable up` | Start all services | +| `trainable init` | Setup wizard — writes compose file, configures secrets, launches | +| `trainable reconfigure` | Add or replace LLM providers without losing existing keys | +| `trainable up` | Start all services (works from any directory) | | `trainable down` | Stop all services | +### Reconfiguring + +Re-running `trainable init` on top of an existing config never clobbers your +other keys — it offers to add/replace a single provider or start over, and +preserves everything you don't touch. `trainable reconfigure` is a friendly +alias for the same flow. + +## Version-pinned images + +`trainable up` pins the backend and frontend images to the installed wheel's +version (e.g. `0.0.4`), so `pip install -U trainable-ai && trainable up` always +pulls the matching `ghcr.io/.../:` rather than reusing a stale +`:latest` from your local Docker cache. + ## Requirements -- Docker with Compose plugin -- [Anthropic API key](https://console.anthropic.com/) -- [Modal account](https://modal.com/) — get tokens from [modal.com/settings](https://modal.com/settings) +- Docker with the Compose plugin +- At least one LLM provider key (see Providers above) +- [Modal account](https://modal.com/) — tokens from + [modal.com/settings](https://modal.com/settings) From 90e21d181d6b8d7d2f551b04997dea7bbc705ce9 Mon Sep 17 00:00:00 2001 From: Lucas Tonon Date: Sat, 18 Jul 2026 22:52:35 -0300 Subject: [PATCH 02/67] docs: fix broken issue-tracker link (trainable-monorepo -> trainable) (#123) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4 --- CONTRIBUTING.md | 2 +- docs/ARCHITECTURE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ca29e2..76852e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,6 +77,6 @@ All tests must pass before submitting a PR. ## Reporting Issues -- Use [GitHub Issues](https://github.com/lucastononro/trainable-monorepo/issues) +- Use [GitHub Issues](https://github.com/lucastononro/trainable/issues) - Include steps to reproduce, expected vs actual behavior, and environment details - For security vulnerabilities, please email the maintainer directly instead of opening a public issue diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e5d37c7..ec7751b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -291,7 +291,7 @@ Under the hood: `log()` prints JSON to stdout → sandbox streams it → `metric ## Directory Structure ``` -trainable-monorepo/ +trainable/ ├── backend/ │ ├── main.py # FastAPI app, CORS, lifecycle │ ├── db.py # Async SQLAlchemy engine + session From a99bae52cb00fbf33950fa75271dcc5fdfcc0fd4 Mon Sep 17 00:00:00 2001 From: Lucas Tonon Date: Sat, 18 Jul 2026 22:52:47 -0300 Subject: [PATCH 03/67] fix(frontend): use relative /api URLs instead of hardcoded http://host:8000 (#96) Replace getSSEBase()/getBackendUrl() (which returned http://host:8000) with relative /api paths at all EventSource and raw file/image/PDF fetch sites, routing through the existing Next rewrite proxy so HTTPS deploys no longer hit mixed-content blocking or an unexposed port. Both helpers are deleted. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4 --- frontend/src/app/page.tsx | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 6e90963..9b656be 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -111,20 +111,6 @@ import { SyntaxHighlighter.registerLanguage('python', python); SyntaxHighlighter.registerLanguage('json', json); -// --------------------------------------------------------------------------- -// SSE / Backend helpers -// --------------------------------------------------------------------------- - -function getSSEBase() { - if (typeof window === 'undefined') return 'http://localhost:8000'; - return `http://${window.location.hostname}:8000`; -} - -function getBackendUrl() { - if (typeof window === 'undefined') return 'http://localhost:8000'; - return `http://${window.location.hostname}:8000`; -} - // --------------------------------------------------------------------------- // ChatItem interface // --------------------------------------------------------------------------- @@ -322,7 +308,7 @@ export default function HomePage() { const connectSSE = useCallback( (sid: string) => { if (sseRef.current) sseRef.current.close(); - const url = `${getSSEBase()}/api/sessions/${sid}/stream`; + const url = `/api/sessions/${sid}/stream`; const source = new EventSource(url); source.onopen = () => setSseConnected(true); @@ -2399,7 +2385,7 @@ const HtmlPanel = memo(function HtmlPanel({ artifact }: { artifact: HtmlArtifact ); } - const rawUrl = `${getBackendUrl()}/api/files/raw?path=${encodeURIComponent(artifact.path)}`; + const rawUrl = `/api/files/raw?path=${encodeURIComponent(artifact.path)}`; const sizeLabel = humanArtifactBytes(artifact.size); return ( @@ -2545,14 +2531,14 @@ const FileViewer = memo(function FileViewer({
{/* eslint-disable-next-line @next/next/no-img-element */} {fileName}
) : isPdf ? (