AI Source Bubbles: improvements and fixes (BL-16549)#8064
Conversation
- Rename AiSourceBubbles -> AiTranslation across C# and TypeScript. - Alpha2: treat "pending" translation_status as in-progress rather than a hard failure (it was aborting otherwise-successful translations). - Translation progress dialog: always wait on completion, showing an OK button; Cancel is only present while work is in progress. - Add a "Remove AI Source Translations" book menu command (gated by save permission) that strips all AI source translations and confirms via a Bloom message box. - Resilient supported-languages aggregation: one engine failing to list its languages (e.g. a DeepL key lacking the languages:read scope) no longer blanks the whole union or blocks translation; the error, when every engine fails, is shown red and wraps instead of scrolling the dialog. - Fix: Google credentials were written onto every engine's wire record, wiping the other engines' validation when Google creds were edited. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| src/BloomExe/AiTranslation/AiTranslationService.cs | New core service coordinating multi-engine AI translation; resilient language listing (one engine failure doesn't blank the dropdown), clean fingerprint-based validation, and correct normalization utilities. |
| src/BloomExe/AiTranslation/Alpha2TranslationProvider.cs | New Alpha2 provider; correctly treats 'pending' and 'running' translation statuses as in-progress, and now uses CancellationToken.None for cleanup on the failure/cancel path so collections aren't orphaned. |
| src/BloomExe/web/controllers/CollectionSettingsApi.cs | Adds three async AI translation endpoints (validate, supported-languages, alpha2-source-languages) and extends StoreAdvancedSettingsData; each engine's credentials are kept isolated so Google credential edits no longer wipe other engines' validation. |
| src/BloomBrowserUI/collection/AiTranslationSettingsGroup.tsx | Large new settings UI for multi-engine AI translation; language controls hoisted to module scope to avoid remount-on-render; debounced per-engine validation with proper effect justification comments; Google credentials correctly sent only to the Google engine record. |
| src/BloomBrowserUI/react_components/Progress/ProgressDialog.tsx | Adds showOkButtonWhenDone prop so the AI translation dialog explicitly waits for user acknowledgement rather than showing a generic Close button on completion; Cancel is correctly suppressed after work finishes. |
| src/BloomExe/AiTranslation/AiTranslationBookScanner.cs | New scanner for the book DOM; GetAiChildDivs returns a List snapshot so removal during iteration is safe; RemoveAllAiDivs correctly strips page-level and bloomDataDiv AI entries. |
| src/BloomExe/AiTranslation/AiTranslationBookUpdater.cs | Orchestrates whole-book translation; CountStaleAiDivsWithoutMutating clones the DOM to probe stale divs safely; CancellationToken bridged correctly from BackgroundWorker; parallel-engine approach is sound. |
| src/BloomBrowserUI/bookEdit/sourceBubbles/BloomSourceBubbles.tsx | Adds AI tab labels (icon + language name) using deprecated ReactDOM.render (intentional TODO); fixes styledSelectChangeHandler to use closest('a.sourceTextTab') so clicks on the rendered icon still resolve the href; fixes broken sort comparator. |
| src/BloomExe/Collection/CollectionSettings.cs | Adds AiTranslationTargetLanguageTag and AiTranslationEngines with EnsureAiTranslationEngines() to normalize the list to exactly one entry per known provider; save/load round-trips all engine fields including validation state. |
| src/BloomExe/web/controllers/BookCommandsApi.cs | Adds removeAiSourceTranslations endpoint that removes all AI divs from the book, saves, and shows a confirmation dialog; correctly runs on UI thread but not sync to avoid deadlock. |
| src/BloomExe/AiTranslation/GoogleTranslationProvider.cs | New Google Translate provider using JWT/service-account OAuth; correctly decodes HTML entities from the API response; RSA private key handled in-process only. |
| src/BloomBrowserUI/collection/AdvancedSettingsPanel.tsx | Integrates the AI translation settings group into the existing Advanced Settings panel; uses memoized AI settings extraction to prevent spurious effect re-triggers when unrelated settings change. |
Reviews (5): Last reviewed commit: "Fix engine validation stuck in "Testing...." | Re-trigger Greptile
…ary (BL-16549) - WebView2Browser: drop the leftover "--disable-web-security" browser flag that an earlier DeepL experiment added. It disabled web security for the ENTIRE app browser, not just AI translation; AI translation now runs server-side in C#, so the flag is unneeded and reverts to the secure default. (Flagged by Devin.) - BloomSourceBubbles: replace the nested ternary in the source-tab sort comparator with an if-chain, per the repo style rule. (Flagged by Greptile.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| engine.ValidatedConfigurationFingerprint ?? "" | ||
| ) | ||
| ); | ||
| engineElement.Add(new XElement("ApiKey", engine.ApiKey ?? "")); |
There was a problem hiding this comment.
[Devin] Investigate: API keys and Google private key stored in plain text in the collection file
The DeepL/Google/Alpha2 API keys and the Google private key are written as plain XML (ApiKey, ServiceAccountEmail, PrivateKey) into the .bloomCollection file, so anyone with the file can read them and they travel if the collection is shared. This matches how Bloom already stores other settings (pre-existing design), but noting it so the team can decide whether AI credentials warrant different handling.
|
[Claude Opus 4.8] Consulted Devin on 2026-07-14 up to commit Bugs (4)
Investigate (3) — mirrored as inline threads:
Informational (4) — not posted (low signal). CI: |
…sions (BL-16549) - Hoist AiTranslationTargetLanguageControl to module scope, feeding it live data via a React context provided around the Configr pane. Defining it inside the hook gave it a new identity every render, so React remounted it -- closing the target-language dropdown / losing focus whenever another settings field changed. (Devin + Greptile.) - Add the required justification comments to three useEffects per the front-end AGENTS.md rule. (Devin.) - Document (in code) the deliberate decision to leave AI-tab source-language remembering as-is pending a product call. (Devin.) - Plaintext-credentials storage tracked separately as BL-16550. (Devin.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-16549)
Refines the AI source-translation settings and translation flow:
- Rename the feature UI to "AI Source Translation"; add provider brand
marks (DeepL, Google, SIL) next to target languages and enable labels.
- Add a SIL Alpha2 source-language chooser and surface, per engine, when
the chosen target language isn't supported ("will be skipped"), plus
guidance when no provider is selected or no service supports a language.
- Resilient supported-languages listing: one engine failing no longer
blanks the target-language dropdown or blocks translation.
- Fix: Google credentials were written onto every engine's wire record,
wiping other engines' validation when Google credentials were edited.
- Skip Alpha2 text boxes that lack source-language text, reporting the
count in the progress dialog.
- Add missing GoogleServiceAccountDescription XLF entry (was referenced
in code with only an English fallback) and translator context notes.
Includes host-side unit tests for the scanner, updater, service, and
Alpha2 provider.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bles # Conflicts: # PAPERCUTS.md
On the failure/cancel path, the best-effort deletes of the source and output text collections were passed the request's CancellationToken. When the user cancels, that token is already tripped, so the first cleanup SendAsync threw OperationCanceledException immediately and (being best-effort) was swallowed — orphaning the storage-consuming Alpha2 collections on every cancel. Pass CancellationToken.None so cleanup still runs after cancellation. Reported by Greptile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| return ( | ||
| <BloomDialog | ||
| {...propsForBloomDialog} | ||
| fullScreen={true} |
There was a problem hiding this comment.
[Devin] Investigate: LanguageChooserDialog is now fullscreen for all callers
Adding fullScreen={true} and disableDragging={true} here changes the dialog's presentation for every caller of LanguageChooserDialog, not just the new AI-translation target-language picker. In particular the existing Collection Settings "Change Language" chooser (the WinForms-hosted path) now opens fullscreen and non-draggable too. If that dialog was previously a smaller, draggable window, this is a visible UX change to an existing feature. The new inner flex-container wrapper suggests this was to fix a layout issue for the AI picker, but the flag is applied globally. Worth confirming whether making the existing chooser fullscreen everywhere is intended.
…ge (BL-16549) The aiTranslationFlatSettings memo keyed on the whole `settings` object, but extractAiTranslationFlatSettings returns a fresh object each call, so any unrelated Advanced-tab setting change handed the AI settings group a new reference. Mid-validation that cancelled the in-flight probe (its finally then skipped clearing the pending flag because it was cancelled) and the re-run hit the probe-key early-return without restarting — leaving the row stuck on "Testing translation..." forever. Key the memo on the serialized AI settings so its reference only changes when the AI settings actually change. Reported by Devin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| setIsLoadingSupportedLanguages(true); | ||
| setSupportedLanguagesMessage(""); | ||
| try { | ||
| const response = await postJsonAsync( |
There was a problem hiding this comment.
[Devin] Investigate: Supported-language fetches fail silently
If the settings/aiTranslationSupportedLanguages request (and the Alpha2 source-language fetch) fails at the network level, the client ends up with an empty language list and no visible error — the failure isn't surfaced to the user. This is largely by design (a missing list must not block the dialog, and per-engine failures are reported by the server's aggregated message), but a genuine connectivity failure is currently indistinguishable from "no languages", so flagging for a decision on whether to surface it.
|
|
||
| if (aiTranslationConfigurationChanged) | ||
| { | ||
| dialog.ChangeThatRequiresRestart(); |
There was a problem hiding this comment.
[Devin] Investigate: Any AI-engine config change requires a Bloom restart
Any change to an AI engine's enabled state, credentials, target/source language flags the Collection Settings dialog as requiring a restart (ChangeThatRequiresRestart). This is consistent with how Bloom already treats most collection-settings changes, and the source-bubble generation reads this config at load, so a restart is defensible — but flagging in case a lighter-weight apply is wanted for, e.g., just editing an API key.
|
[Claude Opus 4.8] Consulted Devin on 2026-07-15 (~22:35 UTC) up to commit |
[Claude Opus 4.8]
AI Source Bubbles / AI translation for Bloom: shows machine translations of a book's source text as extra source bubbles when editing, driven by 0..n configured engines (DeepL, Google, SIL Alpha2).
This branch also includes recent fixes:
pending(queued)translation_statusas in-progress rather than a hard failure, which was aborting otherwise-successful translations.languages:readscope) no longer blanks the whole target-language dropdown or blocks translation. When every engine fails, the error is shown in red and wraps instead of scrolling the settings dialog.Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16549
Devin review
This change is