diff --git a/electron/services/xcOpenCodeWebService.ts b/electron/services/xcOpenCodeWebService.ts index 9fb3055..52202ef 100644 --- a/electron/services/xcOpenCodeWebService.ts +++ b/electron/services/xcOpenCodeWebService.ts @@ -1,4 +1,4 @@ -import { spawn, ChildProcess } from 'child_process'; +import { spawn, exec, ChildProcess } from 'child_process'; import { app } from 'electron'; import path from 'path'; import log from 'electron-log'; @@ -9,8 +9,10 @@ const HEALTH_CHECK_TIMEOUT = 2000; class XCOpenCodeWebService { private process: ChildProcess | null = null; + private processPid: number | null = null; private healthCheckTimer: NodeJS.Timeout | null = null; private _isRunning = false; + private _isStarting = false; get port(): number { return XCOPENCODEWEB_PORT; @@ -81,6 +83,13 @@ class XCOpenCodeWebService { return { success: true }; } + if (this._isStarting) { + log.info('[XCOpenCodeWebService] Already starting'); + return { success: true }; + } + + this._isStarting = true; + try { const exePath = this.getExePath(); const exeArgs = this.getExeArgs(); @@ -88,10 +97,10 @@ class XCOpenCodeWebService { this.process = spawn(exePath, exeArgs, { stdio: 'pipe', - shell: true, - detached: false, }); + this.processPid = this.process.pid ?? null; + this.process.stdout?.on('data', (data) => { log.info(`[XCOpenCodeWeb] ${data}`); }); @@ -103,18 +112,23 @@ class XCOpenCodeWebService { this.process.on('error', (err) => { log.error('[XCOpenCodeWebService] Process error:', err); this._isRunning = false; + this._isStarting = false; this.process = null; + this.processPid = null; }); this.process.on('exit', (code) => { log.info(`[XCOpenCodeWebService] Process exited with code ${code}`); this._isRunning = false; + this._isStarting = false; this.process = null; + this.processPid = null; }); await new Promise((resolve) => setTimeout(resolve, 2000)); this._isRunning = true; + this._isStarting = false; this.startHealthCheck(); log.info(`[XCOpenCodeWebService] Started successfully on port ${this.port}`); @@ -122,6 +136,7 @@ class XCOpenCodeWebService { } catch (error: any) { log.error('[XCOpenCodeWebService] Failed to start:', error); this._isRunning = false; + this._isStarting = false; return { success: false, error: error.message }; } } @@ -132,31 +147,28 @@ class XCOpenCodeWebService { this.healthCheckTimer = null; } - if (!this.process) { + if (!this.processPid) { log.info('[XCOpenCodeWebService] Not running'); return { success: true }; } - try { - log.info('[XCOpenCodeWebService] Stopping...'); + return new Promise((resolve) => { + log.info(`[XCOpenCodeWebService] Stopping process ${this.processPid}...`); - this.process.kill('SIGTERM'); - - await new Promise((resolve) => setTimeout(resolve, 1000)); + exec(`taskkill /F /PID ${this.processPid}`, (error) => { + this.process = null; + this.processPid = null; + this._isRunning = false; - if (this.process && !this.process.killed) { - this.process.kill('SIGKILL'); - } - - this.process = null; - this._isRunning = false; - - log.info('[XCOpenCodeWebService] Stopped'); - return { success: true }; - } catch (error: any) { - log.error('[XCOpenCodeWebService] Failed to stop:', error); - return { success: false, error: error.message }; - } + if (error) { + log.error('[XCOpenCodeWebService] Failed to stop:', error); + resolve({ success: false, error: error.message }); + } else { + log.info('[XCOpenCodeWebService] Stopped'); + resolve({ success: true }); + } + }); + }); } } diff --git a/services/xcopencodeweb/XCOpenCodeWeb.exe b/services/xcopencodeweb/XCOpenCodeWeb.exe index e9377dc..fa52d8a 100644 Binary files a/services/xcopencodeweb/XCOpenCodeWeb.exe and b/services/xcopencodeweb/XCOpenCodeWeb.exe differ diff --git a/src/modules/opencode/OpenCodePage.tsx b/src/modules/opencode/OpenCodePage.tsx index 42d5d88..fef6010 100644 --- a/src/modules/opencode/OpenCodePage.tsx +++ b/src/modules/opencode/OpenCodePage.tsx @@ -1,14 +1,15 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState, useRef } from 'react' export const OpenCodePage: React.FC = () => { - const [isRunning, setIsRunning] = useState(false) - const [error, setError] = useState(null) - const [port, setPort] = useState(3002) + const [isHealthy, setIsHealthy] = useState(false) + const [port, setPort] = useState(9999) + const startedRef = useRef(false) + const restartingRef = useRef(false) useEffect(() => { let mounted = true - const init = async () => { + const start = async () => { try { const portResult = await window.electronAPI.xcOpenCodeWebGetPort() if (mounted) { @@ -16,47 +17,64 @@ export const OpenCodePage: React.FC = () => { } const result = await window.electronAPI.xcOpenCodeWebStart() + if (!result.success && mounted) { + console.error('Failed to start XCOpenCodeWeb:', result.error) + } + restartingRef.current = false + } catch (err) { + console.error('Failed to start XCOpenCodeWeb:', err) + restartingRef.current = false + } + } + + const checkStatus = async () => { + try { + const status = await window.electronAPI.xcOpenCodeWebGetStatus() if (mounted) { - if (result.success) { - setIsRunning(true) - setError(null) - } else { - setError(result.error || '启动失败') + setIsHealthy(status.running) + if (!status.running && !restartingRef.current) { + restartingRef.current = true + start() } } } catch (err) { if (mounted) { - setError(err instanceof Error ? err.message : '启动失败') + setIsHealthy(false) + if (!restartingRef.current) { + restartingRef.current = true + start() + } } } } - init() + if (!startedRef.current) { + startedRef.current = true + start() + } + + const interval = setInterval(checkStatus, 2000) return () => { mounted = false + clearInterval(interval) window.electronAPI.xcOpenCodeWebStop() - setIsRunning(false) + startedRef.current = false } }, []) return ( -
-
-

- OpenCode -

- - {isRunning ? '运行中' : '启动中...'} - - {error && ( - {error} - )} -
- {isRunning && ( +
+ + {!isHealthy && ( +
+
+
+ )} + {isHealthy && (