feat(desktop): updater and autostart

This commit is contained in:
xxnuo
2026-01-10 17:05:14 +08:00
parent 28484c65ec
commit 6ef06ee81f
4 changed files with 148 additions and 17 deletions

View File

@@ -18,8 +18,10 @@ let locale = 'en';
let messages = getMessages(locale);
let tray = null;
const repoUrl = 'https://github.com/xxnuo/MTranServer';
const releasesUrl = 'https://github.com/xxnuo/MTranServer/releases';
let portCheckPromise = null;
let desktopLogPath = null;
let newVersionAvailable = null;
function getLocalHost(host) {
if (!host || host === '0.0.0.0') return '127.0.0.1';
@@ -38,6 +40,38 @@ function getSettingsUrl(server) {
return `http://${getLocalHost(server.host)}:${server.port}/ui/settings`;
}
function compareVersions(current, latest) {
const parseCurrent = current.replace(/^v/, '').split('.').map(Number);
const parseLatest = latest.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < Math.max(parseCurrent.length, parseLatest.length); i++) {
const a = parseCurrent[i] || 0;
const b = parseLatest[i] || 0;
if (a < b) return -1;
if (a > b) return 1;
}
return 0;
}
async function checkForUpdate() {
try {
const response = await fetch(releasesUrl, {
headers: { 'Accept': 'text/html' },
redirect: 'follow',
});
if (!response.ok) return;
const html = await response.text();
const match = html.match(/\/xxnuo\/MTranServer\/releases\/tag\/v?([\d.]+)/);
if (!match) return;
const latestVersion = match[1];
const currentVersion = app.getVersion();
if (compareVersions(currentVersion, latestVersion) < 0) {
newVersionAvailable = latestVersion;
updateTray();
}
} catch {
}
}
function getStatusLabel() {
const status = getServerStatus();
if (status === 'running') {
@@ -76,6 +110,8 @@ function updateTray() {
messages,
statusLabel: getStatusLabel(),
versionLabel: app.getVersion(),
newVersionLabel: newVersionAvailable,
autoStartEnabled: app.getLoginItemSettings().openAtLogin,
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
onOpenRepo: () => shell.openExternal(repoUrl),
@@ -83,10 +119,18 @@ function updateTray() {
onRestart: restartServer,
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
onToggleAutoStart: toggleAutoStart,
onOpenReleasePage: () => shell.openExternal(releasesUrl),
onQuit: quitApp
});
}
function toggleAutoStart() {
const current = app.getLoginItemSettings().openAtLogin;
app.setLoginItemSettings({ openAtLogin: !current });
updateTray();
}
async function logDesktop(message, error) {
if (!desktopLogPath) return;
const timestamp = new Date().toISOString();
@@ -244,6 +288,8 @@ export async function startDesktop() {
messages,
statusLabel: getStatusLabel(),
versionLabel: app.getVersion(),
newVersionLabel: newVersionAvailable,
autoStartEnabled: app.getLoginItemSettings().openAtLogin,
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
onOpenRepo: () => shell.openExternal(repoUrl),
@@ -251,6 +297,8 @@ export async function startDesktop() {
onRestart: restartServer,
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
onToggleAutoStart: toggleAutoStart,
onOpenReleasePage: () => shell.openExternal(releasesUrl),
onQuit: quitApp
});
@@ -260,6 +308,8 @@ export async function startDesktop() {
return;
}
checkForUpdate();
const getConfigResponse = async () => buildDesktopResponse();
const applyConfig = async (config) => {
desktopConfig = await saveDesktopConfig(config);

View File

@@ -76,28 +76,78 @@ function normalizeConfig(input) {
};
}
function getDiffConfig(config) {
const defaults = getDefaultDesktopConfig();
const diff = {};
if (config.locale !== defaults.locale) {
diff.locale = config.locale;
}
const serverDiff = {};
const serverKeys = Object.keys(defaults.server);
for (const key of serverKeys) {
if (config.server[key] !== defaults.server[key]) {
serverDiff[key] = config.server[key];
}
}
if (Object.keys(serverDiff).length > 0) {
diff.server = serverDiff;
}
return diff;
}
function isConfigDefault(config) {
const diff = getDiffConfig(config);
return Object.keys(diff).length === 0;
}
export async function loadDesktopConfig() {
try {
const raw = await fsPromises.readFile(desktopConfigPath, 'utf8');
const parsed = YAML.parse(raw);
return normalizeConfig(parsed);
} catch (error) {
const defaults = getDefaultDesktopConfig();
await saveDesktopConfig(defaults);
return defaults;
const normalized = normalizeConfig(parsed);
const diff = getDiffConfig(normalized);
if (Object.keys(diff).length === 0) {
try {
await fsPromises.unlink(desktopConfigPath);
} catch {
}
} else {
const currentDiff = getDiffConfig(normalizeConfig(parsed));
const parsedKeys = Object.keys(parsed?.server || {});
const defaultKeys = Object.keys(getDefaultDesktopConfig().server);
const hasRemovedKeys = parsedKeys.some(k => !defaultKeys.includes(k));
if (hasRemovedKeys) {
await fsPromises.mkdir(serverConfigDir, { recursive: true });
const data = YAML.stringify(diff);
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
}
}
return normalized;
} catch {
return getDefaultDesktopConfig();
}
}
export async function saveDesktopConfig(config) {
const normalized = normalizeConfig(config);
await fsPromises.mkdir(serverConfigDir, { recursive: true });
const data = YAML.stringify(normalized);
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
const diff = getDiffConfig(normalized);
if (Object.keys(diff).length === 0) {
try {
await fsPromises.unlink(desktopConfigPath);
} catch {
}
} else {
await fsPromises.mkdir(serverConfigDir, { recursive: true });
const data = YAML.stringify(diff);
await fsPromises.writeFile(desktopConfigPath, data, 'utf8');
}
return normalized;
}
export async function resetDesktopConfig() {
const defaults = getDefaultDesktopConfig();
await saveDesktopConfig(defaults);
return defaults;
try {
await fsPromises.unlink(desktopConfigPath);
} catch {
}
return getDefaultDesktopConfig();
}

View File

@@ -15,6 +15,8 @@ const messages = {
trayOpenModels: 'Open Model Folder',
trayOpenConfig: 'Open Config Folder',
trayVersion: 'Version',
trayNewVersion: 'New version available: v{version}',
trayAutoStart: 'Start at Login',
trayQuit: 'Quit',
appName: 'MTranServer',
portInUseTitle: 'Port is in use',
@@ -41,6 +43,8 @@ const messages = {
trayOpenModels: '打开模型目录',
trayOpenConfig: '打开配置目录',
trayVersion: '版本号',
trayNewVersion: '发现新版本: v{version}',
trayAutoStart: '开机启动',
trayQuit: '退出程序',
appName: 'MTranServer',
portInUseTitle: '端口被占用',
@@ -67,6 +71,8 @@ const messages = {
trayOpenModels: 'モデルフォルダを開く',
trayOpenConfig: '設定フォルダを開く',
trayVersion: 'バージョン',
trayNewVersion: '新しいバージョンがあります: v{version}',
trayAutoStart: 'ログイン時に起動',
trayQuit: '終了',
appName: 'MTranServer',
portInUseTitle: 'ポートが使用中です',

View File

@@ -8,6 +8,8 @@ export function createTray({
messages,
statusLabel,
versionLabel,
newVersionLabel,
autoStartEnabled,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -15,6 +17,8 @@ export function createTray({
onRestart,
onOpenModels,
onOpenConfig,
onToggleAutoStart,
onOpenReleasePage,
onQuit
}) {
if (tray) return tray;
@@ -25,6 +29,8 @@ export function createTray({
messages,
statusLabel,
versionLabel,
newVersionLabel,
autoStartEnabled,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -32,6 +38,8 @@ export function createTray({
onRestart,
onOpenModels,
onOpenConfig,
onToggleAutoStart,
onOpenReleasePage,
onQuit
});
return tray;
@@ -41,6 +49,8 @@ export function updateTrayMenu({
messages,
statusLabel,
versionLabel,
newVersionLabel,
autoStartEnabled,
onOpenBrowserUi,
onOpenBrowserDocs,
onOpenRepo,
@@ -48,10 +58,12 @@ export function updateTrayMenu({
onRestart,
onOpenModels,
onOpenConfig,
onToggleAutoStart,
onOpenReleasePage,
onQuit
}) {
if (!tray) return;
const contextMenu = Menu.buildFromTemplate([
const menuItems = [
{
label: messages.trayOpenUi,
click: onOpenBrowserUi
@@ -76,6 +88,12 @@ export function updateTrayMenu({
{ label: messages.trayRestart, click: onRestart }
]
},
{
label: messages.trayAutoStart,
type: 'checkbox',
checked: autoStartEnabled,
click: onToggleAutoStart
},
{ type: 'separator' },
{
label: messages.trayData,
@@ -87,11 +105,18 @@ export function updateTrayMenu({
{
label: `${messages.trayVersion}: ${versionLabel}`,
enabled: false
},
{
label: messages.trayQuit,
click: onQuit
}
]);
];
if (newVersionLabel) {
menuItems.push({
label: messages.trayNewVersion.replace('{version}', newVersionLabel),
click: onOpenReleasePage
});
}
menuItems.push({
label: messages.trayQuit,
click: onQuit
});
const contextMenu = Menu.buildFromTemplate(menuItems);
tray.setContextMenu(contextMenu);
}