You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

8.6 KiB

多角度编辑器技术实现记录

背景

本功能在 TEST-s 分支中为图片输入链路增加“图片多角度”生成能力。当前入口已从底部固定生成控制台迁移到画布连接菜单:用户创建图片输入节点并上传图片后,从图片输出端拖拽打开菜单,在“图片对比”下方点击“图片多角度”,弹出多角度编辑器,配置视角后生成新的图片生成节点。

核心文件:

  • src/components/MultiAngleEditor.tsx
  • src/components/ConnectionDropMenu.tsx
  • src/components/WorkflowCanvas.tsx
  • src/components/composer/GenerationComposer.tsx
  • src/i18n/index.tsx
  • src/components/__tests__/MultiAngleEditor.test.tsx
  • src/components/__tests__/ConnectionDropMenu.test.tsx
  • src/components/__tests__/WorkflowCanvas.test.tsx
  • src/components/__tests__/GenerationComposer.test.tsx

当前功能范围

已实现:

  • 从底部控制台移除“多角度”入口。
  • 在图片输出拖拽菜单中新增“图片多角度”动作,位置位于“图片对比”下方。
  • 点击“图片多角度”后打开独立浮层编辑器。
  • 编辑器支持自定义、多种预设、地球仪视角仪表、摄像头视线、图片预览、提示词开关。
  • 点击“生成多角度”后创建新的 nanoBanana 图片生成节点。
  • 新节点会写入原图片作为 inputImages,并写入非空多角度提示词。
  • 新节点会和源图片节点建立图片连线,并立即调用 regenerateNode(newNodeId)
  • 多角度提示词不包含外部平台名称。

未实现:

  • 真正 3D 场景渲染。当前地球仪是 SVG 视角仪表。
  • 批量多角度生成。
  • 将多角度参数持久化到源图片节点数据。
  • 针对不同模型或服务商的提示词适配层。

入口迁移

旧实现把“多角度”作为底部控制台的 chip:

风格 / 标记 / 聚焦 / 多角度

本次调整后,底部控制台只保留常规生成和参考图能力,多角度不再在 GenerationComposer 中渲染。新的入口在 ConnectionDropMenu 的图片目标菜单中:

export type MenuAction = "multiAngleImage";

IMAGE_TARGET_OPTIONS 中新增动作项,放在 imageCompare 后:

{
  type: "multiAngleImage",
  label: "Image Multi-Angle",
  isAction: true,
  icon: ...
}

中文文案由 src/i18n/index.tsx 提供:

connectionMenu.multiAngleImage = 图片多角度

状态模型

多角度配置集中在 src/components/MultiAngleEditor.tsx

export interface MultiAngleSettings {
  preset: MultiAnglePresetId;
  horizontal: number;
  vertical: MultiAngleVertical;
  zoom: MultiAngleZoom;
  includePrompt: boolean;
  promptText: string;
}

字段含义:

  • preset:当前预设 ID。
  • horizontal:水平环绕角度,范围 0-315
  • vertical:垂直俯仰,可选值 -30 / 0 / 30 / 60
  • zoom:景别缩放,0=全景1=中景2=特写
  • includePrompt:是否启用额外提示词输入框。
  • promptText:用户输入或预设写入的额外提示词。

默认值:

export const DEFAULT_MULTI_ANGLE_SETTINGS = {
  preset: "custom",
  horizontal: 0,
  vertical: -30,
  zoom: 1,
  includePrompt: false,
  promptText: "",
};

预设配置

预设由 MULTI_ANGLE_PRESET_SETTINGS 统一维护。

预设 horizontal vertical zoom includePrompt promptText
自定义 0 -30 中景 false
鱼眼视角 0 30 特写 true 超近距离鱼眼镜头,强烈超广角透视,圆形画面边界与黑色暗角,明显桶形畸变,画面中心主体放大,边缘向外弯曲拉伸
倾斜视角 45 -30 中景 true dutch angle, tilted frame
正面俯拍 0 60 中景 false
正面仰拍 0 -30 中景 false
全景俯拍 45 30 全景 false
背面视角 180 0 中景 false

点击预设会替换当前 MultiAngleSettings,并同步右侧参数、左侧地球仪、摄像头位置和提示词状态。

视角仪表

MultiAngleEditor 内部用 SVG 实现地球仪式视角仪表。

主要元素:

  • 地球仪网格:circle、多组 ellipse 和中心基准线。
  • 图片预览:始终在中心,仅受景别缩放影响。
  • 摄像头:独立高层级 SVG,避免被图片遮挡。
  • 视线:从摄像头连到图片中心。

水平环绕:

