Build Wisp: on-device transcription studio (web + native, one codebase)
Private, offline speech-to-text that runs Whisper on the user's own device — free, no account, no per-minute fees. Replaces Otter.ai / Rev. - Pure, tested engine: chunking, overlap timestamp-stitching, exports (SRT/VTT/TXT/MD/JSON), WAV codec, resampler, job queue, model catalog (142 tests). - Platform-abstracted TranscriptionEngine: transformers.js on web (loaded from CDN at runtime to dodge Metro's onnxruntime-web bundling limits), whisper.rn on native. Shared pipeline orchestrates decode -> chunk -> transcribe -> stitch. - Cross-platform StorageRepo (Dexie web / expo-sqlite native), Zod-validated. - UI: library + search, import, live-progress transcription, synced click-to-seek editor, multi-format export; model picker + privacy in settings. - Web ships as a single-page PWA with COOP/COEP isolation for threaded WASM; Docker (nginx) image + Traefik compose for wisp.briggen.dev. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { ScrollView, StyleSheet, Pressable, View } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { MaxContentWidth, Spacing } from '@/constants/theme';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { listModels } from '@/lib/models/catalog';
|
||||
import { useTranscribe } from '@/stores/transcribeStore';
|
||||
|
||||
export default function SettingsScreen() {
|
||||
const theme = useTheme();
|
||||
const modelId = useTranscribe((s) => s.modelId);
|
||||
const setModel = useTranscribe((s) => s.setModel);
|
||||
const models = listModels();
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.fill}>
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<ThemedText type="subtitle">Model</ThemedText>
|
||||
<ThemedText type="small" themeColor="textSecondary">
|
||||
Smaller models are faster and run well on any CPU; larger ones are more accurate but want a GPU.
|
||||
The model downloads once, then works fully offline.
|
||||
</ThemedText>
|
||||
|
||||
{models.map((m) => {
|
||||
const selected = m.id === modelId;
|
||||
return (
|
||||
<Pressable key={m.id} onPress={() => setModel(m.id)}>
|
||||
<ThemedView
|
||||
type={selected ? 'backgroundSelected' : 'backgroundElement'}
|
||||
style={[styles.card, selected && { borderColor: '#3c87f7', borderWidth: 1 }]}>
|
||||
<View style={styles.rowBetween}>
|
||||
<ThemedText type="smallBold">{m.label}</ThemedText>
|
||||
{selected && <ThemedText type="small" style={{ color: '#3c87f7' }}>✓ selected</ThemedText>}
|
||||
</View>
|
||||
<ThemedText type="small" themeColor="textSecondary">
|
||||
{cap(m.tier)} · ~{m.approxMB} MB · {m.multilingual ? 'multilingual' : 'English-only'}
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
|
||||
<View style={styles.spacer} />
|
||||
<ThemedText type="subtitle">Privacy</ThemedText>
|
||||
<ThemedText type="small" themeColor="textSecondary">
|
||||
Wisp transcribes entirely on your device using OpenAI's Whisper model. Your audio is never
|
||||
uploaded to any server — there is no account, no per-minute fee, and it keeps working with the
|
||||
network off. The only download is the model file itself.
|
||||
</ThemedText>
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
function cap(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fill: { flex: 1 },
|
||||
content: { padding: Spacing.three, gap: Spacing.two, maxWidth: MaxContentWidth, width: '100%', alignSelf: 'center' },
|
||||
card: { padding: Spacing.three, borderRadius: Spacing.three, gap: Spacing.one },
|
||||
rowBetween: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
||||
spacer: { height: Spacing.three },
|
||||
});
|
||||
Reference in New Issue
Block a user