fix(transcriptions): render segments that have no timings
An OpenAI-compatible ASR answering in json/text format returns no timestamps, and services/asr_backend.py records that honestly as `end: None` rather than inventing a number. The segment list called `.toFixed()` on it unconditionally, so the render threw and the whole Transcriptions view went blank — a transcript that merely lacked timings became one the user could not read at all. Show whichever bound is known and nothing when neither is, so the text stays readable either way. Non-finite values are treated as unknown too, so a bad timing prints nothing rather than NaN. Fixes #1798. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HR6J9zKQop9TGGVUwjypnF
This commit is contained in:
co-authored by
Claude Opus 5
parent
f2302e8c95
commit
4e6c36848c
@@ -0,0 +1,2 @@
|
||||
1788271637990
|
||||
d8d64392-e276-e11d-730f-432cfd53e5f2
|
||||
@@ -58,6 +58,8 @@ the frozen-backend fallback mirror it for their toolchains.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The Transcriptions view no longer goes blank on a transcript whose segments have no timings — an ASR backend that returns text without timestamps is rendered as text instead of crashing the page (#1798)
|
||||
|
||||
- The generation compute-time budget is now a Settings control (Performance & Device) instead of an env-var-only setting the timeout error recommended with no UI path — the error copy points there too, and long CPU/MPS renders get an upfront heads-up before they start (#1787)
|
||||
- Windows: the backend can now start when the install path contains non-English characters (e.g. a CJK username) on a non-UTF-8 system code page — a new or broken Python environment now builds at an ASCII-safe path automatically (a healthy existing one is never relocated), and a specific error message names the cause and a working fix if the interpreter still crashes in `site` (#1783)
|
||||
- Exports and other native-picker actions no longer 403 with "Invalid or expired desktop authorization" when the desktop app and backend resolve different data directories, e.g. dev mode or a custom data folder (#1781)
|
||||
|
||||
@@ -26,6 +26,23 @@ function saveTranscriptions(list) {
|
||||
localStorage.setItem(TRANSCRIPTIONS_KEY, JSON.stringify(list));
|
||||
}
|
||||
|
||||
/** A segment's "12.0s – 15.5s" label, tolerant of missing timings (#1798).
|
||||
*
|
||||
* Not every ASR path produces a timed segment: an OpenAI-compatible backend
|
||||
* answering in `json`/`text` format has no timings at all, and
|
||||
* `services/asr_backend.py` records that honestly as `end: None` rather than
|
||||
* inventing a number. Calling `.toFixed()` on it threw during render and took
|
||||
* the whole Transcriptions view down, so a transcript that merely lacked
|
||||
* timings became one the user could not read at all. Render whichever half is
|
||||
* known, and nothing when neither is. */
|
||||
export function segTimeRange(seg) {
|
||||
const known = (v) => typeof v === 'number' && Number.isFinite(v);
|
||||
const start = known(seg?.start) ? `${seg.start.toFixed(1)}s` : null;
|
||||
const end = known(seg?.end) ? `${seg.end.toFixed(1)}s` : null;
|
||||
if (start && end) return `${start} – ${end}`;
|
||||
return start || end || '';
|
||||
}
|
||||
|
||||
export function addTranscription(entry) {
|
||||
const list = loadTranscriptions();
|
||||
const newEntry = {
|
||||
@@ -288,7 +305,7 @@ export default function TranscriptionsPage() {
|
||||
className="txn-detail__seg flex gap-[8px] py-[3px] text-[var(--text-xs)]"
|
||||
>
|
||||
<span className="txn-detail__seg-time shrink-0 font-mono text-fg-subtle min-w-[80px]">
|
||||
{seg.start.toFixed(1)}s – {seg.end.toFixed(1)}s
|
||||
{segTimeRange(seg)}
|
||||
</span>
|
||||
<span className="txn-detail__seg-text text-fg">{seg.text}</span>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ vi.mock('../hooks/useEffectiveDictationShortcut', () => ({
|
||||
}));
|
||||
vi.mock('react-hot-toast', () => ({ toast }));
|
||||
|
||||
import TranscriptionsPage, { addTranscription } from './Transcriptions';
|
||||
import TranscriptionsPage, { addTranscription, segTimeRange } from './Transcriptions';
|
||||
|
||||
describe('Transcriptions capture entry point', () => {
|
||||
beforeEach(() => {
|
||||
@@ -67,3 +67,39 @@ describe('Transcriptions capture entry point', () => {
|
||||
expect(await screen.findByText('The shared capture path works.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// #1798: an OpenAI-compatible ASR answering in json/text format returns no
|
||||
// timings, and services/asr_backend.py records that honestly as `end: null`
|
||||
// rather than inventing a number. The segment list called `.toFixed()` on it
|
||||
// unconditionally, which threw during render and took the whole
|
||||
// Transcriptions view down — a transcript that merely lacked timings became
|
||||
// one the user could not read at all.
|
||||
describe('segments without timings (#1798)', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('renders a segment whose end is null instead of crashing the view', async () => {
|
||||
addTranscription({
|
||||
text: 'hello from an untimed backend',
|
||||
language: 'en',
|
||||
segments: [{ text: 'hello from an untimed backend', start: 0, end: null }],
|
||||
});
|
||||
|
||||
render(<TranscriptionsPage />);
|
||||
fireEvent.click(await screen.findByText('hello from an untimed backend'));
|
||||
|
||||
// The transcript itself must still be readable — this is the regression:
|
||||
// before the guard, the null `end` threw and nothing rendered at all.
|
||||
expect(screen.getAllByText('hello from an untimed backend').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('formats what is known and never prints NaN', () => {
|
||||
expect(segTimeRange({ start: 12, end: 15.55 })).toBe('12.0s – 15.6s');
|
||||
expect(segTimeRange({ start: 0, end: null })).toBe('0.0s');
|
||||
expect(segTimeRange({ start: null, end: 4 })).toBe('4.0s');
|
||||
expect(segTimeRange({ text: 'no timings' })).toBe('');
|
||||
expect(segTimeRange(undefined)).toBe('');
|
||||
expect(segTimeRange({ start: NaN, end: Infinity })).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user