| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- /**
- * fun 结点:根据描述词(prompt)用 python/imagedl 搜索并下载一张图片到 savePath
- * 入参:prompt(描述词), savePath(相对路径时基于 folderPath)
- */
- const path = require('path')
- const fs = require('fs')
- const { spawnSync } = require('child_process')
- const { getPythonExeFromConfig } = require('../../../python-exe-from-config.js')
- const configPath = process.env.STATIC_ROOT
- ? path.join(path.dirname(path.resolve(process.env.STATIC_ROOT)), 'config.js')
- : path.join(__dirname, '..', '..', '..', '..', 'config.js')
- const config = fs.existsSync(configPath) ? require(configPath) : {}
- const projectRoot = (config.projectRoot && fs.existsSync(config.projectRoot))
- ? config.projectRoot
- : path.dirname(path.resolve(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 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)
- }
- /** spawnSync 非 0 退出时拼详细日志(含启动失败、信号、截断后的 stdout/stderr) */
- function formatSpawnFailure (r, pythonPath, scriptPath) {
- const maxLen = 1500
- const trunc = (s) => {
- const t = String(s || '').trim()
- if (!t) return ''
- return t.length > maxLen ? `${t.slice(0, maxLen)}…(truncated)` : t
- }
- const parts = []
- if (pythonPath) {
- const abs = path.isAbsolute(pythonPath) || /^[A-Za-z]:/.test(pythonPath)
- const missing = abs && !fs.existsSync(pythonPath)
- parts.push(`python=${pythonPath}${missing ? ' (file missing)' : ''}`)
- }
- if (scriptPath) parts.push(`script=${scriptPath}`)
- if (r.error) {
- const e = r.error
- const code = e.code != null ? String(e.code) : ''
- parts.push(`spawnError${code ? `[${code}]` : ''}: ${e.message || e}`)
- }
- if (r.signal) parts.push(`signal=${r.signal}`)
- if (r.status !== null && r.status !== undefined) parts.push(`exitCode=${r.status}`)
- else parts.push('exitCode=(null)')
- const stderr = trunc(r.stderr)
- const stdout = trunc(r.stdout)
- if (stderr) parts.push(`stderr: ${stderr}`)
- if (stdout) parts.push(`stdout: ${stdout}`)
- return parts.length ? parts.join(' | ') : 'download-img 执行失败(无子进程输出)'
- }
- 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 = getPythonExeFromConfig(config)
- 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: formatSpawnFailure(r, pythonPath, scriptPath) }
- }
- // 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 (_) {
- const raw = String(out || '').trim()
- const tail = raw.length > 800 ? raw.slice(-800) : raw
- return { success: false, error: `无法解析 JSON 输出 | stdout(tail): ${tail || '(empty)'}` }
- }
- if (!result.success) return { success: false, error: result.error || '未下载到图片' }
- return { success: true, path: result.path }
- }
- module.exports = { executeDownloadImg }
|