You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(changelog): close the [Unreleased] gaps before v1.14.0 (#1192)
The release audit (git log v1.13.0..main) found 27 merged commits but
only 11 represented in [Unreleased]. Adds the 14 missing entries and
corrects two existing problems:
- The #1137 entry (per-transformation model selection) was filed under
[1.13.0], but its commit is not in the v1.13.0 tag — v1.13.0 shipped a
changelog entry for an unshipped fix. Moved verbatim to [Unreleased].
- The anthropic_compatible entry was a one-line stub with no issue
reference; rewritten in house style and pointed at #675.
New entries cover the DNS-pinning hardening (#1063), worker concurrency
(#893), episode max_tokens in the UI (#991), error-text capping (#1136),
the IME/chat composer perf fix (#1147), the anthropic shim removal
(#1055), the esperanto floor bump (#1176), ja-JP translation quality
(#998), i18n placeholder parity (#1159), the OLLAMA_API_BASE docs fix
(#1148), and the CI/tooling chores (#1134, #940, #1133, #1158).
A ### Changed section was added; every commit in the range is now
represented.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+18-2Lines changed: 18 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,16 +11,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
-**Docling formula & vision enrichment toggles** in Settings → Content Processing. Two new opt-in checkboxes surface content-core 2.x's `docling_formulas` (extract mathematical formulas as structured markup) and `docling_vision` (describe images and extract chart data with a vision model) enrichment flags, mirroring the existing OCR toggle. Both default off; the vision help text warns it is significantly slower and may call a vision model. Like OCR, the toggles only take effect through the Docling engine, so they are gated on Docling availability. The settings persist via `GET`/`PUT /api/settings` and are threaded into content-core extraction alongside `docling_ocr` (migration 23 backfills the new fields); labels and help are translated across all 14 locales (#1131)
12
12
-**OpenRouter now supports text-to-speech and speech-to-text**, not just LLMs and embeddings (esperanto 2.25.0). The provider's modalities were extended so the Settings UI offers TTS/STT when registering OpenRouter models. Because OpenRouter's `/models` listing does not reliably tag audio models, discovery seeds the working defaults esperanto ships — `microsoft/mai-voice-2` (TTS) and `openai/whisper-1` + `openai/whisper-large-v3` (STT) — alongside the API-discovered language models; any other `vendor/model` id can be added manually. Note that `microsoft/mai-voice-2` uses Microsoft neural voice names (e.g. `en-US-AvaNeural`), not OpenAI's `alloy`/`nova` set (#987)
13
13
- Newer ElevenLabs models are now offered in model discovery: `eleven_v3`, `eleven_flash_v2_5` and `eleven_flash_v2` for text-to-speech, and `scribe_v2` for speech-to-text — alongside the existing models. Esperanto passes the `model_id` straight through to ElevenLabs, so no provider changes were needed (#1167)
14
-
-Add `anthropic_compatible` credential provider (Anthropic-compatible endpoints with a custom base URL).
14
+
-**`anthropic_compatible` credential provider**, mirroring the existing `openai_compatible` one, so endpoints that only speak the Anthropic Messages format (e.g. Kimi) can be connected with a base URL plus API key — from Settings → API Keys or via `ANTHROPIC_COMPATIBLE_BASE_URL` / `ANTHROPIC_COMPATIBLE_API_KEY`. The connection test carries one Anthropic-specific wrinkle: a 404/405 from `GET /models` is reported as "listing unsupported, register models manually" rather than a failed connection, since Messages-only endpoints are legitimately unable to list. Model discovery uses `GET /v1/models`with the Anthropic headers and shares the base-URL normalization and request-time re-validation used by provisioning. Provider card and credential form translated across all 14 locales (#675)
15
15
-**First-class oMLX provider** in Settings → API Keys: native Esperanto `omlx` profile identity (language + embedding), default base URL `http://localhost:11435/v1` (avoids SurrealDB on 8000), optional API key, connection test and `/v1/models` discovery. No remapping to `openai-compatible` and no `OPENAI_COMPATIBLE_*` env mirroring. Docs under `docs/5-CONFIGURATION/omlx.md` (#1048; upstream lfnovo/esperanto#228)
16
16
- **Four new AI providers** (surfaced by esperanto 2.25.1) are now in the provider matrix. **Cohere** (`COHERE_API_KEY`) adds language and embedding models via Cohere's native v2 API (`command-*` chat, `embed-*`); model discovery is bespoke (esperanto's `AIFactory.get_provider_models`) since Cohere is not OpenAI-compatible, and Cohere reranking is intentionally left out of scope (tracked at #1087). **PayPerQ / PPQ** (`PPQ_API_KEY`, `https://api.ppq.ai/v1`) is a multi-modality OpenAI-compatible gateway offering language, embedding, speech-to-text and text-to-speech. **Novita** (`NOVITA_API_KEY`, `https://api.novita.ai/openai`) is an OpenAI-compatible LLM gateway. PPQ and Novita auto-discover their models through the standard `/models` endpoint. In addition, the existing **Deepgram** provider now also supports **speech-to-text** (Nova/Whisper transcription models, e.g. `nova-3`) alongside its Aura text-to-speech voices. All four are configured from Settings → Models exactly like the existing providers (#1170)
17
+
-**Worker concurrency is now configurable** via `OPEN_NOTEBOOK_WORKER_MAX_TASKS`. The surreal-commands worker always ran with a hardcoded concurrency of 5, which overloads single-GPU and local-LLM setups and trips provider rate limits when several sources are processed at once. The variable is wired into every launch point — `make worker-start`/`make start-all`, `dev-init.sh`, and the Docker `supervisord.conf` (wrapped in `sh -c` so the shell actually expands it, since `command=` does not run through a shell) — and defaults to 5; set it to 1 for strictly sequential processing. It is read at worker launch time, so changing it requires restarting the worker. This also removes a phantom entry from the docs: `SURREAL_COMMANDS_MAX_TASKS` was documented but read nowhere (the worker's `--max-tasks` Typer option has no envvar binding) and has been replaced with the real variable in `.env.example` and the environment reference (#893)
18
+
- Episode profiles now expose an optional **"Max output tokens"** field in the UI. The `max_tokens` value already existed on the podcast episode profile model but could only be set through the API; the profile form now offers it as a number input (positive integer, clearable to unset), mirroring how `num_segments` is handled — zod validation, form defaults/reset and submit payload — and the episode card shows the configured value in its details. Translated across all 14 locales (#991)
19
+
- Local commits are now gated by the same checks as CI: a new `.pre-commit-config.yaml` runs ruff lint (with `--fix`), ruff format, mypy (through `uv run` so it matches CI exactly) and basic hygiene hooks (large files, merge conflict markers, YAML/TOML syntax, trailing whitespace, EOF newlines), with install/run/skip instructions in the development-setup docs (#940)
20
+
- The i18n locale parity test now also compares **interpolation placeholders**, not just keys. It previously only checked that every locale defined the same key set, so a value carrying a stray single-brace `{count}` instead of i18next's `{{count}}` — or a placeholder that does not exist in the en-US reference — passed silently and rendered as literal text at runtime. Every leaf string is now checked against en-US for both missing/extra `{{...}}` placeholders and stray single-brace tokens; this caught and fixed `podcasts.generationStartedDesc` in zh-CN and zh-TW, which referenced a `{{name}}` placeholder the reference string does not have (#1159)
21
+
- The dev Docker build (`build-regular`) is gated on image-affecting paths, so docs- and config-only PRs no longer wait several minutes for a byte-identical image. A `paths-filter` job decides whether the build steps run; the job itself always executes so the required status check always reports — deliberately avoiding the workflow-level paths filter that would leave the required check permanently pending and deadlock merges. PRs touching the Dockerfile, dependencies, app or frontend build as before, and the release build is untouched (#1134)
22
+
- Release tooling: `rc-stack.sh up` now `docker pull`s `lfnovo/open_notebook:<tag>` before booting so a same-named local build cannot shadow the registry artifact during release verification (non-fatal for local-only tags), and a new `--with-runtimes` flag enables the opt-in Docling/Crawl4AI heavy engines on the RC stack so the published image's opt-in install path can be exercised with real data. Both gotchas are documented in `RELEASE_PROCESS.md`; the default release-test compose leaves the runtimes off to keep fresh/upgrade runs fast (#1133)
23
+
- README now links to [@lfnovo on X](https://x.com/lfnovo) for project updates (#1158)
24
+
25
+
### Changed
26
+
- Bumped the esperanto floor to `>=2.25.1`. This is the dependency change that unlocks the oMLX provider, the Cohere/Deepgram STT/PPQ/Novita providers, OpenRouter TTS/STT, Vertex service-account auth and the native Anthropic `to_langchain()` base URL forwarding listed elsewhere in this release. A clean drop-in — no application code changes were required (#1176)
27
+
- Removed the `ChatAnthropic` re-injection shim used by `anthropic_compatible` models. esperanto 2.25.1 forwards a custom `base_url` natively through `to_langchain()`, so the `_to_langchain()` helper in `provision.py` and the `_open_notebook_provider` marker it depended on (its only consumer) are gone; provisioning now calls `model.to_langchain()` directly. No behavior change — custom Anthropic-compatible endpoints keep working, now through the upstream path (#1055)
28
+
- Japanese (ja-JP) translations reviewed and improved throughout: "トランスフォーメーション" replaced with the natural 変換, duration formatting given proper spacing, and accessibility labels, error messages and assorted phrasings rewritten to read naturally rather than as literal translations (#998)
17
29
18
30
### Fixed
19
31
-**HTTP proxy no longer breaks worker/API startup.**`websockets` 15.0 began auto-detecting `HTTP_PROXY`/`HTTPS_PROXY` and tunnels even `ws://` connections through the proxy, so with a proxy set the internal SurrealDB websocket (`ws://host.docker.internal:8018/rpc` or `ws://surrealdb:8000/rpc`) was routed through the external proxy, which rejected the internal host with HTTP 403 and killed the worker on startup. Open Notebook now injects `host.docker.internal,surrealdb,localhost,127.0.0.1` into `no_proxy`/`NO_PROXY` at startup (merged with any user value, never clobbering it), and the `.env.example` / docs `NO_PROXY` examples now include the internal DB hosts (#1160)
20
32
- Auto-assign no longer silently re-populates optional model defaults that were deliberately cleared. Previously `POST /models/auto-assign` treated every empty slot as "missing" and filled it, so an optional slot a user intentionally cleared (to fall back to the chat model) got re-assigned on the next run. Auto-assign now fills only the two required slots (chat, embedding); the optional slots (transformation, tools, large context, TTS, STT) are left untouched. `large_context` now also falls back to the chat model when unset (matching transformation/tools) instead of returning nothing, and the Settings UI shows an inline hint on each empty optional slot — "using chat model (…)" for the text slots, "not configured" for TTS/STT (#1098)
21
33
- PPQ model discovery now lists all modalities, not just chat. PPQ's `/v1/models` returns only chat/language models by default; the discovery URL now requests `?type=all` so the embedding, speech-to-text and text-to-speech models this multi-modality gateway advertises actually surface in the provider matrix (`PPQ_MODEL_TYPES` classifies them into the right slots) (#1180)
22
34
- Vertex credentials now configure text-to-speech (and other Vertex providers) correctly when used through a stored credential rather than environment variables. `Credential.to_esperanto_config()` emitted the generic `project`/`location` keys, but esperanto's Vertex providers expect `vertex_project`/`vertex_location` — so credential-linked Vertex TTS crashed with `__init__() got an unexpected keyword argument 'project'`. The config now maps to the Vertex-specific key names for the `vertex` provider only; the `Credential` schema/API fields stay `project`/`location`, and non-Vertex providers are unaffected (#1151)
23
35
- Deleting a notebook now also deletes its chat sessions instead of leaving orphaned `chat_session` records behind. The notebook `delete()` cascade enumerates the notebook's chat sessions (via the existing `refers_to` relation) and deletes each one alongside the notes and exclusive sources it already handled (#1124)
36
+
-**Outbound requests to user-supplied provider endpoints are now pinned to a DNS-vetted IP address.** Validating a URL and then handing it to httpx left a DNS-rebinding TOCTOU window: httpx re-resolves the hostname at connect time, so a hostname that passed validation could resolve to a link-local or cloud metadata address (e.g. `169.254.169.254`, `fd00:ec2::254`) on the actual connection. A new `prepare_pinned_http_target()` helper resolves once, rejects dangerous addresses, and rewrites the request URL to the vetted IP while preserving the original hostname in the `Host` header and TLS SNI (IDNA-encoded), so routing and certificate verification are unaffected. Applied consistently to the credential-save, connection-test and model-discovery paths for `openai_compatible`, `ollama`, `azure` and the OpenAI provider's custom `base_url` (#1063)
37
+
- Per-transformation model selection now actually takes effect. A transformation's assigned `model_id` was persisted but ignored at execution time: both call sites that run the transformation graph (the source-processing graph and the `run_transformation` background command) invoked it without a config, so the graph node always read a `None` model and fell back to the global default transformation model. Both now forward `transformation.model_id` through the LangGraph `configurable` config, so a transformation configured to use a specific model runs on that model (an unset `model_id` still falls back to the default) (#1137)
38
+
- Error text surfaced by the sources API is now capped at 200 characters. `GET` source status and synchronous processing failures returned the raw command/result `error_message` unbounded, which could push arbitrary internal exception text (including stack detail from third-party extraction libraries) to clients and did not match the truncation already applied elsewhere in the API. Both paths now go through a `None`-safe truncation helper that appends an ellipsis when it cuts (#1136)
39
+
-**Typing in the chat composer no longer lags in long conversations**, which was especially painful for IME users (Japanese, Chinese, Korean) whose per-keystroke composition events made the whole message history re-render. The composer input state moved into a dedicated `ChatComposer` child so keystrokes stay local to it, each message row is memoized with `React.memo`, and the reference-click handler is stabilized with `useCallback` — which in turn required memoizing `openModal`/`closeModal` in `useModalManager`, since their changing identities were re-rendering every memoized row on each composer keystroke. Send behavior and the Ctrl/Cmd+Enter shortcut are unchanged (#1147)
40
+
- The quick-start guide and the two Ollama compose examples set `OLLAMA_BASE_URL`, which no code path reads — the variable the provider registry actually looks for is `OLLAMA_API_BASE`. Following the examples verbatim left Ollama permanently unavailable with no error to explain why. All three shipped files now use the correct name (#1148)
24
41
25
42
## [1.13.0] - 2026-07-13
26
43
@@ -40,7 +57,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
57
- The legacy provider/model string fields on podcast profiles (`outline_provider`, `outline_model`, `transcript_provider`, `transcript_model` on episode profiles; `tts_provider`, `tts_model` on speaker profiles) are gone from the database, the API and the UI — the app has ignored them since the Model registry references landed in v1.11. Migration 22 first best-effort maps any profile whose `outline_llm`/`transcript_llm`/`voice_model` reference is still empty to an existing model record (matching provider + name + type; no auto-creation, since a migration must not touch credentials), then drops the six columns; the startup data migration that used to retry this mapping on every boot (`open_notebook/podcasts/migration.py`) was deleted. Accepted trade-off: profiles whose mapping never converged (e.g. the provider credential was never configured) lose the legacy strings and stay unresolved — they were already non-functional and the UI already flags them as needing model selection, so you just re-pick the models in the profile form once (#1107)
41
58
42
59
### Fixed
43
-
- Per-transformation model selection now actually takes effect. A transformation's assigned `model_id` was persisted but ignored at execution time: both call sites that run the transformation graph (the source-processing graph and the `run_transformation` background command) invoked it without a config, so the graph node always read a `None` model and fell back to the global default transformation model. Both now forward `transformation.model_id` through the LangGraph `configurable` config, so a transformation configured to use a specific model runs on that model (an unset `model_id` still falls back to the default) (#1137)
44
60
- Uploading a file content-core can't extract now fails immediately at ingestion with a clear `415 Unsupported Media Type` error that names the detected MIME type, instead of enqueueing a background job that retried up to 15 times over ~1 hour before surfacing a generic "Failed" with no actionable detail. The pre-flight uses content-core 2.x's header-only `check_file_support()` — the same routing real extraction uses, so the verdict can't disagree with what would happen downstream — and the source-retry endpoint is guarded the same way; unexpected check errors (e.g. a file removed before a retry) fall through to normal extraction rather than becoming a hard rejection (#975)
45
61
- Podcast episode cards no longer show "— / —" for the outline, transcript and speaker model rows on new episodes: the API now resolves the snapshot's model references (`outline_llm`/`transcript_llm`/`voice_model`) to provider/name display fields at serialization time — batched into a single query per request, so listing episodes never does a per-row model lookup — and the card falls back to the legacy snapshot strings for old episodes and degrades to "—" when a referenced model was deleted (#1114)
46
62
- Renaming a speaker profile no longer breaks the episode profiles that use it: `episode_profile.speaker_config` now stores a `record<speaker_profile>` reference instead of the profile name (migration 20 converts existing rows; references whose speaker profile no longer exists at migration time become null, and any reference that later stops resolving is treated as "needs setup" — the UI asks you to pick a speaker again). The `POST /api/podcasts/generate` contract is unchanged — it still accepts the speaker profile by name and resolves it at the API boundary (#630)
0 commit comments