Ariver
2026-06-03 95c5cdd691f173ead217f7a3df8b46f7fc2b0737
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package config
 
import (
    "encoding/json"
    "os"
    "voicesnap/internal/logger"
    "voicesnap/internal/model"
    "voicesnap/internal/paths"
)
 
const (
    HotkeyModeTap            = "tap"
    HotkeyModeHold           = "hold"
    LanguageModeAuto         = "auto"
    LanguageModeManual       = "manual"
    ModelSelectionModeAuto   = "auto"
    ModelSelectionModeManual = "manual"
)
 
// Config holds the application configuration, compatible with the WPF version's config.json.
type Config struct {
    HotkeyVK                 int    `json:"HotkeyVK"`
    HotkeyMode               string `json:"HotkeyMode"`
    AutoHide                 bool   `json:"AutoHide"`
    SoundFeedback            bool   `json:"SoundFeedback"`
    HideDockIcon             bool   `json:"HideDockIcon"`
    CopyToClipboard          bool   `json:"CopyToClipboard"`
    DeviceName               string `json:"DeviceName,omitempty"`
    IndicatorX               int    `json:"IndicatorX,omitempty"`
    IndicatorY               int    `json:"IndicatorY,omitempty"`
    SelectedModelID          string `json:"SelectedModelID,omitempty"`
    ModelSelectionMode       string `json:"ModelSelectionMode,omitempty"`
    LanguageMode             string `json:"LanguageMode,omitempty"`
    LanguageID               string `json:"LanguageID,omitempty"`
    ModelDownloadUrl         string `json:"ModelDownloadUrl"`
    FallbackModelDownloadUrl string `json:"FallbackModelDownloadUrl"`
}
 
// Default returns a default configuration.
func Default() *Config {
    return &Config{
        HotkeyVK:                 0xA5, // Right Alt
        HotkeyMode:               HotkeyModeHold,
        AutoHide:                 true,
        SoundFeedback:            true,
        HideDockIcon:             true,
        CopyToClipboard:          true,
        SelectedModelID:          model.DefaultModelID,
        ModelSelectionMode:       ModelSelectionModeAuto,
        LanguageMode:             LanguageModeAuto,
        LanguageID:               model.DefaultLanguageID,
        ModelDownloadUrl:         "http://www.maikami.com/voicesnap/sensevoice.zip",
        FallbackModelDownloadUrl: "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",
    }
}
 
func configPath() string {
    return paths.File("config.json")
}
 
// Load reads the config from disk. Returns default config on error.
func Load() (*Config, error) {
    path := configPath()
    data, err := os.ReadFile(path)
    if err != nil {
        if os.IsNotExist(err) {
            cfg := Default()
            Save(cfg)
            return cfg, nil
        }
        return nil, err
    }
 
    cfg := Default()
    if err := json.Unmarshal(data, cfg); err != nil {
        return nil, err
    }
    cfg.HotkeyMode = NormalizeHotkeyMode(cfg.HotkeyMode)
    cfg.LanguageMode = NormalizeLanguageMode(cfg.LanguageMode)
    cfg.ModelSelectionMode = NormalizeModelSelectionMode(cfg.ModelSelectionMode)
    languageID := cfg.LanguageID
    cfg.LanguageID = model.NormalizeLanguageID(cfg.LanguageID)
    if languageID != "" && languageID != cfg.LanguageID {
        logger.Info("Unknown LanguageID %q, falling back to %q", languageID, cfg.LanguageID)
    }
    selectedModelID := cfg.SelectedModelID
    cfg.SelectedModelID = model.NormalizeModelID(cfg.SelectedModelID)
    if selectedModelID != "" && selectedModelID != cfg.SelectedModelID {
        logger.Info("Unknown SelectedModelID %q, falling back to %q", selectedModelID, cfg.SelectedModelID)
    }
    return cfg, nil
}
 
func NormalizeLanguageMode(mode string) string {
    switch mode {
    case LanguageModeAuto, LanguageModeManual:
        return mode
    default:
        return LanguageModeAuto
    }
}
 
func NormalizeModelSelectionMode(mode string) string {
    switch mode {
    case ModelSelectionModeAuto, ModelSelectionModeManual:
        return mode
    default:
        return ModelSelectionModeAuto
    }
}
 
func NormalizeHotkeyMode(mode string) string {
    switch mode {
    case HotkeyModeTap, HotkeyModeHold:
        return mode
    default:
        return HotkeyModeHold
    }
}
 
// Save writes the config to disk.
func Save(cfg *Config) {
    if err := paths.Ensure(); err != nil {
        logger.Error("Failed to create app data dir: %v", err)
        return
    }
 
    data, err := json.MarshalIndent(cfg, "", "  ")
    if err != nil {
        logger.Error("Failed to marshal config: %v", err)
        return
    }
    if err := os.WriteFile(configPath(), data, 0644); err != nil {
        logger.Error("Failed to save config: %v", err)
    }
}