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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ tauri-plugin-opener = "2"
tauri-plugin-dialog = "2.6"
tauri-plugin-fs = "2"
tauri-plugin-log = "2"
tauri-plugin-autostart = "2"
tauri-build = { version = "2", features = [] }

# Windows-specific dependencies
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/apps/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ tauri-plugin-opener = { workspace = true }
tauri-plugin-dialog = { workspace = true }
tauri-plugin-fs = { workspace = true }
tauri-plugin-log = { workspace = true }
tauri-plugin-autostart = { workspace = true }

# Inherited from workspace
tokio = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions src/apps/desktop/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"windows": ["main"],
"permissions": [
"log:default",
"autostart:default",
"core:default",
"core:path:default",
"core:event:default",
Expand Down
5 changes: 5 additions & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ pub async fn run() {
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(
tauri_plugin_autostart::Builder::new()
.app_name("BitFun")
.build(),
)
.manage(app_state)
.manage(coordinator_state)
.manage(scheduler_state)
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@tauri-apps/plugin-fs": "^2.0.0",
"@tauri-apps/plugin-log": "^2.8.0",
"@tauri-apps/plugin-opener": "^2.5.2",
"@tauri-apps/plugin-autostart": "^2.0.0",
"@tiptap/core": "^3.20.4",
"@tiptap/extension-link": "^3.20.4",
"@tiptap/extension-placeholder": "^3.20.4",
Expand Down
4 changes: 4 additions & 0 deletions src/web-ui/src/app/scenes/settings/settingsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export const SETTINGS_CATEGORIES: ConfigCategoryDef[] = [
'shell',
'pwsh',
'powershell',
'autostart',
'login',
'boot',
'launch',
],
},
{
Expand Down
31 changes: 31 additions & 0 deletions src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { api } from './ApiClient';
import { createTauriCommandError } from '../errors/TauriCommandError';
import { openUrl } from '@tauri-apps/plugin-opener';
import { disable as autostartDisable, enable as autostartEnable, isEnabled as autostartIsEnabled } from '@tauri-apps/plugin-autostart';
import { createLogger } from '@/shared/utils/logger';


Expand Down Expand Up @@ -123,6 +124,36 @@ export class SystemAPI {
throw createTauriCommandError('set_macos_edit_menu_mode', error, { mode });
}
}

/** Desktop only: whether the app is registered to launch at OS login. */
async getLaunchAtLoginEnabled(): Promise<boolean> {
if (typeof window === 'undefined' || !('__TAURI__' in window)) {
return false;
}
try {
return await autostartIsEnabled();
} catch (error) {
log.error('Failed to read launch-at-login state', error);
throw createTauriCommandError('autostart_is_enabled', error);
}
}

/** Desktop only: register or unregister launch at OS login. */
async setLaunchAtLoginEnabled(enabled: boolean): Promise<void> {
if (typeof window === 'undefined' || !('__TAURI__' in window)) {
return;
}
try {
if (enabled) {
await autostartEnable();
} else {
await autostartDisable();
}
} catch (error) {
log.error('Failed to set launch-at-login', { enabled, error });
throw createTauriCommandError('autostart_set', error, { enabled });
}
}
}


Expand Down
99 changes: 99 additions & 0 deletions src/web-ui/src/infrastructure/config/components/BasicsConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FolderOpen, Upload } from 'lucide-react';
import {
Alert,
Select,
Switch,
Tooltip,
ConfigPageLoading,
ConfigPageMessage,
Expand Down Expand Up @@ -189,6 +190,103 @@ function BasicsAppearanceSection() {
);
}

