From 4c18edf74f633a11b95d97517878d25cfcdc0020 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 9 Mar 2026 19:41:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(remote):=20=E6=B7=BB=E5=8A=A0=20/api/files/?= =?UTF-8?q?upload=20=E8=B7=AF=E7=94=B1=E6=94=AF=E6=8C=81=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- remote/frp/frpc-runtime.toml | 11 +- remote/frp/frpc.toml | 11 +- remote/src/core/App.js | 13 ++ remote/src/routes/files.js | 36 +++++ .../src/services/opencode/OpenCodeService.js | 97 ++++++++++++ remote/uploads/package.json | 138 ++++++++++++++++++ 6 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 remote/src/services/opencode/OpenCodeService.js create mode 100644 remote/uploads/package.json diff --git a/remote/frp/frpc-runtime.toml b/remote/frp/frpc-runtime.toml index c8183ad..8c775ca 100644 --- a/remote/frp/frpc-runtime.toml +++ b/remote/frp/frpc-runtime.toml @@ -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 + diff --git a/remote/frp/frpc.toml b/remote/frp/frpc.toml index eb203a7..b3d31c8 100644 --- a/remote/frp/frpc.toml +++ b/remote/frp/frpc.toml @@ -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 + diff --git a/remote/src/core/App.js b/remote/src/core/App.js index 5cd17fd..0cd0f53 100644 --- a/remote/src/core/App.js +++ b/remote/src/core/App.js @@ -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'); diff --git a/remote/src/routes/files.js b/remote/src/routes/files.js index cee51c7..39eb517 100644 --- a/remote/src/routes/files.js +++ b/remote/src/routes/files.js @@ -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(); diff --git a/remote/src/services/opencode/OpenCodeService.js b/remote/src/services/opencode/OpenCodeService.js new file mode 100644 index 0000000..8726d8d --- /dev/null +++ b/remote/src/services/opencode/OpenCodeService.js @@ -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; diff --git a/remote/uploads/package.json b/remote/uploads/package.json new file mode 100644 index 0000000..bbd3e72 --- /dev/null +++ b/remote/uploads/package.json @@ -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 + } + } +}