Files
XCDesktop/api/modules/ai/routes.ts
2026-03-08 01:34:54 +08:00

113 lines
3.1 KiB
TypeScript

import express, { type Request, type Response } from 'express'
import { spawn } from 'child_process'
import path from 'path'
import { fileURLToPath } from 'url'
import fs from 'fs/promises'
import fsSync from 'fs'
import { asyncHandler } from '../../utils/asyncHandler.js'
import { successResponse } from '../../utils/response.js'
import { resolveNotebookPath } from '../../utils/pathSafety.js'
import { ValidationError, NotFoundError, InternalError } from '../../../shared/errors/index.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const router = express.Router()
const PYTHON_TIMEOUT_MS = 30000
const spawnPythonWithTimeout = (
scriptPath: string,
args: string[],
stdinContent: string,
timeoutMs: number = PYTHON_TIMEOUT_MS
): Promise<string> => {
return new Promise((resolve, reject) => {
const pythonProcess = spawn('python', args, {
env: { ...process.env },
})
let stdout = ''
let stderr = ''
let timeoutId: NodeJS.Timeout | null = null
const cleanup = () => {
if (timeoutId) {
clearTimeout(timeoutId)
timeoutId = null
}
}
timeoutId = setTimeout(() => {
cleanup()
pythonProcess.kill()
reject(new Error(`Python script timed out after ${timeoutMs}ms`))
}, timeoutMs)
pythonProcess.stdout.on('data', (data) => {
stdout += data.toString()
})
pythonProcess.stderr.on('data', (data) => {
stderr += data.toString()
})
pythonProcess.on('close', (code) => {
cleanup()
if (code !== 0) {
reject(new Error(`Python script exited with code ${code}. Stderr: ${stderr}`))
} else {
resolve(stdout)
}
})
pythonProcess.on('error', (err) => {
cleanup()
reject(new Error(`Failed to start python process: ${err.message}`))
})
pythonProcess.stdin.write(stdinContent)
pythonProcess.stdin.end()
})
}
router.post(
'/doubao',
asyncHandler(async (req: Request, res: Response) => {
const { task, path: relPath } = req.body as { task?: string; path?: string }
if (!task) throw new ValidationError('Task is required')
if (!relPath) throw new ValidationError('Path is required')
const { fullPath } = resolveNotebookPath(relPath)
try {
await fs.access(fullPath)
} catch {
throw new NotFoundError('File not found')
}
const content = await fs.readFile(fullPath, 'utf-8')
const projectRoot = path.resolve(__dirname, '..', '..', '..')
const scriptPath = path.join(projectRoot, 'tools', 'doubao', 'main.py')
if (!fsSync.existsSync(scriptPath)) {
throw new InternalError(`Python script not found: ${scriptPath}`)
}
try {
const result = await spawnPythonWithTimeout(scriptPath, ['--task', task], content)
await fs.writeFile(fullPath, result, 'utf-8')
successResponse(res, { message: 'Task completed successfully' })
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error'
throw new InternalError(`AI task failed: ${message}`)
}
})
)
export const createAiRoutes = (): express.Router => router
export default createAiRoutes