package model import ( "encoding/json" "os" "path/filepath" "time" "voicesnap/internal/paths" ) type InstallState struct { SchemaVersion int `json:"schemaVersion"` SelectedModelID string `json:"selectedModelId"` InstalledModels map[string]InstalledModelState `json:"installedModels"` } type InstalledModelState struct { InstalledAt int64 `json:"installedAt"` Version string `json:"version"` Path string `json:"path"` SourceDirKind string `json:"sourceDirKind"` } func NewInstallState() *InstallState { return &InstallState{ SchemaVersion: 1, SelectedModelID: DefaultModelID, InstalledModels: make(map[string]InstalledModelState), } } func LoadInstallState() (*InstallState, error) { return LoadInstallStateFromRoot(paths.ModelsRoot()) } func LoadInstallStateFromRoot(modelsRoot string) (*InstallState, error) { data, err := os.ReadFile(filepath.Join(modelsRoot, "state.json")) if err != nil { if os.IsNotExist(err) { return NewInstallState(), nil } return NewInstallState(), err } state := NewInstallState() if err := json.Unmarshal(data, state); err != nil { return NewInstallState(), err } normalizeInstallState(state) return state, nil } func SaveInstallState(state *InstallState) error { return SaveInstallStateToRoot(paths.ModelsRoot(), state) } func SaveInstallStateToRoot(modelsRoot string, state *InstallState) error { normalizeInstallState(state) if err := os.MkdirAll(modelsRoot, 0755); err != nil { return err } data, err := json.MarshalIndent(state, "", " ") if err != nil { return err } target := filepath.Join(modelsRoot, "state.json") tmp := target + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { return err } return os.Rename(tmp, target) } func UpdateInstalledModelState(modelsRoot string, profile ModelProfile, relPath, sourceDirKind string) error { state, _ := LoadInstallStateFromRoot(modelsRoot) state.SelectedModelID = profile.ID state.InstalledModels[profile.ID] = InstalledModelState{ InstalledAt: time.Now().UnixMilli(), Version: "unknown", Path: relPath, SourceDirKind: sourceDirKind, } return SaveInstallStateToRoot(modelsRoot, state) } func normalizeInstallState(state *InstallState) { if state.SchemaVersion == 0 { state.SchemaVersion = 1 } state.SelectedModelID = NormalizeModelID(state.SelectedModelID) if state.InstalledModels == nil { state.InstalledModels = make(map[string]InstalledModelState) } }