Documentation
¶
Overview ¶
Package settings persists app-level configuration (AI provider keys, charging cost, Rivian session) in the shared Postgres database.
Storage shape: one row per (user_id, namespace) in the user_settings table, with the value column holding a JSONB object of string keys. The public Store API keeps the flat key/value illusion the Manager, charging and rivian helpers were built against — the namespace is derived from the key's first segment ("ai.*" → "ai", "charging.*" → "charging", "rivian.*" → "rivian").
Index ¶
- Constants
- Variables
- func AIConsent(ctx context.Context, s *Store) bool
- func AmbientTempOptIn(ctx context.Context, s *Store) bool
- func AsOverrides(networks []ChargingNetwork) []dcfcrates.NetworkOverride
- func GetFlatRate(ctx context.Context, s *Store) (bool, error)
- func GetOffpeak(ctx context.Context, s *Store) (offpeak.Schedule, bool, error)
- func NormalizeHomeCurrency(c string) (string, bool)
- func PatchChargingConfig(ctx context.Context, s *Store, pricePerKWh *float64, currency *string) error
- func PatchUnits(ctx context.Context, s *Store, distance, temperature *string) error
- func ResetChargingNetworks(ctx context.Context, s *Store) error
- func RivianReceiptsOptIn(ctx context.Context, s *Store) bool
- func SeedDistanceUnit(ctx context.Context, s *Store, distance string) (bool, error)
- func SetAIConsent(ctx context.Context, s *Store, on bool) error
- func SetAmbientTempOptIn(ctx context.Context, s *Store, on bool) error
- func SetChargingConfig(ctx context.Context, s *Store, cfg ChargingConfig) error
- func SetChargingNetworks(ctx context.Context, s *Store, networks []ChargingNetwork) error
- func SetFlatRate(ctx context.Context, s *Store, on bool) error
- func SetHomeLocation(ctx context.Context, s *Store, h HomeLocation) error
- func SetOffpeak(ctx context.Context, s *Store, sch offpeak.Schedule) error
- func SetPlannerFavorites(ctx context.Context, s *Store, list []PlannerFavorite) error
- func SetPlannerPrefs(ctx context.Context, s *Store, p PlannerPrefs) error
- func SetRivianReceiptsOptIn(ctx context.Context, s *Store, on bool) error
- func ValidateHomeLocation(h HomeLocation) error
- func ValidateNetworkLocations(networks []ChargingNetwork) error
- type AIConfig
- type AIImagePublic
- type AIPublic
- type AISpeechPublic
- type AIUpdate
- type ChargingConfig
- type ChargingNetwork
- type Factory
- type GPSPublic
- type GPSUpdate
- type HomeLocation
- type KV
- type Manager
- func (m *Manager) Analyzer() *ai.Analyzer
- func (m *Manager) GPSPublic() GPSPublic
- func (m *Manager) GeminiCreds() (apiKey, model string)
- func (m *Manager) ImageCreds() (provider, apiKey, model string)
- func (m *Manager) ListModels(ctx context.Context, provider string) ([]string, error)
- func (m *Manager) OpenAICreds() (apiKey, model string)
- func (m *Manager) Provider() ai.Provider
- func (m *Manager) Public() AIPublic
- func (m *Manager) RecapPublic() RecapPublic
- func (m *Manager) RecapWeatherEnabled() bool
- func (m *Manager) SpeechCreds() (provider, apiKey, model string)
- func (m *Manager) Update(ctx context.Context, patch AIUpdate) (AIPublic, error)
- func (m *Manager) UpdateGPS(ctx context.Context, patch GPSUpdate) (GPSPublic, error)
- func (m *Manager) UpdateRecap(ctx context.Context, patch RecapUpdate) (RecapPublic, error)
- func (m *Manager) VisionCreds() (provider, apiKey, model string)
- type NetworkPref
- type PlannerFavorite
- type PlannerPrefs
- type ProInfo
- type RecapPublic
- type RecapUpdate
- type Store
- func (s *Store) Close() error
- func (s *Store) Delete(ctx context.Context, key string) error
- func (s *Store) GetAll(ctx context.Context) (map[string]string, error)
- func (s *Store) Set(ctx context.Context, key, value string) error
- func (s *Store) SetMany(ctx context.Context, kv map[string]string) error
- type Units
Constants ¶
const ( DefaultGasPricePerGallon = 4.0 DefaultComparisonMPG = 20.0 )
Defaults for the gas-equivalent comparison chip on the trip planner. $4/gal and 20 MPG approximate a same-class ICE pickup; both are user-editable.
const ( HomeSourceCity = "city" HomeSourceAddress = "address" HomeSourceCoordinates = "coordinates" HomeSourceCharge = "charge" )
The ways a home can be set (HomeLocation.Source), for the Settings card only.
const ( ProviderOpenAI = "openai" ProviderAnthropic = "anthropic" ProviderGemini = "gemini" )
Supported AI providers.
const ( DefaultGPSMissingPct float64 = 0.40 DefaultGPSStaleSec int = 300 DefaultGPSJumpCount int = 2 )
Defaults for the GPS accuracy heuristic. Tuned to surface drives with genuinely bad GPS (long tunnels, modem-side dropouts) while ignoring single anomalies and brief parking-garage stalls.
const ( DefaultOpenAIModel = "gpt-4o-mini" DefaultAnthropicModel = "claude-haiku-4-5-20251001" DefaultGeminiModel = "gemini-2.5-flash" DefaultOpenAIImageModel = "gpt-image-1" DefaultGeminiImageModel = "gemini-2.5-flash-image" // Speech-to-text defaults. Whisper still has a single public model; // Gemini uses whatever text model you point at /generateContent with // audio inline. gemini-2.5-flash is the cheapest multimodal tier. DefaultOpenAISpeechModel = "whisper-1" DefaultGeminiSpeechModel = "gemini-2.5-flash" )
Defaults used when nothing is stored and no env override is present.
const ( ImageProviderOpenAI = "openai" ImageProviderGemini = "gemini" )
Supported image generation providers. Anthropic isn't in this set because the Messages API has no image-generation surface.
const ( SpeechProviderOpenAI = "openai" SpeechProviderGemini = "gemini" )
Supported speech-to-text providers. Same caveat as image: Anthropic has no audio-input API yet.
const ( DriveModeEveryday = "EVERYDAY" DriveModeDistance = "DISTANCE" DriveModeSport = "SPORT" DriveModeWinter = "WINTER" DriveModeOffRoadAuto = "OFF_ROAD_AUTO" )
Valid driveMode values for Rivian's planTrip2 enum. Names cribbed from the home-assistant-rivian integration's DRIVE_MODE_MAP and confirmed against the live gateway. The mobile app's user-facing labels don't match the enum; mapping below.
EVERYDAY → "All-Purpose" (default) DISTANCE → "Conserve" (range-extension) SPORT → "Sport" WINTER → "Snow" OFF_ROAD_AUTO → "All-Terrain"
DISTANCE produces materially different plans (one fewer stop + shorter route on long trips) so the knob is load-bearing.
const ( DistanceMi = "mi" DistanceKm = "km" TempC = "c" TempF = "f" // TempAuto asks for temperature to follow distance again. TempAuto = "auto" SourceUser = "user" SourceRegion = "region" SourceUnset = "unset" SourceFollow = "follows_distance" )
Unit values and sources.
const DefaultHomeCurrency = "USD"
DefaultHomeCurrency is the ISO-4217 code used when the operator hasn't set anything. Picked to match the app's target audience; the UI exposes the field so it can be changed.
const FavoritesMax = 25
FavoritesMax caps how many favorites we'll persist. Plenty for a human roster, keeps the JSON blob bounded.
const LabelMaxLen = 80
LabelMaxLen caps the human label so a rogue paste doesn't blow up the UI chip. Address-shape strings often go ~120 chars; we trim hard so chips stay compact.
const MaxHomePricePerKWh = 5.0
MaxHomePricePerKWh is the dearest home rate a patch accepts, per kilowatt hour.
Variables ¶
var ErrBadUnit = errors.New("settings: unknown unit")
ErrBadUnit is a unit value outside the lists.
var ErrHomeLocationInvalid = errors.New("latitude must be between -90 and 90 and longitude between -180 and 180")
ErrHomeLocationInvalid: a latitude or longitude that isn't a finite value in range.
var ErrNetworkLocationInvalid = errors.New("network location must be a latitude and a longitude in range")
ErrNetworkLocationInvalid: a network row's lat/lon is half given, out of range or 0,0.
var ErrNoPreviousHome = errors.New("no previous home location")
ErrNoPreviousHome: there's no earlier home to go back to.
var HomeCurrencies = []string{"USD", "CAD", "EUR", "GBP", "AUD", "NZD", "CHF", "SEK", "NOK", "DKK"}
HomeCurrencies are the currencies a patch accepts for the home rate: the set the cost calculator and the lease calculator already offer.
Functions ¶
func AIConsent ¶ added in v0.37.6
AIConsent reports whether the user turned AI advice on. Off by default, and off if the setting cannot be read.
func AmbientTempOptIn ¶ added in v0.37.8
AmbientTempOptIn reports whether the user turned the lookup on. Off by default, and off if the setting cannot be read.
func AsOverrides ¶ added in v0.18.84
func AsOverrides(networks []ChargingNetwork) []dcfcrates.NetworkOverride
AsOverrides converts a persisted network list into the slug-keyed override slice the tripadvice cost estimator consumes. Custom user-added rows (no Slug) are passed through with their Name so the substring matcher in tripadvice can still hit them.
func GetFlatRate ¶ added in v0.38.26
GetFlatRate reports whether the user said their rate is flat. False when unset.
func GetOffpeak ¶ added in v0.38.10
GetOffpeak returns the saved schedule; ok is false when there is none (or the stored text no longer parses, which reads as none rather than failing a page).
func NormalizeHomeCurrency ¶ added in v0.38.21
NormalizeHomeCurrency returns c upper-cased when it is one of HomeCurrencies.
func PatchChargingConfig ¶ added in v0.38.21
func PatchChargingConfig(ctx context.Context, s *Store, pricePerKWh *float64, currency *string) error
PatchChargingConfig writes only the home-rate fields that are set (nil leaves the stored value alone); the gas price and comparison MPG are never touched. The caller has validated the values.
func PatchUnits ¶ added in v0.39.6
PatchUnits records the person's own choice. distance and temperature are each nil to leave alone; temperature may be TempAuto to follow distance again. A distance choice is source "user" and replaces a seeded one.
func ResetChargingNetworks ¶ added in v0.18.84
ResetChargingNetworks clears the user's persisted overrides so the next Get returns the seed defaults. Drives the "Reset to defaults" button on the Settings panel.
func RivianReceiptsOptIn ¶ added in v0.36.10
RivianReceiptsOptIn reports whether the user opted in. Off by default.
func SeedDistanceUnit ¶ added in v0.39.6
SeedDistanceUnit records a client's regional default, once: only if the account has no distance unit yet (the first writer wins, so two clients signing in together agree). It reports whether this call wrote it. A choice the person makes later always replaces it.
func SetAIConsent ¶ added in v0.37.6
SetAIConsent records the choice.
func SetAmbientTempOptIn ¶ added in v0.37.8
SetAmbientTempOptIn records the choice.
func SetChargingConfig ¶
func SetChargingConfig(ctx context.Context, s *Store, cfg ChargingConfig) error
SetChargingConfig persists the provided cost settings. Negative prices are rejected by coercing to zero so the UI can't persist a nonsensical value.
func SetChargingNetworks ¶
func SetChargingNetworks(ctx context.Context, s *Store, networks []ChargingNetwork) error
SetChargingNetworks persists the provided price book, overwriting any prior value. Empty names and non-positive prices are dropped silently so a partially filled UI form can't corrupt the list. Pass an empty slice to clear the user's overrides; the next Get will return the defaults again.
func SetFlatRate ¶ added in v0.38.26
SetFlatRate saves the choice; off removes the key (the default).
func SetHomeLocation ¶
func SetHomeLocation(ctx context.Context, s *Store, h HomeLocation) error
SetHomeLocation persists a home location (or clears it with Set=false), keeping the home it replaces as the previous one so the change can be undone.
func SetOffpeak ¶ added in v0.38.10
SetOffpeak saves an already validated schedule (offpeak.Normalize); a schedule with no windows clears it.
func SetPlannerFavorites ¶ added in v0.18.77
func SetPlannerFavorites(ctx context.Context, s *Store, list []PlannerFavorite) error
SetPlannerFavorites persists the favorites list. Validates each entry's shape (non-zero coords, non-empty label) and caps the total at FavoritesMax — invalid entries are silently dropped to keep the persisted blob clean.
func SetPlannerPrefs ¶
func SetPlannerPrefs(ctx context.Context, s *Store, p PlannerPrefs) error
SetPlannerPrefs persists the defaults. Empty DriveMode clears the stored value; nil HasAdapter clears the stored value.
func SetRivianReceiptsOptIn ¶ added in v0.36.10
SetRivianReceiptsOptIn records the choice.
func ValidateHomeLocation ¶ added in v0.38.21
func ValidateHomeLocation(h HomeLocation) error
ValidateHomeLocation checks a home about to be saved: finite, lat in [-90, 90], lon in [-180, 180], and a known source. A clear (Set=false) is always valid.
func ValidateNetworkLocations ¶ added in v0.38.22
func ValidateNetworkLocations(networks []ChargingNetwork) error
ValidateNetworkLocations checks the optional location on each row before a save: both or neither, finite, in range and not 0,0. A built-in network can't carry one (the save drops it).
Types ¶
type AIConfig ¶
type AIConfig struct {
Provider string // "", "openai", "anthropic", "gemini"
OpenAIModel string
OpenAIKey string
AnthropicModel string
AnthropicKey string
GeminiModel string
GeminiKey string
// Image generation is a separate pipeline from text analysis and so
// has its own provider + per-provider model overrides. The API keys
// are shared with the text providers above.
ImageProvider string // "", "openai", "gemini"
ImageOpenAIModel string
ImageGeminiModel string
// Speech-to-text pipeline (voice notes). Same shape as Image.
SpeechProvider string // "", "openai", "gemini"
SpeechOpenAIModel string
SpeechGeminiModel string
// RecapWeatherEnabled toggles the optional weather lookup that
// runs during trip recap generation. Off by default; the feature
// hits an external HTTP API (Open-Meteo) so it must be opt-in.
RecapWeatherEnabled bool
// GPS accuracy heuristic thresholds applied client-side on the
// drive detail page. See DefaultGPS* for the meaning of each.
GPSMissingPct float64
GPSStaleSec int
GPSJumpCount int
}
AIConfig is the raw persisted AI configuration. Keys are never returned from handlers; this struct is the internal representation.
type AIImagePublic ¶
type AIImagePublic struct {
Provider string `json:"provider"` // "", openai|gemini
Effective string `json:"effective,omitempty"` // what auto-selection picked
// Per-provider model overrides. Empty string means "use the default".
OpenAIModel string `json:"openai_model,omitempty"`
GeminiModel string `json:"gemini_model,omitempty"`
Ready bool `json:"ready"`
}
AIImagePublic is the subset surfaced to the Settings UI. Per-provider key presence is derived from the top-level Providers map, so we only repeat the model choices and the selected provider here.
type AIPublic ¶
type AIPublic struct {
Provider string `json:"provider"` // "", openai|anthropic|gemini
Effective string `json:"effective_provider,omitempty"`
Model string `json:"effective_model,omitempty"`
Providers map[string]ProInfo `json:"providers"`
Ready bool `json:"ready"`
// Image is the public view of the image-generation configuration.
Image AIImagePublic `json:"image"`
// Speech is the public view of the voice-transcription configuration.
Speech AISpeechPublic `json:"speech"`
}
AIPublic is the redacted view returned to the UI. Keys are reported only as "set" / "not set"; the actual secret never leaves the server.
type AISpeechPublic ¶
type AISpeechPublic struct {
Provider string `json:"provider"`
Effective string `json:"effective,omitempty"`
OpenAIModel string `json:"openai_model,omitempty"`
GeminiModel string `json:"gemini_model,omitempty"`
Ready bool `json:"ready"`
}
AISpeechPublic mirrors AIImagePublic for the speech-to-text pipeline.
type AIUpdate ¶
type AIUpdate struct {
Provider *string `json:"provider,omitempty"`
OpenAIModel *string `json:"openai_model,omitempty"`
OpenAIKey *string `json:"openai_api_key,omitempty"`
AnthropicModel *string `json:"anthropic_model,omitempty"`
AnthropicKey *string `json:"anthropic_api_key,omitempty"`
GeminiModel *string `json:"gemini_model,omitempty"`
GeminiKey *string `json:"gemini_api_key,omitempty"`
// Image generation settings.
ImageProvider *string `json:"image_provider,omitempty"`
ImageOpenAIModel *string `json:"image_openai_model,omitempty"`
ImageGeminiModel *string `json:"image_gemini_model,omitempty"`
// Speech-to-text settings.
SpeechProvider *string `json:"speech_provider,omitempty"`
SpeechOpenAIModel *string `json:"speech_openai_model,omitempty"`
SpeechGeminiModel *string `json:"speech_gemini_model,omitempty"`
}
AIUpdate is the JSON body accepted by PUT /api/settings/ai. A nil pointer means "leave this field alone"; an explicit empty string clears a key.
type ChargingConfig ¶
type ChargingConfig struct {
// HomePricePerKWh is the retail cost in HomeCurrency per kilowatt
// hour. Zero means "not configured"; in that case estimated cost
// is not computed.
HomePricePerKWh float64 `json:"home_price_per_kwh"`
// HomeCurrency is the ISO-4217 code displayed next to estimated
// cost. Defaults to USD.
HomeCurrency string `json:"home_currency"`
// GasPricePerGallon and ComparisonMPG feed the gas-equivalent chip
// on the trip planner. Zero on either disables the chip.
GasPricePerGallon float64 `json:"gas_price_per_gallon"`
ComparisonMPG float64 `json:"comparison_mpg"`
}
ChargingConfig is the user-configurable cost-of-energy settings applied locally to estimate the price of sessions Rivian reports as free (home AC, L2 on non-RAN chargers, etc.).
func GetChargingConfig ¶
func GetChargingConfig(ctx context.Context, s *Store) (ChargingConfig, error)
GetChargingConfig returns the stored home-charging cost settings, filling defaults where unset.
type ChargingNetwork ¶
type ChargingNetwork struct {
Name string `json:"name"`
PricePerKWh float64 `json:"price_per_kwh"`
Currency string `json:"currency"`
MemberPrice float64 `json:"member_price_per_kwh,omitempty"`
MemberActive bool `json:"member_active,omitempty"`
MemberPlan string `json:"member_plan,omitempty"`
Slug string `json:"slug,omitempty"`
// Preferred = "rank this network high in Rivian's planner".
// Forwarded as networkPreferences[].preference=1 on plan
// requests. Unset / false sends nothing for that network.
Preferred bool `json:"preferred,omitempty"`
// Seeded is computed on read, never stored: the row's rates are still the built-in
// table's (or blank), so the user has not entered a rate for this network. Its
// price fields are returned as 0 ("unset"): the table's mid-2025 figures are dated
// estimates that no network publishes as a price and are not shown or applied as
// one. A row the user edited has Seeded false.
Seeded bool `json:"seeded,omitempty"`
// Lat/Lon are where a network row of the user's own (no built-in Slug) is, when they
// gave one (#906): both or neither. Private to this user: only their charge map's
// label and the pricing prefill read it. Never in AsOverrides, the chargers archive,
// the DCFC Tracker links, demo data or anyone else's price book.
Lat *float64 `json:"lat,omitempty"`
Lon *float64 `json:"lon,omitempty"`
}
ChargingNetwork is one entry in the price book. Two use cases share this row:
- Manual charge-session pricing on the Charges page (one-click "apply EA rate" for a recorded session). Uses Name + PricePerKWh + Currency.
- Trip planner per-stop cost estimation. Adds MemberPrice + MemberActive so the planner can quote both "guest" and "your actual" rates without us tracking memberships behind the scenes. Slug ties a row to the built-in dcfcrates.Networks table for match-pattern lookup (so a charger name like "Electrify America - Pflugerville" still maps to the EA row without the user re-entering patterns).
New fields are all optional with omitempty so the manual-pricing UI keeps deserialising older JSON blobs unchanged.
func DefaultChargingNetworks ¶ added in v0.18.84
func DefaultChargingNetworks() []ChargingNetwork
DefaultChargingNetworks returns the seed list derived from dcfcrates.Networks. Same order as the built-in table so the Settings panel renders predictably across reloads.
func GetChargingNetworks ¶
func GetChargingNetworks(ctx context.Context, s *Store) ([]ChargingNetwork, error)
GetChargingNetworks returns the configured price book. When the store has no entry yet, returns the seed defaults from dcfcrates.Networks so the Settings UI always has something to show on first load. The seed result is NOT persisted - the user has to hit Save once before their list diverges from the seed.
type Factory ¶
type Factory struct {
// contains filtered or unexported fields
}
Factory hands out per-user *Store instances. The settings store is just (db, userID), so a per-user instance is essentially free — caching is purely so callers reuse the same pointer across requests and don't churn the heap on every handler hit.
type GPSPublic ¶
type GPSPublic struct {
MissingPct float64 `json:"missing_pct"`
StaleSec int `json:"stale_sec"`
JumpCount int `json:"jump_count"`
}
GPSPublic is the redacted view of the GPS accuracy thresholds. Surfaced through /api/config so every page that needs to apply them (drive detail, live panel) reads one source.
type GPSUpdate ¶
type GPSUpdate struct {
MissingPct *float64 `json:"missing_pct,omitempty"`
StaleSec *int `json:"stale_sec,omitempty"`
JumpCount *int `json:"jump_count,omitempty"`
}
GPSUpdate is the JSON body accepted by PUT /api/admin/settings/gps.
type HomeLocation ¶
type HomeLocation struct {
Set bool `json:"set"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
// Label is the short, user-visible name surfaced everywhere
// the point is referenced (trip planner From: Home, drive
// timeline "departed Home"). Defaults to "Home" on save when
// the client doesn't provide one — keeps planner UI compact.
Label string `json:"label,omitempty"`
// Address is the full geocoded address kept for context on
// the Settings card. Never used as the primary label.
Address string `json:"address,omitempty"`
// Source is how the home was set: "city", "address", "coordinates" or "charge" ("" before it was recorded).
Source string `json:"source,omitempty"`
}
HomeLocation is the user's "home" base — used by the trip planner to offer a one-click Origin / Destination preset, and (eventually) by the recap path to recognise round trips that end at home.
Set is the boolean the UI uses to decide whether to render the Home preset button at all. Latitude/Longitude are the resolved coords; Label is the human-readable name (city + state) so the UI doesn't need to reverse-geocode every time.
func GetHomeLocation ¶
func GetHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)
GetHomeLocation reads the saved home location. Returns Set=false when no location has been configured (zero-value HomeLocation).
func PreviousHomeLocation ¶ added in v0.38.21
func PreviousHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)
PreviousHomeLocation is the home the latest change replaced (Set=false when there is none).
func RestorePreviousHomeLocation ¶ added in v0.38.21
func RestorePreviousHomeLocation(ctx context.Context, s *Store) (HomeLocation, error)
RestorePreviousHomeLocation swaps the current home and the previous one (so restoring twice returns to where it started). ErrNoPreviousHome when there's nothing to restore.
type KV ¶
type KV interface {
GetAll(ctx context.Context) (map[string]string, error)
Set(ctx context.Context, key, value string) error
Delete(ctx context.Context, key string) error
}
KV is the minimal key/value backing store the Manager needs. Both the per-user *Store in this package and the install-wide *appsettings.Store satisfy it. The Manager itself doesn't care which it talks to — but in practice Rivolt wires it to appsettings (AI keys are an install concern: the operator pays the LLM bill).
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the current AI configuration and the corresponding Analyzer. It is concurrency-safe; handlers call Analyzer() on every request.
func NewManager ¶
NewManager loads any persisted config and builds the initial Analyzer. envSeed is merged in for values that are not yet stored, so first-boot behaviour matches the previous env-only configuration.
func (*Manager) Analyzer ¶
Analyzer returns the currently configured analyzer, or nil if AI is disabled (no key for the selected provider).
func (*Manager) GeminiCreds ¶
GeminiCreds returns the currently stored Gemini API key and model. The caller must treat the key as a secret and never log it. Used by the profile-image generation endpoint which doesn't go through Analyzer.
func (*Manager) ImageCreds ¶
ImageCreds returns the provider, API key, and model to use for image generation. Provider is one of "openai" or "gemini", or the empty string when no image-capable provider has a key configured. Respects the user's explicit ImageProvider setting and falls back to auto-selection otherwise.
func (*Manager) ListModels ¶
ListModels fetches the catalogue of usable models for a provider, using the stored API key. The caller is expected to have already saved a key; we deliberately don't accept an ad-hoc key here so the UI has one place (PUT /api/settings/ai) that persists secrets.
func (*Manager) OpenAICreds ¶
OpenAICreds returns the stored OpenAI API key and text model. Used by non-Analyzer endpoints (e.g. Whisper transcription) that need OpenAI auth directly.
func (*Manager) Provider ¶
Provider returns the active ai.Provider, or nil when AI is disabled. Features outside of shot analysis (coach, comparator, ask, digest, profile-name) call this directly so they can wrap the provider in their own task-specific helper.
func (*Manager) RecapPublic ¶
func (m *Manager) RecapPublic() RecapPublic
RecapPublic returns the redacted recap settings DTO.
func (*Manager) RecapWeatherEnabled ¶
RecapWeatherEnabled returns the current value of the recap weather enrichment toggle. Cheap; safe to call per request.
func (*Manager) SpeechCreds ¶
SpeechCreds returns the provider, API key, and model to use for voice transcription. Same contract as ImageCreds: empty provider means "no speech-capable provider has a key configured".
func (*Manager) Update ¶
Update applies the patch, persists it, and rebuilds the analyzer. Returns the new public view (or an error if the patch is invalid).
func (*Manager) UpdateGPS ¶
UpdateGPS applies the patch and persists. Values are clamped to non-negative ranges so a malformed payload can't silently disable the heuristic by way of a negative threshold.
func (*Manager) UpdateRecap ¶
func (m *Manager) UpdateRecap(ctx context.Context, patch RecapUpdate) (RecapPublic, error)
UpdateRecap applies the patch and persists. Doesn't touch the AI analyzer because recap settings are orthogonal to provider config.
func (*Manager) VisionCreds ¶
VisionCreds returns the provider, API key and model to use for image UNDERSTANDING (vision input, e.g. scanning a coffee bag). This is distinct from ImageCreds, which generates images. Vision follows the main chat provider so the bag gets read by whichever LLM you've chosen to analyse your shots. Supported providers: "openai" and "gemini". Anthropic Claude supports vision but isn't wired here yet; if the user's chat provider is Anthropic we fall back to whichever of OpenAI/Gemini has a key. Returns empty provider when no vision- capable key is set.
type NetworkPref ¶ added in v0.20.0
NetworkPref is one entry in a Rivian networkPreferences[] array. Defined here (not in rivian/) because settings is imported by rivian; the api layer maps these to rivian.NetworkPreference.
func NetworkPreferenceList ¶ added in v0.20.0
func NetworkPreferenceList(networks []ChargingNetwork) []NetworkPref
NetworkPreferenceList returns Rivian networkId entries for the networks the user marked Preferred. Empty when the user picked none — caller then omits networkPreferences entirely.
Wire shape mirrors the Rivian Android app (3.6 + 3.12 confirmed via apktool): NetworkPreferencesInput has a single `networkId` field; no `preference` value is sent. The app submits ONLY the preferred IDs; networks omitted from the list are not treated as "deprioritise" but as "no opinion." Preference=1 is included on the Go-side struct so the rivian client still has a value, but the GraphQL serializer is what determines whether it goes on the wire.
Networks span multiple operator IDs (RAN = RAN+RWN; FLO = FLOC+FLOU); all share the user's per-slug toggle and we emit one row per ID.
type PlannerFavorite ¶ added in v0.18.77
type PlannerFavorite struct {
ID string `json:"id"`
Label string `json:"label"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
PlannerFavorite is one saved place — coordinates plus a short user-visible name. ID is a UUID minted client-side so deletes and renames don't need a separate lookup.
func GetPlannerFavorites ¶ added in v0.18.77
func GetPlannerFavorites(ctx context.Context, s *Store) ([]PlannerFavorite, error)
GetPlannerFavorites returns the user's saved favorites. Returns nil (not an error) when nothing has been saved or the stored blob fails to parse — a corrupt blob is treated the same as "not configured" so a single bad write doesn't permanently break the planner page.
type PlannerPrefs ¶
type PlannerPrefs struct {
// DriveMode is one of EVERYDAY / DISTANCE / SPORT / WINTER /
// OFF_ROAD_AUTO. Empty string means "no default — let Rivian
// pick" (the gateway's default is EVERYDAY, so empty and
// EVERYDAY produce the same plan).
DriveMode string `json:"drive_mode"`
// HasAdapter — does the vehicle have the Tesla NACS adapter?
// Pointer so absent/false/true are all distinguishable on the
// wire; default is unset.
HasAdapter *bool `json:"has_adapter,omitempty"`
}
PlannerPrefs are the user's saved trip-planner defaults. The SPA pre-fills the per-trip form from these so the user doesn't have to pick the same drive mode every time.
func GetPlannerPrefs ¶
func GetPlannerPrefs(ctx context.Context, s *Store) (PlannerPrefs, error)
GetPlannerPrefs returns the saved planner defaults. Returns the zero-value PlannerPrefs when nothing has been configured.
type RecapPublic ¶
type RecapPublic struct {
WeatherEnabled bool `json:"weather_enabled"`
}
RecapPublic is the redacted view of the install-wide recap config returned to the UI. Lives in its own DTO (and on its own endpoint) because the recap toggle isn't an AI configuration knob — it's a data-egress switch (Open-Meteo, see internal/weather) that happens to be consumed by an AI feature. Surfacing it under "AI" sent the wrong signal about what disabling AI does/doesn't hide.
type RecapUpdate ¶
type RecapUpdate struct {
WeatherEnabled *bool `json:"weather_enabled,omitempty"`
}
RecapUpdate is the JSON body accepted by PUT /api/settings/recap. Same nil-pointer-means-untouched contract as AIUpdate.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a thin key/value wrapper around a Postgres connection pool scoped to a single user.
func OpenStore ¶
OpenStore binds a pooled connection to a user_id. The pool is owned by the caller; Store does not close it.
func (*Store) GetAll ¶
GetAll returns every stored key/value pair across every namespace, flattened. Keys keep their "ai.openai.model" / "charging.home_currency" shape so callers that were built against the old sqlite KV table don't need to know about namespaces.
func (*Store) Set ¶
Set writes a single value. Empty string is a valid value ("cleared by operator"); callers that want to delete should use Delete instead. Writes are atomic at the (user_id, namespace) row level.
func (*Store) SetMany ¶ added in v0.38.21
SetMany writes several values of one namespace in a single statement, so the row changes all at once or not at all and a concurrent writer of the same row sees either none or all of them (Set one by one let two writers interleave their keys). Every key must belong to the same namespace.
type Units ¶ added in v0.39.6
type Units struct {
DistanceUnit string `json:"distance_unit"`
TemperatureUnit string `json:"temperature_unit"`
DistanceSource string `json:"distance_source"`
TemperatureSource string `json:"temperature_source"`
}
Units is the resolved setting: always concrete values, with where each came from.