fix(remote): 添加 /api/files/upload 路由支持文件上传
This commit is contained in:
@@ -7,17 +7,24 @@ log.level = "info"
|
||||
log.maxDays = 7
|
||||
|
||||
[[proxies]]
|
||||
name = "remote-desktop"
|
||||
name = "desktop-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3000
|
||||
remotePort = 8080
|
||||
|
||||
[[proxies]]
|
||||
name = "gitea-web"
|
||||
name = "gitea-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3001
|
||||
remotePort = 8081
|
||||
|
||||
[[proxies]]
|
||||
name = "opencode-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3002
|
||||
remotePort = 8082
|
||||
|
||||
|
||||
|
||||
@@ -7,17 +7,24 @@ log.level = "info"
|
||||
log.maxDays = 7
|
||||
|
||||
[[proxies]]
|
||||
name = "remote-desktop"
|
||||
name = "desktop-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3000
|
||||
remotePort = 8080
|
||||
|
||||
[[proxies]]
|
||||
name = "gitea-web"
|
||||
name = "gitea-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3001
|
||||
remotePort = 8081
|
||||
|
||||
[[proxies]]
|
||||
name = "opencode-remote"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 3002
|
||||
remotePort = 8082
|
||||
|
||||
|
||||
|
||||
@@ -118,6 +118,11 @@ class App {
|
||||
});
|
||||
});
|
||||
|
||||
this.container.register('openCodeService', (c) => {
|
||||
const OpenCodeService = require('../services/opencode/OpenCodeService');
|
||||
return new OpenCodeService();
|
||||
});
|
||||
|
||||
this.container.register('giteaService', (c) => {
|
||||
const GiteaService = require('../services/network/GiteaService');
|
||||
const config = c.resolve('config');
|
||||
@@ -191,6 +196,10 @@ class App {
|
||||
frpService.start();
|
||||
logger.info('FRP service started');
|
||||
|
||||
const openCodeService = this.container.resolve('openCodeService');
|
||||
openCodeService.start();
|
||||
logger.info('OpenCode service started');
|
||||
|
||||
const giteaService = this.container.resolve('giteaService');
|
||||
giteaService.start();
|
||||
logger.info('Gitea service started');
|
||||
@@ -469,6 +478,10 @@ class App {
|
||||
frpService.stop();
|
||||
logger.info('FRP service stopped');
|
||||
|
||||
const openCodeService = this.container.resolve('openCodeService');
|
||||
openCodeService.stop();
|
||||
logger.info('OpenCode service stopped');
|
||||
|
||||
const giteaService = this.container.resolve('giteaService');
|
||||
giteaService.stop();
|
||||
logger.info('Gitea service stopped');
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { fileService } = require('../services/file');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
@@ -17,6 +19,40 @@ router.get('/', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/upload', upload.single('file'), (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No file provided' });
|
||||
}
|
||||
|
||||
const remotePath = req.body.path || '';
|
||||
const filename = req.file.originalname;
|
||||
|
||||
let targetDir;
|
||||
if (remotePath) {
|
||||
targetDir = path.join(fileService.uploadDir, remotePath);
|
||||
} else {
|
||||
targetDir = fileService.uploadDir;
|
||||
}
|
||||
|
||||
if (!targetDir.startsWith(fileService.uploadDir)) {
|
||||
return res.status(403).json({ error: 'Invalid path' });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
|
||||
const targetPath = path.join(targetDir, filename);
|
||||
fs.writeFileSync(targetPath, req.file.buffer);
|
||||
|
||||
res.json({ success: true, filename });
|
||||
} catch (error) {
|
||||
logger.error('Failed to upload file', { error: error.message });
|
||||
res.status(500).json({ error: 'Failed to upload file' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/drives', (req, res) => {
|
||||
try {
|
||||
const drives = fileService.getDrives();
|
||||
|
||||
97
remote/src/services/opencode/OpenCodeService.js
Normal file
97
remote/src/services/opencode/OpenCodeService.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const { spawn } = require('child_process');
|
||||
const config = require('../../config');
|
||||
const logger = require('../../utils/logger');
|
||||
|
||||
class OpenCodeService {
|
||||
constructor() {
|
||||
this.process = null;
|
||||
this.isRunning = false;
|
||||
this.port = 3002;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.isRunning) {
|
||||
logger.warn('OpenCode service is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
const password = config.get('security.password');
|
||||
if (!password) {
|
||||
logger.error('OpenCode password not found in config');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info('Starting OpenCode service', { port: this.port });
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
OPENCODE_SERVER_PASSWORD: password
|
||||
};
|
||||
|
||||
this.process = spawn('opencode', ['serve', '--port', this.port.toString()], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env,
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
this.isRunning = true;
|
||||
|
||||
this.process.stdout.on('data', (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
logger.info(`[OpenCode] ${output}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.process.stderr.on('data', (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
logger.error(`[OpenCode] ${output}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.process.on('error', (error) => {
|
||||
logger.error('OpenCode process error', { error: error.message });
|
||||
this.isRunning = false;
|
||||
});
|
||||
|
||||
this.process.on('close', (code) => {
|
||||
logger.info('OpenCode process closed', { code });
|
||||
this.isRunning = false;
|
||||
this.process = null;
|
||||
});
|
||||
|
||||
logger.info('OpenCode service started successfully');
|
||||
} catch (error) {
|
||||
logger.error('Failed to start OpenCode service', { error: error.message });
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.isRunning || !this.process) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('Stopping OpenCode service');
|
||||
|
||||
try {
|
||||
this.process.kill();
|
||||
this.process = null;
|
||||
this.isRunning = false;
|
||||
logger.info('OpenCode service stopped');
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop OpenCode service', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
running: this.isRunning,
|
||||
port: this.port
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OpenCodeService;
|
||||
138
remote/uploads/package.json
Normal file
138
remote/uploads/package.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"name": "xcdesktop",
|
||||
"version": "0.0.0",
|
||||
"description": "一站式 AI 工作台 - 集成笔记管理、时间追踪、任务管理、AI 辅助等多种功能",
|
||||
"keywords": [
|
||||
"markdown",
|
||||
"note",
|
||||
"笔记",
|
||||
"electron",
|
||||
"react",
|
||||
"typescript",
|
||||
"时间追踪",
|
||||
"任务管理"
|
||||
],
|
||||
"author": "Your Name",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/your-username/xcdesktop.git"
|
||||
},
|
||||
"private": true,
|
||||
"main": "dist-electron/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"client:dev": "vite",
|
||||
"build": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.api.json && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"check": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.api.json",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"test:run": "vitest run",
|
||||
"server:dev": "nodemon",
|
||||
"dev": "concurrently \"npm run client:dev\" \"npm run server:dev\"",
|
||||
"watch:electron": "tsup --config tsup.electron.ts --watch",
|
||||
"electron:dev": "concurrently -k \"npm run server:dev\" \"npm run client:dev\" \"wait-on tcp:3001 tcp:5173 && npm run watch:electron\"",
|
||||
"build:electron": "tsup --config tsup.electron.ts",
|
||||
"build:api": "tsup electron/server.ts --format esm --out-dir dist-api --clean --no-splitting --target esnext",
|
||||
"electron:build": "npm run build && npm run build:electron && npm run build:api && electron-builder"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@milkdown/core": "^7.18.0",
|
||||
"@milkdown/plugin-block": "^7.18.0",
|
||||
"@milkdown/plugin-history": "^7.18.0",
|
||||
"@milkdown/plugin-listener": "^7.18.0",
|
||||
"@milkdown/plugin-math": "^7.5.9",
|
||||
"@milkdown/plugin-prism": "^7.18.0",
|
||||
"@milkdown/preset-commonmark": "^7.18.0",
|
||||
"@milkdown/preset-gfm": "^7.18.0",
|
||||
"@milkdown/react": "^7.18.0",
|
||||
"@types/jszip": "^3.4.0",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"axios": "^1.13.5",
|
||||
"chokidar": "^5.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"electron-log": "^5.4.3",
|
||||
"express": "^4.21.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.511.0",
|
||||
"multer": "^2.0.2",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"recharts": "^3.7.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.19.13",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vercel/node": "^5.3.6",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-plugin-react-dev-locator": "^1.0.0",
|
||||
"concurrently": "^9.2.0",
|
||||
"electron": "^40.2.1",
|
||||
"electron-builder": "^26.7.0",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"happy-dom": "^20.7.0",
|
||||
"jsdom": "^28.1.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tsup": "^8.5.1",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.5",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^4.0.18",
|
||||
"wait-on": "^9.0.3"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.xcdesktop.app",
|
||||
"productName": "XCDesktop",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"dist-electron/**/*",
|
||||
"dist-api/**/*",
|
||||
"shared/**/*",
|
||||
"tools/**/*",
|
||||
"package.json"
|
||||
],
|
||||
"asarUnpack": [
|
||||
"tools/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "nsis"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user