Resolve conflicts in ask.py and chat.py by merging the try/except error
handling from main with the extract_text_content helper from the PR.
Also apply the same fix to source_chat.py and transformation.py which
had the same vulnerable isinstance/str() pattern for structured LLM
response content (e.g. Gemini's envelope format).
- Catch ConfigurationError alongside ValueError in get_default_model()
to preserve graceful fallback after ValueError→ConfigurationError migration
- Add _truncate() helper to error_classifier to cap pass-through and
default error messages at 200 chars, avoiding verbose internal details
Replace generic "An unexpected error occurred" messages with descriptive,
user-friendly error messages when LLM operations fail. Errors like invalid
API keys, wrong model names, and rate limits now surface clearly in the UI.
Adds error classification utility, global FastAPI exception handlers, and
frontend getApiErrorMessage() helper. Bumps version to 1.7.2.
Add empty-content validation in content_process() after extract_content()
returns. Sources with no extractable text (e.g. YouTube videos without
transcripts) now raise ValueError immediately instead of silently saving
an empty source. ValueError is already configured as a permanent failure
in the retry config, so no retries are wasted on unrecoverable situations.
Closes#527
Add min-w-0 to chat input flex container and textarea to prevent
field-sizing-content from expanding the layout. Replace break-words
with break-all on user message bubbles to force wrapping at any
character for strings without spaces.
Add break-all to SourceCard title and InlineEdit display text so long
unbroken strings wrap instead of overflowing the container. Add min-w-0
to NoteEditorDialog form to prevent grid item expansion.
Also fix RecordID type error in notes API by converting command_id to
string before passing to NoteResponse (fixes 500 on note create/update).
Long strings without spaces caused the Add Source dialog to expand
beyond the viewport due to field-sizing-content on textareas
propagating width through CSS grid/flex layout.
Add min-w-0 to Textarea, WizardContainer content layers, and the
AddSourceDialog form to prevent flex/grid items from expanding to
fit content. Add overflow-hidden to base DialogContent as a safeguard.
The Proxy's get handler checked `prop in target` before looking up
translations. Since the target is a function, `Function.name` is a
built-in property that shadowed translation keys like `t.common.name`,
causing the raw proxy path to render instead of the translated string.
Move translation lookup before target property checks so i18n keys
always take priority. Also remove unnecessary `|| 'Name'` fallback
in CreateNotebookDialog.
- Add fr-FR locale with French translations (875 keys)
- Register fr-FR in locale index, LanguageToggle, and date-locale
- Add common.french key to all existing locale files
- Sync fr-FR key structure to match current en-US after cleanup
Based on #514, rebased and adapted to the current locale structure.
Closes#481
Co-authored-by: saikrishna-prathapaneni <saiprathapaneni23@gmail.com>
- Add GitHub Actions workflow to run backend (pytest) and frontend
(vitest) tests on PRs and pushes to main
- Replace hardcoded locale parity tests with dynamic discovery from
the resources registry, so new languages are tested automatically
- Add unused key detection test that scans source files for i18n
key references, with optional chaining normalization
- Remove 149 genuinely unused translation keys from all 7 locale files
- Add missing keys to it-IT (7) and ru-RU (5) with English fallback
Esperanto's AIFactory.create_text_to_speech() did not accept a config
dict like the other factory methods, so credentials configured via the
UI were not passed through. Fixed upstream in esperanto 2.9.2.
Refs #571
Docker Compose v2 derives container names from the directory name using
dashes. Since the docs instruct users to create an `open-notebook-local`
folder, the correct container name is `open-notebook-local-ollama-1`,
not `open_notebook-ollama-1`.
Fixes#575
* feat: expose embed command_id in note API responses
Note.save() already returns the command_id from the embed_note
background job, but the API routes discarded it. This surfaces
the command_id in NoteResponse for both POST and PUT endpoints,
enabling callers to poll GET /api/commands/jobs/{command_id} to
know when embedding has completed.
* Add tests for note API command_id response
Source.vectorize() wrapped its own ValueError in DatabaseOperationError,
bypassing the stop_on=[ValueError] retry guard in process_source_command.
This caused up to 15 retries when processing files with no extractable
text, blocking sync API requests indefinitely.
- Re-raise ValueError directly in Source.vectorize() instead of wrapping
- Add .strip() check to catch whitespace-only content
- Skip vectorization gracefully in save_source() when content is empty
- Add unit tests for vectorize error handling
Fixes#560
* feat: replace provider config with credential-based system (#477)
Introduce a new credential management system replacing the old
ProviderConfig singleton and standalone Models page. Each credential
stores encrypted API keys and provider-specific configuration with
full CRUD support via a unified settings UI.
Backend:
- Add Credential domain model with encrypted API key storage
- Add credentials API router (CRUD, discovery, registration, testing)
- Add encryption utilities for secure key storage
- Add key_provider for DB-first env-var fallback provisioning
- Add connection tester and model discovery services
- Integrate ModelManager with credential-based config
- Add provider name normalization for Esperanto compatibility
- Add database migrations 11-12 for credential schema
Frontend:
- Rewrite settings/api-keys page with credential management UI
- Add model discovery dialog with search and custom model support
- Add compact default model assignments (primary/advanced layout)
- Add inline model testing and credential connection testing
- Add env-var migration banner
- Update navigation to unified settings page
- Remove standalone models page and old settings components
i18n:
- Update all 7 locale files with credential and model management keys
Closes#477
Co-Authored-By: JFMD <git@jfmd.us>
Co-Authored-By: OraCatQAQ <570768706@qq.com>
* fix: address PR #540 review comments
- Fix docs referencing removed Models page
- Fix error-handler returning raw messages instead of i18n keys
- Fix auth.py misleading docstring and missing no-password guard
- Fix connection_tester using wrong env var for openai_compatible
- Add provision_provider_keys before model discovery/sync
- Update CLAUDE.md to reflect credential-based system
- Fix missing closing brace in api-keys page useEffect
* fix: add logging to credential migration and surface errors in UI
- Add comprehensive logging to migrate-from-env and
migrate-from-provider-config endpoints (start, per-provider
progress, success/failure with stack traces, final summary)
- Fix frontend migration hooks ignoring errors array from response
- Show error toast when migration fails instead of "nothing to migrate"
- Invalidate status/envStatus queries after migration so banner updates
* docs: update CLAUDE.md files for credential system
Replace stale ProviderConfig and /api-keys/ references across 8 CLAUDE.md
files to reflect the new Credential-based system from PR #540.
* docs: update user documentation for credential-based system
Replace env var API key instructions with Settings UI credential
workflow across all user-facing documentation. The new flow is:
set OPEN_NOTEBOOK_ENCRYPTION_KEY → start services → add credential
in Settings UI → test → discover models → register.
- Rewrite ai-providers.md, api-configuration.md, environment-reference.md
- Update all quick-start guides and installation docs
- Update ollama.md, openai-compatible.md, local-tts/stt networking sections
- Update reverse-proxy.md, development-setup.md, security.md
- Fix broken links to non-existent docs/deployment/ paths
- Add credentials endpoints to api-reference.md
- Move all API key env vars to deprecated/legacy sections
* chore: bump version to 1.7.0-rc1
Release candidate for credential-based provider management system.
* fix: initialize provider before try block in test_credential
Prevents UnboundLocalError when Credential.get() throws (e.g.,
invalid credential_id) before provider is assigned.
* fix: reorder down migration to drop index before table
Removes duplicate REMOVE FIELD statement and reorders so the index
is dropped before the table, preventing rollback failures.
* refactor: simplify encryption key to always derive via SHA-256
Remove the dual code path in _ensure_fernet_key() that detected native
Fernet keys. Since the credential system is new, always deriving via
SHA-256 removes unnecessary complexity. Also removes the generate_key()
function and Fernet.generate_key() references from docs.
* fix: correct mock patch targets in embedding tests and URL validation
Fix embedding tests patching wrong module path for model_manager
(was targeting open_notebook.utils.embedding.model_manager but it's
imported locally from open_notebook.ai.models). Also fix URL validation
to allow unresolvable hostnames since they may be valid in the
deployment environment (e.g., Azure endpoints, internal DNS).
* feat: add global setup banner for encryption and migration status
Show a persistent banner in AppShell when encryption key is missing
(red) or env var API keys can be migrated (amber), so users see
these prompts on every page instead of only on Settings > API Keys.
Includes a docs link for the encryption banner and i18n support
across all 7 locales.
* docs: several improvements to docker-compose e env examples
* Update README.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* docs: fix env var format in README and update model setup instructions
Align the encryption key snippet in README Step 2 with the list
format used in the compose file. Replace deprecated "Settings →
Models" instructions with credential-based Discover Models flow.
* fix: address credential system review issues
- Fix SSRF bypass via IPv4-mapped IPv6 addresses (::ffff:169.254.x.x)
- Fix TTS connection test missing config parameter
- Add Azure-specific model discovery using api-key auth header
- Add Vertex static model list for credential-based discovery
- Fix PROVIDER_DISCOVERY_FUNCTIONS incorrect azure/vertex mapping
- Extract business logic to api/credentials_service.py (service layer)
- Move credential Pydantic schemas to api/models.py
- Update tests to use new service imports and ValueError assertions
* fix: sanitize error responses and migrate key_provider to Credential
- Replace raw exception messages in all credential router 500 responses
with generic error strings (internal details logged server-side only)
- Refactor key_provider.py to use Credential.get_by_provider() instead
of deprecated ProviderConfig.get_instance()
- Remove unused functions (get_provider_configs, get_default_api_key,
get_provider_config) that were dead code
---------
Co-authored-by: JFMD <git@jfmd.us>
Co-authored-by: OraCatQAQ <570768706@qq.com>
Some LLM providers (notably Gemini, DeepSeek via OpenAI-compatible
proxies) return ai_message.content as a list of content parts:
[{'type': 'text', 'text': '...', 'extras': {...}}]
The current code uses str() on non-string content, which produces the
Python repr of the entire list — not valid JSON. This breaks
PydanticOutputParser.parse() with OutputParserException.
This adds extract_text_content() to properly unwrap text from both
string and structured content formats, applied in ask.py, chat.py,
and prompt.py.
Fixes#329
Introduce conditional handling for solo podcasts in prompts/podcast/transcript.jinja. Changes add tailored final-segment wording, strict solo-speaker formatting and guidelines (force use of the single speaker name, require minimum turns, enforce not inventing other speakers), and additional transcript-generation reminders (flow, root "transcript" key, transitions). Multi-speaker behavior and existing format instructions are preserved.
* fix: use sync get_state() for SqliteSaver in chat routers
Replace async aget_state() calls with sync get_state() wrapped in
asyncio.to_thread() to fix SqliteSaver compatibility issues.
SqliteSaver does not support async methods, so we need to run
the sync version in a separate thread.
This is a follow-up to #519 which fixed the same issue in
graph_utils.py but missed four locations:
- chat.py: get_session() and execute_chat()
- source_chat.py: get_source_chat_session() and stream_source_chat_response()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: translate comments to English
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Luis Novo <lfnovo@gmail.com>
Happened when requested to include a file using the api.
Tests Done:
. Requested again to include the same file, and it did not throw any
error.
. After, I requested the include of lots of other files, and had no
issue.
Co-authored-by: Luiza Carneiro <luiza.carneiro@cloudera.com>
Add comprehensive documentation for setting up local speech-to-text
using Speaches with faster-whisper. Includes model recommendations,
GPU acceleration, Docker networking, and troubleshooting.
Also updates related docs with cross-references to the new guide.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add complete Russian language support to Open Notebook following the
established i18n patterns from ja-JP and pt-BR implementations.
Changes:
- Create ru-RU locale file with all translation keys
- Register ru-RU in locales/index.ts resources and languages array
- Add ru-RU key integrity test
- Add Russian date-fns locale mapping
- Add Russian option to LanguageToggle dropdown
- Add `russian: "Русский"` key to all existing locales
- Update README.md supported languages list
- Update locales CLAUDE.md documentation
Fixes#523
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Luis Novo <lfnovo@gmail.com>
* Create new file index.ts
* Content added
* updated words
* Add Italian language support to locales
* Fix typos in Italian localization strings
* Fix typo in Italian locale for defaultAssignments
Add max-height and overflow constraints to MarkdownEditor component
to prevent the textbox from expanding indefinitely when typing.
The editor now scrolls internally instead of pushing the save
button out of view.
- Added max-h-[500px] to limit editor height
- Added overflow-hidden to container
- Made internal editor content scrollable with overflow-y-auto
Adds OPEN_NOTEBOOK_CHUNK_SIZE and OPEN_NOTEBOOK_CHUNK_OVERLAP environment
variables to allow users to configure chunking behavior for different
embedding models with varying context window limits.
Key changes:
- CHUNK_SIZE is now configurable via OPEN_NOTEBOOK_CHUNK_SIZE (default: 1200)
- CHUNK_OVERLAP is configurable via OPEN_NOTEBOOK_CHUNK_OVERLAP (default: 15%)
- Validation with warnings for invalid or out-of-range values
- Updated documentation with configuration examples
This enables users of models like mxbai-embed-large with limited context
windows to reduce chunk size accordingly.
Closes#510
SqliteSaver does not support async methods like aget_state().
Use asyncio.to_thread() to run the sync get_state() call from
async context, maintaining compatibility with the existing
sync graph invocations.
Closes#509
* chore: bump surreal-commands to 1.3.1
Required for improved retry logging with command_id in error messages.
Ref #513
* chore: update uv.lock for surreal-commands 1.3.1
* fix: filter empty content in rebuild embeddings queries
Update collect_items_for_rebuild() to properly filter out items with
empty or whitespace-only content before submitting embedding jobs.
Changes:
- Sources: add string::trim(full_text) != '' filter
- Notes: add string::trim(content) != '' filter
- Insights: add content != none AND string::trim(content) != '' filter
(previously had no content filter at all)
This prevents unnecessary job submissions that would fail validation
in the individual embed commands.
Ref #513
* feat: add command_id to embedding error logs
Add get_command_id() helper to extract command_id from execution context.
Include command_id in error logs for all embedding commands:
- embed_note_command
- embed_insight_command
- embed_source_command
- create_insight_command
This makes it easier to trace failed embedding jobs back to specific
command records in the database.
Ref #513
* fix: improve logging for embedding commands
Log improvements:
- Add command_id to all embedding error logs for traceability
- Transaction conflicts in repo_insert now log at DEBUG (not ERROR)
- Embedding API errors log at DEBUG, only ERROR when retries exhausted
- Friendlier retry messages: "This will be retried automatically"
- Include model name and command_id in generate_embeddings errors
Files changed:
- commands/embedding_commands.py: command_id in logs, friendlier messages
- open_notebook/database/repository.py: DEBUG for transaction conflicts
- open_notebook/utils/embedding.py: DEBUG logging, pass-through command_id
Ref #513
* fix: correct field names in rebuild embeddings status endpoint
The API status endpoint was looking for wrong field names:
- sources_processed → sources_submitted
- notes_processed → notes_submitted
- insights_processed → insights_submitted
- processed_items → jobs_submitted
- failed_items → failed_submissions
The command outputs "_submitted" because embedding happens async
(we count jobs submitted, not items processed).
Ref #513
* fix: update rebuild UI text to reflect async job submission
Changed terminology from "Completed/processed" to "Jobs Submitted"
since the rebuild command submits embedding jobs for async processing,
not completing them synchronously.
Updated in all locales: en-US, pt-BR, zh-CN, zh-TW, ja-JP
Ref #513
* refactor: migrate retry strategy from allowlist to blocklist
- Change from `retry_on: [RuntimeError, ...]` to `stop_on: [ValueError]`
- This is more resilient: new exception types auto-retry by default
- Simplified exception handling: ValueError = permanent, else = retry
- Transient errors logged at DEBUG (surreal-commands logs final failure)
- Permanent errors (ValueError) logged at ERROR
Ref #513
Migrate insight creation to the command system with automatic retry logic
to prevent SurrealDB transaction conflicts during batch imports.
Changes:
- Add create_insight_command with retry logic for transaction conflicts
- Add run_transformation_command for async transformation execution
- Make Source.add_insight() fire-and-forget (returns command_id)
- Update POST /sources/{id}/insights to return 202 Accepted immediately
- Frontend polls command status until complete, then refreshes
- Auto-update notebook page icon when source gains insights
- Add i18n keys for insight generation feedback
Related to #489
* chore: force env file on api
* docs: add reverse proxy timeout configuration for all proxies
- Add timeout configuration to Caddy examples (read_timeout, write_timeout)
- Add Traefik serversTransport timeout configuration
- Increase nginx timeout from 300s to 600s to match frontend
- Expand timeout troubleshooting section with symptoms and solutions
- Reference API_CLIENT_TIMEOUT and ESPERANTO_LLM_TIMEOUT env vars
Fixes#447
* chore: force env file on api
* fix: Docker networking and permissions documentation
- Add HOSTNAME=0.0.0.0 as default in Dockerfiles for reverse proxy compatibility
- Document Linux extra_hosts requirement for host.docker.internal
- Add user: root to SurrealDB examples for bind mount permissions on Linux
- Update environment reference and reverse proxy docs
Fixes#483, fixes#485, fixes#409
Update proxy configuration to use industry-standard environment variables
(HTTP_PROXY, HTTPS_PROXY, NO_PROXY) instead of custom variables.
The underlying libraries (esperanto, content-core, podcast-creator)
now automatically detect proxy settings from these standard variables.
- Bump content-core>=1.14.1 (fixes#494)
- Bump esperanto>=2.18
- Bump podcast-creator>=0.9
- Update documentation with new proxy configuration