Skip to content

AI Source Bubbles: improvements and fixes (BL-16549)#8064

Draft
hatton wants to merge 11 commits into
masterfrom
BL-16549AiSourceBubbles
Draft

AI Source Bubbles: improvements and fixes (BL-16549)#8064
hatton wants to merge 11 commits into
masterfrom
BL-16549AiSourceBubbles

Conversation

@hatton

@hatton hatton commented Jul 14, 2026

Copy link
Copy Markdown
Member

[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:

  • Alpha2 "pending" status: the poll loop now treats the pending (queued) translation_status as in-progress rather than a hard failure, which was aborting otherwise-successful translations.
  • Translation progress dialog: on completion it now always waits, showing an OK button; Cancel is only present while work is in progress.
  • "Remove AI Source Translations" book menu command (Collection tab, gated by save permission) that strips all AI source translations from a book and confirms via a Bloom message box.
  • Resilient supported-languages listing: one engine failing to list its languages (e.g. a DeepL key without the languages:read scope) 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.
  • Fix: Google credentials were being written onto every engine's wire record, which wiped the other engines' validation whenever Google credentials were edited.

Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16549

Devin review


This change is Reviewable

hatton and others added 5 commits July 14, 2026 06:16
- 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>
@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ships the multi-engine AI Source Bubbles feature along with several targeted fixes found during development. It introduces three translation providers (DeepL, Google, SIL Alpha2), a book scanner/updater pipeline that runs before editing begins, and a new collection-settings panel for configuring and validating each engine.

  • Alpha2 pending-status fix: IsTranslationComplete now correctly treats \"pending\" and \"running\" as in-progress rather than hard failures; cleanup on cancel/failure now passes CancellationToken.None so Alpha2 text collections are not orphaned.
  • Progress dialog: adds showOkButtonWhenDone prop so the translation dialog explicitly waits for user acknowledgement on completion; Cancel is hidden once work finishes.
  • "Remove AI Source Translations" command: new collection-tab menu item strips all -x-ai divs from the book, saves, and confirms via a message box.
  • Resilient language listing: per-engine failures accumulate and are only surfaced when every engine fails; the fix wraps the error text to avoid layout overflow.
  • Google credentials isolation: unflattenAiTranslationSettings now sends Google's serviceAccountEmail/privateKey only on the Google engine record, preventing edits from wiping other engines' validation fingerprints.

Important Files Changed

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

Comment thread src/BloomBrowserUI/collection/AiTranslationSettingsGroup.tsx Outdated
…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>
Comment thread src/BloomBrowserUI/collection/AiTranslationSettingsGroup.tsx Outdated
Comment thread src/BloomBrowserUI/bookEdit/sourceBubbles/BloomSourceBubbles.tsx
engine.ValidatedConfigurationFingerprint ?? ""
)
);
engineElement.Add(new XElement("ApiKey", engine.ApiKey ?? ""));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@hatton

hatton commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

[Claude Opus 4.8] Consulted Devin on 2026-07-14 up to commit e5711826a.

Bugs (4)

  • Web security disabled for the whole app browser (--disable-web-security) — fixed in e5711826a (Devin now marks it Resolved).
  • 3× "missing justification comment on a useEffect" (AiTranslationSettingsGroup.tsx L416/L432/L616), tagged "Repo rule" — I couldn't find a hard rule mandating this, so treated as an optional style suggestion; left as-is (surfaced in the developer's decision report).

Investigate (3) — mirrored as inline threads:

  • Target-language control defined inside the hook → remounts (Greptile flagged this too).
  • Clicking an AI source tab may be remembered as the source language.
  • API keys stored plaintext in the collection file (pre-existing design).

Informational (4) — not posted (low signal).

CI: pr-automation + PR Review Tracker green (this repo runs no build/test CI). Greptile: passed. CodeRabbit: no activity.

hatton and others added 3 commits July 14, 2026 19:24
…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>
Comment thread src/BloomExe/AiTranslation/Alpha2TranslationProvider.cs
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}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
Comment thread src/BloomBrowserUI/collection/AdvancedSettingsPanel.tsx
setIsLoadingSupportedLanguages(true);
setSupportedLanguagesMessage("");
try {
const response = await postJsonAsync(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@hatton

hatton commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

[Claude Opus 4.8] Consulted Devin on 2026-07-15 (~22:35 UTC) up to commit 0c5ce29ed. Result: 0 open bugs. Fixed this run and resolved on their threads — the target-language control remount (also Greptile P2) and the "Testing…" validation-stuck bug. Documented decision + resolved — AI source tab remembered as source language. Left open for developer decision — plaintext API-key storage, LanguageChooserDialog now fullscreen for all callers, silent supported-language fetch failures, and restart-required on any AI config change. CI green; Greptile's P1 (Alpha2 cleanup leak on cancel) fixed and resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant