cherry-studio/src/main/services/ASRServerService.ts
2025-04-10 16:17:46 +08:00

136 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ChildProcess, spawn } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { IpcChannel } from '@shared/IpcChannel'
import { app, ipcMain } from 'electron'
import log from 'electron-log'
/**
* ASR服务器服务用于管理ASR服务器进程
*/
class ASRServerService {
private asrServerProcess: ChildProcess | null = null
/**
* 注册IPC处理程序
*/
public registerIpcHandlers(): void {
// 启动ASR服务器
ipcMain.handle(IpcChannel.Asr_StartServer, this.startServer.bind(this))
// 停止ASR服务器
ipcMain.handle(IpcChannel.Asr_StopServer, this.stopServer.bind(this))
}
/**
* 启动ASR服务器
* @returns Promise<{success: boolean, pid?: number, error?: string}>
*/
private async startServer(): Promise<{success: boolean, pid?: number, error?: string}> {
try {
if (this.asrServerProcess) {
return { success: true, pid: this.asrServerProcess.pid }
}
// 获取服务器文件路径
log.info('App path:', app.getAppPath())
// 在开发环境和生产环境中使用不同的路径
let serverPath = ''
let isExeFile = false
// 首先检查是否有打包后的exe文件
const exePath = path.join(app.getAppPath(), 'resources', 'cherry-asr-server.exe')
if (fs.existsSync(exePath)) {
serverPath = exePath
isExeFile = true
log.info('检测到打包后的exe文件:', serverPath)
} else if (process.env.NODE_ENV === 'development') {
// 开发环境
serverPath = path.join(app.getAppPath(), 'src', 'renderer', 'src', 'assets', 'asr-server', 'server.js')
} else {
// 生产环境
serverPath = path.join(app.getAppPath(), 'public', 'asr-server', 'server.js')
}
log.info('ASR服务器路径:', serverPath)
// 检查文件是否存在
if (!fs.existsSync(serverPath)) {
return { success: false, error: '服务器文件不存在' }
}
// 启动服务器进程
if (isExeFile) {
// 如果是exe文件直接启动
this.asrServerProcess = spawn(serverPath, [], {
stdio: 'pipe',
detached: false
})
} else {
// 如果是js文件使用node启动
this.asrServerProcess = spawn('node', [serverPath], {
stdio: 'pipe',
detached: false
})
}
// 处理服务器输出
this.asrServerProcess.stdout?.on('data', (data) => {
log.info(`[ASR Server] ${data.toString()}`)
})
this.asrServerProcess.stderr?.on('data', (data) => {
log.error(`[ASR Server Error] ${data.toString()}`)
})
// 处理服务器退出
this.asrServerProcess.on('close', (code) => {
log.info(`[ASR Server] 进程退出,退出码: ${code}`)
this.asrServerProcess = null
})
// 等待一段时间确保服务器启动
await new Promise(resolve => setTimeout(resolve, 1000))
return { success: true, pid: this.asrServerProcess.pid }
} catch (error) {
log.error('启动ASR服务器失败:', error)
return { success: false, error: (error as Error).message }
}
}
/**
* 停止ASR服务器
* @param _event IPC事件
* @param pid 进程ID
* @returns Promise<{success: boolean, error?: string}>
*/
private async stopServer(_event: Electron.IpcMainInvokeEvent, pid?: number): Promise<{success: boolean, error?: string}> {
try {
if (!this.asrServerProcess) {
return { success: true }
}
// 检查PID是否匹配
if (pid && this.asrServerProcess.pid !== pid) {
log.warn(`请求停止的PID (${pid}) 与当前运行的ASR服务器PID (${this.asrServerProcess.pid}) 不匹配`)
}
// 杀死进程
this.asrServerProcess.kill()
// 等待一段时间确保进程已经退出
await new Promise(resolve => setTimeout(resolve, 500))
this.asrServerProcess = null
return { success: true }
} catch (error) {
log.error('停止ASR服务器失败:', error)
return { success: false, error: (error as Error).message }
}
}
}
// 导出单例实例
export const asrServerService = new ASRServerService()