topfans/backend/scripts/patch_dify_bg_prompt.mjs
Lenticular Studio Agent 67cf3d4177 chore: 清理 laserCompositor 微服务残留
- 删除已弃用的 compositor_client.go
- 删除激光合成微服务代码
- 添加 gateway 合成控制器和测试文件
- 添加 Dify prompt 补丁脚本

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-23 22:44:03 +08:00

151 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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>
*
* app_id 在 URL 里: https://your-dify/app/<APP_ID>/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 <app_id>')
console.error(' app_id 在 URL 里: https://your-dify/app/<APP_ID>/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)
})