chore: 添加远程桌面控制组件、文档和构建产物
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
console.log('Testing connection to remote...');
|
||||
const ws = new WebSocket('ws://146.56.248.142:8083/ws?password=wzw20040525', {
|
||||
handshakeTimeout: 5000
|
||||
});
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected!');
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.log('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Connection timeout');
|
||||
process.exit(1);
|
||||
}, 8000);
|
||||
@@ -1,268 +0,0 @@
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
const BASE_URL = 'http://localhost:3000';
|
||||
const PASSWORD = 'wzw20040525';
|
||||
const CHUNK_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
function request(options, body = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(options.path, BASE_URL);
|
||||
url.searchParams.set('password', PASSWORD);
|
||||
|
||||
const client = url.protocol === 'https:' ? https : http;
|
||||
|
||||
const reqOptions = {
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: url.pathname + url.search,
|
||||
method: options.method,
|
||||
headers: options.headers || {}
|
||||
};
|
||||
|
||||
const req = client.request(reqOptions, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
||||
} catch {
|
||||
resolve({ status: res.statusCode, data });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
|
||||
if (body) {
|
||||
if (body instanceof FormData) {
|
||||
req.write(body.getBuffer());
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
req.write(body);
|
||||
} else {
|
||||
req.write(JSON.stringify(body));
|
||||
}
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function testGetDrives() {
|
||||
console.log('\n=== 测试1: 获取驱动器列表 ===');
|
||||
try {
|
||||
const driveUrl = new URL('/api/files/browse', BASE_URL);
|
||||
driveUrl.searchParams.set('allowSystem', 'true');
|
||||
driveUrl.searchParams.set('password', PASSWORD);
|
||||
|
||||
const res = await request({
|
||||
method: 'GET',
|
||||
path: driveUrl.pathname + driveUrl.search
|
||||
});
|
||||
console.log('状态:', res.status);
|
||||
console.log('currentPath:', res.data.currentPath);
|
||||
console.log('parentPath:', res.data.parentPath);
|
||||
|
||||
// 检查是否返回了驱动器(盘符如 C:, D:)
|
||||
const drives = res.data.items?.filter(item => item.name.match(/^[A-Z]:$/i));
|
||||
if (drives && drives.length > 0) {
|
||||
console.log('✓ 驱动器列表:', drives.map(d => d.name).join(', '));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果返回的是目录列表而非驱动器,说明 allowSystem 未生效
|
||||
console.log('✗ 未获取到驱动器列表');
|
||||
console.log('返回的项目:', res.data.items?.slice(0, 5).map(i => i.name).join(', '));
|
||||
return false;
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testUploadToSystemDir() {
|
||||
console.log('\n=== 测试2: 上传到系统目录 ===');
|
||||
const testContent = 'Hello Remote System Directory Test ' + Date.now();
|
||||
const filename = 'D:\\xc_test_file.txt';
|
||||
|
||||
try {
|
||||
// 1. 开始上传
|
||||
console.log('1. 开始上传...');
|
||||
const startUrl = new URL('/api/files/upload/start', BASE_URL);
|
||||
startUrl.searchParams.set('password', PASSWORD);
|
||||
|
||||
const startRes = await new Promise((resolve, reject) => {
|
||||
const req = http.request({
|
||||
hostname: startUrl.hostname,
|
||||
port: startUrl.port,
|
||||
path: startUrl.pathname + startUrl.search,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
|
||||
catch { resolve({ status: res.statusCode, data }); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(JSON.stringify({
|
||||
filename: filename,
|
||||
totalChunks: 1,
|
||||
fileSize: Buffer.byteLength(testContent)
|
||||
}));
|
||||
req.end();
|
||||
});
|
||||
console.log('开始上传响应:', startRes.data);
|
||||
|
||||
if (!startRes.data.fileId) {
|
||||
console.error('未获取到fileId');
|
||||
return false;
|
||||
}
|
||||
|
||||
const fileId = startRes.data.fileId;
|
||||
|
||||
// 2. 上传分块
|
||||
console.log('2. 上传分块...');
|
||||
const chunk = Buffer.from(testContent);
|
||||
|
||||
const chunkUrl = new URL('/api/files/upload/chunk', BASE_URL);
|
||||
chunkUrl.searchParams.set('password', PASSWORD);
|
||||
|
||||
const FormData = require('form-data');
|
||||
const form = new FormData();
|
||||
form.append('fileId', fileId);
|
||||
form.append('chunkIndex', '0');
|
||||
form.append('chunk', chunk, { filename: 'chunk', contentType: 'application/octet-stream' });
|
||||
|
||||
const chunkRes = await new Promise((resolve, reject) => {
|
||||
const req = http.request({
|
||||
hostname: chunkUrl.hostname,
|
||||
port: chunkUrl.port,
|
||||
path: chunkUrl.pathname + chunkUrl.search,
|
||||
method: 'POST',
|
||||
headers: form.getHeaders()
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
|
||||
catch { resolve({ status: res.statusCode, data }); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(form.getBuffer());
|
||||
req.end();
|
||||
});
|
||||
console.log('分块上传响应:', chunkRes.data);
|
||||
|
||||
// 3. 合并文件
|
||||
console.log('3. 合并文件...');
|
||||
const mergeUrl = new URL('/api/files/upload/merge', BASE_URL);
|
||||
mergeUrl.searchParams.set('password', PASSWORD);
|
||||
|
||||
const mergeRes = await new Promise((resolve, reject) => {
|
||||
const req = http.request({
|
||||
hostname: mergeUrl.hostname,
|
||||
port: mergeUrl.port,
|
||||
path: mergeUrl.pathname + mergeUrl.search,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => data += c);
|
||||
res.on('end', () => {
|
||||
try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
|
||||
catch { resolve({ status: res.statusCode, data }); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(JSON.stringify({
|
||||
fileId: fileId,
|
||||
totalChunks: 1,
|
||||
filename: filename
|
||||
}));
|
||||
req.end();
|
||||
});
|
||||
console.log('合并响应:', mergeRes.data);
|
||||
|
||||
return mergeRes.data.success === true;
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testDownloadFromSystemDir() {
|
||||
console.log('\n=== 测试3: 从系统目录下载 ===');
|
||||
const filename = 'D:\\xc_test_file.txt';
|
||||
|
||||
try {
|
||||
const encodedPath = encodeURIComponent(filename);
|
||||
const res = await request({
|
||||
method: 'GET',
|
||||
path: `/api/files/${encodedPath}`
|
||||
});
|
||||
console.log('状态:', res.status);
|
||||
console.log('响应类型:', typeof res.data);
|
||||
return res.status === 200;
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testDeleteFile() {
|
||||
console.log('\n=== 测试4: 删除测试文件 ===');
|
||||
const filename = 'D:\\xc_test_file.txt';
|
||||
|
||||
try {
|
||||
const encodedPath = encodeURIComponent(filename);
|
||||
const res = await request({
|
||||
method: 'DELETE',
|
||||
path: `/api/files/${encodedPath}`
|
||||
});
|
||||
console.log('状态:', res.status);
|
||||
console.log('响应:', res.data);
|
||||
return res.data.success === true || res.status === 200;
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('========================================');
|
||||
console.log('远程文件传输功能测试');
|
||||
console.log('目标服务器: 146.56.248.142:8080');
|
||||
console.log('========================================');
|
||||
|
||||
const results = [];
|
||||
|
||||
// 测试1: 获取驱动器列表
|
||||
results.push({ name: '获取驱动器列表', pass: await testGetDrives() });
|
||||
|
||||
// 测试2: 上传到系统目录
|
||||
results.push({ name: '上传到系统目录', pass: await testUploadToSystemDir() });
|
||||
|
||||
// 测试3: 从系统目录下载
|
||||
results.push({ name: '从系统目录下载', pass: await testDownloadFromSystemDir() });
|
||||
|
||||
// 测试4: 删除测试文件
|
||||
results.push({ name: '删除测试文件', pass: await testDeleteFile() });
|
||||
|
||||
// 汇总结果
|
||||
console.log('\n========================================');
|
||||
console.log('测试结果汇总:');
|
||||
console.log('========================================');
|
||||
results.forEach(r => {
|
||||
console.log(`${r.pass ? '✓' : '✗'} ${r.name}`);
|
||||
});
|
||||
|
||||
const allPass = results.every(r => r.pass);
|
||||
console.log(`\n总体结果: ${allPass ? '全部通过' : '存在失败'}`);
|
||||
process.exit(allPass ? 0 : 1);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,89 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
const fs = require('fs');
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:3003/ws?password=wzw20040525');
|
||||
|
||||
let fileId = 'upload_test_' + Date.now();
|
||||
const testContent = Buffer.from('Hello World Test File Content');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('=== Connected, starting upload test ===');
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadStart',
|
||||
fileId: fileId,
|
||||
filename: 'test.txt',
|
||||
totalChunks: 1,
|
||||
fileSize: testContent.length,
|
||||
requestId: 'req1'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data, isBinary) => {
|
||||
if (isBinary) {
|
||||
console.log('Binary data received');
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = JSON.parse(data.toString());
|
||||
console.log('Received:', msg.type, msg.fileId || '');
|
||||
|
||||
if (msg.type === 'fileUploadStart') {
|
||||
console.log('Session started, sending chunk...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadChunk',
|
||||
fileId: fileId,
|
||||
chunkIndex: 0,
|
||||
data: testContent.toString('base64'),
|
||||
requestId: 'req2'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadChunk') {
|
||||
console.log('Chunk sent, sending merge...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadMerge',
|
||||
fileId: fileId,
|
||||
filename: 'test.txt',
|
||||
totalChunks: 1,
|
||||
requestId: 'req3'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadResult') {
|
||||
console.log('=== Upload Result:', msg.success ? 'SUCCESS' : 'FAILED', msg.filename || msg.error);
|
||||
|
||||
if (msg.success) {
|
||||
console.log('\n=== Testing download ===');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileDownloadStart',
|
||||
filename: 'test.txt',
|
||||
filePath: 'test.txt',
|
||||
allowSystem: true,
|
||||
requestId: 'req4'
|
||||
}));
|
||||
} else {
|
||||
ws.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'fileDownloadStart') {
|
||||
console.log('Download started, size:', msg.size);
|
||||
}
|
||||
|
||||
if (msg.type === 'fileDownloadChunk') {
|
||||
console.log('Download chunk:', msg.chunkIndex, 'progress:', msg.progress + '%');
|
||||
}
|
||||
|
||||
if (msg.type === 'fileDownloadComplete') {
|
||||
console.log('=== Download Result:', msg.success ? 'SUCCESS' : 'FAILED');
|
||||
console.log('=== ALL TESTS PASSED ===');
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => { console.error('Error:', err.message); });
|
||||
|
||||
setTimeout(() => { console.log('=== TIMEOUT ==='); process.exit(1); }, 20000);
|
||||
@@ -1,13 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
console.log('Testing port 8080...');
|
||||
const ws1 = new WebSocket('ws://146.56.248.142:8080/ws?password=wzw20040525');
|
||||
ws1.on('open', () => console.log('8080: Connected'));
|
||||
ws1.on('error', () => console.log('8080: Failed'));
|
||||
ws1.on('close', () => {
|
||||
console.log('Testing port 8083...');
|
||||
const ws2 = new WebSocket('ws://146.56.248.142:8083/ws?password=wzw20040525');
|
||||
ws2.on('open', () => console.log('8083: Connected'));
|
||||
ws2.on('error', () => console.log('8083: Failed'));
|
||||
setTimeout(() => process.exit(0), 3000);
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://146.56.248.142:8083/ws?password=wzw20040525');
|
||||
|
||||
let fileId = 'test_' + Date.now();
|
||||
const testContent = Buffer.from('Hello Remote Test');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('=== Connected to 146.56.248.142:8083 ===');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadStart',
|
||||
fileId: fileId,
|
||||
filename: 'test.txt',
|
||||
totalChunks: 1,
|
||||
fileSize: testContent.length,
|
||||
requestId: 'req1'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data, isBinary) => {
|
||||
if (isBinary) return;
|
||||
const msg = JSON.parse(data.toString());
|
||||
console.log('Received:', msg.type);
|
||||
|
||||
if (msg.type === 'fileUploadStart') {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadChunk',
|
||||
fileId: fileId,
|
||||
chunkIndex: 0,
|
||||
data: testContent.toString('base64'),
|
||||
requestId: 'req2'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadChunk') {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadMerge',
|
||||
fileId: fileId,
|
||||
filename: 'test.txt',
|
||||
totalChunks: 1,
|
||||
requestId: 'req3'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadResult') {
|
||||
console.log('=== Upload:', msg.success ? 'SUCCESS' : 'FAILED');
|
||||
if (msg.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileDownloadStart',
|
||||
filename: 'test.txt',
|
||||
filePath: 'test.txt',
|
||||
allowSystem: false,
|
||||
requestId: 'req4'
|
||||
}));
|
||||
} else {
|
||||
ws.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'fileDownloadComplete') {
|
||||
console.log('=== Download:', msg.success ? 'SUCCESS' : 'FAILED');
|
||||
console.log('=== ALL TESTS PASSED ===');
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', err => console.error('Error:', err.message));
|
||||
|
||||
setTimeout(() => { console.log('TIMEOUT'); process.exit(1); }, 15000);
|
||||
@@ -1,30 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
console.log('Starting...');
|
||||
const ws = new WebSocket('ws://146.56.248.142:8083/ws?password=wzw20040525');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected!');
|
||||
|
||||
let fileId = 'test_' + Date.now();
|
||||
const testContent = Buffer.from('Hello');
|
||||
|
||||
console.log('Sending fileUploadStart...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadStart',
|
||||
fileId: fileId,
|
||||
filename: 'F:/test.txt',
|
||||
totalChunks: 1,
|
||||
fileSize: 5,
|
||||
requestId: 'req1'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data, isBinary) => {
|
||||
console.log('Got message:', isBinary ? 'binary' : data.toString().substring(0,80));
|
||||
});
|
||||
|
||||
ws.on('error', e => console.log('Error:', e.message));
|
||||
ws.on('close', () => console.log('Closed'));
|
||||
|
||||
setTimeout(() => { console.log('Timeout'); process.exit(1); }, 10000);
|
||||
@@ -1,57 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
|
||||
console.log('Testing upload to remote 8083...');
|
||||
const ws = new WebSocket('ws://146.56.248.142:8083/ws?password=wzw20040525');
|
||||
|
||||
let fileId = 'test_' + Date.now();
|
||||
const testContent = Buffer.from('Hello Test 中文');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected, sending fileUploadStart with F:/xxx.txt...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadStart',
|
||||
fileId: fileId,
|
||||
filename: 'F:/小问题.txt',
|
||||
totalChunks: 1,
|
||||
fileSize: testContent.length,
|
||||
requestId: 'req1'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data, isBinary) => {
|
||||
if (isBinary) return;
|
||||
const msg = JSON.parse(data.toString());
|
||||
console.log('Received:', msg.type);
|
||||
|
||||
if (msg.type === 'fileUploadStart') {
|
||||
console.log('Session started, sending chunk...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadChunk',
|
||||
fileId: fileId,
|
||||
chunkIndex: 0,
|
||||
data: testContent.toString('base64'),
|
||||
requestId: 'req2'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadChunk') {
|
||||
console.log('Chunk sent, sending merge...');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'fileUploadMerge',
|
||||
fileId: fileId,
|
||||
filename: 'F:/小问题.txt',
|
||||
totalChunks: 1,
|
||||
requestId: 'req3'
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'fileUploadResult') {
|
||||
console.log('=== Upload Result:', msg.success ? 'SUCCESS' : 'FAILED', msg.error || '');
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', err => console.error('Error:', err.message));
|
||||
|
||||
setTimeout(() => { console.log('Timeout'); process.exit(1); }, 15000);
|
||||
@@ -1,32 +0,0 @@
|
||||
const WebSocket = require('ws');
|
||||
const ws = new WebSocket('ws://127.0.0.1:3001/ws?password=wzw20040525');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected, sending fileBrowse...');
|
||||
ws.send(JSON.stringify({ type: 'fileBrowse', path: 'C:\\', allowSystem: true, requestId: 'test1' }));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
console.log('Received raw:', data.toString().substring(0, 200));
|
||||
|
||||
if (Buffer.isBuffer(data) || data instanceof ArrayBuffer) {
|
||||
console.log('Binary data');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const msg = JSON.parse(data);
|
||||
console.log('Received:', msg.type);
|
||||
if (msg.type === 'fileBrowseResult') {
|
||||
console.log('Items:', msg.items?.slice(0,3));
|
||||
ws.close();
|
||||
process.exit(0);
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('Parse error:', e.message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => { console.error('Error:', err.message); });
|
||||
|
||||
setTimeout(() => { console.log('Timeout'); process.exit(1); }, 10000);
|
||||
99
remote/xcopencodeweb/README.md
Normal file
99
remote/xcopencodeweb/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# XCOpenCodeWeb
|
||||
|
||||
XCOpenCodeWeb 是一个基于 Web 的 AI 编程助手界面,用于与 OpenCode 服务器交互。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 使用单文件 exe(推荐)
|
||||
|
||||
直接下载 `XCOpenCodeWeb.exe`,双击运行:
|
||||
|
||||
```bash
|
||||
# 默认端口 3000
|
||||
XCOpenCodeWeb.exe
|
||||
|
||||
# 指定端口
|
||||
XCOpenCodeWeb.exe --port 8080
|
||||
|
||||
# 查看帮助
|
||||
XCOpenCodeWeb.exe --help
|
||||
```
|
||||
|
||||
启动后访问 http://localhost:3000
|
||||
|
||||
### 从源码运行
|
||||
|
||||
需要安装 [Bun](https://bun.sh) 或 Node.js 20+
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
bun install
|
||||
|
||||
# 构建前端
|
||||
bun run build
|
||||
|
||||
# 启动服务器
|
||||
bun server/index.js --port 3000
|
||||
```
|
||||
|
||||
## 构建单文件 exe
|
||||
|
||||
```bash
|
||||
cd web
|
||||
bun run build:exe
|
||||
```
|
||||
|
||||
输出:`web/XCOpenCodeWeb.exe`(约 320MB)
|
||||
|
||||
详细文档:[docs/single-file-exe-build.md](docs/single-file-exe-build.md)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── ui/ # 前端组件库
|
||||
├── web/
|
||||
│ ├── src/ # 前端源码
|
||||
│ ├── server/ # 后端服务器
|
||||
│ ├── bin/ # CLI 工具
|
||||
│ └── dist/ # 构建输出
|
||||
├── docs/ # 文档
|
||||
└── AGENTS.md # AI Agent 参考文档
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 开发模式
|
||||
bun run dev # 前端热更新
|
||||
bun run dev:server # 启动开发服务器
|
||||
|
||||
# 构建
|
||||
bun run build # 构建前端
|
||||
bun run build:exe # 构建单文件 exe
|
||||
|
||||
# 代码检查
|
||||
bun run type-check:web # TypeScript 类型检查
|
||||
bun run lint:web # ESLint 检查
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
- [Bun](https://bun.sh) - 运行时和打包工具
|
||||
- [React](https://react.dev) - 前端框架
|
||||
- [Express](https://expressjs.com) - 后端服务器
|
||||
- [Tailwind CSS](https://tailwindcss.com) - 样式框架
|
||||
|
||||
## 配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `XCOpenCodeWeb_PORT` | 服务器端口(默认 3000) |
|
||||
| `OPENCODE_HOST` | 外部 OpenCode 服务器地址 |
|
||||
| `OPENCODE_PORT` | 外部 OpenCode 端口 |
|
||||
| `OPENCODE_SKIP_START` | 跳过启动 OpenCode |
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
BIN
remote/xcopencodeweb/XCOpenCodeWeb.exe
Normal file
BIN
remote/xcopencodeweb/XCOpenCodeWeb.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user