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 => { 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