#!/usr/bin/env node /** * 一键更新 Dify workflow 的【背景层】prompt — 改为「用户描述主导,默认前缀弱化」 * * 触发场景: * - 用户描述(AI 生成描述)在 Dify 工作流的 prompt 模板中被默认前缀淹没 * - 见 frontend/composables/useLaserDifyGenerate.js 的 resolveRenderConfigs 注释: * 前端已加权 userPrompt 进 bg_prompt,后端 controller 也有兜底 helper, * 但 Dify 模板自身的固定前缀需要同步改,才能让用户描述真正主导生图。 * * 用法: * 1) 在 Dify 控制台 → Settings → Account → 生成 Personal Access Token(管理员权限) * 2) 运行: DIFY_BASE_URL=https://api.dify.ai/v1 DIFY_ADMIN_TOKEN=pat-xxx \ * node patch_dify_bg_prompt.mjs * * app_id 在 URL 里: https://your-dify/app//workflow * * 安全约束: * - PAT 仅通过环境变量传入,不写入文件 / 不打印到日志 * - 不直接覆盖线上 App,而是导入为新 App,人工在控制台决定切换时机 * * 脚本流程: * 1) GET /apps/{app_id}/export → 下载当前 DSL * 2) 在 workflow.graph.nodes 中找到 title === "MiniMax 批量背景图" 的节点 * 3) 把 body.data.prompt 改成新模板 * 4) POST /apps/import → 创建新 App(避免直接覆盖线上) * 5) 输出新 App 的 ID,您可选择: * a) 直接把新 App 当作 laser_card_variants_v2 部署 * b) 或者导出 DSL 后手工在控制台替换原 App */ import fs from 'node:fs/promises' import path from 'node:path' const DIFY_BASE_URL = process.env.DIFY_BASE_URL || 'http://localhost/v1' const DIFY_ADMIN_TOKEN = process.env.DIFY_ADMIN_TOKEN // 新背景层 prompt 模板 — 用户描述主导,默认前缀弱化 // 与 docs/dify/laser_card_variants_v1.yml 的 http-bg-all 节点 body.data.prompt 保持一致 const NEW_BG_PROMPT = '以用户描述为主导生成主视觉。背景描述(必须遵循,不可偏离): {{#code-param.bg_prompts_all#}}。辅助元素: 镭射卡金属底质感(弱化处理,不喧宾夺主), 丰富色彩层次, 优雅渐变光影。' const TARGET_NODE_TITLE = 'MiniMax 批量背景图' if (!DIFY_ADMIN_TOKEN) { console.error('❌ Missing DIFY_ADMIN_TOKEN env var (PAT with admin scope)') console.error(' 在 Dify 控制台 → Settings → Account → 生成 Personal Access Token') process.exit(1) } const appId = process.argv[2] if (!appId) { console.error('❌ Usage: node patch_dify_bg_prompt.mjs ') console.error(' app_id 在 URL 里: https://your-dify/app//workflow') process.exit(1) } const headers = { Authorization: `Bearer ${DIFY_ADMIN_TOKEN}`, 'Content-Type': 'application/json', } async function api(method, urlPath, body) { const url = `${DIFY_BASE_URL.replace(/\/$/, '')}${urlPath}` const init = { method, headers } if (body) init.body = JSON.stringify(body) const res = await fetch(url, init) const text = await res.text() if (!res.ok) { throw new Error(`HTTP ${res.status} ${method} ${urlPath}\n${text.slice(0, 500)}`) } return text ? JSON.parse(text) : null } async function main() { console.log(`▶ Exporting app ${appId}...`) const dsl = await api('GET', `/apps/${appId}/export`) const nodes = dsl?.workflow?.graph?.nodes || [] const bgNode = nodes.find((n) => n?.data?.title === TARGET_NODE_TITLE) if (!bgNode) { console.error(`❌ 未找到 "${TARGET_NODE_TITLE}" 节点,请检查 app_id 或确认 workflow 未被改名`) console.error(' 当前 App 包含的节点 title:') nodes.forEach((n) => console.error(` - ${n?.data?.title || n?.id}`)) process.exit(2) } console.log(`▶ 找到节点 ${bgNode.id},更新 prompt...`) const oldData = bgNode.data let oldPrompt = '' try { const parsed = JSON.parse(oldData.body?.data || '{}') oldPrompt = parsed.prompt || '' } catch { console.warn('⚠️ 现有 body.data 不是合法 JSON,跳过 oldPrompt 对比') } console.log(` 旧 prompt: ${oldPrompt.slice(0, 80)}${oldPrompt.length > 80 ? '...' : ''}`) console.log(` 新 prompt: ${NEW_BG_PROMPT.slice(0, 80)}...`) if (oldPrompt === NEW_BG_PROMPT) { console.log('') console.log('ℹ️ 旧 prompt 与新 prompt 完全一致,无需更新') console.log(' 但仍会执行导入步骤,确保 DSL 其它字段同步') } // 构造新 body.data(必须保持 JSON 字符串格式,这是 Dify 节点存储方式) const newBodyData = JSON.stringify({ model: 'image-01', prompt: NEW_BG_PROMPT, aspect_ratio: '3:4', n: 5, prompt_optimizer: true, response_format: 'url', }) oldData.body = { ...(oldData.body || {}), data: newBodyData } // 同时更新节点 desc,与 yml 文件注释保持一致 oldData.desc = '一次生成 5 种不同风格的背景图(用户描述占主导,默认前缀弱化)' console.log(`▶ Importing as new app...`) const imported = await api('POST', '/apps/import', { data: dsl, import_mode: 'yaml-content', }) const newAppId = imported?.app_id || imported?.id console.log('') console.log('✅ 完成!') console.log(` 新 App ID: ${newAppId}`) console.log(` 新 App 名称: ${imported?.name}`) console.log('') console.log('下一步:') console.log(' 1) 进 Dify 控制台打开新 App,确认背景层 prompt 已更新:') console.log(` "${NEW_BG_PROMPT.slice(0, 100)}..."`) console.log(' 2) 发布新 App,获取新 API Key') console.log(' 3) 替换 backend/.env 中的 DIFY_API_KEY') console.log(' 4) 重启 Gateway') console.log('') console.log('或者:把当前 App 在控制台手工改背景层节点的 prompt 也可以(改 title === "MiniMax 批量背景图" 的节点的 body.data.prompt 字段)') } main().catch((err) => { console.error('❌ 失败:', err.message) if (err.message?.includes('401')) { console.error(' 401 通常是 PAT 无效或权限不足,请确认 Personal Access Token 包含 apps:write 作用域') } if (err.message?.includes('404')) { console.error(' 404 通常是 app_id 错误或 DIFY_BASE_URL 路径不对') } process.exit(99) })