function BasicsLaunchAtLoginSection() {
const { t } = useTranslation('settings/basics');
const isTauri = typeof window !== 'undefined' && '__TAURI__' in window;
const [enabled, setEnabled] = useState(false);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error' | 'info'; text: string } | null>(null);

const showMessage = useCallback((type: 'success' | 'error' | 'info', text: string) => {
setMessage({ type, text });
setTimeout(() => setMessage(null), 3000);
}, []);

useEffect(() => {
if (!isTauri) {
setLoading(false);
return;
}

let cancelled = false;
void (async () => {
try {
setLoading(true);
const v = await systemAPI.getLaunchAtLoginEnabled();
if (!cancelled) {
setEnabled(v);
}
} catch (error) {
log.error('Failed to load launch-at-login state', error);
if (!cancelled) {
showMessage('error', t('launchAtLogin.messages.loadFailed'));
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
})();

return () => {
cancelled = true;
};
}, [isTauri, showMessage, t]);

const handleToggle = useCallback(
async (next: boolean) => {
const previous = enabled;
setEnabled(next);
setSaving(true);
try {
await systemAPI.setLaunchAtLoginEnabled(next);
} catch (error) {
setEnabled(previous);
log.error('Failed to set launch-at-login', { next, error });
showMessage('error', t('launchAtLogin.messages.saveFailed'));
} finally {
setSaving(false);
}
},
[enabled, showMessage, t]
);

if (!isTauri) {
return null;
}

if (loading) {
return <ConfigPageLoading text={t('launchAtLogin.messages.loading')} />;
}

return (
<div className="bitfun-launch-at-login-config">
<div className="bitfun-launch-at-login-config__content">
<ConfigPageMessage message={message} />
<ConfigPageSection
title={t('launchAtLogin.sections.title')}
description={t('launchAtLogin.sections.hint')}
>
<ConfigPageRow
label={t('launchAtLogin.toggleLabel')}
description={t('launchAtLogin.toggleDescription')}
align="center"
>
<Switch
checked={enabled}
onChange={(e) => {
void handleToggle(e.target.checked);
}}
disabled={saving}
/>
</ConfigPageRow>
</ConfigPageSection>
</div>
</div>
);
}

function BasicsLoggingSection() {
const { t } = useTranslation('settings/basics');
const [configLevel, setConfigLevel] = useState<BackendLogLevel>('info');
Expand Down Expand Up @@ -767,6 +865,7 @@ const BasicsConfig: React.FC = () => {
<ConfigPageHeader title={t('title')} subtitle={t('subtitle')} />
<ConfigPageContent className="bitfun-basics-config__content">
<BasicsAppearanceSection />
<BasicsLaunchAtLoginSection />
<BasicsLoggingSection />
<BasicsTerminalSection />
</ConfigPageContent>
Expand Down
2 changes: 1 addition & 1 deletion src/web-ui/src/locales/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"searchNoResults": "No matching settings",
"searchClear": "Clear search",
"tabDescriptions": {
"basics": "Language, theme, logging, and terminal shell.",
"basics": "Language, theme, logging, terminal shell, and launch at login.",
"models": "AI models, API keys, providers, and proxy.",
"sessionConfig": "Session behavior, tools, and timeouts.",
"aiContext": "Rules and memory for AI context.",
Expand Down
15 changes: 14 additions & 1 deletion src/web-ui/src/locales/en-US/settings/basics.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"title": "Basics",
"subtitle": "Interface language, theme, logging, and terminal",
"subtitle": "Interface language, theme, logging, terminal, and login startup",
"appearance": {
"title": "Appearance",
"hint": "Interface language and visual theme",
Expand Down Expand Up @@ -48,6 +48,19 @@
}
}
},
"launchAtLogin": {
"sections": {
"title": "Login",
"hint": "Start BitFun when you sign in to this account"
},
"toggleLabel": "Launch at login",
"toggleDescription": "Automatically start BitFun after you log in. Exact behavior depends on your operating system.",
"messages": {
"loading": "Loading...",
"loadFailed": "Failed to read launch-at-login setting",
"saveFailed": "Failed to update launch-at-login setting"
}
},
"logging": {
"sections": {
"logging": "Logging",
Expand Down
2 changes: 1 addition & 1 deletion src/web-ui/src/locales/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"searchNoResults": "没有匹配的配置",
"searchClear": "清除搜索",
"tabDescriptions": {
"basics": "语言、主题、日志与终端 Shell。",
"basics": "语言、主题、日志、终端 Shell 与开机启动。",
"models": "AI 模型、API 密钥、供应商与代理。",
"sessionConfig": "会话行为、工具与超时。",
"aiContext": "规则与记忆等 AI 上下文。",
Expand Down
15 changes: 14 additions & 1 deletion src/web-ui/src/locales/zh-CN/settings/basics.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"title": "基础",
"subtitle": "界面语言、主题、日志与终端",
"subtitle": "界面语言、主题、日志、终端与登录启动",
"appearance": {
"title": "外观",
"hint": "界面语言与视觉主题",
Expand Down Expand Up @@ -48,6 +48,19 @@
}
}
},
"launchAtLogin": {
"sections": {
"title": "登录时启动",
"hint": "登录本用户后是否自动运行 BitFun"
},
"toggleLabel": "开机自动启动",
"toggleDescription": "登录系统后自动启动 BitFun;具体表现因操作系统而异。",
"messages": {
"loading": "加载中…",
"loadFailed": "无法读取开机启动设置",
"saveFailed": "无法更新开机启动设置"
}
},
"logging": {
"sections": {
"logging": "日志",
Expand Down
Loading