topfans/docs/dify/laser_card_variants_v1.yml
Lenticular Studio Agent af7908e72e feat: 接入微达API中转站,重构镭射卡生图流程
- 替换中转站从 xbcl.link 到 weda.cc
- prompt 模板改为镭射卡全图生成(去掉 6 层合成/抠图依赖)
- 4 路并发调用 + 原图展示 = 5 张 variant
- 前端提示词中译英支持
- 全局 Vue errorHandler
- WebSocket 鉴权失败跳登录
- 删除已弃用的 laserCompositor 微服务

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

575 lines
23 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

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.

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.6
workflow:
conversation_variables: []
environment_variables:
- description: 'laser-compositor 合成服务地址'
name: LASER_COMPOSITOR_HOST
value: 'http://host.docker.internal:18090/api/v1/laser'
value_type: string
- description: 'MiniMax API 密钥'
name: MINIMAX_API_KEY
value: 'sk-api-oezuuNMr5iwPdlJ1JgTJTSzhMhGtaUR5Odjjg0ZqVQ7MoMIqLuE_ginMWRkNiAiDgMY6MvTVkYCWSQ8SK1-LuldrFmohCHxCgZIbxsFYr9zxA8z08Eb8nbo'
value_type: string
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 → code-param
- 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
# code-param → http-bg-all一次生成 5 张背景图)
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: http-request
id: code-param-source-http-bg-all-target
source: code-param
sourceHandle: source
target: http-bg-all
targetHandle: target
type: custom
zIndex: 0
# code-param → http-overlay-all一次生成 5 张装饰图)
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: http-request
id: code-param-source-http-overlay-all-target
source: code-param
sourceHandle: source
target: http-overlay-all
targetHandle: target
type: custom
zIndex: 0
# code-param → code-prepare传递 variants 元数据)
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: code
id: code-param-source-code-prepare-target
source: code-param
sourceHandle: source
target: code-prepare
targetHandle: target
type: custom
zIndex: 0
# http-bg-all → code-prepare
- data:
isInIteration: false
isInLoop: false
sourceType: http-request
targetType: code
id: http-bg-all-source-code-prepare-target
source: http-bg-all
sourceHandle: source
target: code-prepare
targetHandle: target
type: custom
zIndex: 0
# http-overlay-all → code-prepare
- data:
isInIteration: false
isInLoop: false
sourceType: http-request
targetType: code
id: http-overlay-all-source-code-prepare-target
source: http-overlay-all
sourceHandle: source
target: code-prepare
targetHandle: target
type: custom
zIndex: 0
# code-prepare → loop-variants
- data:
isInIteration: false
isInLoop: false
sourceType: code
targetType: iteration
id: code-prepare-source-loop-variants-target
source: code-prepare
sourceHandle: source
target: loop-variants
targetHandle: target
type: custom
zIndex: 0
# loop-variants → code-agg
- 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
# code-agg → end
- 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 → code-call-compositor直接合成bg/overlay 已预生成)
- data:
isInIteration: true
iteration_id: loop-variants
isInLoop: false
sourceType: start
targetType: code
id: iter-start-source-code-call-compositor-target
source: iter-start
sourceHandle: source
target: code-call-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
# ================================================================
# 代码节点 — 参数展开(同时输出合并的 prompt 字符串)
# ================================================================
- 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 idx, pc in enumerate(preset_list):\n if pc in config_map:\n cfg = config_map[pc]\n variants.append({\n \"preset_id\": pc,\n \"variant_index\": idx,\n \"grating_config\": cfg.get(\"grating_config\", {}),\n \"bg_prompt\": cfg.get(\"bg_prompt\", \"\"),\n \"overlay_prompt\": cfg.get(\"overlay_prompt\", \"\"),\n })\n bg_parts = [v.get(\"bg_prompt\", \"\") or \"\" for v in variants]\n ov_parts = [v.get(\"overlay_prompt\", \"\") or \"\" for v in variants]\n style_names = {\"dream\": \"梦幻\", \"classic\": \"经典\", \"holoFull\": \"全息炫彩\", \"ice\": \"冰晶\", \"sunset\": \"落日\"}\n style_list = [style_names.get(v[\"preset_id\"], v[\"preset_id\"]) for v in variants]\n bg_descs = [bp if bp else f\"{sn}风格渐变背景\" for sn, bp in zip(style_list, bg_parts)]\n ov_descs = [op if op else f\"柔和光晕\" for sn, op in zip(style_list, ov_parts)]\n bg_prompts_all = \" | \".join(bg_descs)\n overlay_prompts_all = \" | \".join(ov_descs)\n return {\n \"variants\": variants,\n \"variant_count\": len(variants),\n \"bg_prompts_all\": bg_prompts_all,\n \"overlay_prompts_all\": overlay_prompts_all,\n }\n"
code_language: python3
dependencies: []
desc: '解析 JSON 字符串 → 构建 variants 列表 + 合并 prompt'
outputs:
variant_count:
children: null
type: number
variants:
children: null
type: array[object]
bg_prompts_all:
children: null
type: string
overlay_prompts_all:
children: null
type: string
selected: false
title: 参数展开
type: code
variables:
- value_selector:
- start
- preset_codes
variable: preset_codes
- value_selector:
- start
- render_configs
variable: render_configs
height: 120
id: code-param
position:
x: 320
y: 300
positionAbsolute:
x: 320
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 前置 HTTP — 一次生成 5 张背景图 (n=5)
# ================================================================
- data:
authorization:
config:
api_key: '{{#env.MINIMAX_API_KEY#}}'
type: bearer
type: api-key
body:
data: '{"model": "image-01", "prompt": "Generate 5 distinct background images based on these style directions. Each background must be a clean empty backdrop (no people, no products, no holographic card objects, no product photography). Laser holographic effects will be applied in post-processing, so focus only on the artistic background direction. Background directions: {{#code-param.bg_prompts_all#}}", "aspect_ratio": "3:4", "n": 5, "prompt_optimizer": true, "response_format": "url"}'
type: json
desc: '一次生成 5 种不同风格的背景图(干净背景,镭射效果后处理)'
headers: 'Content-Type: application/json'
isInIteration: false
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 10
max_read_timeout: 200
max_write_timeout: 30
title: MiniMax 批量背景图
type: http-request
url: 'https://api.minimaxi.com/v1/image_generation'
variables:
- value_selector:
- code-param
- bg_prompts_all
variable: bg_prompts_all
height: 93
id: http-bg-all
position:
x: 610
y: 180
positionAbsolute:
x: 610
y: 180
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 前置 HTTP — 一次生成 5 张装饰图 (n=5)
# ================================================================
- data:
authorization:
config:
api_key: '{{#env.MINIMAX_API_KEY#}}'
type: bearer
type: api-key
body:
data: '{"model": "image-01", "prompt": "Generate 5 distinct transparent overlay decoration layers. Each overlay must be a clean transparent decoration on empty background (no people, no products, no holographic card objects). Soft glows, micro-particles, light flow effects that enhance atmosphere without occluding main subject. Overlay directions: {{#code-param.overlay_prompts_all#}}", "aspect_ratio": "3:4", "n": 5, "prompt_optimizer": true, "response_format": "url"}'
type: json
desc: '一次生成 5 种不同风格的装饰层(透明叠加,不承载主题,镭射效果由 compositor 保证)'
headers: 'Content-Type: application/json'
isInIteration: false
method: post
params: ''
selected: false
timeout:
max_connect_timeout: 10
max_read_timeout: 200
max_write_timeout: 30
title: MiniMax 批量装饰图
type: http-request
url: 'https://api.minimaxi.com/v1/image_generation'
variables:
- value_selector:
- code-param
- overlay_prompts_all
variable: overlay_prompts_all
height: 93
id: http-overlay-all
position:
x: 610
y: 420
positionAbsolute:
x: 610
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 代码节点 — 将 bg/overlay URLs 与 variants 配对
# ================================================================
- data:
code: "import json\n\ndef main(bg_body: str, overlay_body: str, variants: list) -> dict:\n # 解析 MiniMax 批量响应,提取 5 张图 URL\n bg_data = json.loads(bg_body) if isinstance(bg_body, str) else bg_body\n bg_urls = (bg_data.get(\"data\") or {}).get(\"image_urls\", [])\n ov_data = json.loads(overlay_body) if isinstance(overlay_body, str) else overlay_body\n overlay_urls = (ov_data.get(\"data\") or {}).get(\"image_urls\", [])\n\n enriched = []\n for i, v in enumerate(variants):\n enriched.append({\n \"preset_id\": v.get(\"preset_id\", f\"v_{i}\"),\n \"variant_index\": v.get(\"variant_index\", i),\n \"grating_config\": v.get(\"grating_config\", {}),\n \"bg_prompt\": v.get(\"bg_prompt\", \"\"),\n \"overlay_prompt\": v.get(\"overlay_prompt\", \"\"),\n \"bg_url\": bg_urls[i] if i < len(bg_urls) else \"\",\n \"overlay_url\": overlay_urls[i] if i < len(overlay_urls) else \"\",\n })\n return {\"enriched_variants\": enriched}\n"
code_language: python3
dependencies: []
desc: '解析 MiniMax 批量响应 → 将 5 bg_urls + 5 overlay_urls 配对到每个 variant'
outputs:
enriched_variants:
children: null
type: array[object]
selected: false
title: 批量结果配对
type: code
variables:
- value_selector:
- http-overlay-all
- body
variable: overlay_body
- value_selector:
- http-bg-all
- body
variable: bg_body
- value_selector:
- code-param
- variants
variable: variants
height: 84
id: code-prepare
position:
x: 900
y: 300
positionAbsolute:
x: 900
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
# ================================================================
# 循环节点(并行 5
# ================================================================
- data:
desc: '对每个 variant 并行合成最终镭射卡'
error_handle_mode: terminated
height: 351
iterator_selector:
- code-prepare
- enriched_variants
is_parallel: true
output_selector:
- code-call-compositor
- compose_result
output_type: array[string]
parallel_nums: 5
selected: false
startNodeType: start
start_node_id: iter-start
title: 遍历 Variants并行5
type: iteration
width: 953
height: 351
id: loop-variants
position:
x: 1200
y: 300
positionAbsolute:
x: 1200
y: 300
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 953
zIndex: 1
# ================================================================
# 循环内部 — 开始
# ================================================================
- data:
desc: 当前 variant_item含预生成的 bg_url / overlay_url
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: 1287
y: 420
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
zIndex: 1001
# ================================================================
# 循环内部 — Code 节点:调 compositor 合成
# 直接从 enriched item 读取 bg_url / overlay_url不再调 MiniMax
# ================================================================
- data:
code: "import json\nimport requests\n\ndef main(\n cutout_url: str,\n item: dict,\n compositor_host: str\n) -> dict:\n \"\"\"\n 从 enriched item 读取预生成的 bg_url / overlay_url\n 调 laser-compositor /compose 合成镭射卡。\n \"\"\"\n preset_id = item.get(\"preset_id\", \"unknown\")\n variant_index = item.get(\"variant_index\", 0)\n grating_config = item.get(\"grating_config\", {})\n bg_url = item.get(\"bg_url\", \"\")\n overlay_url = item.get(\"overlay_url\", \"\")\n\n compose_body = {\n \"background_url\": bg_url,\n \"cutout_url\": cutout_url,\n \"overlay_url\": overlay_url,\n \"grating_config\": grating_config,\n \"export_width\": 450,\n \"export_height\": 600,\n \"variant_index\": variant_index,\n \"output_oss_key\": f\"laser-card/dify/{preset_id}\",\n }\n\n try:\n resp = requests.post(\n f\"{compositor_host}/compose\",\n json=compose_body,\n timeout=60,\n )\n resp.raise_for_status()\n compose_resp = resp.json()\n except Exception as e:\n return {\"compose_result\": json.dumps({\n \"status\": \"failed\",\n \"error\": f\"compositor call failed: {e}\",\n \"preset_id\": preset_id,\n \"variant_index\": variant_index,\n \"width\": 450,\n \"height\": 600,\n }, ensure_ascii=False)}\n\n compose_resp[\"preset_id\"] = preset_id\n return {\"compose_result\": json.dumps(compose_resp, ensure_ascii=False)}\n"
code_language: python3
dependencies:
- name: requests
version: 2.32.3
desc: '用预生成的 bg_url/overlay_url → 调 compositor 合成 → 注入 preset_id'
outputs:
compose_result:
children: null
type: string
selected: false
title: 调 compositor 合成(批量优化版)
type: code
variables:
- value_selector:
- start
- cutout_url
variable: cutout_url
- value_selector:
- loop-variants
- item
variable: item
- value_selector:
- env
- LASER_COMPOSITOR_HOST
variable: compositor_host
extent: parent
height: 93
id: code-call-compositor
parentId: loop-variants
position:
x: 957
y: 120
positionAbsolute:
x: 1767
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\"{item.get('preset_id', f'variant_{i}')} 合成失败: {item.get('error', 'unknown')}\")\n return {\n \"status\": \"succeeded\" if len(variants) > 0 else \"failed\",\n \"variants\": json.dumps(variants, ensure_ascii=False),\n \"warnings\": json.dumps(warnings, ensure_ascii=False),\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