Ariver
2026-06-28 fc6d61e41b1b0e0686d1346694445dc119b97872
Fix hold recording head and tail clipping
6 files modified
84 ■■■■ changed files
CHANGELOG.md 15 ●●●●● patch | view | raw | blame | history
privatevoice.src/app.go 15 ●●●● patch | view | raw | blame | history
privatevoice.src/app_live_caption_test.go 44 ●●●●● patch | view | raw | blame | history
privatevoice.src/build/darwin/Info.plist 4 ●●●● patch | view | raw | blame | history
privatevoice.src/frontend/package-lock.json 4 ●●●● patch | view | raw | blame | history
privatevoice.src/frontend/package.json 2 ●●● patch | view | raw | blame | history
CHANGELOG.md
@@ -1,5 +1,20 @@
# Changelog
## v2.1.36 (2026-06-28)
### 首尾字稳定性修复
- **Hold 模式首字保护扩展到所有模型**:按下热键后立即启动本地预采集,确认不是组合键后保留这段音频,降低 SenseVoice / Qwen3-ASR / Moonshine / Parakeet 等离线模型开头 1-2 个字丢失的概率。
- **松键尾部采集保护扩展到所有模型**:松开热键后默认继续采集约 300ms 再停止录音,降低最后 1-2 个字被截断的概率。
- **保留取消和组合键隔离**:组合键或过早释放仍会丢弃预采集音频,不进入识别流程。
### 构建
- build: `20260628.2354`
- 说明: 本版本为首尾字稳定性修复的本地验证候选包。
---
## v2.1.35 (2026-06-23)
### 输入配置页排版优化
privatevoice.src/app.go
@@ -28,8 +28,8 @@
)
const (
    appVersion        = "2.1.35"
    appBuild          = "20260623.1435"
    appVersion        = "2.1.36"
    appBuild          = "20260628.2354"
    appDisplayVersion = appVersion + " (build " + appBuild + ")"
    appName           = "PrivateVoice Dictation"
@@ -37,6 +37,7 @@
    silenceTimeoutDuration   = 3 * time.Second // auto-stop after this much silence in free-talk
    silenceGracePeriod       = 2 * time.Second // don't auto-stop within first 2s of free-talk
    holdActivationDelay      = 180 * time.Millisecond
    defaultReleaseTailDelay  = 300 * time.Millisecond
    doneIndicatorHideDelayMs = 250
    liveCaptionInterval      = 120 * time.Millisecond
    liveCaptionMaxBytes      = 108
@@ -985,9 +986,12 @@
}
func releaseTailCaptureDelayForEngine(eng engine.Engine) time.Duration {
    if eng == nil {
        return 0
    }
    tailCaptureEng, ok := eng.(engine.ReleaseTailCaptureEngine)
    if !ok {
        return 0
        return defaultReleaseTailDelay
    }
    delay := tailCaptureEng.ReleaseTailCaptureDelay()
    if delay < 0 {
@@ -997,9 +1001,12 @@
}
func holdPreCaptureEnabledForEngine(eng engine.Engine) bool {
    if eng == nil {
        return false
    }
    preCaptureEng, ok := eng.(engine.HoldPreCaptureEngine)
    if !ok {
        return false
        return true
    }
    return preCaptureEng.HoldPreCaptureEnabled()
}
privatevoice.src/app_live_caption_test.go
@@ -29,12 +29,30 @@
    }
}
func TestReleaseTailCaptureDelayDefaultsToZero(t *testing.T) {
func TestReleaseTailCaptureDelayCanBeDisabledByEngineCapability(t *testing.T) {
    a := &App{}
    a.replaceEngine(plainTestEngine{})
    a.replaceEngine(tailCaptureTestEngine{delay: 0})
    if got := a.releaseTailCaptureDelay(); got != 0 {
        t.Fatalf("releaseTailCaptureDelay() = %v, want 0", got)
    }
}
func TestReleaseTailCaptureDelayDefaultsToProtectionWindow(t *testing.T) {
    a := &App{}
    a.replaceEngine(plainTestEngine{})
    if got := a.releaseTailCaptureDelay(); got != defaultReleaseTailDelay {
        t.Fatalf("releaseTailCaptureDelay() = %v, want %v", got, defaultReleaseTailDelay)
    }
}
func TestReleaseTailCaptureDelayDisabledWithoutEngine(t *testing.T) {
    a := &App{}
    a.replaceEngine(nil)
    if got := a.releaseTailCaptureDelay(); got != 0 {
        t.Fatalf("releaseTailCaptureDelay() = %v, want 0 without engine", got)
    }
}
@@ -47,12 +65,30 @@
    }
}
func TestHoldPreCaptureDefaultsToFalse(t *testing.T) {
func TestHoldPreCaptureCanBeDisabledByEngineCapability(t *testing.T) {
    a := &App{}
    a.replaceEngine(holdPreCaptureTestEngine{enabled: false})
    if a.holdPreCaptureEnabled.Load() {
        t.Fatal("hold pre-capture should respect engine capability false")
    }
}
func TestHoldPreCaptureDefaultsToTrueForReadyEngine(t *testing.T) {
    a := &App{}
    a.replaceEngine(plainTestEngine{})
    if !a.holdPreCaptureEnabled.Load() {
        t.Fatal("hold pre-capture should default to true for ready engines")
    }
}
func TestHoldPreCaptureDisabledWithoutEngine(t *testing.T) {
    a := &App{}
    a.replaceEngine(nil)
    if a.holdPreCaptureEnabled.Load() {
        t.Fatal("hold pre-capture should default to false")
        t.Fatal("hold pre-capture should be disabled without an engine")
    }
}
privatevoice.src/build/darwin/Info.plist
@@ -17,9 +17,9 @@
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>2.1.35</string>
    <string>2.1.36</string>
    <key>CFBundleVersion</key>
    <string>20260623.1435</string>
    <string>20260628.2354</string>
    <key>ITSAppUsesNonExemptEncryption</key>
    <false/>
    <key>LSApplicationCategoryType</key>
privatevoice.src/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
  "name": "privatevoice-dictation-frontend",
  "version": "2.1.35",
  "version": "2.1.36",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "privatevoice-dictation-frontend",
      "version": "2.1.35",
      "version": "2.1.36",
      "dependencies": {
        "@wailsio/runtime": "latest"
      },
privatevoice.src/frontend/package.json
@@ -1,7 +1,7 @@
{
  "name": "privatevoice-dictation-frontend",
  "private": true,
  "version": "2.1.35",
  "version": "2.1.36",
  "type": "module",
  "scripts": {
    "dev": "vite dev",