| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "reasonix/internal/config" |
| 5 | "strings" |
| 6 | ) |
| 7 | |
| 8 | // SetCloseBehavior updates desktop-only window close behavior without rebuilding |
| 9 | // the active controller. It must stay out of provider-visible prompt/request data. |
| 10 | func (a *App) SetCloseBehavior(mode string) error { |
| 11 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopCloseBehavior(mode) }) |
| 12 | } |
| 13 | |
| 14 | // SetStatusBarStyle updates the desktop status bar metric label style. UI-only, |
| 15 | // no rebuild needed. |
| 16 | func (a *App) SetStatusBarStyle(style string) error { |
| 17 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopStatusBarStyle(style) }) |
| 18 | } |
| 19 | |
| 20 | // SetStatusBarItems updates the ordered visible desktop status bar items. |
| 21 | // UI-only, no rebuild needed. |
| 22 | func (a *App) SetStatusBarItems(items []string) error { |
| 23 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopStatusBarItems(items) }) |
| 24 | } |
| 25 | |
| 26 | // SetDesktopLanguage updates the desktop UI language and the user-level response |
| 27 | // language preference used by model-facing desktop sessions. |
| 28 | func (a *App) SetDesktopLanguage(lang string) error { |
| 29 | responseLanguage := "" |
| 30 | mutate := func(c *config.Config) error { |
| 31 | if err := c.SetDesktopLanguage(lang); err != nil { |
| 32 | return err |
| 33 | } |
| 34 | if err := c.SetLanguage(lang); err != nil { |
| 35 | return err |
| 36 | } |
| 37 | responseLanguage = c.ResponseLanguage() |
| 38 | return nil |
| 39 | } |
| 40 | err := a.applyConfigOnly(mutate) |
| 41 | if err != nil { |
| 42 | return err |
| 43 | } |
| 44 | if strings.TrimSpace(lang) != "" && !strings.EqualFold(strings.TrimSpace(lang), "auto") { |
| 45 | a.setDesktopLocale(lang) |
| 46 | } |
| 47 | refreshBackendNoticeLocale(lang) |
| 48 | a.updateTrayLocale(lang) |
| 49 | a.applyResponseLanguageToLiveControllers(responseLanguage) |
| 50 | return nil |
| 51 | } |
| 52 | |
| 53 | // SetDesktopCurrency persists a display-only preference and re-selects the |
| 54 | // occurrence-time valuations already stored in each tab. Provider price tables |
| 55 | // and live controllers are intentionally untouched. |
| 56 | func (a *App) SetDesktopCurrency(currency string) error { |
| 57 | err := a.applyConfigOnly(func(c *config.Config) error { |
| 58 | return c.SetDesktopCurrency(currency) |
| 59 | }) |
| 60 | if err != nil { |
| 61 | return err |
| 62 | } |
| 63 | |
| 64 | a.sessionRemovalMu.Lock() |
| 65 | defer a.sessionRemovalMu.Unlock() |
| 66 | a.mu.RLock() |
| 67 | tabs := append([]*WorkspaceTab(nil), a.runtimeTabsLocked()...) |
| 68 | a.mu.RUnlock() |
| 69 | for _, tab := range tabs { |
| 70 | a.repriceTabUsageForCurrentCurrency(tab) |
| 71 | } |
| 72 | return nil |
| 73 | } |
| 74 | |
| 75 | func (a *App) desktopEffectivePricingCurrency(cfg *config.Config) string { |
| 76 | // Display currency only — never the provider list-price region. |
| 77 | if cfg == nil { |
| 78 | return "" |
| 79 | } |
| 80 | if pref := cfg.DisplayCurrencyPref(); pref != "" { |
| 81 | return pref |
| 82 | } |
| 83 | return cfg.ExplicitDisplayCurrency() |
| 84 | } |
| 85 | |
| 86 | func (a *App) desktopOfficialPricingLanguage(cfg *config.Config) string { |
| 87 | // Used only for display-language adjacent UI; list prices use billing_currency. |
| 88 | if a.desktopEffectivePricingCurrency(cfg) == "CNY" { |
| 89 | return "zh" |
| 90 | } |
| 91 | return "en" |
| 92 | } |
| 93 | |
| 94 | // SetTrayLocale mirrors the resolved desktop UI language into the native tray |
| 95 | // menu. It is runtime-only; the persisted preference remains [desktop].language. |
| 96 | func (a *App) SetTrayLocale(locale string) error { |
| 97 | a.setDesktopLocale(locale) |
| 98 | trayLocale := "en" |
| 99 | if strings.HasPrefix(strings.ToLower(strings.TrimSpace(locale)), "zh") { |
| 100 | trayLocale = "zh" |
| 101 | } |
| 102 | a.updateTrayLocale(trayLocale) |
| 103 | a.emitProjectTreeChanged() |
| 104 | return nil |
| 105 | } |
| 106 | |
| 107 | // SetDesktopAppearance updates only desktop theme preferences. It does not |
| 108 | // rebuild the active controller and must stay out of provider-visible requests. |
| 109 | func (a *App) SetDesktopAppearance(theme, style string) error { |
| 110 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopAppearance(theme, style) }) |
| 111 | } |
| 112 | |
| 113 | // SetDesktopTerminalTheme updates only the integrated terminal colours. It is |
| 114 | // applied live by the frontend and does not rebuild the active controller. |
| 115 | func (a *App) SetDesktopTerminalTheme(theme string) error { |
| 116 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopTerminalTheme(theme) }) |
| 117 | } |
| 118 | |
| 119 | // SetDesktopLayoutStyle updates only the desktop layout style. It does not |
| 120 | // rebuild the active controller and must stay out of provider-visible requests. |
| 121 | func (a *App) SetDesktopLayoutStyle(style string) error { |
| 122 | if err := a.applyConfigOnly(func(c *config.Config) error { |
| 123 | return c.SetDesktopLayoutStyle(style) |
| 124 | }); err != nil { |
| 125 | return err |
| 126 | } |
| 127 | return a.applySingleSurfaceTabPolicy() |
| 128 | } |
| 129 | |
| 130 | // SetDesktopCheckUpdates updates only the desktop startup update-check |
| 131 | // preference. Manual checks in Settings are unaffected. |
| 132 | func (a *App) SetDesktopCheckUpdates(enabled bool) error { |
| 133 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopCheckUpdates(enabled) }) |
| 134 | } |
| 135 | |
| 136 | // SetDesktopUpdateChannel is retained for older Wails clients. The config layer |
| 137 | // clears the retired preference and every updater request uses Stable. |
| 138 | func (a *App) SetDesktopUpdateChannel(channel string) error { |
| 139 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopUpdateChannel(channel) }) |
| 140 | } |
| 141 | |
| 142 | // SetDesktopTelemetry sets whether the desktop sends the anonymous launch ping. |
| 143 | func (a *App) SetDesktopTelemetry(enabled bool) error { |
| 144 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopTelemetry(enabled) }) |
| 145 | } |
| 146 | |
| 147 | // SetDesktopMetrics sets whether the desktop sends aggregate desktop metrics, |
| 148 | // starting or stopping the live aggregator so the toggle takes effect immediately. |
| 149 | func (a *App) SetDesktopMetrics(enabled bool) error { |
| 150 | if err := a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopMetrics(enabled) }); err != nil { |
| 151 | return err |
| 152 | } |
| 153 | switch { |
| 154 | case enabled && a.metrics.Load() == nil && version != "dev": |
| 155 | a.metrics.Store(newMetricsAggregator(config.MemoryUserDir())) |
| 156 | if cfg, err := config.Load(); err == nil { |
| 157 | a.recordSettingsMetricsSnapshot(cfg) |
| 158 | } |
| 159 | case !enabled: |
| 160 | a.metrics.Store(nil) |
| 161 | } |
| 162 | return nil |
| 163 | } |
| 164 | |
| 165 | // SetDesktopConversationWidth sets the max transcript width preference. |
| 166 | // standard = 960px fixed; full = 90% of the parent, with a 960px floor. Pure config-only. |
| 167 | func (a *App) SetDesktopConversationWidth(width string) error { |
| 168 | return a.applyConfigOnly(func(c *config.Config) error { return c.SetDesktopConversationWidth(width) }) |
| 169 | } |
| 170 | |
| 171 | // MigrateDesktopPreferences imports old browser-local desktop preferences into |
| 172 | // the user config once. Existing [desktop] values win so stale localStorage never |
| 173 | // overwrites an explicit config edit. |
| 174 | func (a *App) MigrateDesktopPreferences(language, theme, style string) error { |
| 175 | return a.applyConfigOnly(func(c *config.Config) error { |
| 176 | if strings.TrimSpace(c.Desktop.Language) == "" { |
| 177 | if err := c.SetDesktopLanguage(language); err != nil { |
| 178 | return err |
| 179 | } |
| 180 | } |
| 181 | if strings.TrimSpace(c.Desktop.Theme) == "" && strings.TrimSpace(c.Desktop.ThemeStyle) == "" { |
| 182 | if err := c.SetDesktopAppearance(theme, style); err != nil { |
| 183 | return err |
| 184 | } |
| 185 | } |
| 186 | return nil |
| 187 | }) |
| 188 | } |
| 189 |