# 打光功能实施文档 Version: 0.1 Status: Draft Scope: `popiart-node-canvas` Target area: bottom composer, image node controls only ## 1. 目标 在底部 composer 的图片节点控制区中新增「打光」入口,尺寸、布局、交互状态与现有「风格」「标记」「聚焦」「参考」保持一致。 该功能是图片节点专有能力,只在当前选中图片节点时出现或可用。这里的图片节点包含 `imageInput` 图片输入节点和 `nanoBanana` 图片生成节点。用户必须先上传图片,或当前图片节点已有可编辑图片素材后,才能点击「打光」按钮。点击后弹出「打光效果」面板,用户可以调整全局打光参数、选择主光源方向、开启轮廓光,并在智能模式中输入描述、上传打光参考图、选择预设打光效果。 当前版本不扩展到视频、音频、3D 或其它媒体节点。 ## 2. 用户体验 ### 2.1 底部入口 在底部 composer 顶部功能格子区域新增「打光」按钮: - 位置建议:放在「聚焦」之后、「参考」之前。 - 尺寸:与现有格子一致,当前实现约为 `h-14 w-16`。 - 可见范围: - 仅当前 composer 处于图片节点模式时显示,包括 `imageInput` 和 `nanoBanana`。 - 非图片节点、流程处理节点、多选状态、空创建状态不显示。 - 可点击条件: - 当前图片节点已经有至少一张上传图片或可编辑图片素材。 - `imageInput` 判断 `data.image` 是否存在。 - `nanoBanana` 判断 `draft.inputImages.length > 0`,并兼容当前节点已有 `outputImage` 或上游连接图片的场景。 - 如果没有图片,按钮保持禁用,不弹窗。 - 状态: - 默认:深色背景、灰色文字、灰色描边。 - Hover:提高描边和文字亮度。 - 激活:使用与其它 active chip 一致的 cyan 高亮。 - 不可用:没有上传图片时禁用,title 提示 `请先上传图片`。 - 文案: - 中文:`打光` - 图标:优先使用 lucide 图标,如 `Sun`, `Lightbulb`, `Sparkles` 或 `Flashlight`;如果项目当前没有 lucide,可先沿用现有符号风格,后续统一替换。 ### 2.2 弹窗基础模式 点击可用状态下的「打光」打开居中的「打光效果」弹窗。基础模式内容对应示意图三: - 标题:`打光效果` - 关闭按钮:右上角。 - 左侧预览区: - 顶部为视角切换:`透视` / `正面` - 中间为球形/空间光照预览图。 - 底部为 `重置参数`。 - 右侧参数区: - 分组标题:`全局` - `智能模式` 开关,默认关闭。 - `亮度` slider。 - 亮度百分比输入或只读显示,默认 `50%`。 - `颜色` 色块选择,默认白色。 - `主光源` 方向按钮: - `左侧` - `顶部` - `右侧` - `前方` - `底部` - `后方` - `轮廓光` 开关,默认关闭。 - 底部右侧: - 成本提示,如 `14` - 生成/应用按钮,沿用 composer 的上箭头视觉。 ### 2.3 智能模式 开启 `智能模式` 后弹窗横向扩展,对应示意图四: - 保留左侧预览区。 - 中间保留 `全局` 参数区。 - 右侧新增智能模式区: - 标题:`智能模式` - 文本输入框,占一整行宽度,placeholder: - `简单描述你想实现的打光效果,或者情绪风格` - `打光参考图` 上传按钮。 - `预设` 网格。 - 预设建议首批 8 个: - `过曝胶片` - `蓝色逆光` - `伦勃朗光` - `赛博朋克` - `落日迷幻` - `神秘暗调` - `黄金时刻` - `清晨冷灰` - 每个预设包含: - id - label - thumbnail - prompt fragment - structured lighting patch ## 3. 数据模型 新增打光参数类型,建议放在 `src/types/nodes.ts` 或单独 `src/types/lighting.ts` 后再导出。 ```ts export type LightingViewMode = "perspective" | "front"; export type LightingDirection = "left" | "top" | "right" | "front" | "bottom" | "back"; export interface LightingPreset { id: string; label: string; thumbnail: string; prompt: string; settings: Partial; } export interface LightingSettings { enabled: boolean; viewMode: LightingViewMode; smartMode: boolean; brightness: number; color: string; mainLightDirection: LightingDirection; rimLight: boolean; smartPrompt: string; referenceImage: string | null; selectedPresetId: string | null; } ``` 在 `NanoBananaNodeData` 增加: ```ts lighting?: LightingSettings; ``` 默认值建议放在 `src/store/utils/nodeDefaults.ts`: ```ts export const DEFAULT_LIGHTING_SETTINGS: LightingSettings = { enabled: false, viewMode: "perspective", smartMode: false, brightness: 50, color: "#ffffff", mainLightDirection: "front", rimLight: false, smartPrompt: "", referenceImage: null, selectedPresetId: null, }; ``` ## 4. 文件拆分建议 ### 4.1 新增组件 新增目录: ```text src/components/lighting/ ``` 建议文件: - `LightingEffectModal.tsx` - 弹窗容器和整体布局。 - `LightingPreview.tsx` - 左侧光照预览。 - 第一版可用 CSS/SVG/Canvas 静态模拟,不必接真实 3D。 - `LightingControls.tsx` - 亮度、颜色、方向、轮廓光。 - `LightingSmartPanel.tsx` - 智能模式输入、参考图、预设网格。 - `lightingPresets.ts` - 预设配置。 - `index.ts` - 统一导出。 ### 4.2 修改现有文件 - `src/components/composer/GenerationComposer.tsx` - 增加 active chip 类型:`"lighting"`。 - 在图片节点模式的功能格子区域插入「打光」按钮。 - 增加是否允许打光的派生状态,例如 `canOpenLightingPanel`。 - 当没有上传图片或可编辑图片素材时禁用按钮。 - 管理 `LightingEffectModal` 打开/关闭。 - 从当前 draft 或当前节点数据中读取 lighting。 - 应用弹窗保存结果到当前 composer draft。 - `src/types/nodes.ts` - 新增 `LightingSettings` 类型或从 `types/lighting.ts` 导入。 - `NanoBananaNodeData` 增加 `lighting?: LightingSettings`。 - `src/store/utils/nodeDefaults.ts` - 给图片节点补默认 `lighting`。 - `src/store/execution/nanoBananaExecutor.ts` - 将 `lighting` 转成 prompt 增强或 provider 参数。 - `src/components/__tests__/...` - 增加 composer 入口和弹窗交互测试。 ## 5. 与生成链路的关系 第一阶段不要改 provider 协议,先采用 prompt 增强方式。该增强只作用于图片节点生成: ```ts function buildLightingPrompt(settings: LightingSettings): string { if (!settings.enabled) return ""; // 根据方向、亮度、颜色、轮廓光、智能描述、预设 prompt 拼接自然语言。 } ``` 图片生成执行时: 1. 读取 `NanoBananaNodeData.inputPrompt`。 2. 读取 `NanoBananaNodeData.lighting`。 3. 确认当前节点有可编辑图片输入;没有图片时不启用 lighting。 4. 生成 lighting prompt fragment。 5. 将 fragment 附加到最终 prompt。 示例: ```text Lighting: front key light, 50% brightness, white color temperature, soft cinematic illumination, rim light disabled. ``` 如果智能模式开启: ```text Lighting style: {smartPrompt}. Use the selected lighting preset: {presetPrompt}. ``` 如果有参考图: - 第一版:把 reference image 加入 `inputImages` 或 `dynamicInputs` 前要谨慎,避免改变原始参考图语义。 - 推荐第一版只保存,不传给 provider。 - 第二阶段再支持「参考图作为打光参考」的 provider-specific 参数。 ## 6. 分阶段实施计划 ### Phase 1: 文档与类型 目标:先定义功能边界,不改变运行行为。 任务: - 新增本文档。 - 新增 `LightingSettings` 类型。 - 在图片节点数据结构中加入可选 `lighting`。 - 新增默认 lighting 配置。 验收: - TypeScript 类型通过。 - 旧 workflow 不需要迁移也能正常打开,因为 `lighting` 是可选字段。 ### Phase 2: 底部 composer 入口 目标:图片节点底部出现「打光」格子,并且只有上传图片后才能打开弹窗。 任务: - 扩展 `activeChip` 类型。 - 添加「打光」按钮,尺寸与其它按钮一致。 - 仅在图片节点模式显示「打光」按钮。 - 计算当前图片节点是否已经上传图片。 - 没有图片时禁用按钮并显示 `请先上传图片` 提示。 - 新增 `LightingEffectModal` 的基础 shell。 - 点击可用按钮打开弹窗。 - 关闭按钮可关闭弹窗。 验收: - 底部「打光」按钮和其它格子同宽同高。 - 非图片节点看不到「打光」按钮。 - 图片节点没有上传图片时,「打光」按钮不可点击。 - 图片节点上传图片后,「打光」按钮可点击。 - 按钮 active 状态可见。 - 弹窗打开和关闭稳定。 ### Phase 3: 基础参数 UI 目标:完成示意图三的基础模式。 任务: - 实现标题、关闭按钮、左右布局。 - 实现视角切换。 - 实现静态光照预览。 - 实现亮度 slider。 - 实现颜色选择。 - 实现主光源方向按钮组。 - 实现轮廓光开关。 - 实现重置参数。 - 实现保存/应用按钮。 验收: - 所有控件都有受控状态。 - 重置后恢复默认值。 - 应用后写入当前图片节点的 `data.lighting`。 - 关闭再打开能读回当前节点保存的值。 - 如果上传图片被移除,弹窗不能再次打开。 ### Phase 4: 智能模式 UI 目标:完成示意图四的智能模式完整面板。 任务: - 开启智能模式后扩展弹窗宽度。 - 添加智能描述输入框。 - 添加打光参考图上传按钮。 - 添加 8 个预设卡片。 - 点击预设后: - 设置 `selectedPresetId` - 合并预设 `settings` - 写入或追加 `smartPrompt` - 上传参考图后显示缩略状态。 验收: - 智能模式开关不会丢失基础参数。 - 预设选中状态清晰。 - 参考图可上传、替换、清除。 ### Phase 5: 生成执行接入 目标:打光参数实际影响图片生成。 任务: - 新增 `buildLightingPrompt(settings)` 工具函数。 - 在 `nanoBananaExecutor` 或 prompt 构造点追加 lighting fragment。 - 确保未启用 lighting 时生成结果与旧逻辑一致。 - 对智能模式、预设、亮度、方向分别加单元测试。 验收: - `enabled: false` 不改变请求 prompt。 - `enabled: true` 会附加明确的 lighting 描述。 - 智能模式描述会进入最终 prompt。 - 预设 prompt 会进入最终 prompt。 ### Phase 6: 测试与回归 目标:确保不会破坏 composer、节点编辑和保存加载。 建议测试: - `GenerationComposer.test.tsx` - 渲染打光按钮。 - 点击打开弹窗。 - 应用参数调用 `updateNodeData`。 - `LightingEffectModal.test.tsx` - 控件初始值。 - 亮度/颜色/方向/轮廓光交互。 - 智能模式展开。 - 预设选择。 - `nodeDefaults.test.ts` - 图片节点默认包含 lighting。 - `nanoBananaExecutor.test.ts` - lighting prompt 拼接。 - `workflowStore.integration.test.ts` - 保存/加载旧 workflow 不报错。 ## 7. 实现细节 ### 7.1 Composer 状态同步 现有 composer 有本地 draft 和 dirty fields。打光弹窗建议不要直接每次改控件就写 store,而是: 1. 打开弹窗时从当前节点读取 lighting。 2. 弹窗内部维护临时状态。 3. 点击应用后调用 composer 的 patch/update。 4. 如果当前没有图片节点,或当前图片节点没有上传图片,则不允许打开弹窗,也不写入 draft。 这样可以避免用户在弹窗里试参数时频繁污染 undo history。 ### 7.2 旧工作流兼容 读取时始终使用: ```ts const lighting = data.lighting ?? DEFAULT_LIGHTING_SETTINGS; ``` 保存时只有用户打开并应用后才写入,避免旧工作流文件无意义膨胀。 ### 7.3 预览实现策略 第一版建议用轻量 CSS/SVG: - 一个圆形背景模拟空间球。 - 一张竖向图片卡片作为主体。 - 根据 `mainLightDirection` 改变光束渐变角度。 - 根据 `brightness` 改变光束 opacity。 - 根据 `color` 改变光束颜色。 - `rimLight` 开启时给主体卡片增加边缘高光。 后续如果需要真实 3D 预览,再引入 Three.js,但第一版不建议增加复杂度。 ## 8. 风险与注意事项 - 不要把打光功能做成隐藏的新节点。当前需求是图片节点的 composer 参数入口,不是图节点类型。 - 不要在空创建模式、视频节点、音频节点、3D 节点或流程处理节点上开放打光入口。 - 不要在没有上传图片时弹出打光面板。 - 不要让弹窗直接调用生成接口。生成仍然由现有 composer/节点执行按钮触发。 - 不要把参考图默认混入 `inputImages`,否则可能改变图片生成语义。 - 不要一次性重构整个 `GenerationComposer.tsx`。这个文件已经很大,应先最小接入,后续再拆。 - 注意中文文案编码,当前仓库已有部分乱码,新增文件必须使用 UTF-8。 ## 9. 推荐提交顺序 1. `docs: add lighting effect implementation plan` 2. `feat: add lighting settings types and defaults` 3. `feat: add lighting chip to generation composer` 4. `feat: add lighting effect modal basic controls` 5. `feat: add lighting smart mode presets` 6. `feat: apply lighting settings to image generation prompt` 7. `test: cover lighting composer and executor behavior` ## 10. 最小可交付版本 MVP 包含: - 图片节点底部「打光」格子。 - 未上传图片时按钮禁用。 - 上传图片后点击按钮弹出打光面板。 - 点击打开弹窗。 - 亮度、颜色、主光源方向、轮廓光。 - 参数保存到当前图片节点。 - 生成时把打光参数拼入 prompt。 智能模式、预设缩略图、参考图上传可以作为第二个增量交付。