Skip to content

fix(engines): warn before a long CPU synth burns the whole budget - #1302

Merged
debpalash merged 3 commits into
mainfrom
fix/cpu-longtext-preflight
Jul 29, 2026
Merged

fix(engines): warn before a long CPU synth burns the whole budget#1302
debpalash merged 3 commits into
mainfrom
fix/cpu-longtext-preflight

Conversation

@debpalash

@debpalash debpalash commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Closes #1299, #1260.

The gap I left in #1288

#1288 warns when the GPU is under-provisioned. A CPU-only host is a benign routing verdict — nothing is misconfigured — so routingNotice() correctly stays silent for it.

But "nothing is misconfigured" and "this will finish in time" are different claims. #1299 and #1260 are both CPU hosts that hit the 300s budget on long text and got no warning at all, which is the exact failure #1288 was meant to prevent. I missed it.

Threshold

Not invented — the backend's own definition of past-short. generate_timeout_for() in services/model_manager.py gives the first 1200 characters the flat budget and only then starts extending it. Below that the job is inside a budget the backend already considers generous; above it, on CPU-class hardware, it is the shape that times out.

Ordinary sentences on a CPU laptop stay quiet. The constant carries a comment pointing at its backend twin so the two don't drift.

Precedence

A real hardware caveat still wins — one toast, naming the routing reason, not generic "long text" advice. There's a test pinning that (cpu_fallback + long text → the caveat, and explicitly not cpuLongText).

Tests

5 new cases: fires on long text on a CPU host, quiet on short text, quiet on long text on an accelerated host, once-only per engine, and the precedence case above.

engines.cpuLongText translated in all 21 locales. Full frontend suite 1615 passing; locale parity + CJK guard green.

Adds a CPU-only preflight toast when synthesis input text exceeds 1,200 characters, preserving existing hardware caveat precedence and ensuring the warning fires once per engine per session (with deduping and CPU-tuned suppression), translated across all 21 locales. This targets CPU generations that can exceed the backend compute budget and get abandoned while still consuming resources. Risk worth a human look: verify CPU/routing detection, the “once per engine” dedupe logic, and warning precedence across engine types and translations.

#1288 closed the under-provisioned-GPU gap but left the CPU one open, and I
missed it: a CPU-only host is a BENIGN routing verdict, so routingNotice()
correctly stays silent — yet #1299 and #1260 are exactly that shape, CPU hosts
that hit the 300s budget on long text with no warning at all. "Nothing is
misconfigured" and "this will finish in time" are different claims.

Threshold is the backend's own definition of past-short: generate_timeout_for()
gives the first 1200 characters the flat budget before extending it, so
ordinary sentences on a CPU laptop stay quiet and only the shape that actually
times out is flagged. Hardware caveats still take precedence — one toast, and
it names the real reason rather than generic advice.

5 tests; engines.cpuLongText translated in all 21 locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread frontend/src/utils/generatePreflight.js
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a preflight warning for long CPU synthesis while resolving the previously reported self-referential recommendation.

  • Passes generation text into the shared preflight and warns once per engine when CPU execution exceeds 1200 characters.
  • Uses alternate guidance for OmniVoice GGUF and Supertonic-3 and preserves routing-caveat precedence.
  • Adds localized messages, regression tests, performance documentation, and test configuration isolation.

Important Files Changed

Filename Overview
frontend/src/utils/generatePreflight.js The CPU-tuned engine IDs match the canonical backend registry and the alternate warning fully resolves the previous thread.
frontend/src/api/generate.ts Supplies the FormData text to the existing non-blocking preflight without changing request behavior.
frontend/src/test/generatePreflight.test.js Covers both canonical CPU-tuned engine IDs, generic-engine behavior, threshold handling, deduplication, acceleration, and warning precedence.
tests/conftest.py Restores imported configuration path constants after tests that reload configuration modules.

Reviews (3): Last reviewed commit: "fix(engines): don't tell CPU-tuned engin..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

