mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 09:57:48 +00:00
* feat(speech): run GGUF models on transcribe-cpp, falling back to the CPU The speech host child can now load a second engine. A model whose files are GGUF runs on transcribe-cpp (Metal on Apple silicon, Vulkan on Windows and Linux, CPU otherwise); everything else stays on sherpa-onnx, which remains the only engine on Intel Macs. A GPU that fails to load, errors, stalls for 25 s or crashes the process moves the dictation to a CPU-only process (TRANSCRIBE_BACKENDS=cpu) that replays the audio the GPU had not decoded yet, so no words are lost or repeated. A GPU that crashed or would not load is remembered per transcribe-cpp version and OS release, and voice.speechGpu = 'off' keeps it on the CPU. Packaging copies transcribe-cpp, koffi and the target's native package into resources/node_modules outside asar, fails when a target's package is missing, and ships the MSVC runtime beside transcribe.dll on Windows. No catalog model is GGUF yet, so nothing runs on transcribe-cpp until the catalog change that follows. * fix(speech): keep the speech queue running after a failed task, and drop audio frames after a discard * fix(speech): keep the engine's file reads off the main thread's hot paths The engine lookup and the GPU failure marker are read once per process instead of on every dictation start and mobile catalog request, the marker is written asynchronously, and the child reads the hotwords file with fs/promises. The baseline moves the existing engine presence checks from stt-worker-paths to the shared package table and adds the once-per-process marker reads.
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
import { net } from 'electron'
|
|
import { ModelManager } from '../speech/model-manager'
|
|
import { SttService } from '../speech/stt-service'
|
|
import { createSttGpuPolicy } from '../speech/stt-gpu-failure-marker'
|
|
import { spawnSttWorkerWithCpuFallback } from '../speech/stt-worker-cpu-fallback'
|
|
import { spawnSttUtilityProcess } from '../speech/stt-worker-utility-process'
|
|
import type { SpeechServiceFactories } from '../speech/speech-runtime-service'
|
|
import type {
|
|
DownloadIncomingMessage,
|
|
OpenSpeechDownloadRequest
|
|
} from '../speech/speech-model-download-response'
|
|
|
|
/** Chromium honors the app's proxy settings; Node https does not. */
|
|
export const openElectronSpeechDownloadRequest: OpenSpeechDownloadRequest = (url) => {
|
|
const request = net.request({ method: 'GET', url })
|
|
return {
|
|
setHeader: (name, value) => {
|
|
request.setHeader(name, value)
|
|
},
|
|
listen: ({ error, response, redirect }) => {
|
|
const onResponse = (incoming: Electron.IncomingMessage): void =>
|
|
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Electron's IncomingMessage is a Readable stream at runtime; its typings omit the stream half.
|
|
response(incoming as unknown as DownloadIncomingMessage)
|
|
request.on('error', error)
|
|
request.on('response', onResponse)
|
|
request.on('redirect', redirect)
|
|
return () => {
|
|
request.off('error', error)
|
|
request.off('response', onResponse)
|
|
request.off('redirect', redirect)
|
|
}
|
|
},
|
|
abort: () => {
|
|
request.abort()
|
|
},
|
|
end: () => {
|
|
request.end()
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Importing these factories loads Electron net. */
|
|
export const electronSpeechServiceFactories: SpeechServiceFactories = {
|
|
createModelManager: (customModelsDir) =>
|
|
new ModelManager(customModelsDir, openElectronSpeechDownloadRequest),
|
|
createSttService: (models, gpuEnabled) => {
|
|
const gpu = createSttGpuPolicy(models.getModelsDir(), gpuEnabled)
|
|
return new SttService(models, () => spawnSttWorkerWithCpuFallback(spawnSttUtilityProcess, gpu))
|
|
}
|
|
}
|