113 lines
3.1 KiB
TypeScript
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
|