mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:25:00 +00:00
electron
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,4 +11,8 @@ web/dist
|
|||||||
one-api
|
one-api
|
||||||
.DS_Store
|
.DS_Store
|
||||||
tiktoken_cache
|
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/react-vchart": "~1.8.8",
|
||||||
"@visactor/vchart": "~1.8.8",
|
"@visactor/vchart": "~1.8.8",
|
||||||
"@visactor/vchart-semi-theme": "~1.8.8",
|
"@visactor/vchart-semi-theme": "~1.8.8",
|
||||||
|
"antd": "^5.27.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"country-flag-icons": "^1.5.19",
|
"country-flag-icons": "^1.5.19",
|
||||||
|
|||||||
Reference in New Issue
Block a user