mirror of
https://github.com/xxnuo/MTranServer.git
synced 2026-09-03 06:35:20 +08:00
feat(desktop): simplify
This commit is contained in:
121
desktop/app.js
121
desktop/app.js
@@ -1,4 +1,4 @@
|
||||
import { app, dialog, nativeImage, shell, Menu } from 'electron';
|
||||
import { app, dialog, nativeImage, shell, Menu, screen } from 'electron';
|
||||
import fs from 'fs/promises';
|
||||
import fsSync from 'fs';
|
||||
import path from 'path';
|
||||
@@ -7,13 +7,6 @@ import { loadDesktopConfig, saveDesktopConfig, resetDesktopConfig, getDefaultDes
|
||||
import { resolveLocale, getMessages } from './i18n.js';
|
||||
import { getFreePort, isPortAvailable } from './ports.js';
|
||||
import { getServerStatus, startServerWithConfig, stopServerInstance } from './server.js';
|
||||
import {
|
||||
createMainWindow,
|
||||
createSettingsWindow,
|
||||
showMainWindow,
|
||||
showSettingsWindow,
|
||||
updateWindowUrls
|
||||
} from './windows.js';
|
||||
import { createTray, updateTrayMenu } from './tray.js';
|
||||
import { registerIpcHandlers } from './ipc.js';
|
||||
|
||||
@@ -83,11 +76,10 @@ function updateTray() {
|
||||
messages,
|
||||
statusLabel: getStatusLabel(),
|
||||
versionLabel: app.getVersion(),
|
||||
onOpenMain: showMainWindow,
|
||||
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
|
||||
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
|
||||
onOpenRepo: () => shell.openExternal(repoUrl),
|
||||
onOpenSettings: showSettingsWindow,
|
||||
onOpenSettings: () => shell.openExternal(getSettingsUrl(desktopConfig.server)),
|
||||
onRestart: restartServer,
|
||||
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
|
||||
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
|
||||
@@ -167,10 +159,6 @@ async function restartServer() {
|
||||
await ensureWritableDirs();
|
||||
const ok = await startServer();
|
||||
if (!ok) return false;
|
||||
updateWindowUrls({
|
||||
mainUrl: getUiUrl(desktopConfig.server),
|
||||
settingsUrl: getSettingsUrl(desktopConfig.server)
|
||||
});
|
||||
updateTray();
|
||||
return true;
|
||||
} catch {
|
||||
@@ -232,40 +220,16 @@ async function ensureWritableDirs() {
|
||||
}
|
||||
}
|
||||
|
||||
function getLoadingUrl() {
|
||||
const html = `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${messages.appName}</title>
|
||||
<style>
|
||||
html,body{margin:0;height:100%;font-family:system-ui,Segoe UI,Arial,sans-serif;background:#f5f5f5;color:#1f2937}
|
||||
.wrap{height:100%;display:flex;align-items:center;justify-content:center}
|
||||
.card{padding:24px 32px;border:1px solid #e5e7eb;border-radius:16px;background:#fff;box-shadow:0 12px 30px rgba(0,0,0,0.08);text-align:center;min-width:260px}
|
||||
.title{font-size:18px;font-weight:600;margin-bottom:8px}
|
||||
.desc{font-size:14px;color:#6b7280}
|
||||
.dot{display:inline-block;animation:blink 1.2s infinite}
|
||||
@keyframes blink{0%,100%{opacity:.2}50%{opacity:1}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="card">
|
||||
<div class="title">${messages.appName}</div>
|
||||
<div class="desc">${messages.trayServiceStatus}: ${messages.trayServiceRunning} <span class="dot">...</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
||||
function buildDesktopResponse() {
|
||||
return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
}
|
||||
|
||||
export async function startDesktop() {
|
||||
app.isQuitting = false;
|
||||
Menu.setApplicationMenu(null);
|
||||
if (app.dock?.hide) {
|
||||
app.dock.hide();
|
||||
}
|
||||
desktopConfig = await loadDesktopConfig();
|
||||
updateLocale(desktopConfig.locale);
|
||||
desktopLogPath = path.join(desktopConfig.server.configDir, 'desktop.log');
|
||||
@@ -273,12 +237,6 @@ export async function startDesktop() {
|
||||
await logDesktop('desktop starting');
|
||||
await ensureWritableDirs();
|
||||
|
||||
const preloadPath = path.join(__dirname, 'preload.cjs');
|
||||
const loadingUrl = getLoadingUrl();
|
||||
const mainWindow = createMainWindow({ url: loadingUrl, preload: preloadPath });
|
||||
createSettingsWindow({ url: loadingUrl, preload: preloadPath, parent: mainWindow });
|
||||
mainWindow.once('ready-to-show', () => mainWindow.show());
|
||||
|
||||
const trayIcon = getTrayIcon();
|
||||
tray = createTray({
|
||||
icon: trayIcon,
|
||||
@@ -286,11 +244,10 @@ export async function startDesktop() {
|
||||
messages,
|
||||
statusLabel: getStatusLabel(),
|
||||
versionLabel: app.getVersion(),
|
||||
onOpenMain: showMainWindow,
|
||||
onOpenBrowserUi: () => shell.openExternal(getUiUrl(desktopConfig.server)),
|
||||
onOpenBrowserDocs: () => shell.openExternal(getDocsUrl(desktopConfig.server)),
|
||||
onOpenRepo: () => shell.openExternal(repoUrl),
|
||||
onOpenSettings: showSettingsWindow,
|
||||
onOpenSettings: () => shell.openExternal(getSettingsUrl(desktopConfig.server)),
|
||||
onRestart: restartServer,
|
||||
onOpenModels: () => shell.openPath(desktopConfig.server.modelDir),
|
||||
onOpenConfig: () => shell.openPath(desktopConfig.server.configDir),
|
||||
@@ -303,52 +260,52 @@ export async function startDesktop() {
|
||||
return;
|
||||
}
|
||||
|
||||
updateWindowUrls({
|
||||
mainUrl: getUiUrl(desktopConfig.server),
|
||||
settingsUrl: getSettingsUrl(desktopConfig.server)
|
||||
});
|
||||
const getConfigResponse = async () => buildDesktopResponse();
|
||||
const applyConfig = async (config) => {
|
||||
desktopConfig = await saveDesktopConfig(config);
|
||||
updateLocale(desktopConfig.locale);
|
||||
const ok = await restartServer();
|
||||
if (!ok) return buildDesktopResponse();
|
||||
return buildDesktopResponse();
|
||||
};
|
||||
const resetConfig = async () => {
|
||||
desktopConfig = await resetDesktopConfig();
|
||||
updateLocale(desktopConfig.locale);
|
||||
const ok = await restartServer();
|
||||
if (!ok) return buildDesktopResponse();
|
||||
return buildDesktopResponse();
|
||||
};
|
||||
const restartAndRespond = async () => {
|
||||
const ok = await restartServer();
|
||||
if (!ok) return buildDesktopResponse();
|
||||
return buildDesktopResponse();
|
||||
};
|
||||
|
||||
globalThis.mtranDesktopControl = {
|
||||
getConfig: getConfigResponse,
|
||||
applyConfig,
|
||||
resetConfig,
|
||||
restartServer: restartAndRespond
|
||||
};
|
||||
|
||||
registerIpcHandlers({
|
||||
getConfig: async () => ({
|
||||
config: desktopConfig,
|
||||
status: getServerStatus(),
|
||||
version: app.getVersion()
|
||||
}),
|
||||
applyConfig: async (config) => {
|
||||
desktopConfig = await saveDesktopConfig(config);
|
||||
updateLocale(desktopConfig.locale);
|
||||
const ok = await restartServer();
|
||||
if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
},
|
||||
resetConfig: async () => {
|
||||
desktopConfig = await resetDesktopConfig();
|
||||
updateLocale(desktopConfig.locale);
|
||||
const ok = await restartServer();
|
||||
if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
},
|
||||
restartServer: async () => {
|
||||
const ok = await restartServer();
|
||||
if (!ok) return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
return { config: desktopConfig, status: getServerStatus(), version: app.getVersion() };
|
||||
},
|
||||
getConfig: getConfigResponse,
|
||||
applyConfig,
|
||||
resetConfig,
|
||||
restartServer: restartAndRespond,
|
||||
getStatus: async () => ({ status: getServerStatus() }),
|
||||
openExternal: async (url) => shell.openExternal(url),
|
||||
openPath: async (targetPath) => shell.openPath(targetPath)
|
||||
});
|
||||
|
||||
app.on('activate', () => showMainWindow());
|
||||
app.on('before-quit', (event) => {
|
||||
if (app.isQuitting) return;
|
||||
event.preventDefault();
|
||||
quitApp();
|
||||
});
|
||||
app.on('window-all-closed', (event) => event.preventDefault());
|
||||
|
||||
updateTray();
|
||||
}
|
||||
|
||||
export function focusMainWindow() {
|
||||
showMainWindow();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ export function createTray({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
onOpenMain,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -21,12 +20,11 @@ export function createTray({
|
||||
if (tray) return tray;
|
||||
tray = new Tray(icon);
|
||||
tray.setToolTip(tooltip);
|
||||
tray.on('click', () => onOpenMain());
|
||||
tray.on('click', () => tray.popUpContextMenu());
|
||||
updateTrayMenu({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
onOpenMain,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -43,7 +41,6 @@ export function updateTrayMenu({
|
||||
messages,
|
||||
statusLabel,
|
||||
versionLabel,
|
||||
onOpenMain,
|
||||
onOpenBrowserUi,
|
||||
onOpenBrowserDocs,
|
||||
onOpenRepo,
|
||||
@@ -56,16 +53,16 @@ export function updateTrayMenu({
|
||||
if (!tray) return;
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: messages.trayOpenMain,
|
||||
click: onOpenMain
|
||||
label: messages.trayOpenUi,
|
||||
click: onOpenBrowserUi
|
||||
},
|
||||
{
|
||||
label: messages.trayOpenBrowser,
|
||||
submenu: [
|
||||
{ label: messages.trayOpenUi, click: onOpenBrowserUi },
|
||||
{ label: messages.trayOpenDocs, click: onOpenBrowserDocs },
|
||||
{ label: messages.trayOpenRepo, click: onOpenRepo }
|
||||
]
|
||||
label: messages.trayOpenDocs,
|
||||
click: onOpenBrowserDocs
|
||||
},
|
||||
{
|
||||
label: messages.trayOpenRepo,
|
||||
click: onOpenRepo
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
|
||||
@@ -24,6 +24,27 @@ export interface Config {
|
||||
}
|
||||
|
||||
let globalConfig: Config | null = null;
|
||||
let fileConfigCache: Partial<Config> | null = null;
|
||||
|
||||
function getConfigFilePath(homeDir: string) {
|
||||
return path.join(homeDir, 'server.json');
|
||||
}
|
||||
|
||||
function readConfigFile(homeDir: string): Partial<Config> {
|
||||
if (fileConfigCache) return fileConfigCache;
|
||||
const configPath = getConfigFilePath(homeDir);
|
||||
try {
|
||||
const raw = fs.readFileSync(configPath, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
fileConfigCache = parsed as Partial<Config>;
|
||||
return fileConfigCache;
|
||||
}
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function getArgValue(flag: string): string | null {
|
||||
const index = process.argv.indexOf(flag);
|
||||
@@ -88,12 +109,13 @@ export function getConfig(): Config {
|
||||
}
|
||||
|
||||
const homeDir = path.join(os.homedir(), '.config', 'mtran');
|
||||
const fileConfig = readConfigFile(homeDir);
|
||||
|
||||
const configDir = getString('--config-dir', 'MT_CONFIG_DIR', path.join(homeDir, 'server'));
|
||||
const configDir = getString('--config-dir', 'MT_CONFIG_DIR', fileConfig.configDir || path.join(homeDir, 'server'));
|
||||
const localModelsDir = path.join(process.cwd(), 'models');
|
||||
const defaultModelDir = fs.existsSync(localModelsDir) ? localModelsDir : path.join(homeDir, 'models');
|
||||
const modelDir = getString('--model-dir', 'MT_MODEL_DIR', defaultModelDir);
|
||||
const logDir = getString('--log-dir', 'MT_LOG_DIR', path.join(homeDir, 'logs'));
|
||||
const modelDir = getString('--model-dir', 'MT_MODEL_DIR', fileConfig.modelDir || defaultModelDir);
|
||||
const logDir = getString('--log-dir', 'MT_LOG_DIR', fileConfig.logDir || path.join(homeDir, 'logs'));
|
||||
|
||||
globalConfig = {
|
||||
homeDir,
|
||||
@@ -101,26 +123,26 @@ export function getConfig(): Config {
|
||||
modelDir,
|
||||
logDir,
|
||||
|
||||
logLevel: getString('--log-level', 'MT_LOG_LEVEL', 'warn'),
|
||||
host: getString('--host', 'MT_HOST', '0.0.0.0'),
|
||||
port: getString('--port', 'MT_PORT', '8989'),
|
||||
logLevel: getString('--log-level', 'MT_LOG_LEVEL', fileConfig.logLevel || 'warn'),
|
||||
host: getString('--host', 'MT_HOST', fileConfig.host || '0.0.0.0'),
|
||||
port: getString('--port', 'MT_PORT', fileConfig.port || '8989'),
|
||||
|
||||
enableWebUI: getBool('--ui', 'MT_ENABLE_UI', true),
|
||||
enableOfflineMode: getBool('--offline', 'MT_OFFLINE', false),
|
||||
enableWebUI: getBool('--ui', 'MT_ENABLE_UI', fileConfig.enableWebUI ?? true),
|
||||
enableOfflineMode: getBool('--offline', 'MT_OFFLINE', fileConfig.enableOfflineMode ?? false),
|
||||
|
||||
workerIdleTimeout: getInt('--worker-idle-timeout', 'MT_WORKER_IDLE_TIMEOUT', 60),
|
||||
workersPerLanguage: getInt('--workers-per-language', 'MT_WORKERS_PER_LANGUAGE', 1),
|
||||
maxLengthBreak: getInt('--max-length-break', 'MT_MAX_LENGTH_BREAK', 128),
|
||||
workerIdleTimeout: getInt('--worker-idle-timeout', 'MT_WORKER_IDLE_TIMEOUT', fileConfig.workerIdleTimeout ?? 60),
|
||||
workersPerLanguage: getInt('--workers-per-language', 'MT_WORKERS_PER_LANGUAGE', fileConfig.workersPerLanguage ?? 1),
|
||||
maxLengthBreak: getInt('--max-length-break', 'MT_MAX_LENGTH_BREAK', fileConfig.maxLengthBreak ?? 128),
|
||||
|
||||
apiToken: getString('--api-token', 'MT_API_TOKEN', ''),
|
||||
apiToken: getString('--api-token', 'MT_API_TOKEN', fileConfig.apiToken || ''),
|
||||
|
||||
logToFile: getBool('--log-to-file', 'MT_LOG_TO_FILE', false),
|
||||
logConsole: getBool('--log-console', 'MT_LOG_CONSOLE', true),
|
||||
logRequests: getBool('--log-requests', 'MT_LOG_REQUESTS', false),
|
||||
logToFile: getBool('--log-to-file', 'MT_LOG_TO_FILE', fileConfig.logToFile ?? false),
|
||||
logConsole: getBool('--log-console', 'MT_LOG_CONSOLE', fileConfig.logConsole ?? true),
|
||||
logRequests: getBool('--log-requests', 'MT_LOG_REQUESTS', fileConfig.logRequests ?? false),
|
||||
|
||||
checkUpdate: getBool('--check-update', 'MT_CHECK_UPDATE', true),
|
||||
checkUpdate: getBool('--check-update', 'MT_CHECK_UPDATE', fileConfig.checkUpdate ?? true),
|
||||
|
||||
cacheSize: getInt('--cache-size', 'MT_CACHE_SIZE', 1000),
|
||||
cacheSize: getInt('--cache-size', 'MT_CACHE_SIZE', fileConfig.cacheSize ?? 1000),
|
||||
};
|
||||
|
||||
return globalConfig;
|
||||
@@ -133,4 +155,26 @@ export function setConfig(config: Partial<Config>) {
|
||||
|
||||
export function resetConfig() {
|
||||
globalConfig = null;
|
||||
fileConfigCache = null;
|
||||
}
|
||||
|
||||
export function saveConfigFile(config: Partial<Config>) {
|
||||
const current = getConfig();
|
||||
const next = { ...current, ...config };
|
||||
const configPath = getConfigFilePath(current.homeDir);
|
||||
fs.mkdirSync(current.homeDir, { recursive: true });
|
||||
fs.writeFileSync(configPath, JSON.stringify(next, null, 2), 'utf8');
|
||||
fileConfigCache = next;
|
||||
}
|
||||
|
||||
export function clearConfigFile() {
|
||||
const homeDir = path.join(os.homedir(), '.config', 'mtran');
|
||||
const configPath = getConfigFilePath(homeDir);
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
fs.unlinkSync(configPath);
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import fs from 'fs/promises';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import { getConfig } from '@/config/index.js';
|
||||
import { getConfig, setConfig, resetConfig, saveConfigFile, clearConfigFile } from '@/config/index.js';
|
||||
import * as logger from '@/logger/index.js';
|
||||
import { initRecords } from '@/models/index.js';
|
||||
import { cleanupAllEngines } from '@/services/index.js';
|
||||
@@ -36,6 +36,119 @@ export async function startServer({ handleSignals = true } = {}) {
|
||||
app.use(requestLogger());
|
||||
}
|
||||
|
||||
const getDesktopControl = () => (globalThis as any).mtranDesktopControl;
|
||||
const getSettingsPayload = () => {
|
||||
const current = getConfig();
|
||||
return {
|
||||
config: {
|
||||
locale: 'system',
|
||||
server: {
|
||||
host: current.host,
|
||||
port: Number(current.port),
|
||||
logLevel: current.logLevel,
|
||||
enableWebUI: current.enableWebUI,
|
||||
enableOfflineMode: current.enableOfflineMode,
|
||||
workerIdleTimeout: current.workerIdleTimeout,
|
||||
workersPerLanguage: current.workersPerLanguage,
|
||||
apiToken: current.apiToken,
|
||||
logDir: current.logDir,
|
||||
logToFile: current.logToFile,
|
||||
logConsole: current.logConsole,
|
||||
logRequests: current.logRequests,
|
||||
maxLengthBreak: current.maxLengthBreak,
|
||||
checkUpdate: current.checkUpdate,
|
||||
cacheSize: current.cacheSize,
|
||||
modelDir: current.modelDir,
|
||||
configDir: current.configDir
|
||||
}
|
||||
},
|
||||
status: 'running',
|
||||
version: VERSION
|
||||
};
|
||||
};
|
||||
const toNumber = (value: unknown, fallback: number) => {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
};
|
||||
const toBool = (value: unknown, fallback: boolean) => {
|
||||
if (typeof value === 'boolean') return value;
|
||||
return fallback;
|
||||
};
|
||||
const toString = (value: unknown, fallback: string) => {
|
||||
if (typeof value === 'string') return value;
|
||||
return fallback;
|
||||
};
|
||||
const applyServerConfig = (input: any) => {
|
||||
const current = getConfig();
|
||||
const next = {
|
||||
host: toString(input.host, current.host),
|
||||
port: String(toNumber(input.port, Number(current.port))),
|
||||
logLevel: toString(input.logLevel, current.logLevel),
|
||||
enableWebUI: toBool(input.enableWebUI, current.enableWebUI),
|
||||
enableOfflineMode: toBool(input.enableOfflineMode, current.enableOfflineMode),
|
||||
workerIdleTimeout: toNumber(input.workerIdleTimeout, current.workerIdleTimeout),
|
||||
workersPerLanguage: toNumber(input.workersPerLanguage, current.workersPerLanguage),
|
||||
apiToken: toString(input.apiToken, current.apiToken),
|
||||
logDir: toString(input.logDir, current.logDir),
|
||||
logToFile: toBool(input.logToFile, current.logToFile),
|
||||
logConsole: toBool(input.logConsole, current.logConsole),
|
||||
logRequests: toBool(input.logRequests, current.logRequests),
|
||||
maxLengthBreak: toNumber(input.maxLengthBreak, current.maxLengthBreak),
|
||||
checkUpdate: toBool(input.checkUpdate, current.checkUpdate),
|
||||
cacheSize: toNumber(input.cacheSize, current.cacheSize),
|
||||
modelDir: toString(input.modelDir, current.modelDir),
|
||||
configDir: toString(input.configDir, current.configDir)
|
||||
};
|
||||
setConfig(next);
|
||||
saveConfigFile(next);
|
||||
logger.setLogLevel(next.logLevel as any);
|
||||
};
|
||||
|
||||
app.get('/ui/api/settings', async (_, res) => {
|
||||
const control = getDesktopControl();
|
||||
if (control?.getConfig) {
|
||||
const payload = await control.getConfig();
|
||||
res.json(payload);
|
||||
return;
|
||||
}
|
||||
res.json(getSettingsPayload());
|
||||
});
|
||||
|
||||
app.post('/ui/api/settings/apply', async (req, res) => {
|
||||
const control = getDesktopControl();
|
||||
if (control?.applyConfig) {
|
||||
const payload = await control.applyConfig(req.body?.config || req.body);
|
||||
res.json(payload);
|
||||
return;
|
||||
}
|
||||
const input = req.body?.config?.server || req.body?.server || {};
|
||||
applyServerConfig(input);
|
||||
res.json(getSettingsPayload());
|
||||
});
|
||||
|
||||
app.post('/ui/api/settings/reset', async (_, res) => {
|
||||
const control = getDesktopControl();
|
||||
if (control?.resetConfig) {
|
||||
const payload = await control.resetConfig();
|
||||
res.json(payload);
|
||||
return;
|
||||
}
|
||||
clearConfigFile();
|
||||
resetConfig();
|
||||
res.json(getSettingsPayload());
|
||||
});
|
||||
|
||||
app.post('/ui/api/settings/restart', async (_, res) => {
|
||||
const control = getDesktopControl();
|
||||
if (control?.restartServer) {
|
||||
const payload = await control.restartServer();
|
||||
res.json(payload);
|
||||
return;
|
||||
}
|
||||
res.json(getSettingsPayload());
|
||||
});
|
||||
|
||||
RegisterRoutes(app);
|
||||
|
||||
app.use('/ui', (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@@ -6,14 +6,17 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
applyDesktopConfig,
|
||||
getDesktopConfig,
|
||||
isDesktop,
|
||||
resetDesktopConfig,
|
||||
restartDesktopServer
|
||||
restartDesktopServer,
|
||||
applyWebConfig,
|
||||
getWebConfig,
|
||||
resetWebConfig,
|
||||
restartWebServer
|
||||
} from '@/lib/desktop'
|
||||
import type { DesktopConfig } from '@/lib/desktop'
|
||||
|
||||
@@ -73,6 +76,10 @@ function toNumber(value: string, fallback: number) {
|
||||
export function DesktopSettingsPage() {
|
||||
const { t } = useTranslation()
|
||||
const desktopAvailable = isDesktop()
|
||||
const getConfigData = desktopAvailable ? getDesktopConfig : getWebConfig
|
||||
const applyConfig = desktopAvailable ? applyDesktopConfig : applyWebConfig
|
||||
const resetConfig = desktopAvailable ? resetDesktopConfig : resetWebConfig
|
||||
const restartServer = desktopAvailable ? restartDesktopServer : restartWebServer
|
||||
const [config, setConfig] = useState<DesktopConfig | null>(null)
|
||||
const [form, setForm] = useState<FormState | null>(null)
|
||||
const [status, setStatus] = useState('')
|
||||
@@ -81,11 +88,7 @@ export function DesktopSettingsPage() {
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!desktopAvailable) {
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
getDesktopConfig()
|
||||
getConfigData()
|
||||
.then((response) => {
|
||||
if (!response) return
|
||||
setConfig(response.config)
|
||||
@@ -97,7 +100,7 @@ export function DesktopSettingsPage() {
|
||||
toast.error(t('loadingConfigFailed'))
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [desktopAvailable, t])
|
||||
}, [getConfigData, t])
|
||||
|
||||
const serverStatusLabel = useMemo(() => {
|
||||
if (!status) return ''
|
||||
@@ -131,7 +134,7 @@ export function DesktopSettingsPage() {
|
||||
}
|
||||
}
|
||||
try {
|
||||
const response = await applyDesktopConfig(nextConfig)
|
||||
const response = await applyConfig(nextConfig)
|
||||
if (response) {
|
||||
setConfig(response.config)
|
||||
setForm(toForm(response.config))
|
||||
@@ -147,10 +150,9 @@ export function DesktopSettingsPage() {
|
||||
}
|
||||
|
||||
const handleRestart = async () => {
|
||||
if (!desktopAvailable) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const response = await restartDesktopServer()
|
||||
const response = await restartServer()
|
||||
if (response) {
|
||||
setConfig(response.config)
|
||||
setForm(toForm(response.config))
|
||||
@@ -166,10 +168,9 @@ export function DesktopSettingsPage() {
|
||||
}
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!desktopAvailable) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const response = await resetDesktopConfig()
|
||||
const response = await resetConfig()
|
||||
if (response) {
|
||||
setConfig(response.config)
|
||||
setForm(toForm(response.config))
|
||||
@@ -184,22 +185,6 @@ export function DesktopSettingsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!desktopAvailable) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-6">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<Alert>
|
||||
<AlertTitle>{t('desktopOnly')}</AlertTitle>
|
||||
<AlertDescription>{t('desktopOnlyDesc')}</AlertDescription>
|
||||
</Alert>
|
||||
<div className="mt-6">
|
||||
<Button onClick={() => (window.location.href = '/ui/')}>{t('backToMain')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading || !form || !config) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-6">
|
||||
@@ -250,20 +235,22 @@ export function DesktopSettingsPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{t('locale')}</label>
|
||||
<Select value={form.locale} onValueChange={(value) => setForm({ ...form, locale: value })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">{t('system')}</SelectItem>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
<SelectItem value="zh">中文</SelectItem>
|
||||
<SelectItem value="ja">日本語</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{desktopAvailable && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{t('locale')}</label>
|
||||
<Select value={form.locale} onValueChange={(value) => setForm({ ...form, locale: value })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">{t('system')}</SelectItem>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
<SelectItem value="zh">中文</SelectItem>
|
||||
<SelectItem value="ja">日本語</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
|
||||
@@ -50,3 +50,31 @@ export async function restartDesktopServer() {
|
||||
if (!window.mtranDesktop) return null
|
||||
return window.mtranDesktop.restartServer()
|
||||
}
|
||||
|
||||
async function fetchSettings(path: string, body?: unknown) {
|
||||
const res = await fetch(`/ui/api/settings${path}`, {
|
||||
method: body ? 'POST' : 'GET',
|
||||
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`Request failed: ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function getWebConfig() {
|
||||
return fetchSettings('')
|
||||
}
|
||||
|
||||
export async function applyWebConfig(config: DesktopConfig) {
|
||||
return fetchSettings('/apply', { config })
|
||||
}
|
||||
|
||||
export async function resetWebConfig() {
|
||||
return fetchSettings('/reset', {})
|
||||
}
|
||||
|
||||
export async function restartWebServer() {
|
||||
return fetchSettings('/restart', {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user