CPU long-text warning

Layer / File(s) Summary
CPU long-text preflight logic
frontend/src/utils/generatePreflight.js
Adds CPU-only long-text detection, tuned-engine message selection, deduplication, and routing-caveat precedence.
Input propagation and warning messaging
frontend/src/api/generate.ts, frontend/src/i18n/locales/*, docs/performance.md
Passes submitted text to preflight checks, adds localized warning keys, and documents warning behavior.
CPU long-text behavior tests
frontend/src/test/generatePreflight.test.js
Covers routing, thresholds, deduplication, fallback precedence, and CPU-tuned engine messaging.
Configuration path test isolation
tests/conftest.py
Restores configuration path constants and re-synchronizes reloaded modules between tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 7 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description skips the required template sections and checklist fields, so it is far from the repository's expected format. Rewrite it with Summary, Changes, Type, Testing, Checklist, and Release cadence sections, and fill the missing checklist items.
Out of Scope Changes check ⚠️ Warning tests/conftest.py adds a config-path restore fixture unrelated to the CPU warning scope, so the PR includes unrelated test harness changes. Move the config-path drift fix to a separate PR unless it is required for this issue and documented in the linked scope.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed Conventional-commit style title matches the CPU long-text warning feature, and the issue reference is present in the PR body.
Linked Issues check ✅ Passed The long-text CPU preflight warning, 1,200-character threshold, precedence rules, and tests all line up with #1299.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Cross-Platform Default Parity ✅ Passed No OS branch in the new preflight path; it keys off backend routing on all hosts, and all 21 locales include the new strings.
I18n Completeness (21 Locales) ✅ Passed All three keys exist in all 21 locale files, and the changed frontend code routes toast text through i18n; no hardcoded user-facing copy was added.
Local-First Guarantee ✅ Passed Only local /engines lookup and toast logic were added; bug-report GitHub flow is unchanged and preflight swallows failures, so no new required cloud path.
Backward Compatibility ✅ Passed PASS: Changes are frontend/docs/tests only; no backend/migrations files changed, and locale JSONs validate, so no schema or engine-weight migration risk.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/utils/generatePreflight.js`:
- Around line 75-80: Update the documentation associated with generatePreflight
to describe the 1200-character CPU/long-text warning threshold, that the warning
is emitted only once per engine, and the recommended alternatives. Keep the
documented behavior synchronized with LONG_TEXT_CHARS and the existing preflight
warning implementation.
- Line 94: Update the long-text guard in generatePreflight to count Unicode code
points with Array.from(text ?? '').length instead of String.length, matching the
backend threshold for astral characters. Preserve the existing onCpu and
LONG_TEXT_CHARS conditions, and add a boundary test covering astral text at the
configured threshold.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 94489bb5-6d66-4094-b18a-ae7650d10109

📥 Commits

Reviewing files that changed from the base of the PR and between 47c5169 and 973113b.

📒 Files selected for processing (24)
  • frontend/src/api/generate.ts
  • frontend/src/i18n/locales/ar.json
  • frontend/src/i18n/locales/de.json
  • frontend/src/i18n/locales/en.json
  • frontend/src/i18n/locales/es.json
  • frontend/src/i18n/locales/fr.json
  • frontend/src/i18n/locales/hi.json
  • frontend/src/i18n/locales/id.json
  • frontend/src/i18n/locales/it.json
  • frontend/src/i18n/locales/ja.json
  • frontend/src/i18n/locales/ko.json
  • frontend/src/i18n/locales/nl.json
  • frontend/src/i18n/locales/pl.json
  • frontend/src/i18n/locales/pt.json
  • frontend/src/i18n/locales/ru.json
  • frontend/src/i18n/locales/sv.json
  • frontend/src/i18n/locales/th.json
  • frontend/src/i18n/locales/tr.json
  • frontend/src/i18n/locales/uk.json
  • frontend/src/i18n/locales/vi.json
  • frontend/src/i18n/locales/zh-CN.json
  • frontend/src/i18n/locales/zh-TW.json
  • frontend/src/test/generatePreflight.test.js
  • frontend/src/utils/generatePreflight.js

Comment on lines +75 to +80
// Mirrors the backend's own definition of "past short": generate_timeout_for()
// in services/model_manager.py gives the first 1200 characters the flat budget
// and only then starts extending it. Below this the job is inside the budget
// the backend considers generous; above it, on a CPU-class host, it is the
// shape that times out. Keep the two in sync.
const LONG_TEXT_CHARS = 1200;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the warning behavior. This PR adds a user-visible CPU/long-text threshold but includes no corresponding documentation; document the threshold, once-per-engine behavior, and suggested alternatives in this PR. As per coding guidelines, “Synchronize documentation in the same PR.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/generatePreflight.js` around lines 75 - 80, Update the
documentation associated with generatePreflight to describe the 1200-character
CPU/long-text warning threshold, that the warning is emitted only once per
engine, and the recommended alternatives. Keep the documented behavior
synchronized with LONG_TEXT_CHARS and the existing preflight warning
implementation.

Source: Coding guidelines

*/
function warnIfLongTextOnCpu(active, entry, text) {
const onCpu = entry?.routing_status === 'cpu_only' || entry?.effective_device === 'cpu';
if (!onCpu || (text || '').length <= LONG_TEXT_CHARS) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "LONG_TEXT_CHARS|warnIfLongTextOnCpu|\\.length|repeat\\(" \
  frontend/src/utils/generatePreflight.js \
  frontend/src/test/generatePreflight.test.js

Repository: debpalash/OmniVoice-Studio

Length of output: 693


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== generatePreflight.js relevant section =="
sed -n '60,130p' frontend/src/utils/generatePreflight.js

echo
echo "== generator tests relevant section =="
sed -n '180,235p' frontend/src/test/generatePreflight.test.js

echo
echo "== search for LONG_TEXT_CHARS / preflight warning keys/docs =="
rg -n "LONG_TEXT_CHARS|long-text|Warning:|Text exceeds|max .characters|1200" -S .

Repository: debpalash/OmniVoice-Studio

Length of output: 14576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re, subprocess
from pathlib import Path

js = Path("frontend/src/utils/generatePreflight.js").read_text()
# Extract warnIfLongTextOnCpu body and the call site(s) for context.
start = js.index("function warnIfLongTextOnCpu")
brace = js.index("{", start)
depth = 0
end = None
for i in range(brace, len(js)):
    if js[i] == "{":
        depth += 1
    elif js[i] == "}":
        depth -= 1
        if depth == 0:
            end = i + 1
            break
func = js[start:end]
print("warnIfLongTextOnCpu:")
print(func)
print("uses .length literal:", ".length" in func)
print("uses Array.from literal:", "Array.from" in func)
PY

node - <<'JS'
const text = '😀'.repeat(601);
console.log("JavaScript text.length:", text.length);
console.log("Array.from(text).length:", Array.from(text).length);
console.log("'abc😀def'.length:", 'abc😀def'.length);
console.log("Array.from('abc😀def').length:", Array.from('abc😀def').length);
JS

Repository: debpalash/OmniVoice-Studio

Length of output: 798


Match the backend’s character grapheme threshold at Line 94. String.length counts UTF-16 code units, so 601 astral characters trip at 1,202 while the Python backend uses 601, causing false-long-text warnings; use Array.from(text ?? '').length and add an astral-code-point boundary test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/generatePreflight.js` at line 94, Update the long-text
guard in generatePreflight to count Unicode code points with Array.from(text ??
'').length instead of String.length, matching the backend threshold for astral
characters. Preserve the existing onCpu and LONG_TEXT_CHARS conditions, and add
a boundary test covering astral text at the configured threshold.

debpalash and others added 2 commits July 29, 2026 16:11
Greptile P1. The advice names OmniVoice GGUF and Supertonic-3 as the CPU-tuned
alternatives — shown to someone already running one of them, it is advice to
switch to what they are using. Those two now get the same warning without the
self-referential clause; the engine set matches the backend's own timeout
message so the two can't disagree about who is CPU-tuned.

Also documents the preflight in docs/performance.md (docs-sync rule): both
warning shapes, why the threshold is 1200 characters (it is the figure the
budget itself uses), that they are advisory and once-per-engine-per-session,
and the CPU-tuned exception.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/conftest.py`:
- Around line 516-518: Update the fixture’s finally block around the core.config
lookup so it conditionally skips restoration when the module is absent instead
of returning from finally. Preserve restoration when cfg exists, and ensure any
original test exception propagates unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dffdd7f2-0ef6-4b09-a0f1-9e3bdaa1a231

📥 Commits

Reviewing files that changed from the base of the PR and between 973113b and a513fbd.

📒 Files selected for processing (25)
  • docs/performance.md
  • frontend/src/i18n/locales/ar.json
  • frontend/src/i18n/locales/de.json
  • frontend/src/i18n/locales/en.json
  • frontend/src/i18n/locales/es.json
  • frontend/src/i18n/locales/fr.json
  • frontend/src/i18n/locales/hi.json
  • frontend/src/i18n/locales/id.json
  • frontend/src/i18n/locales/it.json
  • frontend/src/i18n/locales/ja.json
  • frontend/src/i18n/locales/ko.json
  • frontend/src/i18n/locales/nl.json
  • frontend/src/i18n/locales/pl.json
  • frontend/src/i18n/locales/pt.json
  • frontend/src/i18n/locales/ru.json
  • frontend/src/i18n/locales/sv.json
  • frontend/src/i18n/locales/th.json
  • frontend/src/i18n/locales/tr.json
  • frontend/src/i18n/locales/uk.json
  • frontend/src/i18n/locales/vi.json
  • frontend/src/i18n/locales/zh-CN.json
  • frontend/src/i18n/locales/zh-TW.json
  • frontend/src/test/generatePreflight.test.js
  • frontend/src/utils/generatePreflight.js
  • tests/conftest.py
🚧 Files skipped from review as they are similar to previous changes (19)
  • frontend/src/i18n/locales/tr.json
  • frontend/src/i18n/locales/zh-CN.json
  • frontend/src/i18n/locales/ja.json
  • frontend/src/i18n/locales/nl.json
  • frontend/src/i18n/locales/ru.json
  • frontend/src/i18n/locales/pt.json
  • frontend/src/i18n/locales/zh-TW.json
  • frontend/src/i18n/locales/fr.json
  • frontend/src/i18n/locales/es.json
  • frontend/src/i18n/locales/it.json
  • frontend/src/i18n/locales/vi.json
  • frontend/src/i18n/locales/ko.json
  • frontend/src/i18n/locales/pl.json
  • frontend/src/i18n/locales/hi.json
  • frontend/src/i18n/locales/ar.json
  • frontend/src/i18n/locales/uk.json
  • frontend/src/i18n/locales/de.json
  • frontend/src/test/generatePreflight.test.js
  • frontend/src/utils/generatePreflight.js

Comment thread tests/conftest.py
Comment on lines +516 to +518
cfg = sys.modules.get("core.config")
if cfg is None:
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not return from the fixture’s finally block.

If a test removes core.config from sys.modules and fails, Line 518 suppresses that failure, creating a false-positive test result. Replace the return with a conditional that skips restoration while allowing the original exception to propagate.

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 518-518: return inside finally blocks cause exceptions to be silenced

(B012)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/conftest.py` around lines 516 - 518, Update the fixture’s finally block
around the core.config lookup so it conditionally skips restoration when the
module is absent instead of returning from finally. Preserve restoration when
cfg exists, and ensure any original test exception propagates unchanged.

Source: Linters/SAST tools

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.

[Bug] TTS generate ran for more than 300s of actual compute time and was abandoned — t

1 participant