const horizontalRadians = (settings.horizontal * Math.PI) / 180;
const cameraX = 100 + Math.sin(horizontalRadians) * 78;

垂直俯仰:

const cameraY = 100 - (settings.vertical / 60) * 52;
const globePitchRotation = -(settings.vertical / 60) * 16;
const globePitchScaleY = 1 - Math.abs(settings.vertical) / 260;

图片本体不随垂直俯仰上下移动,只有摄像头和地球仪网格随参数变化。

鼠标交互

左侧地球仪区域支持拖拽:

  • 鼠标相对中心的角度换算为水平环绕。
  • 鼠标垂直位置吸附到 -30 / 0 / 30 / 60
  • 拖拽更新 MultiAngleSettings
  • 右侧滑块调整时,左侧摄像头和地球仪同步更新。

地球仪区域使用:

data-composer-local-pointer="true"

同时在事件回调中 stopPropagation(),避免拖拽被 React Flow 画布平移或选区逻辑截获。

提示词策略

多角度提示词由 buildMultiAnglePrompt(settings) 生成。

模板:

【多角度】基于输入图片生成,保持同一主体和画风。使用{水平描述},{垂直描述},景别为{景别}{可选额外提示词},构图稳定、画面自然。【/多角度】

水平提示词:

0°:
水平环绕 0°,相机正对主体

非 0°:
水平环绕 {angle}°,按目标视角等效为主体向右转 {360 - angle}°,不要生成反向视角

垂直提示词:

vertical > 0:
垂直俯仰 {vertical}°,正面俯拍视角,相机从上方向下看主体

vertical < 0:
垂直俯仰 {vertical}°,正面仰拍视角,相机从下方向上看主体

即使 includePrompt=false,生成时也会发送基础多角度提示词,避免图片生成接口因空文本报 Missing text input

运行逻辑

WorkflowCanvas 维护浮层状态:

interface MultiAngleDialogState {
  sourceNodeId: string;
  sourceHandleId: string;
  flowPosition: { x: number; y: number };
  settings: MultiAngleSettings;
}

点击菜单动作时:

  1. 读取当前连接拖拽状态。
  2. 确认来源是图片输出。
  3. 写入 multiAngleDialog 状态。
  4. 关闭连接菜单。

点击“生成多角度”时:

  1. 通过 getImageFromNode(sourceNodeId) 读取源图。
  2. 创建新的 nanoBanana 节点。
  3. 写入:
    • inputImages: [sourceImage]
    • inputPrompt: buildMultiAnglePrompt(settings)
    • 默认图像模型和基础生成参数
  4. 创建从源图片节点到新生成节点的图片连线。
  5. 选中新节点。
  6. 关闭多角度浮层。
  7. 调用 regenerateNode(newNodeId)

相关实现函数:

function buildMultiAngleNodeData(
  sourceImage: string,
  settings: MultiAngleSettings
): Partial<NanoBananaNodeData>

测试覆盖

当前相关测试:

src/components/__tests__/MultiAngleEditor.test.tsx
src/components/__tests__/ConnectionDropMenu.test.tsx
src/components/__tests__/WorkflowCanvas.test.tsx
src/components/__tests__/GenerationComposer.test.tsx

覆盖点:

  • 多角度编辑器默认参数。
  • 鱼眼预设参数和固定提示词。
  • 地球仪拖拽和右侧参数同步。
  • 多角度提示词不包含外部平台名称。
  • 图片拖拽菜单包含“图片多角度”动作。
  • “图片多角度”动作为 isAction=true,不会被当作普通节点创建。
  • 画布菜单动作打开多角度浮层。
  • 点击生成后创建并连接新的图片生成节点。
  • 底部控制台不再渲染“多角度”入口。

建议验证命令:

npm run test:run -- src/components/__tests__/MultiAngleEditor.test.tsx src/components/__tests__/ConnectionDropMenu.test.tsx src/components/__tests__/WorkflowCanvas.test.tsx src/components/__tests__/GenerationComposer.test.tsx

维护注意点

  • 新增多角度预设时,扩展 MultiAnglePresetIdMULTI_ANGLE_PRESETSMULTI_ANGLE_PRESET_SETTINGS 和对应测试。
  • 修改提示词语义时,优先调整 buildMultiAnglePrompt() 内部的水平/垂直描述函数。
  • 不要把多角度入口重新放回 GenerationComposer,当前产品入口是图片输出拖拽菜单。
  • 如果未来要做真实 3D 视角,应替换 MultiAngleEditor 内部 SVG 仪表,但保留外部 MultiAngleSettings 合约。
  • .env.local 和服务商 API key 与本功能无关,本次实现没有改动任何服务商配置。