mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:44:40 +00:00
electron
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,4 +11,8 @@ web/dist
|
||||
one-api
|
||||
.DS_Store
|
||||
tiktoken_cache
|
||||
.eslintcache
|
||||
.eslintcache
|
||||
|
||||
electron/node_modules
|
||||
electron/dist
|
||||
electron/package-lock.json
|
||||
172
electron/README.md
Normal file
172
electron/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# New API Electron Desktop App
|
||||
|
||||
This directory contains the Electron wrapper for New API, allowing it to run as a native desktop application on Windows, macOS, and Linux.
|
||||
|
||||
## Architecture
|
||||
|
||||
The Electron app consists of:
|
||||
- **main.js**: Main process that spawns the Go backend server and creates the application window
|
||||
- **preload.js**: Preload script for secure context isolation
|
||||
- **package.json**: Electron dependencies and build configuration
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Build the Go backend first:
|
||||
```bash
|
||||
cd ..
|
||||
go build -o new-api
|
||||
```
|
||||
|
||||
2. Install Electron dependencies:
|
||||
```bash
|
||||
cd electron
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running in Development Mode
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
This will:
|
||||
- Start the Go backend on port 3000
|
||||
- Open an Electron window pointing to `http://localhost:3000`
|
||||
- Enable DevTools for debugging
|
||||
|
||||
## Building for Production
|
||||
|
||||
### Quick Build (Current Platform)
|
||||
|
||||
Use the provided build script:
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Build the frontend (web/dist)
|
||||
2. Build the Go binary for your platform
|
||||
3. Package the Electron app
|
||||
|
||||
### Manual Build Steps
|
||||
|
||||
1. Build frontend:
|
||||
```bash
|
||||
cd ../web
|
||||
DISABLE_ESLINT_PLUGIN='true' bun run build
|
||||
```
|
||||
|
||||
2. Build backend:
|
||||
```bash
|
||||
cd ..
|
||||
# macOS/Linux
|
||||
go build -ldflags="-s -w" -o new-api
|
||||
|
||||
# Windows
|
||||
go build -ldflags="-s -w" -o new-api.exe
|
||||
```
|
||||
|
||||
3. Build Electron app:
|
||||
```bash
|
||||
cd electron
|
||||
npm install
|
||||
|
||||
# All platforms
|
||||
npm run build
|
||||
|
||||
# Or specific platforms
|
||||
npm run build:mac # macOS (DMG, ZIP)
|
||||
npm run build:win # Windows (NSIS installer, Portable)
|
||||
npm run build:linux # Linux (AppImage, DEB)
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
Built apps are located in `electron/dist/`:
|
||||
- **macOS**: `.dmg` and `.zip`
|
||||
- **Windows**: `.exe` installer and portable `.exe`
|
||||
- **Linux**: `.AppImage` and `.deb`
|
||||
|
||||
## Cross-Platform Building
|
||||
|
||||
To build for other platforms:
|
||||
|
||||
```bash
|
||||
# From macOS, build Windows app
|
||||
npm run build:win
|
||||
|
||||
# From macOS, build Linux app
|
||||
npm run build:linux
|
||||
```
|
||||
|
||||
Note: Building macOS apps requires macOS. Building Windows apps with code signing requires Windows.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Port
|
||||
|
||||
The app uses port 3000 by default. To change:
|
||||
|
||||
Edit `electron/main.js`:
|
||||
```javascript
|
||||
const PORT = 3000; // Change to your desired port
|
||||
```
|
||||
|
||||
### Data Directory
|
||||
|
||||
- **Development**: Uses `data/` in the project root
|
||||
- **Production**: Uses Electron's `userData` directory:
|
||||
- macOS: `~/Library/Application Support/New API/data/`
|
||||
- Windows: `%APPDATA%/New API/data/`
|
||||
- Linux: `~/.config/New API/data/`
|
||||
|
||||
### Window Size
|
||||
|
||||
Edit `electron/main.js` in the `createWindow()` function:
|
||||
```javascript
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400, // Change width
|
||||
height: 900, // Change height
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server fails to start
|
||||
|
||||
Check the console logs in DevTools (Cmd/Ctrl+Shift+I). Common issues:
|
||||
- Go binary not found (ensure it's built)
|
||||
- Port 3000 already in use
|
||||
- Database file permission issues
|
||||
|
||||
### Binary not found in production
|
||||
|
||||
Ensure the Go binary is built before running `electron-builder`:
|
||||
```bash
|
||||
go build -o new-api # macOS/Linux
|
||||
go build -o new-api.exe # Windows
|
||||
```
|
||||
|
||||
The binary must be in the project root, not inside `electron/`.
|
||||
|
||||
### Database issues
|
||||
|
||||
If you encounter database errors, delete the data directory and restart:
|
||||
- Dev: `rm -rf data/`
|
||||
- Prod: Clear Electron's userData folder (see "Data Directory" above)
|
||||
|
||||
## Icon
|
||||
|
||||
To add a custom icon:
|
||||
1. Place a 512x512 PNG icon at `electron/icon.png`
|
||||
2. Rebuild the app with `npm run build`
|
||||
|
||||
## Security
|
||||
|
||||
- Context isolation is enabled
|
||||
- Node integration is disabled in renderer process
|
||||
- Only safe APIs are exposed via preload script
|
||||
- Backend runs as a local subprocess with no external network access by default
|
||||
41
electron/build.sh
Executable file
41
electron/build.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building New API Electron App..."
|
||||
|
||||
echo "Step 1: Building frontend..."
|
||||
cd ../web
|
||||
DISABLE_ESLINT_PLUGIN='true' bun run build
|
||||
cd ../electron
|
||||
|
||||
echo "Step 2: Building Go backend..."
|
||||
cd ..
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
echo "Building for macOS..."
|
||||
CGO_ENABLED=1 go build -ldflags="-s -w" -o new-api
|
||||
cd electron
|
||||
npm install
|
||||
npm run build:mac
|
||||
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
echo "Building for Linux..."
|
||||
CGO_ENABLED=1 go build -ldflags="-s -w" -o new-api
|
||||
cd electron
|
||||
npm install
|
||||
npm run build:linux
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
|
||||
echo "Building for Windows..."
|
||||
CGO_ENABLED=1 go build -ldflags="-s -w" -o new-api.exe
|
||||
cd electron
|
||||
npm install
|
||||
npm run build:win
|
||||
else
|
||||
echo "Unknown OS, building for current platform..."
|
||||
CGO_ENABLED=1 go build -ldflags="-s -w" -o new-api
|
||||
cd electron
|
||||
npm install
|
||||
npm run build
|
||||
fi
|
||||
|
||||
echo "Build complete! Check electron/dist/ for output."
|
||||
178
electron/main.js
Normal file
178
electron/main.js
Normal file
@@ -0,0 +1,178 @@
|
||||
const { app, BrowserWindow, dialog } = require('electron');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
|
||||
let mainWindow;
|
||||
let serverProcess;
|
||||
const PORT = 3000;
|
||||
|
||||
function getBinaryPath() {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const platform = process.platform;
|
||||
|
||||
if (isDev) {
|
||||
const binaryName = platform === 'win32' ? 'new-api.exe' : 'new-api';
|
||||
return path.join(__dirname, '..', binaryName);
|
||||
}
|
||||
|
||||
let binaryName;
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
binaryName = 'new-api.exe';
|
||||
break;
|
||||
case 'darwin':
|
||||
binaryName = 'new-api';
|
||||
break;
|
||||
case 'linux':
|
||||
binaryName = 'new-api';
|
||||
break;
|
||||
default:
|
||||
binaryName = 'new-api';
|
||||
}
|
||||
|
||||
return path.join(process.resourcesPath, 'bin', binaryName);
|
||||
}
|
||||
|
||||
function startServer() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const binaryPath = getBinaryPath();
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
console.log('Starting server from:', binaryPath);
|
||||
|
||||
const env = { ...process.env, PORT: PORT.toString() };
|
||||
|
||||
let dataDir;
|
||||
if (isDev) {
|
||||
dataDir = path.join(__dirname, '..', 'data');
|
||||
} else {
|
||||
const userDataPath = app.getPath('userData');
|
||||
dataDir = path.join(userDataPath, 'data');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
env.SQLITE_PATH = path.join(dataDir, 'new-api.db');
|
||||
|
||||
const workingDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
: process.resourcesPath;
|
||||
|
||||
serverProcess = spawn(binaryPath, [], {
|
||||
env,
|
||||
cwd: workingDir
|
||||
});
|
||||
|
||||
serverProcess.stdout.on('data', (data) => {
|
||||
console.log(`Server: ${data}`);
|
||||
});
|
||||
|
||||
serverProcess.stderr.on('data', (data) => {
|
||||
console.error(`Server Error: ${data}`);
|
||||
});
|
||||
|
||||
serverProcess.on('error', (err) => {
|
||||
console.error('Failed to start server:', err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
serverProcess.on('close', (code) => {
|
||||
console.log(`Server process exited with code ${code}`);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
waitForServer(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForServer(resolve, reject, retries = 30) {
|
||||
if (retries === 0) {
|
||||
reject(new Error('Server failed to start within timeout'));
|
||||
return;
|
||||
}
|
||||
|
||||
const req = http.get(`http://localhost:${PORT}`, (res) => {
|
||||
console.log('Server is ready');
|
||||
resolve();
|
||||
});
|
||||
|
||||
req.on('error', () => {
|
||||
setTimeout(() => waitForServer(resolve, reject, retries - 1), 1000);
|
||||
});
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true
|
||||
},
|
||||
title: 'New API',
|
||||
icon: path.join(__dirname, 'icon.png')
|
||||
});
|
||||
|
||||
mainWindow.loadURL(`http://localhost:${PORT}`);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
try {
|
||||
await startServer();
|
||||
createWindow();
|
||||
} catch (err) {
|
||||
console.error('Failed to start application:', err);
|
||||
dialog.showErrorBox('Startup Error', `Failed to start server: ${err.message}`);
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', (event) => {
|
||||
if (serverProcess) {
|
||||
event.preventDefault();
|
||||
|
||||
console.log('Shutting down server...');
|
||||
serverProcess.kill('SIGTERM');
|
||||
|
||||
setTimeout(() => {
|
||||
if (serverProcess) {
|
||||
serverProcess.kill('SIGKILL');
|
||||
}
|
||||
app.exit();
|
||||
}, 5000);
|
||||
|
||||
serverProcess.on('close', () => {
|
||||
serverProcess = null;
|
||||
app.exit();
|
||||
});
|
||||
}
|
||||
});
|
||||
108
electron/package.json
Normal file
108
electron/package.json
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "new-api-electron",
|
||||
"version": "1.0.0",
|
||||
"description": "New API - AI Model Gateway Desktop Application",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development electron .",
|
||||
"build": "electron-builder",
|
||||
"build:mac": "electron-builder --mac",
|
||||
"build:win": "electron-builder --win",
|
||||
"build:linux": "electron-builder --linux"
|
||||
},
|
||||
"keywords": [
|
||||
"ai",
|
||||
"api",
|
||||
"gateway",
|
||||
"openai",
|
||||
"claude"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Calcium-Ion/new-api"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.newapi.desktop",
|
||||
"productName": "New API",
|
||||
"publish": null,
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"icon.png"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../new-api",
|
||||
"to": "bin/new-api",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "../new-api.exe",
|
||||
"to": "bin/new-api.exe",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools",
|
||||
"icon": "icon.png",
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../new-api",
|
||||
"to": "bin/new-api"
|
||||
},
|
||||
{
|
||||
"from": "../web/dist",
|
||||
"to": "web/dist"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"icon": "icon.png",
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../new-api.exe",
|
||||
"to": "bin/new-api.exe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"icon": "icon.png",
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
],
|
||||
"category": "Development",
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../new-api",
|
||||
"to": "bin/new-api"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
}
|
||||
}
|
||||
}
|
||||
6
electron/preload.js
Normal file
6
electron/preload.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { contextBridge } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
version: process.versions.electron,
|
||||
platform: process.platform
|
||||
});
|
||||
@@ -10,6 +10,7 @@
|
||||
"@visactor/react-vchart": "~1.8.8",
|
||||
"@visactor/vchart": "~1.8.8",
|
||||
"@visactor/vchart-semi-theme": "~1.8.8",
|
||||
"antd": "^5.27.4",
|
||||
"axios": "^0.27.2",
|
||||
"clsx": "^2.1.1",
|
||||
"country-flag-icons": "^1.5.19",
|
||||
|
||||
Reference in New Issue
Block a user