Transcription progress UX + use WASM on mobile web
CI / test (push) Successful in 19s
CI / deploy-web (push) Successful in 35s
CI / build-apk (push) Successful in 6m43s

Two issues behind "it just shows downloaded 100% / loads forever / crashes
after a couple seconds":

1. Progress was invisible during transcription. The 'transcribing' stage
   only fired AFTER the first (slow) chunk, so the UI sat at "Loading
   model… 100%" through the entire first inference and looked frozen.
   - pipeline now emits a transcribing kickoff (progress 0) BEFORE the
     first chunk and carries chunkIndex/chunkCount on every event.
   - transcribeStore threads those through; the Library job card shows a
     spinner, "Transcribing… N%", and "part i of N" so a long file's
     progress is legible and obviously advancing.

2. Web crash on mobile. We picked WebGPU + fp16 whenever navigator.gpu
   existed, but mobile WebGPU drivers crash on sustained fp16 Whisper
   inference (transcribes a chunk, then the GPU process dies). Mobile web
   now uses cross-origin-isolated multi-threaded WASM (slower but stable);
   desktop keeps WebGPU.

Native's "loads forever" was the same missing-feedback problem — it was
grinding through chunks with no UI signal; the chunk counter now shows it.

tsc clean, 279 tests pass (pipeline progress test updated for the kickoff
event), web export OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 10:05:54 +02:00
parent e3ed03a471
commit fa12817050
5 changed files with 74 additions and 13 deletions
+17 -4
View File
@@ -163,19 +163,31 @@ function FilterChip({ label, active, onPress }: { label: string; active: boolean
}
function ActiveJob() {
const { stage, progress, partial, cancel } = useTranscribe();
const { stage, progress, partial, chunkIndex, chunkCount, cancel } = useTranscribe();
const pct = Math.round(progress * 100);
const transcribing = stage === 'transcribing';
// While transcribing, show "chunk i/N" so a long file's progress is legible
// and it's obvious work is happening between the (discrete) per-chunk updates.
const label = stage === 'loading' ? `Loading model… ${pct}%` : `Transcribing… ${pct}%`;
const sub =
transcribing && chunkCount
? `part ${Math.min((chunkIndex ?? 0) + (pct < 100 ? 1 : 0), chunkCount)} of ${chunkCount}`
: undefined;
return (
<ThemedView type="backgroundElement" style={styles.card}>
<View style={styles.rowBetween}>
<ThemedText type="smallBold">
{stage === 'loading' ? 'Loading model…' : 'Transcribing…'} {pct}%
</ThemedText>
<View style={styles.jobTitleRow}>
<ActivityIndicator size="small" />
<ThemedText type="smallBold">{label}</ThemedText>
</View>
<Pressable onPress={cancel} hitSlop={8}>
<ThemedText type="small" themeColor="textSecondary">Cancel</ThemedText>
</Pressable>
</View>
<ProgressBar value={progress} />
{sub && (
<ThemedText type="small" themeColor="textSecondary">{sub}</ThemedText>
)}
{partial.length > 0 && (
<ThemedText type="small" themeColor="textSecondary" numberOfLines={3}>
{partial.map((s) => s.text).join(' ')}
@@ -240,6 +252,7 @@ const styles = StyleSheet.create({
captureText: { color: '#fff', fontWeight: '700', fontSize: 16 },
card: { padding: Spacing.three, borderRadius: Spacing.three, gap: Spacing.two },
rowBetween: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: Spacing.two },
jobTitleRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.two, flex: 1 },
filterBar: { gap: Spacing.two, paddingVertical: Spacing.one, paddingRight: Spacing.three },
filterChip: { paddingHorizontal: Spacing.three, paddingVertical: Spacing.two, borderRadius: 999 },
chipActive: { color: '#fff', fontWeight: '700' },