Skip to content
Open
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
145 changes: 133 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
flushConfig,
flushSaveConfig,
type DownloadProgress,
type UpdateInfo,
} from '@/stores/appStore';
import {
TitleBar,
Expand Down Expand Up @@ -89,7 +90,12 @@ import { WebUIBetaBanner } from './components/app/WebUIBetaBanner';
import { startGlobalCallbackListener } from './components/connection/callbackCache';
import { useIsMobile } from '@/hooks/useIsMobile';
import { ScrollText } from 'lucide-react';
import { defaultConfig, defaultWindowSize, isValidMxuConfig } from '@/types/config';
import {
defaultConfig,
defaultScheduledUpdateCheckTime,
defaultWindowSize,
isValidMxuConfig,
} from '@/types/config';

const log = loggers.app;

Expand Down Expand Up @@ -208,6 +214,8 @@ function App() {
onboardingCompleted,
backgroundImage,
backgroundOpacity,
scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime,
} = useAppStore(
useShallow((state) => ({
setProjectInterface: state.setProjectInterface,
Expand Down Expand Up @@ -250,6 +258,8 @@ function App() {
onboardingCompleted: state.onboardingCompleted,
backgroundImage: state.backgroundImage,
backgroundOpacity: state.backgroundOpacity,
scheduledUpdateCheckEnabled: state.scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime: state.scheduledUpdateCheckTime,
})),
);

Expand Down Expand Up @@ -509,6 +519,120 @@ function App() {
[setDownloadStatus, setDownloadProgress, setDownloadSavePath, tryAutoInstallUpdate],
);

// 启动检查和定时检查共用更新结果处理
const handleAutoUpdateCheckResult = useCallback(
(updateResult: UpdateInfo) => {
setUpdateInfo(updateResult);
if (updateResult.hasUpdate) {
log.info(`发现新版本: ${updateResult.versionName}`);
useAppStore.getState().setShowUpdateDialog(true);
if (updateResult.downloadUrl) {
startAutoDownload(updateResult);
}
} else if (updateResult.errorCode) {
log.warn(`更新检查返回错误: code=${updateResult.errorCode}`);
useAppStore.getState().setShowUpdateDialog(true);
}
},
[setUpdateInfo, startAutoDownload],
);

// 仅桌面端负责定时更新,避免多个 WebUI 页面重复触发
useEffect(() => {
if (
!scheduledUpdateCheckEnabled ||
loadingState !== 'success' ||
!isTauri() ||
import.meta.env.DEV
) {
return;
}
if (
!projectInterface?.mirrorchyan_rid ||
!projectInterface.version ||
isDebugVersion(projectInterface.version)
) {
return;
}

const { mirrorchyan_rid: resourceId, version: currentVersion, github, name } = projectInterface;
let cancelled = false;
const isUpdateBusy = () => {
const state = useAppStore.getState();
return (
state.downloadStatus === 'downloading' ||
state.downloadStatus === 'completed' ||
state.installStatus !== 'idle'
);
};

const checkForScheduledUpdate = async () => {
const state = useAppStore.getState();
if (state.updateCheckLoading || isUpdateBusy()) return;
state.setUpdateCheckLoading(true);
const { mirrorChyanSettings, proxySettings } = state;
try {
log.info('定时检查更新');
const updateResult = await checkAndPrepareDownload({
resourceId,
currentVersion,
cdk: mirrorChyanSettings.cdk || undefined,
channel: mirrorChyanSettings.channel,
userAgent: 'MXU',
githubUrl: github,
githubPat: mirrorChyanSettings.githubPat || undefined,
proxyUrl: proxySettings?.url,
projectName: name,
});
const current = useAppStore.getState();
// 只在检查参数或更新状态变化时丢弃结果,不比较配置对象引用
if (
cancelled ||
isUpdateBusy() ||
current.mirrorChyanSettings.cdk !== mirrorChyanSettings.cdk ||
current.mirrorChyanSettings.channel !== mirrorChyanSettings.channel ||
current.mirrorChyanSettings.githubPat !== mirrorChyanSettings.githubPat ||
current.proxySettings?.url !== proxySettings?.url
) {
return;
}
if (updateResult) handleAutoUpdateCheckResult(updateResult);
} catch (err) {
log.warn('定时检查更新失败:', err);
} finally {
state.setUpdateCheckLoading(false);
}
};

const [hour, minute] = scheduledUpdateCheckTime.split(':').map(Number);
let timer: ReturnType<typeof setTimeout>;
const scheduleNextCheck = () => {
const now = new Date();
const nextCheck = new Date(now);
nextCheck.setHours(hour, minute, 0, 0);
if (nextCheck.getTime() <= now.getTime()) {
nextCheck.setDate(nextCheck.getDate() + 1);
}

timer = setTimeout(() => {
void checkForScheduledUpdate();
scheduleNextCheck();
}, nextCheck.getTime() - now.getTime());
};

scheduleNextCheck();
return () => {
cancelled = true;
clearTimeout(timer);
};
}, [
loadingState,
projectInterface,
scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime,
handleAutoUpdateCheckResult,
]);

// 设置窗口标题(根据 ProjectInterface V2 协议)
useEffect(() => {
if (!projectInterface) return;
Expand Down Expand Up @@ -648,6 +772,13 @@ function App() {
// 应用配置
if (config.instances.length > 0) {
importConfig(config);
} else {
// 无实例时单独恢复定时检查偏好
const state = useAppStore.getState();
state.setScheduledUpdateCheckEnabled(config.settings.scheduledUpdateCheckEnabled === true);
state.setScheduledUpdateCheckTime(
config.settings.scheduledUpdateCheckTime ?? defaultScheduledUpdateCheckTime,
);
}

// 初始化匿名遥测(仅当 interface 声明了 telemetry.sentry.dsn 且非调试 / 开发版本)
Expand Down Expand Up @@ -1075,17 +1206,7 @@ function App() {
projectName: result.interface.name,
});
if (updateResult) {
setUpdateInfo(updateResult);
if (updateResult.hasUpdate) {
log.info(`发现新版本: ${updateResult.versionName}`);
useAppStore.getState().setShowUpdateDialog(true);
if (updateResult.downloadUrl) {
startAutoDownload(updateResult);
}
} else if (updateResult.errorCode) {
log.warn(`更新检查返回错误: code=${updateResult.errorCode}`);
useAppStore.getState().setShowUpdateDialog(true);
}
handleAutoUpdateCheckResult(updateResult);
}
} catch (err) {
log.warn('自动检查更新失败:', err);
Expand Down
36 changes: 35 additions & 1 deletion src/components/settings/UpdateSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PackageCheck,
Bug,
Network,
Clock,
} from 'lucide-react';
import clsx from 'clsx';

Expand All @@ -30,6 +31,7 @@ import { createProxySettings, proxySettingsForUpdateDownload } from '@/services/
import { resolveI18nText } from '@/services/contentResolver';
import { getInterfaceLangKey } from '@/i18n';
import { loggers } from '@/utils/logger';
import { SwitchButton, TimeInput } from '../FormControls';
import { ReleaseNotes, DownloadProgressBar } from '../UpdateInfoCard';

export function UpdateSection() {
Expand All @@ -42,6 +44,10 @@ export function UpdateSection() {
mirrorChyanSettings,
setMirrorChyanCdk,
setMirrorChyanChannel,
scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime,
setScheduledUpdateCheckEnabled,
setScheduledUpdateCheckTime,
proxySettings,
setProxySettings,
updateInfo,
Expand Down Expand Up @@ -200,7 +206,7 @@ export function UpdateSection() {
isPendingGitHubDownload ||
noUpdateInfoYet);

if (shouldTryMirrorChyan) {
if (shouldTryMirrorChyan && !useAppStore.getState().updateCheckLoading) {
if (isDownloadingFromGitHub) {
addDebugLog('检测到填入 CDK,正在停止 GitHub 下载并切换到 Mirror酱...');
await cancelDownload();
Expand Down Expand Up @@ -309,6 +315,7 @@ export function UpdateSection() {

// 检查更新
const handleCheckUpdate = async () => {
if (useAppStore.getState().updateCheckLoading) return;
if (!projectInterface?.mirrorchyan_rid || !projectInterface?.version) {
addDebugLog('未配置 mirrorchyan_rid 或 version,无法检查更新');
return;
Expand Down Expand Up @@ -415,6 +422,33 @@ export function UpdateSection() {
</div>
</div>

{/* 定时检查更新 */}
<div className="pt-4 border-t border-border space-y-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-accent" />
<span className="font-medium text-text-primary">
{t('settings.scheduledUpdateCheck')}
</span>
</div>
<SwitchButton
value={scheduledUpdateCheckEnabled}
onChange={setScheduledUpdateCheckEnabled}
/>
</div>
{scheduledUpdateCheckEnabled && (
<label className="flex items-center justify-between gap-3">
<span className="text-sm text-text-secondary">
{t('settings.scheduledUpdateCheckTime')}
</span>
<TimeInput
value={scheduledUpdateCheckTime}
onChange={setScheduledUpdateCheckTime}
/>
</label>
)}
</div>

{/* CDK 输入 */}
<div className="pt-4 border-t border-border">
<div className="flex items-center gap-3 mb-3">
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export default {
appearance: 'Appearance',
hotkeys: 'Hotkeys',
general: 'General',
scheduledUpdateCheck: 'Scheduled Update Checks',
scheduledUpdateCheckTime: 'Daily Check Time (Local Time)',
taskSettings: 'Task Settings',
taskSettingsEmpty: 'No settings available to display',
language: 'Language',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default {
appearance: '外観',
hotkeys: 'ショートカットキー',
general: '一般',
scheduledUpdateCheck: '更新の定時確認',
scheduledUpdateCheckTime: '毎日の確認時刻(現地時間)',
taskSettings: 'タスク設定',
taskSettingsEmpty: '表示できる設定項目がありません',
language: '言語',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default {
appearance: '외관',
hotkeys: '단축키',
general: '일반',
scheduledUpdateCheck: '예약 업데이트 확인',
scheduledUpdateCheckTime: '매일 확인 시간 (현지 시간)',
taskSettings: '작업 설정',
taskSettingsEmpty: '표시할 수 있는 설정 항목이 없습니다',
language: '언어',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export default {
appearance: '外观',
hotkeys: '快捷键',
general: '通用',
scheduledUpdateCheck: '定时检查更新',
scheduledUpdateCheckTime: '每日检查时间(本地时间)',
taskSettings: '任务设置',
taskSettingsEmpty: '当前没有可显示的设置项',
language: '语言',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default {
appearance: '外觀',
hotkeys: '快捷鍵',
general: '一般',
scheduledUpdateCheck: '定時檢查更新',
scheduledUpdateCheckTime: '每日檢查時間(本地時間)',
taskSettings: '任務設定',
taskSettingsEmpty: '目前沒有可顯示的設定項',
language: '語言',
Expand Down
17 changes: 17 additions & 0 deletions src/stores/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import {
DEFAULT_MAX_LOGS_PER_INSTANCE,
defaultAddTaskPanelHeight,
defaultMirrorChyanSettings,
defaultScheduledUpdateCheckTime,
defaultScreenshotFrameRate,
defaultWindowSize,
normalizeAddTaskPanelHeight,
normalizeScheduledUpdateCheckTime,
} from '@/types/config';
import type {
ActionConfig,
Expand Down Expand Up @@ -1493,6 +1495,10 @@ export const useAppStore = create<AppState>()(
confirmBeforeDelete: config.settings.confirmBeforeDelete ?? false,
maxLogsPerInstance: config.settings.maxLogsPerInstance ?? DEFAULT_MAX_LOGS_PER_INSTANCE,
autoClearLogsOnLaunch: config.settings.autoClearLogsOnLaunch ?? false,
scheduledUpdateCheckEnabled: config.settings.scheduledUpdateCheckEnabled === true,
scheduledUpdateCheckTime: normalizeScheduledUpdateCheckTime(
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
config.settings.scheduledUpdateCheckTime,
),
// 默认开启;调试 / 开发版本强制关闭
helpImproveSoftware: isTelemetryBlockedByBuild(get().projectInterface)
? false
Expand Down Expand Up @@ -2013,6 +2019,13 @@ export const useAppStore = create<AppState>()(
mirrorChyanSettings: { ...state.mirrorChyanSettings, channel },
})),

// 定时检查更新
scheduledUpdateCheckEnabled: false,
scheduledUpdateCheckTime: defaultScheduledUpdateCheckTime,
setScheduledUpdateCheckEnabled: (enabled) => set({ scheduledUpdateCheckEnabled: enabled }),
setScheduledUpdateCheckTime: (time) =>
set({ scheduledUpdateCheckTime: normalizeScheduledUpdateCheckTime(time) }),

// 代理设置
proxySettings: undefined,
setProxySettings: (settings) => set({ proxySettings: settings }),
Expand Down Expand Up @@ -2482,6 +2495,8 @@ function generateConfig(): MxuConfig {
confirmBeforeDelete: state.confirmBeforeDelete,
maxLogsPerInstance: state.maxLogsPerInstance,
autoClearLogsOnLaunch: state.autoClearLogsOnLaunch,
scheduledUpdateCheckEnabled: state.scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime: state.scheduledUpdateCheckTime,
helpImproveSoftware: state.helpImproveSoftware,
windowSize: bl?.windowSize ?? state.windowSize,
windowPosition: bl?.windowPosition ?? state.windowPosition,
Expand Down Expand Up @@ -2577,6 +2592,8 @@ useAppStore.subscribe(
autoClearLogsOnLaunch: state.autoClearLogsOnLaunch,
helpImproveSoftware: state.helpImproveSoftware,
mirrorChyanSettings: state.mirrorChyanSettings,
scheduledUpdateCheckEnabled: state.scheduledUpdateCheckEnabled,
scheduledUpdateCheckTime: state.scheduledUpdateCheckTime,
proxySettings: state.proxySettings,
welcomeShownHash: state.welcomeShownHash,
devMode: state.devMode,
Expand Down
6 changes: 6 additions & 0 deletions src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,12 @@ export interface AppState {
setMirrorChyanCdk: (cdk: string) => void;
setMirrorChyanChannel: (channel: UpdateChannel) => void;

// 定时检查更新
scheduledUpdateCheckEnabled: boolean;
scheduledUpdateCheckTime: string;
setScheduledUpdateCheckEnabled: (enabled: boolean) => void;
setScheduledUpdateCheckTime: (time: string) => void;

// 代理设置
proxySettings: ProxySettings | undefined;
setProxySettings: (settings: ProxySettings | undefined) => void;
Expand Down
Loading
Loading