Add Monterey compatibility build line
14 files modified
12 files added
| | |
| | | # Changelog |
| | | |
| | | ## v2.1.37 (2026-06-29) |
| | | |
| | | ### 双构建线 |
| | | |
| | | - **默认 macOS 14+ 构建继续支持 Qwen3-ASR**:主 `go.mod` 保持当前 sherpa-onnx 依赖线,中文高级模型能力不回退。 |
| | | - **新增 Monterey 兼容构建线**:新增 `monterey_compat` 构建标签和 `go.monterey.mod`,使用 macOS 11 兼容的旧版 sherpa/onnxruntime。 |
| | | - **兼容包中 Qwen3-ASR 置灰禁用**:Qwen3-ASR 不隐藏,仍显示在中文模型列表中,但标记为不可用并提示需要 macOS 14 或更高版本。 |
| | | - **构建最低系统版本分离**:默认构建声明 macOS 14.0 起,Monterey 兼容脚本覆盖为 macOS 11.0 起。 |
| | | |
| | | ### 构建 |
| | | |
| | | - build: `20260629.0059` |
| | | - 说明: 本版本为双构建线和 Monterey 兼容本地验证候选包。 |
| | | |
| | | --- |
| | | |
| | | ## v2.1.36 (2026-06-28) |
| | | |
| | | ### 首尾字稳定性修复 |
| | |
| | | ) |
| | | |
| | | const ( |
| | | appVersion = "2.1.36" |
| | | appBuild = "20260628.2354" |
| | | appVersion = "2.1.37" |
| | | appBuild = "20260629.0059" |
| | | appDisplayVersion = appVersion + " (build " + appBuild + ")" |
| | | appName = "PrivateVoice Dictation" |
| | | |
| | |
| | | <key>CFBundlePackageType</key> |
| | | <string>APPL</string> |
| | | <key>CFBundleShortVersionString</key> |
| | | <string>2.1.36</string> |
| | | <string>2.1.37</string> |
| | | <key>CFBundleVersion</key> |
| | | <string>20260628.2354</string> |
| | | <string>20260629.0059</string> |
| | | <key>ITSAppUsesNonExemptEncryption</key> |
| | | <false/> |
| | | <key>LSApplicationCategoryType</key> |
| | | <string>public.app-category.productivity</string> |
| | | <key>LSMinimumSystemVersion</key> |
| | | <string>11.0</string> |
| | | <string>14.0</string> |
| | | <key>LSUIElement</key> |
| | | <true/> |
| | | <key>NSHighResolutionCapable</key> |
| | |
| | | { |
| | | "name": "privatevoice-dictation-frontend", |
| | | "version": "2.1.36", |
| | | "version": "2.1.37", |
| | | "lockfileVersion": 3, |
| | | "requires": true, |
| | | "packages": { |
| | | "": { |
| | | "name": "privatevoice-dictation-frontend", |
| | | "version": "2.1.36", |
| | | "version": "2.1.37", |
| | | "dependencies": { |
| | | "@wailsio/runtime": "latest" |
| | | }, |
| | |
| | | { |
| | | "name": "privatevoice-dictation-frontend", |
| | | "private": true, |
| | | "version": "2.1.36", |
| | | "version": "2.1.37", |
| | | "type": "module", |
| | | "scripts": { |
| | | "dev": "vite dev", |
| | |
| | | isDefault: boolean |
| | | downloadSize?: string |
| | | description?: string |
| | | supportedInBuild?: boolean |
| | | unsupportedReason?: string |
| | | } |
| | | |
| | | interface LanguageOption { |
| | |
| | | } |
| | | |
| | | function modelTierLabel(option: ModelOption): string { |
| | | if (!modelSupported(option)) return t('settings.modelUnavailable') |
| | | if (option.isDefault) return t('settings.modelDefault') |
| | | if (option.tier === 'advanced') return t('settings.modelAdvanced') |
| | | return option.tier || '' |
| | | } |
| | | |
| | | function modelSupported(option: ModelOption): boolean { |
| | | return option.supportedInBuild !== false |
| | | } |
| | | |
| | | function modelUnsupportedText(option: ModelOption): string { |
| | | if (option.unsupportedReason === 'requires_macos_14') { |
| | | return t('models.qwen3RequiresMacOS14') |
| | | } |
| | | return t('settings.modelUnavailable') |
| | | } |
| | | |
| | | function modelActionLabel(option: ModelOption): string { |
| | | if (!modelSupported(option)) return t('settings.modelUnavailable') |
| | | if (modelBusyID === option.modelID) { |
| | | if (modelBusyKind === 'download') { |
| | | return modelCancellingID === option.modelID ? t('settings.modelCancelling') : t('settings.modelCancel') |
| | |
| | | |
| | | function modelMeta(option: ModelOption): string { |
| | | let meta = option.installed ? t('settings.modelInstalled') : t('settings.modelNotInstalled') |
| | | if (!modelSupported(option)) { |
| | | return `${meta} · ${modelUnsupportedText(option)}` |
| | | } |
| | | if (option.downloadSize) { |
| | | meta += ` · ${option.downloadSize}` |
| | | } |
| | |
| | | } |
| | | |
| | | async function onModelAction(option: ModelOption) { |
| | | if ((option.isCurrent && option.installed) || modelBusyID) return |
| | | if (!modelSupported(option) || (option.isCurrent && option.installed) || modelBusyID) return |
| | | modelBusyID = option.modelID |
| | | modelBusyKind = option.installed ? 'select' : 'download' |
| | | modelProgress = 0 |
| | |
| | | |
| | | <div class="model-list"> |
| | | {#each modelOptions as option} |
| | | <div class="model-card" class:current={option.isCurrent}> |
| | | <div class="model-card" class:current={option.isCurrent} class:unsupported={!modelSupported(option)}> |
| | | <div class="model-main"> |
| | | <div class="model-title-row"> |
| | | <span class="model-title">{option.displayName}</span> |
| | |
| | | <button |
| | | class="model-action primary" |
| | | class:cancel={canCancelModel(option)} |
| | | disabled={!!modelBusyID && modelBusyID !== option.modelID} |
| | | disabled={!modelSupported(option) || (!!modelBusyID && modelBusyID !== option.modelID)} |
| | | onclick={() => onModelButton(option)} |
| | | > |
| | | {modelActionLabel(option)} |
| | |
| | | background: rgba(0, 122, 255, 0.055); |
| | | } |
| | | |
| | | .model-card.unsupported { |
| | | opacity: 0.76; |
| | | } |
| | | |
| | | .model-card.current::before { |
| | | content: ''; |
| | | position: absolute; |
| | |
| | | "modelCancel": "Cancel", |
| | | "modelCancelling": "Cancelling", |
| | | "modelWorking": "Working", |
| | | "modelUnavailable": "Unavailable", |
| | | "modelInstalled": "Installed", |
| | | "modelNotInstalled": "Not installed", |
| | | "modelActionFailed": "Model action failed", |
| | |
| | | "parakeetV3Desc": "Multilingual European offline model for French, German, Spanish, Italian, and Portuguese.", |
| | | "japaneseDesc": "Dedicated Japanese offline model based on ReazonSpeech.", |
| | | "koreanDesc": "Dedicated Korean offline model for Korean dictation.", |
| | | "qwen3Desc": "Higher quality Chinese offline model for long Chinese dictation, technical terms, and mixed Chinese-English input." |
| | | "qwen3Desc": "Higher quality Chinese offline model for long Chinese dictation, technical terms, and mixed Chinese-English input.", |
| | | "qwen3RequiresMacOS14": "Qwen3-ASR requires macOS 14 or later. This compatibility build does not support downloading it." |
| | | }, |
| | | "permissions": { |
| | | "title": "Permission Check", |
| | |
| | | "modelCancel": "取消", |
| | | "modelCancelling": "正在取消", |
| | | "modelWorking": "处理中", |
| | | "modelUnavailable": "不可用", |
| | | "modelInstalled": "已安装", |
| | | "modelNotInstalled": "未安装", |
| | | "modelActionFailed": "模型操作失败", |
| | |
| | | "parakeetV3Desc": "欧洲多语种离线模型,适合法语、德语、西班牙语、意大利语和葡萄牙语。", |
| | | "japaneseDesc": "基于 ReazonSpeech 的日语专项离线模型。", |
| | | "koreanDesc": "韩语专项离线模型,适合韩语听写。", |
| | | "qwen3Desc": "更高质量的中文离线模型,适合中文长句、技术词和中英混输。" |
| | | "qwen3Desc": "更高质量的中文离线模型,适合中文长句、技术词和中英混输。", |
| | | "qwen3RequiresMacOS14": "Qwen3-ASR 需要 macOS 14 或更高版本,本兼容包暂不支持下载。" |
| | | }, |
| | | "permissions": { |
| | | "title": "权限检查", |
| New file |
| | |
| | | module voicesnap |
| | | |
| | | go 1.25 |
| | | |
| | | require ( |
| | | github.com/gen2brain/malgo v0.11.22 |
| | | github.com/k2-fsa/sherpa-onnx-go v1.12.20 |
| | | github.com/k2-fsa/sherpa-onnx-go-linux v1.12.20 |
| | | github.com/k2-fsa/sherpa-onnx-go-macos v1.12.20 |
| | | github.com/k2-fsa/sherpa-onnx-go-windows v1.12.20 |
| | | github.com/wailsapp/wails/v3 v3.0.0-alpha.71 |
| | | golang.org/x/sys v0.40.0 |
| | | ) |
| | | |
| | | require ( |
| | | dario.cat/mergo v1.0.2 // indirect |
| | | github.com/Microsoft/go-winio v0.6.2 // indirect |
| | | github.com/ProtonMail/go-crypto v1.3.0 // indirect |
| | | github.com/adrg/xdg v0.5.3 // indirect |
| | | github.com/bep/debounce v1.2.1 // indirect |
| | | github.com/cloudflare/circl v1.6.3 // indirect |
| | | github.com/coder/websocket v1.8.14 // indirect |
| | | github.com/cyphar/filepath-securejoin v0.6.1 // indirect |
| | | github.com/ebitengine/purego v0.9.1 // indirect |
| | | github.com/emirpasic/gods v1.18.1 // indirect |
| | | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect |
| | | github.com/go-git/go-billy/v5 v5.7.0 // indirect |
| | | github.com/go-git/go-git/v5 v5.16.4 // indirect |
| | | github.com/go-ole/go-ole v1.3.0 // indirect |
| | | github.com/godbus/dbus/v5 v5.2.2 // indirect |
| | | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect |
| | | github.com/google/uuid v1.6.0 // indirect |
| | | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect |
| | | github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect |
| | | github.com/kevinburke/ssh_config v1.4.0 // indirect |
| | | github.com/klauspost/cpuid/v2 v2.3.0 // indirect |
| | | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect |
| | | github.com/leaanthony/u v1.1.1 // indirect |
| | | github.com/lmittmann/tint v1.1.2 // indirect |
| | | github.com/mattn/go-colorable v0.1.14 // indirect |
| | | github.com/mattn/go-isatty v0.0.20 // indirect |
| | | github.com/pjbgf/sha1cd v0.5.0 // indirect |
| | | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect |
| | | github.com/rivo/uniseg v0.4.7 // indirect |
| | | github.com/samber/lo v1.52.0 // indirect |
| | | github.com/sergi/go-diff v1.4.0 // indirect |
| | | github.com/skeema/knownhosts v1.3.2 // indirect |
| | | github.com/wailsapp/go-webview2 v1.0.23 // indirect |
| | | github.com/xanzy/ssh-agent v0.3.3 // indirect |
| | | golang.org/x/crypto v0.47.0 // indirect |
| | | golang.org/x/net v0.49.0 // indirect |
| | | golang.org/x/text v0.33.0 // indirect |
| | | gopkg.in/warnings.v0 v0.1.2 // indirect |
| | | ) |
| New file |
| | |
| | | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= |
| | | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= |
| | | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= |
| | | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= |
| | | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= |
| | | github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= |
| | | github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= |
| | | github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= |
| | | github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= |
| | | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= |
| | | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= |
| | | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= |
| | | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= |
| | | github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= |
| | | github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= |
| | | github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= |
| | | github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= |
| | | github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= |
| | | github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= |
| | | github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= |
| | | github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= |
| | | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| | | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
| | | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
| | | github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= |
| | | github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= |
| | | github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= |
| | | github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= |
| | | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= |
| | | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= |
| | | github.com/gen2brain/malgo v0.11.22 h1:fRtTbzVI9CDWnfEJGo/GxKxN7pXtCb0NsAeUVUjZk9U= |
| | | github.com/gen2brain/malgo v0.11.22/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww= |
| | | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= |
| | | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= |
| | | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= |
| | | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= |
| | | github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= |
| | | github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= |
| | | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= |
| | | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= |
| | | github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= |
| | | github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= |
| | | github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU= |
| | | github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok= |
| | | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= |
| | | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= |
| | | github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= |
| | | github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= |
| | | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= |
| | | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= |
| | | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= |
| | | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= |
| | | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= |
| | | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
| | | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= |
| | | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= |
| | | github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= |
| | | github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= |
| | | github.com/k2-fsa/sherpa-onnx-go v1.12.20 h1:tQYCk7U2VrL6dO6LRPSJiDZgtXOzISoHaAgMhSa1tkw= |
| | | github.com/k2-fsa/sherpa-onnx-go v1.12.20/go.mod h1:B/ynRbVa5gpYoZYeYgY3zPi4MTfKk95UZueZDSIhbjk= |
| | | github.com/k2-fsa/sherpa-onnx-go-linux v1.12.20 h1:0NY5XVRX/unNDLDAkR3q8jXTUZ1WNTLiPP8MUSELWnQ= |
| | | github.com/k2-fsa/sherpa-onnx-go-linux v1.12.20/go.mod h1:NXEH2rsBgTdqY59YpPq6CtSBlBAXy/8a9FmpLERU97I= |
| | | github.com/k2-fsa/sherpa-onnx-go-macos v1.12.20 h1:9CrKABoDHx/BsFOv3uZcaG1bDjR+5TSK975gS3bNNjI= |
| | | github.com/k2-fsa/sherpa-onnx-go-macos v1.12.20/go.mod h1:ZOhUAXC62Unj0ZNfu6zxSFKcW96aXf7P3BsqiUyOBbE= |
| | | github.com/k2-fsa/sherpa-onnx-go-windows v1.12.20 h1:1Qsp4vkngTkEDxlc+GfA+/1B8ypbxIE0p8fsnfaSlkg= |
| | | github.com/k2-fsa/sherpa-onnx-go-windows v1.12.20/go.mod h1:5AX7TU8+P/gInjglY1ijtWUM2b8iyR0QX4yEngzMe64= |
| | | github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= |
| | | github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= |
| | | github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= |
| | | github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= |
| | | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
| | | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= |
| | | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= |
| | | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
| | | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
| | | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
| | | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
| | | github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= |
| | | github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= |
| | | github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= |
| | | github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= |
| | | github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= |
| | | github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= |
| | | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= |
| | | github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= |
| | | github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= |
| | | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= |
| | | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= |
| | | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
| | | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
| | | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= |
| | | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= |
| | | github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= |
| | | github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= |
| | | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= |
| | | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= |
| | | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
| | | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
| | | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
| | | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
| | | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= |
| | | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= |
| | | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= |
| | | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= |
| | | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= |
| | | github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= |
| | | github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= |
| | | github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= |
| | | github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= |
| | | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= |
| | | github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= |
| | | github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= |
| | | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
| | | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
| | | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
| | | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
| | | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= |
| | | github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= |
| | | github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= |
| | | github.com/wailsapp/wails/v3 v3.0.0-alpha.71 h1:6ERh+1SJJ+tl5E4W49q8pDyQ4yeyi1yj9IdSppKtMx4= |
| | | github.com/wailsapp/wails/v3 v3.0.0-alpha.71/go.mod h1:4saK4A4K9970X+X7RkMwP2lyGbLogcUz54wVeq4C/V8= |
| | | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= |
| | | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= |
| | | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= |
| | | golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= |
| | | golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= |
| | | golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= |
| | | golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= |
| | | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
| | | golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= |
| | | golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= |
| | | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| | | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| | | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| | | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| | | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
| | | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| | | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| | | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| | | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| | | golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= |
| | | golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= |
| | | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
| | | golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= |
| | | golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= |
| | | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
| | | golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= |
| | | golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= |
| | | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
| | | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| | | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| | | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= |
| | | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= |
| | | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= |
| | | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= |
| | | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
| | | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
| | | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
| | | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
| | |
| | | config.ModelConfig.Transducer.Joiner = resolved.Files["joiner"] |
| | | config.ModelConfig.ModelType = model.BackendNemoTransducer |
| | | case model.BackendQwen3ASR: |
| | | config.ModelConfig.Qwen3ASR.ConvFrontend = resolved.Files["conv_frontend"] |
| | | config.ModelConfig.Qwen3ASR.Encoder = resolved.Files["encoder"] |
| | | config.ModelConfig.Qwen3ASR.Decoder = resolved.Files["decoder"] |
| | | config.ModelConfig.Qwen3ASR.Tokenizer = resolved.Files["tokenizer"] |
| | | config.ModelConfig.Qwen3ASR.MaxTotalLen = 1024 |
| | | config.ModelConfig.Qwen3ASR.MaxNewTokens = 256 |
| | | config.ModelConfig.Qwen3ASR.Temperature = 0.0 |
| | | config.ModelConfig.Qwen3ASR.TopP = 0.9 |
| | | config.ModelConfig.Qwen3ASR.Seed = 0 |
| | | if err := applyQwen3ASRConfig(&config, resolved); err != nil { |
| | | return sherpa.OfflineRecognizerConfig{}, err |
| | | } |
| | | default: |
| | | return sherpa.OfflineRecognizerConfig{}, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind) |
| | | } |
| New file |
| | |
| | | //go:build darwin && !monterey_compat |
| | | |
| | | package engine |
| | | |
| | | import ( |
| | | "voicesnap/internal/model" |
| | | |
| | | sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx" |
| | | ) |
| | | |
| | | func applyQwen3ASRConfig(config *sherpa.OfflineRecognizerConfig, resolved model.ResolvedModel) error { |
| | | config.ModelConfig.Qwen3ASR.ConvFrontend = resolved.Files["conv_frontend"] |
| | | config.ModelConfig.Qwen3ASR.Encoder = resolved.Files["encoder"] |
| | | config.ModelConfig.Qwen3ASR.Decoder = resolved.Files["decoder"] |
| | | config.ModelConfig.Qwen3ASR.Tokenizer = resolved.Files["tokenizer"] |
| | | config.ModelConfig.Qwen3ASR.MaxTotalLen = 1024 |
| | | config.ModelConfig.Qwen3ASR.MaxNewTokens = 256 |
| | | config.ModelConfig.Qwen3ASR.Temperature = 0.0 |
| | | config.ModelConfig.Qwen3ASR.TopP = 0.9 |
| | | config.ModelConfig.Qwen3ASR.Seed = 0 |
| | | return nil |
| | | } |
| New file |
| | |
| | | //go:build darwin && monterey_compat |
| | | |
| | | package engine |
| | | |
| | | import ( |
| | | "fmt" |
| | | "voicesnap/internal/model" |
| | | |
| | | sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx" |
| | | ) |
| | | |
| | | func applyQwen3ASRConfig(_ *sherpa.OfflineRecognizerConfig, _ model.ResolvedModel) error { |
| | | return fmt.Errorf("%s requires the macOS 14+ build", model.Qwen3ASRModelID) |
| | | } |
| New file |
| | |
| | | //go:build darwin && monterey_compat |
| | | |
| | | package engine |
| | | |
| | | import ( |
| | | "strings" |
| | | "testing" |
| | | |
| | | "voicesnap/internal/model" |
| | | ) |
| | | |
| | | func TestOfflineConfigForQwen3ASRRejectsMontereyCompatBuild(t *testing.T) { |
| | | _, err := offlineConfigForResolvedModel(model.ResolvedModel{ |
| | | ModelID: model.Qwen3ASRModelID, |
| | | BackendKind: model.BackendQwen3ASR, |
| | | Profile: model.ModelProfile{ |
| | | DisplayName: "Qwen3-ASR", |
| | | BackendKind: model.BackendQwen3ASR, |
| | | ProviderOrder: []string{"cpu"}, |
| | | NumThreads: 4, |
| | | }, |
| | | Files: map[string]string{ |
| | | "conv_frontend": "conv_frontend.onnx", |
| | | "encoder": "encoder.int8.onnx", |
| | | "decoder": "decoder.int8.onnx", |
| | | "tokenizer": "tokenizer", |
| | | }, |
| | | }, "cpu") |
| | | if err == nil { |
| | | t.Fatal("expected qwen3-asr to be rejected in monterey_compat build") |
| | | } |
| | | if !strings.Contains(err.Error(), "macOS 14+") { |
| | | t.Fatalf("error = %q, want macOS 14+ reason", err.Error()) |
| | | } |
| | | } |
| New file |
| | |
| | | //go:build darwin && !monterey_compat |
| | | |
| | | package engine |
| | | |
| | | import ( |
| | | "path/filepath" |
| | | "testing" |
| | | |
| | | "voicesnap/internal/model" |
| | | ) |
| | | |
| | | func TestOfflineConfigForQwen3ASRUsesQwen3Config(t *testing.T) { |
| | | root := t.TempDir() |
| | | files := map[string]string{ |
| | | "conv_frontend": filepath.Join(root, "conv_frontend.onnx"), |
| | | "encoder": filepath.Join(root, "encoder.int8.onnx"), |
| | | "decoder": filepath.Join(root, "decoder.int8.onnx"), |
| | | "tokenizer": filepath.Join(root, "tokenizer"), |
| | | } |
| | | resolved := model.ResolvedModel{ |
| | | ModelID: model.Qwen3ASRModelID, |
| | | BackendKind: model.BackendQwen3ASR, |
| | | Profile: model.ModelProfile{ |
| | | DisplayName: "Qwen3-ASR", |
| | | BackendKind: model.BackendQwen3ASR, |
| | | ProviderOrder: []string{"cpu"}, |
| | | NumThreads: 4, |
| | | }, |
| | | Files: files, |
| | | } |
| | | |
| | | config, err := offlineConfigForResolvedModel(resolved, "cpu") |
| | | if err != nil { |
| | | t.Fatal(err) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.ConvFrontend != files["conv_frontend"] { |
| | | t.Fatalf("conv frontend = %q, want %q", config.ModelConfig.Qwen3ASR.ConvFrontend, files["conv_frontend"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Encoder != files["encoder"] { |
| | | t.Fatalf("encoder = %q, want %q", config.ModelConfig.Qwen3ASR.Encoder, files["encoder"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Decoder != files["decoder"] { |
| | | t.Fatalf("decoder = %q, want %q", config.ModelConfig.Qwen3ASR.Decoder, files["decoder"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Tokenizer != files["tokenizer"] { |
| | | t.Fatalf("tokenizer = %q, want %q", config.ModelConfig.Qwen3ASR.Tokenizer, files["tokenizer"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.MaxTotalLen != 1024 { |
| | | t.Fatalf("max total len = %d, want 1024", config.ModelConfig.Qwen3ASR.MaxTotalLen) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.MaxNewTokens != 256 { |
| | | t.Fatalf("max new tokens = %d, want 256", config.ModelConfig.Qwen3ASR.MaxNewTokens) |
| | | } |
| | | if config.ModelConfig.Tokens != "" { |
| | | t.Fatalf("tokens = %q, want empty for qwen3-asr", config.ModelConfig.Tokens) |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | func TestOfflineConfigForQwen3ASRUsesQwen3Config(t *testing.T) { |
| | | root := t.TempDir() |
| | | files := map[string]string{ |
| | | "conv_frontend": filepath.Join(root, "conv_frontend.onnx"), |
| | | "encoder": filepath.Join(root, "encoder.int8.onnx"), |
| | | "decoder": filepath.Join(root, "decoder.int8.onnx"), |
| | | "tokenizer": filepath.Join(root, "tokenizer"), |
| | | } |
| | | resolved := model.ResolvedModel{ |
| | | ModelID: model.Qwen3ASRModelID, |
| | | BackendKind: model.BackendQwen3ASR, |
| | | Profile: model.ModelProfile{ |
| | | DisplayName: "Qwen3-ASR", |
| | | BackendKind: model.BackendQwen3ASR, |
| | | ProviderOrder: []string{"cpu"}, |
| | | NumThreads: 4, |
| | | }, |
| | | Files: files, |
| | | } |
| | | |
| | | config, err := offlineConfigForResolvedModel(resolved, "cpu") |
| | | if err != nil { |
| | | t.Fatal(err) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.ConvFrontend != files["conv_frontend"] { |
| | | t.Fatalf("conv frontend = %q, want %q", config.ModelConfig.Qwen3ASR.ConvFrontend, files["conv_frontend"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Encoder != files["encoder"] { |
| | | t.Fatalf("encoder = %q, want %q", config.ModelConfig.Qwen3ASR.Encoder, files["encoder"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Decoder != files["decoder"] { |
| | | t.Fatalf("decoder = %q, want %q", config.ModelConfig.Qwen3ASR.Decoder, files["decoder"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.Tokenizer != files["tokenizer"] { |
| | | t.Fatalf("tokenizer = %q, want %q", config.ModelConfig.Qwen3ASR.Tokenizer, files["tokenizer"]) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.MaxTotalLen != 1024 { |
| | | t.Fatalf("max total len = %d, want 1024", config.ModelConfig.Qwen3ASR.MaxTotalLen) |
| | | } |
| | | if config.ModelConfig.Qwen3ASR.MaxNewTokens != 256 { |
| | | t.Fatalf("max new tokens = %d, want 256", config.ModelConfig.Qwen3ASR.MaxNewTokens) |
| | | } |
| | | if config.ModelConfig.Tokens != "" { |
| | | t.Fatalf("tokens = %q, want empty for qwen3-asr", config.ModelConfig.Tokens) |
| | | } |
| | | } |
| | | |
| | | func TestOnlineConfigForXASRUsesStreamingTransducer(t *testing.T) { |
| | | root := t.TempDir() |
| | | files := map[string]string{ |
| New file |
| | |
| | | package model |
| | | |
| | | const UnsupportedReasonRequiresMacOS14 = "requires_macos_14" |
| | | |
| | | func IsModelSupportedInCurrentBuild(modelID string) bool { |
| | | if modelID == Qwen3ASRModelID { |
| | | return qwen3ASRSupportedInCurrentBuild |
| | | } |
| | | return true |
| | | } |
| | | |
| | | func ModelUnsupportedReason(modelID string) string { |
| | | if modelID == Qwen3ASRModelID && !IsModelSupportedInCurrentBuild(modelID) { |
| | | return UnsupportedReasonRequiresMacOS14 |
| | | } |
| | | return "" |
| | | } |
| New file |
| | |
| | | //go:build !monterey_compat |
| | | |
| | | package model |
| | | |
| | | const qwen3ASRSupportedInCurrentBuild = true |
| New file |
| | |
| | | //go:build monterey_compat |
| | | |
| | | package model |
| | | |
| | | const qwen3ASRSupportedInCurrentBuild = false |
| | |
| | | |
| | | if selectionMode == config.ModelSelectionModeManual { |
| | | if profile, err := model.GetModelProfile(cfg.SelectedModelID); err == nil { |
| | | if isModelAvailableForLanguage(profile.ID, settings.EffectiveLanguageID) { |
| | | if !model.IsModelSupportedInCurrentBuild(profile.ID) { |
| | | fallbackReason = "manual model not supported in current build" |
| | | logger.Info("Manual SelectedModelID %q is not supported in this build, falling back to language default %q", cfg.SelectedModelID, modelID) |
| | | } else if isModelAvailableForLanguage(profile.ID, settings.EffectiveLanguageID) { |
| | | return CurrentModel{ |
| | | ModelID: profile.ID, |
| | | Profile: profile, |
| | | SelectionMode: selectionMode, |
| | | LanguageSettings: settings, |
| | | } |
| | | } else { |
| | | fallbackReason = "manual model not available for language" |
| | | logger.Info("Manual SelectedModelID %q is not available for language %q, falling back to language default %q", cfg.SelectedModelID, settings.EffectiveLanguageID, modelID) |
| | | } |
| | | fallbackReason = "manual model not available for language" |
| | | logger.Info("Manual SelectedModelID %q is not available for language %q, falling back to language default %q", cfg.SelectedModelID, settings.EffectiveLanguageID, modelID) |
| | | } else { |
| | | fallbackReason = "invalid manual model selection" |
| | | logger.Info("Invalid manual SelectedModelID %q, falling back to language default %q", cfg.SelectedModelID, modelID) |
| | |
| | | if err != nil { |
| | | fallbackReason = "invalid language default model" |
| | | logger.Info("Invalid default model %q for language %q, falling back to %q", modelID, settings.EffectiveLanguageID, model.DefaultModelID) |
| | | profile = model.DefaultModelProfile() |
| | | modelID = profile.ID |
| | | } else if !model.IsModelSupportedInCurrentBuild(profile.ID) { |
| | | fallbackReason = "language default model not supported in current build" |
| | | logger.Info("Default model %q for language %q is not supported in this build, falling back to %q", modelID, settings.EffectiveLanguageID, model.DefaultModelID) |
| | | profile = model.DefaultModelProfile() |
| | | modelID = profile.ID |
| | | } |
| | |
| | | } |
| | | |
| | | func isModelAvailableForLanguage(modelID, languageID string) bool { |
| | | if !model.IsModelSupportedInCurrentBuild(modelID) { |
| | | return false |
| | | } |
| | | profile, err := model.GetLanguageProfile(languageID) |
| | | if err != nil { |
| | | return false |
| | |
| | | } |
| | | |
| | | func TestResolveManualUsesQwen3ASRForChinese(t *testing.T) { |
| | | if !model.IsModelSupportedInCurrentBuild(model.Qwen3ASRModelID) { |
| | | t.Skip("qwen3-asr is disabled in this build") |
| | | } |
| | | cfg := config.Default() |
| | | cfg.LanguageMode = config.LanguageModeAuto |
| | | cfg.ModelSelectionMode = config.ModelSelectionModeManual |
| | |
| | | } |
| | | } |
| | | |
| | | func TestResolveManualQwen3ASRFallsBackWhenUnsupported(t *testing.T) { |
| | | if model.IsModelSupportedInCurrentBuild(model.Qwen3ASRModelID) { |
| | | t.Skip("qwen3-asr is supported in this build") |
| | | } |
| | | cfg := config.Default() |
| | | cfg.LanguageMode = config.LanguageModeAuto |
| | | cfg.ModelSelectionMode = config.ModelSelectionModeManual |
| | | cfg.SelectedModelID = model.Qwen3ASRModelID |
| | | |
| | | current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) |
| | | if current.ModelID != model.DefaultModelID { |
| | | t.Fatalf("model id = %q, want fallback %q", current.ModelID, model.DefaultModelID) |
| | | } |
| | | if current.FallbackReason != "manual model not supported in current build" { |
| | | t.Fatalf("fallback reason = %q, want unsupported build reason", current.FallbackReason) |
| | | } |
| | | } |
| | | |
| | | func TestResolveManualUsesXASRForChinese(t *testing.T) { |
| | | cfg := config.Default() |
| | | cfg.LanguageMode = config.LanguageModeAuto |
| New file |
| | |
| | | #!/usr/bin/env bash |
| | | # |
| | | # Build the local Monterey-compatible DMG. This build uses the legacy |
| | | # sherpa-onnx/onnxruntime line and disables Qwen3-ASR with the |
| | | # monterey_compat build tag. |
| | | # |
| | | set -euo pipefail |
| | | |
| | | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| | | |
| | | export PRIVATEVOICE_GO_MODFILE="${PRIVATEVOICE_GO_MODFILE:-go.monterey.mod}" |
| | | export PRIVATEVOICE_GO_BUILD_TAGS="${PRIVATEVOICE_GO_BUILD_TAGS:-monterey_compat}" |
| | | export PRIVATEVOICE_LOCAL_BUILD_FLAVOR="${PRIVATEVOICE_LOCAL_BUILD_FLAVOR:-monterey-local}" |
| | | export PRIVATEVOICE_LOCAL_MIN_SYSTEM_VERSION="${PRIVATEVOICE_LOCAL_MIN_SYSTEM_VERSION:-11.0}" |
| | | export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}" |
| | | |
| | | if [[ "$#" -eq 0 ]]; then |
| | | set -- arm64 |
| | | fi |
| | | |
| | | exec "$SCRIPT_DIR/build-local-macos.sh" "$@" |
| | |
| | | LEGACY_LOCAL_IDENTITY="VoiceSnap Local Code Signing" |
| | | LOCAL_IDENTITY="${PRIVATEVOICE_LOCAL_SIGN_IDENTITY:-${VOICESNAP_LOCAL_SIGN_IDENTITY:-}}" |
| | | KEYCHAIN="${PRIVATEVOICE_LOCAL_KEYCHAIN:-${VOICESNAP_LOCAL_KEYCHAIN:-$HOME/Library/Keychains/login.keychain-db}}" |
| | | LOCAL_BUILD_FLAVOR="${PRIVATEVOICE_LOCAL_BUILD_FLAVOR:-local}" |
| | | LOCAL_MIN_SYSTEM_VERSION="${PRIVATEVOICE_LOCAL_MIN_SYSTEM_VERSION:-}" |
| | | GO_MODFILE="${PRIVATEVOICE_GO_MODFILE:-}" |
| | | GO_BUILD_TAGS="${PRIVATEVOICE_GO_BUILD_TAGS:-${PRIVATEVOICE_GO_TAGS:-}}" |
| | | |
| | | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| | | PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" |
| | |
| | | INFO_PLIST="$PROJECT_DIR/build/darwin/Info.plist" |
| | | ENTITLEMENTS="$PROJECT_DIR/build/darwin/entitlements.plist" |
| | | GOMODCACHE="$(go env GOMODCACHE)" |
| | | SHERPA_MACOS_MODULE_VERSION="$(cd "$PROJECT_DIR" && go list -m -f '{{.Version}}' github.com/k2-fsa/sherpa-onnx-go-macos)" |
| | | GO_MOD_ARG="" |
| | | GO_BUILD_TAG_ARG="" |
| | | if [[ -n "$GO_MODFILE" ]]; then |
| | | GO_MOD_ARG="-modfile=$GO_MODFILE" |
| | | fi |
| | | if [[ -n "$GO_BUILD_TAGS" ]]; then |
| | | GO_BUILD_TAG_ARG="-tags=$GO_BUILD_TAGS" |
| | | fi |
| | | if [[ -n "$GO_MOD_ARG" ]]; then |
| | | SHERPA_MACOS_MODULE_VERSION="$(cd "$PROJECT_DIR" && go list "$GO_MOD_ARG" -m -f '{{.Version}}' github.com/k2-fsa/sherpa-onnx-go-macos)" |
| | | else |
| | | SHERPA_MACOS_MODULE_VERSION="$(cd "$PROJECT_DIR" && go list -m -f '{{.Version}}' github.com/k2-fsa/sherpa-onnx-go-macos)" |
| | | fi |
| | | |
| | | VERSION="$(defaults read "$INFO_PLIST" CFBundleShortVersionString 2>/dev/null || echo "2.1.1")" |
| | | BUILD_ID="$(defaults read "$INFO_PLIST" CFBundleVersion 2>/dev/null || date +%Y%m%d.%H%M)" |
| | | DEFAULT_MIN_SYSTEM_VERSION="$(defaults read "$INFO_PLIST" LSMinimumSystemVersion 2>/dev/null || echo "")" |
| | | EFFECTIVE_MIN_SYSTEM_VERSION="${LOCAL_MIN_SYSTEM_VERSION:-$DEFAULT_MIN_SYSTEM_VERSION}" |
| | | if [[ -n "$EFFECTIVE_MIN_SYSTEM_VERSION" && -z "${MACOSX_DEPLOYMENT_TARGET:-}" ]]; then |
| | | export MACOSX_DEPLOYMENT_TARGET="$EFFECTIVE_MIN_SYSTEM_VERSION" |
| | | fi |
| | | |
| | | TARGET_ARCH="" |
| | | INSTALL_AFTER_BUILD=0 |
| | |
| | | |
| | | BUILD_DIR="$PROJECT_DIR/build/local/$DMG_ARCH" |
| | | APP_BUNDLE="$BUILD_DIR/$APP_NAME.app" |
| | | DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-build$BUILD_ID-$DMG_ARCH-local.dmg" |
| | | COMPAT_DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-$DMG_ARCH-local.dmg" |
| | | DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-build$BUILD_ID-$DMG_ARCH-$LOCAL_BUILD_FLAVOR.dmg" |
| | | COMPAT_DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-$DMG_ARCH-$LOCAL_BUILD_FLAVOR.dmg" |
| | | SHERPA_LIB_DIR="$GOMODCACHE/github.com/k2-fsa/sherpa-onnx-go-macos@$SHERPA_MACOS_MODULE_VERSION/lib/$SHERPA_ARCH" |
| | | ONNXRUNTIME_DYLIB="" |
| | | ONNXRUNTIME_DYLIB_NAME="" |
| | |
| | | "$item" |
| | | } |
| | | |
| | | step "Resolving Go dependencies" |
| | | if [[ -n "$GO_MOD_ARG" ]]; then |
| | | (cd "$PROJECT_DIR" && go mod download "$GO_MOD_ARG" github.com/k2-fsa/sherpa-onnx-go-macos) |
| | | else |
| | | (cd "$PROJECT_DIR" && go mod download github.com/k2-fsa/sherpa-onnx-go-macos) |
| | | fi |
| | | |
| | | step "Preflight checks" |
| | | [[ -f "$INFO_PLIST" ]] || fail "Info.plist not found at $INFO_PLIST" |
| | | [[ -f "$ENTITLEMENTS" ]] || fail "entitlements.plist not found at $ENTITLEMENTS" |
| | |
| | | |
| | | step "Building Go binary ($DMG_ARCH)" |
| | | cd "$PROJECT_DIR" |
| | | CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \ |
| | | go build -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \ |
| | | -o "$BUILD_DIR/$APP_NAME" |
| | | if [[ -n "$GO_MOD_ARG" && -n "$GO_BUILD_TAG_ARG" ]]; then |
| | | CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \ |
| | | go build "$GO_MOD_ARG" "$GO_BUILD_TAG_ARG" -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \ |
| | | -o "$BUILD_DIR/$APP_NAME" |
| | | elif [[ -n "$GO_MOD_ARG" ]]; then |
| | | CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \ |
| | | go build "$GO_MOD_ARG" -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \ |
| | | -o "$BUILD_DIR/$APP_NAME" |
| | | elif [[ -n "$GO_BUILD_TAG_ARG" ]]; then |
| | | CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \ |
| | | go build "$GO_BUILD_TAG_ARG" -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \ |
| | | -o "$BUILD_DIR/$APP_NAME" |
| | | else |
| | | CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \ |
| | | go build -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \ |
| | | -o "$BUILD_DIR/$APP_NAME" |
| | | fi |
| | | |
| | | step "Assembling $APP_NAME.app" |
| | | mkdir -p "$APP_BUNDLE/Contents/MacOS" |
| | |
| | | mkdir -p "$APP_BUNDLE/Contents/Resources" |
| | | |
| | | cp "$INFO_PLIST" "$APP_BUNDLE/Contents/Info.plist" |
| | | if [[ -n "$EFFECTIVE_MIN_SYSTEM_VERSION" ]]; then |
| | | /usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion $EFFECTIVE_MIN_SYSTEM_VERSION" "$APP_BUNDLE/Contents/Info.plist" >/dev/null 2>&1 \ |
| | | || /usr/libexec/PlistBuddy -c "Add :LSMinimumSystemVersion string $EFFECTIVE_MIN_SYSTEM_VERSION" "$APP_BUNDLE/Contents/Info.plist" |
| | | fi |
| | | cp "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/Contents/MacOS/$APP_NAME" |
| | | cp "$PROJECT_DIR/build/darwin/icon.icns" "$APP_BUNDLE/Contents/Resources/icon.icns" |
| | | find "$PROJECT_DIR/build/darwin" -maxdepth 1 -type d -name "*.lproj" \ |
| | |
| | | func (s *EngineService) modelStatusMap(profile model.ModelProfile, current modelselection.CurrentModel) map[string]interface{} { |
| | | resolved, err := model.ResolveModel(profile.ID) |
| | | installed := err == nil && resolved.IsUsable() |
| | | supportedInBuild := model.IsModelSupportedInCurrentBuild(profile.ID) |
| | | status := model.ModelNotInstalled |
| | | var missing []string |
| | | var problems []string |
| | |
| | | "supportedLanguages": profile.SupportedLanguageIDs, |
| | | "recommendedLanguage": profile.RecommendedFor, |
| | | "description": profile.Description, |
| | | "supportedInBuild": supportedInBuild, |
| | | "unsupportedReason": model.ModelUnsupportedReason(profile.ID), |
| | | } |
| | | } |
| | | |
| | |
| | | if err != nil { |
| | | return model.ModelProfile{}, err |
| | | } |
| | | if !model.IsModelSupportedInCurrentBuild(profile.ID) { |
| | | return model.ModelProfile{}, fmt.Errorf("model %s requires macOS 14 or later", profile.ID) |
| | | } |
| | | |
| | | current := s.currentModel() |
| | | languageProfile, err := model.GetLanguageProfile(current.LanguageSettings.EffectiveLanguageID) |
| New file |
| | |
| | | //go:build !monterey_compat |
| | | |
| | | package services |
| | | |
| | | import ( |
| | | "testing" |
| | | |
| | | "voicesnap/internal/config" |
| | | "voicesnap/internal/language" |
| | | "voicesnap/internal/model" |
| | | "voicesnap/internal/modelselection" |
| | | ) |
| | | |
| | | func TestQwen3ASRSupportedInDefaultBuild(t *testing.T) { |
| | | cfg := config.Default() |
| | | cfg.LanguageMode = config.LanguageModeManual |
| | | cfg.LanguageID = model.DefaultLanguageID |
| | | service := NewEngineService(cfg) |
| | | profile, err := model.GetModelProfile(model.Qwen3ASRModelID) |
| | | if err != nil { |
| | | t.Fatal(err) |
| | | } |
| | | |
| | | status := service.modelStatusMap(profile, modelselection.CurrentModel{ |
| | | ModelID: model.DefaultModelID, |
| | | Profile: model.DefaultModelProfile(), |
| | | SelectionMode: config.ModelSelectionModeAuto, |
| | | LanguageSettings: language.Resolve(config.LanguageModeManual, model.DefaultLanguageID, nil), |
| | | }) |
| | | |
| | | if got := status["supportedInBuild"]; got != true { |
| | | t.Fatalf("supportedInBuild = %v, want true", got) |
| | | } |
| | | if got := status["unsupportedReason"]; got != "" { |
| | | t.Fatalf("unsupportedReason = %v, want empty", got) |
| | | } |
| | | if _, err := service.allowedModelProfile(model.Qwen3ASRModelID); err != nil { |
| | | t.Fatalf("allowedModelProfile returned error: %v", err) |
| | | } |
| | | } |
| New file |
| | |
| | | //go:build monterey_compat |
| | | |
| | | package services |
| | | |
| | | import ( |
| | | "strings" |
| | | "testing" |
| | | |
| | | "voicesnap/internal/config" |
| | | "voicesnap/internal/language" |
| | | "voicesnap/internal/model" |
| | | "voicesnap/internal/modelselection" |
| | | ) |
| | | |
| | | func TestQwen3ASRVisibleButDisabledInMontereyCompatBuild(t *testing.T) { |
| | | cfg := config.Default() |
| | | cfg.LanguageMode = config.LanguageModeManual |
| | | cfg.LanguageID = model.DefaultLanguageID |
| | | service := NewEngineService(cfg) |
| | | profile, err := model.GetModelProfile(model.Qwen3ASRModelID) |
| | | if err != nil { |
| | | t.Fatal(err) |
| | | } |
| | | |
| | | status := service.modelStatusMap(profile, modelselection.CurrentModel{ |
| | | ModelID: model.DefaultModelID, |
| | | Profile: model.DefaultModelProfile(), |
| | | SelectionMode: config.ModelSelectionModeAuto, |
| | | LanguageSettings: language.Resolve(config.LanguageModeManual, model.DefaultLanguageID, nil), |
| | | }) |
| | | |
| | | if got := status["modelID"]; got != model.Qwen3ASRModelID { |
| | | t.Fatalf("modelID = %v, want %s", got, model.Qwen3ASRModelID) |
| | | } |
| | | if got := status["supportedInBuild"]; got != false { |
| | | t.Fatalf("supportedInBuild = %v, want false", got) |
| | | } |
| | | if got := status["unsupportedReason"]; got != model.UnsupportedReasonRequiresMacOS14 { |
| | | t.Fatalf("unsupportedReason = %v, want %s", got, model.UnsupportedReasonRequiresMacOS14) |
| | | } |
| | | if _, err := service.allowedModelProfile(model.Qwen3ASRModelID); err == nil { |
| | | t.Fatal("expected qwen3-asr to be rejected in Monterey compatibility build") |
| | | } else if !strings.Contains(err.Error(), "macOS 14") { |
| | | t.Fatalf("error = %q, want macOS 14 reason", err.Error()) |
| | | } |
| | | } |