From 56e75cf77d2c24f0230ee44818d77a5b9a417723 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 6 Feb 2026 22:08:01 +0700 Subject: [PATCH] speech: derive all model ids from sherpa catalog --- .../providers/local/sherpa/model-catalog.ts | 195 ++++++++++-------- 1 file changed, 109 insertions(+), 86 deletions(-) diff --git a/packages/server/src/server/speech/providers/local/sherpa/model-catalog.ts b/packages/server/src/server/speech/providers/local/sherpa/model-catalog.ts index 8012fd911..10d85c2e2 100644 --- a/packages/server/src/server/speech/providers/local/sherpa/model-catalog.ts +++ b/packages/server/src/server/speech/providers/local/sherpa/model-catalog.ts @@ -2,89 +2,21 @@ import { z } from "zod"; export type SherpaOnnxModelKind = "stt-online" | "stt-offline" | "tts"; -const MODEL_IDS = { - ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20: "zipformer-bilingual-zh-en-2023-02-20", - PARAFORMER_BILINGUAL_ZH_EN: "paraformer-bilingual-zh-en", - PARAKEET_TDT_0_6B_V3_INT8: "parakeet-tdt-0.6b-v3-int8", - KITTEN_NANO_EN_V0_1_FP16: "kitten-nano-en-v0_1-fp16", - KOKORO_EN_V0_19: "kokoro-en-v0_19", - POCKET_TTS_ONNX_INT8: "pocket-tts-onnx-int8", -} as const; +type DefaultModelRole = "stt" | "tts"; -export type SherpaOnnxModelId = (typeof MODEL_IDS)[keyof typeof MODEL_IDS]; - -export const LOCAL_STT_MODEL_IDS = [ - MODEL_IDS.ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20, - MODEL_IDS.PARAFORMER_BILINGUAL_ZH_EN, - MODEL_IDS.PARAKEET_TDT_0_6B_V3_INT8, -] as const; -export type LocalSttModelId = (typeof LOCAL_STT_MODEL_IDS)[number]; - -export const LOCAL_TTS_MODEL_IDS = [ - MODEL_IDS.KITTEN_NANO_EN_V0_1_FP16, - MODEL_IDS.KOKORO_EN_V0_19, - MODEL_IDS.POCKET_TTS_ONNX_INT8, -] as const; -export type LocalTtsModelId = (typeof LOCAL_TTS_MODEL_IDS)[number]; - -export const DEFAULT_LOCAL_STT_MODEL: LocalSttModelId = MODEL_IDS.PARAKEET_TDT_0_6B_V3_INT8; -export const DEFAULT_LOCAL_TTS_MODEL: LocalTtsModelId = MODEL_IDS.POCKET_TTS_ONNX_INT8; - -const STT_MODEL_ALIASES: Record = { - zipformer: MODEL_IDS.ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20, - "zipformer-bilingual": MODEL_IDS.ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20, - paraformer: MODEL_IDS.PARAFORMER_BILINGUAL_ZH_EN, - parakeet: MODEL_IDS.PARAKEET_TDT_0_6B_V3_INT8, - "parakeet-v3": MODEL_IDS.PARAKEET_TDT_0_6B_V3_INT8, - "parakeet-tdt": MODEL_IDS.PARAKEET_TDT_0_6B_V3_INT8, -}; - -const TTS_MODEL_ALIASES: Record = { - pocket: MODEL_IDS.POCKET_TTS_ONNX_INT8, - "pocket-tts": MODEL_IDS.POCKET_TTS_ONNX_INT8, - kitten: MODEL_IDS.KITTEN_NANO_EN_V0_1_FP16, - kokoro: MODEL_IDS.KOKORO_EN_V0_19, -}; - -function createAliasedModelIdSchema( - values: T, - aliases: Record -) { - return z.preprocess((value) => { - if (typeof value !== "string") { - return value; - } - const normalized = value.trim().toLowerCase(); - if (!normalized) { - return value; - } - return aliases[normalized] ?? normalized; - }, z.enum(values)); -} - -export const LocalSttModelIdSchema = createAliasedModelIdSchema( - LOCAL_STT_MODEL_IDS, - STT_MODEL_ALIASES -); - -export const LocalTtsModelIdSchema = createAliasedModelIdSchema( - LOCAL_TTS_MODEL_IDS, - TTS_MODEL_ALIASES -); - -export type SherpaOnnxModelSpec = { - id: SherpaOnnxModelId; +type SherpaOnnxCatalogEntry = { kind: SherpaOnnxModelKind; archiveUrl?: string; downloadFiles?: Array<{ url: string; relPath: string }>; extractedDir: string; requiredFiles: string[]; description: string; + aliases?: readonly string[]; + defaultFor?: DefaultModelRole; }; -export const SHERPA_ONNX_MODEL_CATALOG: Record = { - [MODEL_IDS.ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20]: { - id: MODEL_IDS.ZIPFORMER_BILINGUAL_ZH_EN_2023_02_20, +export const SHERPA_ONNX_MODEL_CATALOG = { + "zipformer-bilingual-zh-en-2023-02-20": { kind: "stt-online", archiveUrl: "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2", @@ -96,44 +28,45 @@ export const SHERPA_ONNX_MODEL_CATALOG: Record; + +export type SherpaOnnxModelId = keyof typeof SHERPA_ONNX_MODEL_CATALOG; + +type ModelIdByKind = { + [Id in SherpaOnnxModelId]: (typeof SHERPA_ONNX_MODEL_CATALOG)[Id]["kind"] extends K + ? Id + : never; +}[SherpaOnnxModelId]; + +export type LocalSttModelId = ModelIdByKind<"stt-online"> | ModelIdByKind<"stt-offline">; +export type LocalTtsModelId = ModelIdByKind<"tts">; + +const ALL_MODEL_IDS = Object.keys(SHERPA_ONNX_MODEL_CATALOG) as SherpaOnnxModelId[]; + +export const LOCAL_STT_MODEL_IDS = ALL_MODEL_IDS.filter( + (id): id is LocalSttModelId => SHERPA_ONNX_MODEL_CATALOG[id].kind !== "tts" +); + +export const LOCAL_TTS_MODEL_IDS = ALL_MODEL_IDS.filter( + (id): id is LocalTtsModelId => SHERPA_ONNX_MODEL_CATALOG[id].kind === "tts" +); + +function resolveDefaultModelId(role: "stt"): LocalSttModelId; +function resolveDefaultModelId(role: "tts"): LocalTtsModelId; +function resolveDefaultModelId(role: DefaultModelRole): SherpaOnnxModelId { + const match = ALL_MODEL_IDS.find((id) => { + const entry: SherpaOnnxCatalogEntry = SHERPA_ONNX_MODEL_CATALOG[id]; + return entry.defaultFor === role; + }); + if (!match) { + throw new Error(`No default model configured for role '${role}'`); + } + return match; +} + +export const DEFAULT_LOCAL_STT_MODEL = resolveDefaultModelId("stt"); +export const DEFAULT_LOCAL_TTS_MODEL = resolveDefaultModelId("tts"); + +function buildAliasMap(modelIds: readonly T[]): Record { + const aliasMap: Record = {}; + for (const modelId of modelIds) { + const aliases = SHERPA_ONNX_MODEL_CATALOG[modelId as SherpaOnnxModelId].aliases ?? []; + for (const alias of aliases) { + aliasMap[alias.trim().toLowerCase()] = modelId; + } + } + return aliasMap; +} + +function createAliasedModelIdSchema(params: { + modelIds: readonly T[]; + aliases: Record; +}): z.ZodType { + const validIds = new Set(params.modelIds); + return z.preprocess((value) => { + if (typeof value !== "string") { + return value; + } + const normalized = value.trim().toLowerCase(); + if (!normalized) { + return value; + } + return params.aliases[normalized] ?? normalized; + }, z.string().refine((value): value is T => validIds.has(value as T))) as z.ZodType; +} + +const STT_MODEL_ALIASES = buildAliasMap(LOCAL_STT_MODEL_IDS); +const TTS_MODEL_ALIASES = buildAliasMap(LOCAL_TTS_MODEL_IDS); + +export const LocalSttModelIdSchema = createAliasedModelIdSchema({ + modelIds: LOCAL_STT_MODEL_IDS, + aliases: STT_MODEL_ALIASES, +}); + +export const LocalTtsModelIdSchema = createAliasedModelIdSchema({ + modelIds: LOCAL_TTS_MODEL_IDS, + aliases: TTS_MODEL_ALIASES, +}); + +export type SherpaOnnxModelSpec = SherpaOnnxCatalogEntry & { + id: SherpaOnnxModelId; }; export function listSherpaOnnxModels(): SherpaOnnxModelSpec[] { - return Object.values(SHERPA_ONNX_MODEL_CATALOG); + return ALL_MODEL_IDS.map((id) => ({ + id, + ...SHERPA_ONNX_MODEL_CATALOG[id], + })); } export function getSherpaOnnxModelSpec(id: SherpaOnnxModelId): SherpaOnnxModelSpec { @@ -188,5 +208,8 @@ export function getSherpaOnnxModelSpec(id: SherpaOnnxModelId): SherpaOnnxModelSp if (!spec) { throw new Error(`Unknown sherpa-onnx model id: ${id}`); } - return spec; + return { + id, + ...spec, + }; }