- 删除已弃用的 compositor_client.go - 删除激光合成微服务代码 - 添加 gateway 合成控制器和测试文件 - 添加 Dify prompt 补丁脚本 Co-Authored-By: Claude <noreply@anthropic.com>
151 lines
5.9 KiB
JavaScript
151 lines
5.9 KiB
JavaScript
#!/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)
|
||
}) |