Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions apps/desktop-tauri/src-tauri/src/auto_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,29 @@ pub fn install(app: tauri::AppHandle) {
});
}

fn resolve_refresh_interval(settings: &Settings) -> Option<Duration> {
if settings.adaptive_refresh {
return Some(adaptive_delay_now());
const LOW_POWER_MIN_INTERVAL: Duration = Duration::from_secs(30 * 60);

/// Pure upstream `BackgroundWorkPowerPolicy.automaticInterval` port:
/// floor automatic intervals to 30 minutes when low-power mode is on.
/// `None` (manual / no timer) stays `None`.
pub(crate) fn automatic_interval(
requested: Option<Duration>,
low_power_mode_enabled: bool,
) -> Option<Duration> {
let requested = requested?;
if !low_power_mode_enabled {
return Some(requested);
}
refresh_interval(settings.refresh_interval_secs)
Some(requested.max(LOW_POWER_MIN_INTERVAL))
}

fn resolve_refresh_interval(settings: &Settings) -> Option<Duration> {
let requested = if settings.adaptive_refresh {
Some(adaptive_delay_now())
} else {
refresh_interval(settings.refresh_interval_secs)
};
automatic_interval(requested, settings.low_power_mode)
}

fn adaptive_delay_now() -> Duration {
Expand Down Expand Up @@ -226,6 +244,34 @@ mod tests {
assert_eq!(delay, Duration::from_secs(30 * 60));
}

#[test]
fn low_power_mode_floors_fixed_and_adaptive_intervals() {
assert_eq!(
automatic_interval(Some(Duration::from_secs(60)), true),
Some(Duration::from_secs(30 * 60))
);
assert_eq!(
automatic_interval(Some(Duration::from_secs(3600)), true),
Some(Duration::from_secs(3600))
);
assert_eq!(
automatic_interval(Some(Duration::from_secs(60)), false),
Some(Duration::from_secs(60))
);
assert_eq!(automatic_interval(None, true), None);

let settings = Settings {
low_power_mode: true,
adaptive_refresh: false,
refresh_interval_secs: 300,
..Default::default()
};
assert_eq!(
resolve_refresh_interval(&settings),
Some(Duration::from_secs(30 * 60))
);
}

#[test]
fn fixed_cadence_advances_from_the_scheduled_tick() {
let start = Instant::now();
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ pub struct SettingsSnapshot {
refresh_interval_secs: u64,
adaptive_refresh: bool,
refresh_all_providers_on_menu_open: bool,
low_power_mode: bool,
start_at_login: bool,
start_minimized: bool,
show_notifications: bool,
Expand Down Expand Up @@ -563,6 +564,7 @@ impl From<Settings> for SettingsSnapshot {
refresh_interval_secs: settings.refresh_interval_secs,
adaptive_refresh: settings.adaptive_refresh,
refresh_all_providers_on_menu_open: settings.refresh_all_providers_on_menu_open,
low_power_mode: settings.low_power_mode,
start_at_login: settings.start_at_login,
start_minimized: settings.start_minimized,
show_notifications: settings.show_notifications,
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/provider_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fn cookie_source_provider(provider_id: &str) -> Option<codexbar::core::ProviderI
"mistral" => ProviderId::Mistral,
"qoder" => ProviderId::Qoder,
"sakana" => ProviderId::Sakana,
"notion" => ProviderId::Notion,
_ => return None,
})
}
Expand Down Expand Up @@ -184,6 +185,7 @@ fn workspace_provider(provider_id: &str) -> Option<codexbar::core::ProviderId> {
"devin" => ProviderId::Devin,
"opencodego" => ProviderId::OpenCodeGo,
"zed" => ProviderId::Zed,
"xai" => ProviderId::Xai,
_ => return None,
})
}
Expand Down Expand Up @@ -548,6 +550,23 @@ pub fn cookie_source_options_for(provider_id: &str, lang: Language) -> Vec<Cooki
None,
),
],
"notion" => vec![
cookie_option(
lang,
"auto",
"Automatically imports the browser session cookie.",
"",
None,
),
cookie_option(
lang,
"manual",
"",
"Paste a full cookie header or the token_v2 value.",
None,
),
cookie_option(lang, "off", "", "", Some("Notion cookies are disabled.")),
],
_ => Vec::new(),
}
}
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop-tauri/src-tauri/src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct SettingsUpdate {
pub refresh_interval_secs: Option<u64>,
pub adaptive_refresh: Option<bool>,
pub refresh_all_providers_on_menu_open: Option<bool>,
pub low_power_mode: Option<bool>,
pub start_at_login: Option<bool>,
pub start_minimized: Option<bool>,
pub show_notifications: Option<bool>,
Expand Down Expand Up @@ -82,6 +83,8 @@ impl SettingsUpdate {
fn notifies_float_bar(&self) -> bool {
self.enabled_providers.is_some()
|| self.refresh_interval_secs.is_some()
|| self.low_power_mode.is_some()
|| self.adaptive_refresh.is_some()
|| self.codex_custom_sessions_dirs.is_some()
|| self.high_usage_threshold.is_some()
|| self.critical_usage_threshold.is_some()
Expand Down Expand Up @@ -144,6 +147,9 @@ impl SettingsUpdate {
if let Some(v) = self.refresh_all_providers_on_menu_open {
settings.refresh_all_providers_on_menu_open = v;
}
if let Some(v) = self.low_power_mode {
settings.low_power_mode = v;
}
if let Some(ref s) = self.tray_icon_mode
&& let Some(mode) = parse_tray_icon_mode(s)
{
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions apps/desktop-tauri/src/components/providers/providerIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import manus from "./icons/ProviderIcon-manus.svg?raw";
import mimo from "./icons/ProviderIcon-mimo.svg?raw";
import minimax from "./icons/ProviderIcon-minimax.svg?raw";
import mistral from "./icons/ProviderIcon-mistral.svg?raw";
import notion from "./icons/ProviderIcon-notion.svg?raw";
import xai from "./icons/ProviderIcon-xai.svg?raw";
import ollama from "./icons/ProviderIcon-ollama.svg?raw";
import opencode from "./icons/ProviderIcon-opencode.svg?raw";
import opencodego from "./icons/ProviderIcon-opencodego.svg?raw";
Expand Down Expand Up @@ -118,6 +120,8 @@ const RAW: Record<string, string> = {
manus: tint(manus),
mimo: tint(mimo),
minimax: tint(minimax),
notion: tint(notion),
xai: tint(xai),
mistral: tint(mistral),
ollama: tint(ollama),
opencode: tint(opencode),
Expand Down Expand Up @@ -209,6 +213,8 @@ export const PROVIDER_ICON_REGISTRY: Record<string, ProviderIcon> = {
devin: { id: "devin", brandColor: "#111827", fallbackLetter: "D" },
zed: { id: "zed", brandColor: "#084ccf", fallbackLetter: "Z" },
qwencloud: { id: "qwencloud", brandColor: "#615CED", fallbackLetter: "Q" },
notion: { id: "notion", brandColor: "#337EA9", fallbackLetter: "N", svgPath: RAW.notion },
xai: { id: "xai", brandColor: "#8e8e93", fallbackLetter: "X", svgPath: RAW.xai },
};

const ALIASES: Record<string, string> = {
Expand All @@ -221,6 +227,9 @@ const ALIASES: Record<string, string> = {
qwen: "qwencloud",
"qwen cloud": "qwencloud",
"qwen-cloud": "qwencloud",
"notion ai": "notion",
"notion-ai": "notion",
notionai: "notion",
qianwen: "alibaba",
"alibaba token plan": "alibabatokenplan",
"alibaba-token-plan": "alibabatokenplan",
Expand Down Expand Up @@ -268,8 +277,9 @@ const ALIASES: Record<string, string> = {
"azure-openai": "azureopenai",
"t3 chat": "t3chat",
"t3-chat": "t3chat",
xai: "grok",
"x.ai": "grok",
// xai is its own Management API provider (not an alias of consumer Grok).
"x.ai": "xai",
"x-ai": "xai",
supergrok: "grok",
"super-grok": "grok",
"eleven labs": "elevenlabs",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/floatbar/FloatBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down
10 changes: 7 additions & 3 deletions apps/desktop-tauri/src/floatbar/FloatBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,20 @@ export default function FloatBar({ state }: { state: BootstrapState }) {

// The detached floatbar should keep usage fresh, but it must not open or
// focus any other surface. Refresh data only; provider-updated events feed
// this window when the backend completes.
// this window when the backend completes. Respect Low Power Mode's 30-min
// floor for automatic ticks (manual refresh stays elsewhere/immediate).
useEffect(() => {
const intervalMs = Math.max(60_000, settings.refreshIntervalSecs * 1000);
const baseMs = Math.max(60_000, settings.refreshIntervalSecs * 1000);
const intervalMs = settings.lowPowerMode
? Math.max(baseMs, 30 * 60 * 1000)
: baseMs;
const tick = () => {
void refreshProvidersIfStale().catch(() => {});
};
tick();
const id = setInterval(tick, intervalMs);
return () => clearInterval(id);
}, [settings.refreshIntervalSecs]);
}, [settings.refreshIntervalSecs, settings.lowPowerMode]);

useEffect(() => {
const unlisten = listen(FLOAT_BAR_CONFIG_CHANGED_EVENT, () => {
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src/i18n/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ export const ALL_LOCALE_KEYS = [
"RefreshIntervalHelper",
"RefreshAllProvidersOnMenuOpen",
"RefreshAllProvidersOnMenuOpenHelper",
"LowPowerMode",
"LowPowerModeHelper",
"HighUsageWarningHelper",
"CriticalUsageWarningHelper",
"GlobalShortcutFieldLabel",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/surfaces/PopOutPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function settings(): SettingsSnapshot {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src/surfaces/TrayPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ function settings(overrides: Partial<SettingsSnapshot> = {}): SettingsSnapshot {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop-tauri/src/surfaces/TrayPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ const HAS_DASHBOARD = new Set([
"aiand", "commandcode", "copilot", "crof", "crossmodel", "cursor", "deepgram", "deepinfra", "deepseek", "zenmux", "clinepass", "longcat", "neuralwatt", "zoommate",
"doubao", "elevenlabs", "factory", "gemini", "grok", "groq",
"infini", "jetbrains", "kilo", "kimi", "kimik2", "kiro", "manus",
"mimo", "minimax", "mistral", "nanogpt", "ollama", "openaiapi",
"mimo", "minimax", "mistral", "nanogpt", "notion", "ollama", "openaiapi",
"opencode", "opencodego", "openrouter", "perplexity", "qoder", "sakana", "stepfun",
"t3chat", "venice", "vertexai", "warp", "windsurf",
"zai",
"xai", "zai",
]);
/** Provider IDs that have a status page URL in the backend */
const HAS_STATUS_PAGE = new Set([
"alibabatokenplan", "amp", "augment", "azureopenai", "bedrock",
"claude", "codex", "copilot", "deepgram", "deepinfra", "deepseek", "zenmux", "clinepass", "longcat", "neuralwatt", "zoommate", "elevenlabs",
"gemini", "grok", "groq", "kiro", "mistral", "openaiapi",
"openrouter", "vertexai", "windsurf",
"openrouter", "vertexai", "windsurf", "xai",
]);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function CredentialsDispatcher({ providerId, t }: Props) {
case "opencodego":
case "zed":
case "sub2api":
case "xai":
return <OpenAiExtras providerId={providerId} t={t} />;
default:
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ const WORKSPACE_EXTRA_IDS: Record<string, true> = {
opencodego: true,
zed: true,
sub2api: true,
xai: true,
};

function extraConfig(providerId: string, t: Props["t"]) {
Expand Down Expand Up @@ -160,6 +161,13 @@ function extraConfig(providerId: string, t: Props["t"]) {
placeholder: t("Sub2ApiBaseUrlPlaceholder"),
help: t("Sub2ApiBaseUrlHelp"),
};
case "xai":
return {
title: "xAI team",
label: "Team ID",
placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
help: "Required. Shown in the xAI Console URL and team settings. Or set XAI_TEAM_ID. Pair with a Management API key (not an inference key).",
};
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const settings: SettingsSnapshot = {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const settings: SettingsSnapshot = {
refreshIntervalSecs: 300,
adaptiveRefresh: false,
refreshAllProvidersOnMenuOpen: false,
lowPowerMode: false,
startAtLogin: false,
startMinimized: false,
showNotifications: true,
Expand Down Expand Up @@ -144,6 +145,15 @@ describe("GeneralTab language picker", () => {
expect(set).toHaveBeenCalledWith({ predictivePaceWarningEnabled: true });
});

it("updates the low power mode preference", () => {
const set = vi.fn();
render(<GeneralTab settings={settings} set={set} saving={false} />);

fireEvent.click(screen.getByRole("checkbox", { name: "LowPowerMode" }));

expect(set).toHaveBeenCalledWith({ lowPowerMode: true });
});

it("updates the default notification sound set", () => {
const set = vi.fn();
render(
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop-tauri/src/surfaces/settings/tabs/GeneralTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,18 @@ export default function GeneralTab({
onChange={(v) => set({ refreshAllProvidersOnMenuOpen: v })}
/>
</Field>
<Field
label={t("LowPowerMode")}
description={t("LowPowerModeHelper")}
leading
>
<Toggle
checked={settings.lowPowerMode}
disabled={saving}
ariaLabel={t("LowPowerMode")}
onChange={(v) => set({ lowPowerMode: v })}
/>
</Field>
</div>
</section>}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function providerSourceHintShort(
case "manus":
case "mimo":
case "zoommate":
case "notion":
case "t3chat":
case "commandcode":
return t("ProviderSourceWebShort");
Expand Down Expand Up @@ -245,6 +246,7 @@ function providerSourceHintShort(
case "deepgram":
case "groq":
case "llmproxy":
case "xai":
return t("ProviderSourceApiShort");
case "kiro":
return t("ProviderSourceKiroEnvShort");
Expand Down
Loading
Loading