update for whisper

This commit is contained in:
Ryan Vogel
2026-03-28 21:12:24 -04:00
parent bd2e34f3bd
commit 2abf1100ee
7 changed files with 1365 additions and 312 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
{
"expo": {
"name": "mobile-voice",
"name": "Control",
"slug": "mobile-voice",
"version": "1.0.0",
"orientation": "portrait",
@@ -10,6 +10,9 @@
"ios": {
"icon": "./assets/images/icon.png",
"bundleIdentifier": "com.anomalyco.mobilevoice",
"entitlements": {
"com.apple.developer.kernel.extended-virtual-addressing": true
},
"infoPlist": {
"NSMicrophoneUsageDescription": "This app needs microphone access for live speech-to-text dictation.",
"NSAppTransportSecurity": {
+3 -3
View File
@@ -13,6 +13,7 @@
"lint": "expo lint"
},
"dependencies": {
"@fugood/react-native-audio-pcm-stream": "1.1.4",
"@react-navigation/bottom-tabs": "^7.15.5",
"@react-navigation/elements": "^2.9.10",
"@react-navigation/native": "^7.1.33",
@@ -41,14 +42,13 @@
"react-dom": "19.2.0",
"react-native": "0.83.4",
"react-native-audio-api": "^0.11.7",
"react-native-executorch": "^0.8.0",
"react-native-executorch-expo-resource-fetcher": "^0.8.0",
"react-native-gesture-handler": "~2.30.0",
"react-native-reanimated": "4.2.1",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.7.2"
"react-native-worklets": "0.7.2",
"whisper.rn": "0.5.5"
},
"devDependencies": {
"@types/react": "~19.2.2",
+10 -15
View File
@@ -1,25 +1,20 @@
import React from 'react';
import { Slot } from 'expo-router';
import { LogBox } from 'react-native';
import { initExecutorch } from 'react-native-executorch';
import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher';
import React from "react"
import { Slot } from "expo-router"
import { LogBox } from "react-native"
import {
configureNotificationBehavior,
registerBackgroundNotificationTask,
} from '@/notifications/monitoring-notifications';
// Initialize the ExecuTorch resource fetcher before any model hooks run
initExecutorch({ resourceFetcher: ExpoResourceFetcher });
} from "@/notifications/monitoring-notifications"
// Suppress known non-actionable warnings from third-party libs.
LogBox.ignoreLogs([
'RecordingNotificationManager is not implemented on iOS',
'[React Native ExecuTorch] No content-length header',
]);
"RecordingNotificationManager is not implemented on iOS",
"`transcribeRealtime` is deprecated, use `RealtimeTranscriber` instead",
])
configureNotificationBehavior();
registerBackgroundNotificationTask().catch(() => {});
configureNotificationBehavior()
registerBackgroundNotificationTask().catch(() => {})
export default function RootLayout() {
return <Slot />;
return <Slot />
}
File diff suppressed because it is too large Load Diff
+138
View File
@@ -0,0 +1,138 @@
declare module "whisper.rn" {
export type TranscribeOptions = {
language?: string
translate?: boolean
maxLen?: number
prompt?: string
[key: string]: unknown
}
export type TranscribeResult = {
result: string
language: string
segments: {
text: string
t0: number
t1: number
}[]
isAborted?: boolean
}
export type TranscribeRealtimeEvent = {
contextId: number
jobId: number
isCapturing: boolean
isStoppedByAction?: boolean
code: number
data?: TranscribeResult
error?: string
processTime: number
recordingTime: number
}
export type TranscribeRealtimeOptions = TranscribeOptions & {
realtimeAudioSec?: number
realtimeAudioSliceSec?: number
realtimeAudioMinSec?: number
[key: string]: unknown
}
export type WhisperContext = {
id: number
gpu: boolean
reasonNoGPU: string
transcribeRealtime(options?: TranscribeRealtimeOptions): Promise<{
stop: () => Promise<void>
subscribe: (callback: (event: TranscribeRealtimeEvent) => void) => void
}>
transcribeData(
data: ArrayBuffer,
options?: TranscribeOptions,
): {
stop: () => Promise<void>
promise: Promise<TranscribeResult>
}
release(): Promise<void>
}
export type ContextOptions = {
filePath: string | number
useGpu?: boolean
useCoreMLIos?: boolean
useFlashAttn?: boolean
}
export function initWhisper(options: ContextOptions): Promise<WhisperContext>
export function releaseAllWhisper(): Promise<void>
}
declare module "whisper.rn/realtime-transcription/index" {
import type { TranscribeOptions, TranscribeResult, WhisperContext } from "whisper.rn"
export type RealtimeTranscribeEvent = {
type: "start" | "transcribe" | "end" | "error"
sliceIndex: number
data?: TranscribeResult
isCapturing: boolean
processTime: number
recordingTime: number
}
export type RealtimeOptions = {
audioSliceSec?: number
audioMinSec?: number
maxSlicesInMemory?: number
transcribeOptions?: TranscribeOptions
logger?: (message: string) => void
}
export type RealtimeTranscriberCallbacks = {
onTranscribe?: (event: RealtimeTranscribeEvent) => void
onError?: (error: string) => void
onStatusChange?: (isActive: boolean) => void
}
export type RealtimeTranscriberDependencies = {
whisperContext: WhisperContext
audioStream: unknown
vadContext?: unknown
fs?: unknown
}
export class RealtimeTranscriber {
constructor(
dependencies: RealtimeTranscriberDependencies,
options?: RealtimeOptions,
callbacks?: RealtimeTranscriberCallbacks,
)
start(): Promise<void>
stop(): Promise<void>
release(): Promise<void>
updateCallbacks(callbacks: Partial<RealtimeTranscriberCallbacks>): void
}
}
declare module "whisper.rn/realtime-transcription" {
export * from "whisper.rn/realtime-transcription/index"
}
declare module "whisper.rn/src/realtime-transcription" {
export * from "whisper.rn/realtime-transcription/index"
}
declare module "whisper.rn/realtime-transcription/adapters/AudioPcmStreamAdapter" {
export class AudioPcmStreamAdapter {
initialize(config: Record<string, unknown>): Promise<void>
start(): Promise<void>
stop(): Promise<void>
isRecording(): boolean
onData(callback: (data: unknown) => void): void
onError(callback: (error: string) => void): void
onStatusChange(callback: (isRecording: boolean) => void): void
release(): Promise<void>
}
}
declare module "whisper.rn/src/realtime-transcription/adapters/AudioPcmStreamAdapter" {
export * from "whisper.rn/realtime-transcription/adapters/AudioPcmStreamAdapter"
}
+14 -3
View File
@@ -139,8 +139,8 @@ async function notify(input: { type: Type; sessionID: string }): Promise<Notify>
const session = await Session.get(sessionID)
out.title = session.title
let latestUser: string | undefined
for await (const msg of MessageV2.stream(sessionID)) {
if (msg.info.role !== "user") continue
const body = msg.parts
.map((part) => {
if (part.type !== "text") return ""
@@ -151,8 +151,19 @@ async function notify(input: { type: Type; sessionID: string }): Promise<Notify>
.join(" ")
const next = words(body)
if (!next) continue
out.body = next
break
if (msg.info.role === "assistant") {
out.body = next
break
}
if (!latestUser && msg.info.role === "user") {
latestUser = next
}
}
if (!out.body) {
out.body = latestUser
}
} catch (error) {
log.info("notification metadata unavailable", {