topfans/docs/dify/laser_card_variants_v1.yml
2026-06-03 22:19:22 +08:00

482 lines
16 KiB
YAML

app:
description: 'Laser card AI 镭射卡 5-variant 生成器。前端已抠图,传入 cutout_url 直接合成。'
icon: 🤖
icon_background: '#FFEAD5'
mode: workflow
name: laser_card_variants_v1
use_icon_as_answer_icon: false
kind: app
version: 0.1.4
workflow:
conversation_variables: []
environment_variables:
- description: 'laser-compositor 合成服务地址'
name: LASER_COMPOSITOR_HOST
value: 'http://host.docker.internal:7000'
value_type: string
- description: 'MiniMax API 密钥'
name: MINIMAX_API_KEY
value: ''
value_type: secret
features:
file_upload:
image:
enabled: false
number_limits: 3
transfer_methods:
- local_file
- remote_url
opening_statement: ''
retriever_resource:
enabled: false
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
language: ''
voice: ''
graph:
edges:
# start → 参数展开
- data:
isInIteration: false
isInLoop: false
sourceType: start
targetType: code
id: start-source-code-param-target
source: start
sourceHandle: source
target: code-param
targetHandle: target
type: custom
zIndex: 0
# 参数展开 → 循环
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: iteration
id: code-param-source-loop-variants-target
source: code-param
sourceHandle: source
target: loop-variants
targetHandle: target
type: custom
zIndex: 0
# 循环 → 聚合
- data:
isInIteration: false
isInLoop: false
sourceType: iteration
targetType: code
id: loop-variants-source-code-agg-target
source: loop-variants
sourceHandle: source
target: code-agg
targetHandle: target
type: custom
zIndex: 0
# 聚合 → 结束
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: end
id: code-agg-source-end-target
source: code-agg
sourceHandle: source
target: end
targetHandle: target
type: custom
zIndex: 0
# === 循环内部 ===
# iter-start → MiniMax 背景
- data:
isInIteration: true
iteration_id: loop-variants
isInLoop: false
sourceType: start
targetType: http-request
id: iter-start-source-http-bg-target
source: iter-start
sourceHandle: source
target: http-bg
targetHandle: target
type: custom
zIndex: 1000
# MiniMax 背景 → MiniMax 装饰
- data:
isInIteration: true
iteration_id: loop-variants
isInLoop: false
sourceType: http-request
targetType: http-request
id: http-bg-source-http-overlay-target
source: http-bg
sourceHandle: source
target: http-overlay
targetHandle: target
type: custom
zIndex: 1000
# MiniMax 装饰 → compositor
- data:
isInIteration: true
iteration_id: loop-variants
isInLoop: false
sourceType: http-request
targetType: http-request
id: http-overlay-source-http-compositor-target
source: http-overlay
sourceHandle: source
target: http-compositor
targetHandle: target
type: custom
zIndex: 1000
nodes:
# ================================================================
# 开始节点
# ================================================================
- data:
desc: '接收抠图后的 cutout_url 和 generation 参数'
selected: false
title: 开始
type: start
variables:
- label: cutout_url
max_length: 2048
options: []
required: false
type: text-input
variable: cutout_url
- default: '["dream","classic","holoFull","ice","sunset"]'
label: preset_codes
max_length: 2048
options: []
required: false
type: text-input
variable: preset_codes
- label: render_configs
max_length: 20480
options: []
required: true
type: text-input
variable: render_configs
height: 150
id: start
position:
x: 30
y: 300
positionAbsolute:
x: 30
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 代码节点 — 参数展开
# ================================================================
- data:
code: "import json\n\ndef main(preset_codes: str, render_configs: str) -> dict:\n preset_list = []\n if preset_codes and preset_codes.strip():\n try:\n parsed = json.loads(preset_codes)\n if isinstance(parsed, list):\n preset_list = parsed\n except:\n pass\n if not preset_list:\n preset_list = [\"dream\", \"classic\", \"holoFull\", \"ice\", \"sunset\"]\n configs = []\n if render_configs and render_configs.strip():\n try:\n parsed = json.loads(render_configs)\n if isinstance(parsed, list):\n configs = parsed\n except:\n pass\n config_map = {}\n for rc in configs:\n if isinstance(rc, dict) and \"preset_id\" in rc:\n config_map[rc[\"preset_id\"]] = rc\n variants = []\n for pc in preset_list:\n if pc in config_map:\n cfg = config_map[pc]\n variants.append({\n \"preset_id\": pc,\n \"grating_config\": cfg.get(\"grating_config\", {}),\n \"bg_prompt\": cfg.get(\"bg_prompt\", \"\"),\n \"overlay_prompt\": cfg.get(\"overlay_prompt\", \"\"),\n })\n return {\n \"variants\": variants,\n \"variant_count\": len(variants),\n }\n"
code_language: python3
dependencies: []
desc: '解析 JSON 字符串 → 构建 variants 列表'
outputs:
variant_count:
children: null
type: number
variants:
children: null
type: array[object]
selected: false
title: 参数展开
type: code
variables:
- value_selector:
- start
- preset_codes
variable: preset_codes
- value_selector:
- start
- render_configs
variable: render_configs
height: 84
id: code-param
position:
x: 320
y: 300
positionAbsolute:
x: 320
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 循环节点
# ================================================================
- data:
desc: '对每个 variant 分别生成背景图、装饰图、合成最终镭射卡'
error_handle_mode: terminated
height: 351
iterator_selector:
- code-param
- variants
is_parallel: false
output_selector:
- http-compositor
- body
output_type: array[string]
parallel_nums: 1
selected: false
startNodeType: start
start_node_id: iter-start
title: 遍历 Variants
type: iteration
width: 953
height: 351
id: loop-variants
position:
x: 610
y: 300
positionAbsolute:
x: 610
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 953
zIndex: 1
# ================================================================
# 循环内部 — 开始
# ================================================================
- data:
desc: 当前 variant_item
isInIteration: true
iteration_id: loop-variants
selected: false
title: 循环项
type: start
variables: []
extent: parent
height: 54
id: iter-start
parentId: loop-variants
position:
x: 87
y: 120
positionAbsolute:
x: 697
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1001
# ================================================================
# 循环内部 — MiniMax 背景
# ================================================================
- data:
authorization:
config:
api_key: '{{#env.MINIMAX_API_KEY#}}'
type: bearer-api-key
body:
data: '{"model": "image-01", "prompt": "{{#loop-variants.item.bg_prompt#}}", "aspect_ratio": "3:4", "n": 1, "prompt_optimizer": true, "response_format": "url"}'
type: json
desc: '根据 bg_prompt 生成 3:4 背景'
headers: 'Content-Type: application/json'
isInIteration: true
iteration_id: loop-variants
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 10
max_read_timeout: 60
max_write_timeout: 20
title: MiniMax 背景图
type: http-request
url: 'https://api.minimaxi.com/v1/image_generation'
variables: []
extent: parent
height: 93
id: http-bg
parentId: loop-variants
position:
x: 377
y: 120
positionAbsolute:
x: 987
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1001
# ================================================================
# 循环内部 — MiniMax 装饰
# ================================================================
- data:
authorization:
config:
api_key: '{{#env.MINIMAX_API_KEY#}}'
type: bearer-api-key
body:
data: '{"model": "image-01", "prompt": "{{#loop-variants.item.overlay_prompt#}}", "aspect_ratio": "3:4", "n": 1, "prompt_optimizer": true, "response_format": "url"}'
type: json
desc: '根据 overlay_prompt 生成透明装饰层'
headers: 'Content-Type: application/json'
isInIteration: true
iteration_id: loop-variants
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 10
max_read_timeout: 60
max_write_timeout: 20
title: MiniMax 装饰图
type: http-request
url: 'https://api.minimaxi.com/v1/image_generation'
variables: []
extent: parent
height: 93
id: http-overlay
parentId: loop-variants
position:
x: 667
y: 120
positionAbsolute:
x: 1277
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1001
# ================================================================
# 循环内部 — compositor 合成
# ================================================================
- data:
authorization:
type: no-auth
body:
data: '{"background_url": "{{#http-bg.body.data.image_urls[0]#}}", "cutout_url": "{{#start.cutout_url#}}", "overlay_url": "{{#http-overlay.body.data.image_urls[0]#}}", "grating_config": {{#loop-variants.item.grating_config#}}, "export_width": 450, "export_height": 600, "output_oss_key": "laser-card/dify/{{#loop-variants.item.preset_id#}}"}'
type: json
desc: '调用 laser-compositor /compose 6层合成'
headers: 'Content-Type: application/json'
isInIteration: true
iteration_id: loop-variants
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 10
max_read_timeout: 60
max_write_timeout: 20
title: 合成 6 层镭射卡
type: http-request
url: '{{#env.LASER_COMPOSITOR_HOST#}}/compose'
variables: []
extent: parent
height: 93
id: http-compositor
parentId: loop-variants
position:
x: 957
y: 120
positionAbsolute:
x: 1567
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1001
# ================================================================
# 代码节点 — 聚合输出
# ================================================================
- data:
code: "import json\n\ndef main(loop_output: list) -> dict:\n variants = []\n warnings = []\n for i, item in enumerate(loop_output or []):\n if isinstance(item, str):\n try:\n item = json.loads(item)\n except:\n pass\n if isinstance(item, dict) and item.get(\"status\") == \"succeeded\":\n variants.append({\n \"preset_id\": item.get(\"preset_id\", f\"variant_{i}\"),\n \"oss_key\": item.get(\"oss_key\", \"\"),\n \"signed_url\": item.get(\"signed_url\", \"\"),\n \"width\": item.get(\"width\", 450),\n \"height\": item.get(\"height\", 600),\n })\n else:\n warnings.append(f\"variant_{i} 合成失败\")\n return {\n \"status\": \"succeeded\" if len(variants) > 0 else \"failed\",\n \"variants\": json.dumps(variants),\n \"warnings\": json.dumps(warnings),\n }\n"
code_language: python3
dependencies: []
desc: '收集循环结果,输出统一 JSON'
outputs:
status:
children: null
type: string
variants:
children: null
type: string
warnings:
children: null
type: string
selected: false
title: 聚合输出
type: code
variables:
- value_selector:
- loop-variants
- output
variable: loop_output
height: 84
id: code-agg
position:
x: 1610
y: 300
positionAbsolute:
x: 1610
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 结束节点
# ================================================================
- data:
desc: '返回 5 张镭射卡 signed URL'
outputs:
- value_selector:
- code-agg
- status
variable: status
- value_selector:
- code-agg
- variants
variable: variants
- value_selector:
- code-agg
- warnings
variable: warnings
selected: false
title: 结束
type: end
height: 90
id: end
position:
x: 1900
y: 300
positionAbsolute:
x: 1900
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244