/** * fun 结点:根据描述词(prompt)用 python/imagedl 搜索并下载一张图片到 savePath * 入参:prompt(描述词), savePath(相对路径时基于 folderPath) */ const path = require('path') const fs = require('fs') const { spawnSync } = require('child_process') const configPath = process.env.STATIC_ROOT ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js') : path.join(__dirname, '..', '..', '..', '..', 'configs', 'config.js') const projectRoot = path.dirname(path.dirname(path.resolve(configPath))) const config = fs.existsSync(configPath) ? require(configPath) : {} const scriptPath = path.join(projectRoot, 'python', 'scripts', 'download-img-by-prompt.py') const imagedlParent = path.join(projectRoot, 'python') const imagedlRequirements = path.join(projectRoot, 'python', 'imagedl', 'requirements.txt') function getPythonPath() { const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64') const envPy = path.join(base, 'env', 'Scripts', 'python.exe') const scriptsPy = path.join(base, 'Scripts', 'python.exe') const pyEmbedded = path.join(base, 'py', 'python.exe') if (fs.existsSync(envPy)) return envPy if (fs.existsSync(scriptsPy)) return scriptsPy if (fs.existsSync(pyEmbedded)) return pyEmbedded return 'python' } function buildSavePath(savePath, folderPath) { if (!savePath || typeof savePath !== 'string') return null const trimmed = savePath.trim() if (path.isAbsolute(trimmed) || trimmed.match(/^[A-Za-z]:/)) return trimmed return folderPath ? path.join(folderPath, trimmed) : path.resolve(projectRoot, trimmed) } async function executeDownloadImg({ prompt, savePath, folderPath }) { if (!prompt || typeof prompt !== 'string' || !prompt.trim()) return { success: false, error: 'download-img 缺少 prompt 参数' } if (savePath == null) return { success: false, error: 'download-img 缺少 savePath 参数' } const absolutePath = buildSavePath(String(savePath).trim(), folderPath) if (!absolutePath) return { success: false, error: 'download-img savePath 无效' } if (!fs.existsSync(scriptPath)) return { success: false, error: `脚本不存在: ${scriptPath}` } const pythonPath = getPythonPath() const args = [scriptPath, '--prompt', prompt.trim(), '--save-path', absolutePath.replace(/\\/g, '/')] const runScript = () => spawnSync(pythonPath, args, { encoding: 'utf-8', timeout: 120000, env: { ...process.env, PYTHONIOENCODING: 'utf-8', IMAGEDL_PARENT: imagedlParent }, cwd: projectRoot }) let r = runScript() let out = (r.stdout || '').trim() const err = (r.stderr || '').trim() const fullOut = out + (err ? '\n' + err : '') if (r.status !== 0 && fs.existsSync(imagedlRequirements) && (fullOut.includes('No module named') || fullOut.includes('ModuleNotFoundError'))) { spawnSync(pythonPath, ['-m', 'pip', 'install', '-r', imagedlRequirements, '-q'], { encoding: 'utf-8', timeout: 180000, cwd: projectRoot }) r = runScript() out = (r.stdout || '').trim() } if (r.status !== 0) { return { success: false, error: (r.stderr || r.stdout || '').trim() || 'download-img 执行失败' } } // stdout 可能包含进度条等,取最后一行以 { 开头的行作为 JSON const lines = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean) let jsonStr = lines[lines.length - 1] if (!jsonStr || !jsonStr.startsWith('{')) { const lastJson = lines.filter(l => l.startsWith('{')).pop() jsonStr = lastJson || out } let result try { result = JSON.parse(jsonStr) } catch (_) { return { success: false, error: out.slice(-300) || '无法解析输出' } } if (!result.success) return { success: false, error: result.error || '未下载到图片' } return { success: true, path: result.path } } module.exports = { executeDownloadImg }