feat: add Electron desktop client with Windows installer support

This commit is contained in:
YuanShiJiLoong committed 2026-02-23 20:57:44 +09:00
1 parent 3fb7e90b05
commit 3405a24531
11 files changed
+4337 -5

No files matched your search

+3
View File
@@ -39,3 +39,6 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# electron
/dist
+2
View File
@@ -0,0 +1,2 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
+12
View File
@@ -66,6 +66,18 @@ I watched the versatility of these models being gutted. I don't want us to live
--- ---
## 💻 Desktop Client
**No Node.js required!** Download the pre-built installer:
- 📥 [Download Author Setup (Windows)](https://github.com/YuanShiJiLoong/author/releases/latest)
Just install and start writing. All features work out of the box.
> 💡 To build the desktop app from source: `npm run build && npx electron-builder --win`
---
## 🚀 Getting Started ## 🚀 Getting Started
### Requirements ### Requirements
+13
View File
@@ -66,6 +66,19 @@
--- ---
## 💻 桌面客户端
**无需安装 Node.js!** 直接下载安装包:
- 📥 [下载 Author 安装包(Windows)](https://github.com/YuanShiJiLoong/author/releases/latest)
- 📥 [国内下载(Gitee)](https://gitee.com/yuanshijilong/author/releases)
安装即用,所有功能开箱即得。
> 💡 从源码构建桌面应用:`npm run build && npx electron-builder --win`
---
## 🚀 快速开始 ## 🚀 快速开始
### 环境要求 ### 环境要求
+139
View File
@@ -0,0 +1,139 @@
const { app, BrowserWindow, shell } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
let mainWindow;
let nextServer;
const isDev = process.argv.includes('--dev');
const PORT = 3000;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minWidth: 900,
minHeight: 600,
title: 'Author — AI-Powered Creative Writing',
icon: path.join(__dirname, '..', 'public', 'icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
// 隐藏默认菜单栏,更简洁
autoHideMenuBar: true,
show: false,
});
mainWindow.loadURL(`http://localhost:${PORT}`);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// 外部链接在系统浏览器中打开
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('http')) {
shell.openExternal(url);
return { action: 'deny' };
}
return { action: 'allow' };
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function startNextServer() {
return new Promise((resolve, reject) => {
if (isDev) {
// 开发模式:假设 dev server 已在运行
console.log('[Author] Dev mode — connecting to existing dev server...');
resolve();
return;
}
console.log('[Author] Starting production server...');
// 生产模式:运行 next start
const nextBin = path.join(__dirname, '..', 'node_modules', '.bin', 'next');
const projectDir = path.join(__dirname, '..');
nextServer = spawn(
process.platform === 'win32' ? `${nextBin}.cmd` : nextBin,
['start', '-p', String(PORT)],
{
cwd: projectDir,
env: { ...process.env, NODE_ENV: 'production' },
stdio: 'pipe',
shell: true,
}
);
let started = false;
nextServer.stdout.on('data', (data) => {
const output = data.toString();
console.log('[Next.js]', output);
if (!started && output.includes('Ready')) {
started = true;
resolve();
}
});
nextServer.stderr.on('data', (data) => {
console.error('[Next.js Error]', data.toString());
});
nextServer.on('error', (err) => {
console.error('[Next.js] Failed to start:', err);
reject(err);
});
nextServer.on('close', (code) => {
if (!started) {
reject(new Error(`Next.js server exited with code ${code}`));
}
});
// 超时保底 — 15秒内没有就绪也继续
setTimeout(() => {
if (!started) {
started = true;
console.log('[Author] Server startup timeout, loading anyway...');
resolve();
}
}, 15000);
});
}
app.whenReady().then(async () => {
try {
await startNextServer();
} catch (err) {
console.error('[Author] Server start failed:', err);
}
createWindow();
});
app.on('window-all-closed', () => {
if (nextServer) {
nextServer.kill();
}
app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 确保退出时清理子进程
app.on('before-quit', () => {
if (nextServer) {
nextServer.kill();
}
});
+7
View File
@@ -0,0 +1,7 @@
const { contextBridge } = require('electron');
// 最小预加载脚本 — 安全隔离
// 如果将来需要从渲染进程调用 Node.js API,可以通过 contextBridge 暴露
contextBridge.exposeInMainWorld('electronAPI', {
isElectron: true,
});
+1
View File
@@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'standalone',
devIndicators: false, devIndicators: false,
}; };
+4119 -2
View File
File diff suppressed because it is too large. Load diff
+41 -3
View File
@@ -1,12 +1,16 @@
{ {
"name": "author-app", "name": "author-app",
"version": "0.1.0", "version": "1.1.0",
"private": true, "private": true,
"main": "electron/main.js",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint",
"electron:dev": "npm run dev & npx electron . --dev",
"electron:build": "next build && npx electron-builder --win",
"electron:start": "npx electron ."
}, },
"dependencies": { "dependencies": {
"@tiptap/extension-character-count": "^3.19.0", "@tiptap/extension-character-count": "^3.19.0",
@@ -38,9 +42,43 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"electron": "^35.7.5",
"electron-builder": "^26.8.1",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.1.6", "eslint-config-next": "16.1.6",
"pdf-parse": "^2.4.5", "pdf-parse": "^2.4.5",
"tailwindcss": "^4" "tailwindcss": "^4"
},
"build": {
"appId": "com.yuanshijilong.author",
"productName": "Author",
"directories": {
"output": "dist"
},
"files": [
".next/**/*",
"node_modules/**/*",
"public/**/*",
"electron/**/*",
"package.json",
"next.config.mjs"
],
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
]
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
} }
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB