package model import ( "fmt" "sort" ) const ( BackendSenseVoice = "sensevoice" BackendMoonshine = "moonshine" BackendTransducer = "transducer" BackendNemoTransducer = "nemo_transducer" BackendQwen3ASR = "qwen3_asr" BackendXASRStreaming = "x_asr_streaming" ) var languageProfileOrder = []string{ DefaultLanguageID, EnglishLanguageID, FrenchLanguageID, GermanLanguageID, SpanishLanguageID, ItalianLanguageID, PortugueseLanguageID, JapaneseLanguageID, KoreanLanguageID, } var modelProfiles = map[string]ModelProfile{ DefaultModelID: { ID: DefaultModelID, DisplayName: "SenseVoice", BackendKind: BackendSenseVoice, Tier: "default", SupportedLanguageIDs: []string{ "zh-CN", "zh-Hans", "zh-Hant", "yue", "en", "ja", "ko", }, RecommendedFor: []string{"zh-CN"}, Description: "轻量中文模型,适合中文和中英混输。", DownloadURLs: []string{ "http://www.maikami.com/voicesnap/sensevoice.zip", "https://modelscope.cn/models/sherpa-onnx/sherpa-onnx-sense-voice-zh-en-ja-ko-yue/resolve/master/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2", }, InstallDirName: "sensevoice-zh", LegacyDirNames: []string{"sensevoice"}, RequiredFiles: []RequiredFileRule{ { Role: "model", AnyOf: []string{"model.int8.onnx", "model.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"coreml", "cpu"}, NumThreads: 4, }, MoonshineModelID: { ID: MoonshineModelID, DisplayName: "Moonshine English", BackendKind: BackendMoonshine, Tier: "default", SupportedLanguageIDs: []string{EnglishLanguageID}, RecommendedFor: []string{EnglishLanguageID}, Description: "轻量英文离线模型,适合英文听写和英文日常输入。", ApproxSize: "约 239 MiB 下载包,解压后约 270-280 MiB", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-moonshine-base-en-int8.tar.bz2", }, InstallDirName: "moonshine-en", RequiredFiles: []RequiredFileRule{ { Role: "preprocessor", AllOf: []string{"preprocess.onnx"}, Required: true, }, { Role: "encoder", AllOf: []string{"encode.int8.onnx"}, Required: true, }, { Role: "uncached_decoder", AllOf: []string{"uncached_decode.int8.onnx"}, Required: true, }, { Role: "cached_decoder", AllOf: []string{"cached_decode.int8.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 4, }, ParakeetModelID: { ID: ParakeetModelID, DisplayName: "Parakeet English", BackendKind: BackendNemoTransducer, Tier: "advanced", SupportedLanguageIDs: []string{EnglishLanguageID}, RecommendedFor: []string{EnglishLanguageID}, Description: "高质量英文离线模型,适合英文长句、技术词和更高准确率需求。", ApproxSize: "约 478 MiB 下载包", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-nemo-parakeet-unified-en-0.6b-int8-non-streaming.tar.bz2", }, InstallDirName: "parakeet-en", RequiredFiles: []RequiredFileRule{ { Role: "encoder", AllOf: []string{"encoder.int8.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder.int8.onnx"}, Required: true, }, { Role: "joiner", AllOf: []string{"joiner.int8.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 4, }, ParakeetV3ModelID: { ID: ParakeetV3ModelID, DisplayName: "Parakeet TDT v3", BackendKind: BackendNemoTransducer, Tier: "default", SupportedLanguageIDs: []string{ FrenchLanguageID, GermanLanguageID, SpanishLanguageID, ItalianLanguageID, PortugueseLanguageID, }, RecommendedFor: []string{ FrenchLanguageID, GermanLanguageID, SpanishLanguageID, ItalianLanguageID, PortugueseLanguageID, }, Description: "欧洲多语种离线模型,适合法语、德语、西班牙语、意大利语和葡萄牙语听写。", ApproxSize: "约 640 MiB 解压后模型目录", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8.tar.bz2", }, InstallDirName: "parakeet-tdt-0.6b-v3", RequiredFiles: []RequiredFileRule{ { Role: "encoder", AllOf: []string{"encoder.int8.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder.int8.onnx"}, Required: true, }, { Role: "joiner", AllOf: []string{"joiner.int8.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 2, }, JapaneseModelID: { ID: JapaneseModelID, DisplayName: "Zipformer Japanese", BackendKind: BackendTransducer, Tier: "default", SupportedLanguageIDs: []string{JapaneseLanguageID}, RecommendedFor: []string{JapaneseLanguageID}, Description: "日语专项离线模型,基于 ReazonSpeech 日语语料。", ApproxSize: "约 165 MiB int8 关键模型文件,解压目录包含 fp32 对照文件", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-zipformer-ja-reazonspeech-2024-08-01.tar.bz2", }, InstallDirName: "zipformer-ja-reazonspeech", RequiredFiles: []RequiredFileRule{ { Role: "encoder", AllOf: []string{"encoder-epoch-99-avg-1.int8.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder-epoch-99-avg-1.onnx"}, Required: true, }, { Role: "joiner", AllOf: []string{"joiner-epoch-99-avg-1.int8.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 1, }, KoreanModelID: { ID: KoreanModelID, DisplayName: "Zipformer Korean", BackendKind: BackendTransducer, Tier: "default", SupportedLanguageIDs: []string{KoreanLanguageID}, RecommendedFor: []string{KoreanLanguageID}, Description: "韩语专项离线模型,适合韩语听写。", ApproxSize: "约 73 MiB int8 关键模型文件,解压目录包含 fp32 对照文件", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-zipformer-korean-2024-06-24.tar.bz2", }, InstallDirName: "zipformer-ko", RequiredFiles: []RequiredFileRule{ { Role: "encoder", AllOf: []string{"encoder-epoch-99-avg-1.int8.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder-epoch-99-avg-1.onnx"}, Required: true, }, { Role: "joiner", AllOf: []string{"joiner-epoch-99-avg-1.int8.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 2, }, Qwen3ASRModelID: { ID: Qwen3ASRModelID, DisplayName: "Qwen3-ASR", BackendKind: BackendQwen3ASR, Tier: "advanced", SupportedLanguageIDs: []string{DefaultLanguageID, "zh-Hans", "zh-Hant", "yue"}, RecommendedFor: []string{DefaultLanguageID}, Description: "高质量中文离线模型,适合中文长句、技术词和中英混输准确率需求。", ApproxSize: "约 838 MiB 下载包,解压后约 1.9 GiB", DownloadURLs: []string{ "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-qwen3-asr-0.6B-int8-2026-03-25.tar.bz2", }, InstallDirName: "qwen3-asr-0.6b", RequiredFiles: []RequiredFileRule{ { Role: "conv_frontend", AllOf: []string{"conv_frontend.onnx"}, Required: true, }, { Role: "encoder", AllOf: []string{"encoder.int8.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder.int8.onnx"}, Required: true, }, { Role: "tokenizer", AllOf: []string{"tokenizer"}, Required: true, Directory: true, }, { Role: "tokenizer_merges", AllOf: []string{"tokenizer/merges.txt"}, Required: true, }, { Role: "tokenizer_vocab", AllOf: []string{"tokenizer/vocab.json"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 4, }, XASRZhEn960ModelID: { ID: XASRZhEn960ModelID, DisplayName: "X-ASR zh-en 960ms", BackendKind: BackendXASRStreaming, Tier: "experimental", SupportedLanguageIDs: []string{DefaultLanguageID, "zh-Hans", "zh-Hant", "yue", EnglishLanguageID}, RecommendedFor: []string{DefaultLanguageID, EnglishLanguageID}, Description: "实验性中英流式模型,适合评估中英混输和未来边说边出字体验。", ApproxSize: "单个 960 ms 模型目录约 586 MiB", InstallDirName: "x-asr-zh-en-960ms", RequiredFiles: []RequiredFileRule{ { Role: "encoder", AllOf: []string{"encoder-960ms.onnx"}, Required: true, }, { Role: "decoder", AllOf: []string{"decoder-960ms.onnx"}, Required: true, }, { Role: "joiner", AllOf: []string{"joiner-960ms.onnx"}, Required: true, }, { Role: "tokens", AllOf: []string{"tokens.txt"}, Required: true, }, }, ProviderOrder: []string{"cpu"}, NumThreads: 1, }, } var languageProfiles = map[string]LanguageProfile{ DefaultLanguageID: { ID: DefaultLanguageID, DisplayName: "Chinese", NativeName: "中文", UILocale: "zh", SystemMatchers: []string{"zh", "zh-CN", "zh-Hans", "zh-Hant"}, DefaultModelID: DefaultModelID, UpgradeModelIDs: []string{Qwen3ASRModelID, XASRZhEn960ModelID}, }, EnglishLanguageID: { ID: EnglishLanguageID, DisplayName: "English", NativeName: "English", UILocale: "en", SystemMatchers: []string{"en", "en-US", "en-GB", "en-AU", "en-CA"}, DefaultModelID: MoonshineModelID, UpgradeModelIDs: []string{ParakeetModelID, XASRZhEn960ModelID}, }, FrenchLanguageID: { ID: FrenchLanguageID, DisplayName: "French", NativeName: "Français", UILocale: "fr", SystemMatchers: []string{"fr", "fr-FR", "fr-BE", "fr-CA", "fr-CH"}, DefaultModelID: ParakeetV3ModelID, }, GermanLanguageID: { ID: GermanLanguageID, DisplayName: "German", NativeName: "Deutsch", UILocale: "de", SystemMatchers: []string{"de", "de-DE", "de-AT", "de-CH"}, DefaultModelID: ParakeetV3ModelID, }, SpanishLanguageID: { ID: SpanishLanguageID, DisplayName: "Spanish", NativeName: "Español", UILocale: "es", SystemMatchers: []string{"es", "es-ES", "es-MX", "es-US", "es-AR"}, DefaultModelID: ParakeetV3ModelID, }, ItalianLanguageID: { ID: ItalianLanguageID, DisplayName: "Italian", NativeName: "Italiano", UILocale: "it", SystemMatchers: []string{"it", "it-IT", "it-CH"}, DefaultModelID: ParakeetV3ModelID, }, PortugueseLanguageID: { ID: PortugueseLanguageID, DisplayName: "Portuguese", NativeName: "Português", UILocale: "pt", SystemMatchers: []string{"pt", "pt-PT", "pt-BR"}, DefaultModelID: ParakeetV3ModelID, }, JapaneseLanguageID: { ID: JapaneseLanguageID, DisplayName: "Japanese", NativeName: "日本語", UILocale: "ja", SystemMatchers: []string{"ja", "ja-JP"}, DefaultModelID: JapaneseModelID, }, KoreanLanguageID: { ID: KoreanLanguageID, DisplayName: "Korean", NativeName: "한국어", UILocale: "ko", SystemMatchers: []string{"ko", "ko-KR"}, DefaultModelID: KoreanModelID, }, } func GetModelProfile(id string) (ModelProfile, error) { if id == "" { id = DefaultModelID } profile, ok := modelProfiles[id] if !ok { return ModelProfile{}, fmt.Errorf("unknown model profile: %s", id) } return profile, nil } func DefaultModelProfile() ModelProfile { return modelProfiles[DefaultModelID] } func ListModelProfiles() []ModelProfile { profiles := make([]ModelProfile, 0, len(modelProfiles)) for _, profile := range modelProfiles { profiles = append(profiles, profile) } return profiles } func NormalizeModelID(id string) string { if _, err := GetModelProfile(id); err != nil { return DefaultModelID } return id } func GetLanguageProfile(id string) (LanguageProfile, error) { profile, ok := languageProfiles[id] if !ok { return LanguageProfile{}, fmt.Errorf("unknown language profile: %s", id) } return profile, nil } func ListLanguageProfiles() []LanguageProfile { profiles := make([]LanguageProfile, 0, len(languageProfiles)) seen := make(map[string]bool, len(languageProfiles)) for _, id := range languageProfileOrder { if profile, ok := languageProfiles[id]; ok { profiles = append(profiles, profile) seen[id] = true } } for id, profile := range languageProfiles { if !seen[id] { profiles = append(profiles, profile) } } sort.Slice(profiles, func(i, j int) bool { left := languageOrderIndex(profiles[i].ID) right := languageOrderIndex(profiles[j].ID) if left != right { return left < right } return profiles[i].ID < profiles[j].ID }) return profiles } func languageOrderIndex(id string) int { for i, orderedID := range languageProfileOrder { if id == orderedID { return i } } return len(languageProfileOrder) } func NormalizeLanguageID(id string) string { if _, err := GetLanguageProfile(id); err != nil { return DefaultLanguageID } return id }