Your Name 37b7e58f2a
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
Refine video canvas function replica
2026-04-19 08:52:38 +08:00

314 lines
11 KiB
TypeScript
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.

export const VIDEO_CANVAS_FUNCTION_CONFIG_KEY = "video_canvas_function_config";
export type VideoCanvasFunctionKind =
| "multimodal"
| "image"
| "image-edit"
| "video"
| "audio"
| "utility"
| "workflow";
export type VideoCanvasBackendRoute =
| "canvas-generate-image"
| "canvas-generate-video"
| "canvas-workflow-analyze"
| "angle-edit"
| "enhance"
| "enhance-pro"
| "extend"
| "remove-bg"
| "layer-split"
| "local";
export type VideoCanvasFunctionDefinition = {
key: string;
label: string;
kind: VideoCanvasFunctionKind;
route: VideoCanvasBackendRoute;
enabled: boolean;
model?: string;
creditCost?: number;
prompt: string;
description: string;
requiresBackend?: boolean;
};
export type VideoCanvasFunctionConfig = {
enabled: boolean;
defaultImageModel: string;
defaultVideoModel: string;
defaultMultimodalModel: string;
functions: Record<string, VideoCanvasFunctionDefinition>;
};
const defaultFunctions: VideoCanvasFunctionDefinition[] = [
{
key: "character-three-view",
label: "角色三视图",
kind: "image-edit",
route: "canvas-generate-image",
enabled: true,
creditCost: 14,
prompt:
"以输入角色图为唯一角色参考,生成正面、侧面、背面三视图角色设定图。保持五官、发型、服装、颜色和体型一致,干净白底,三视图水平排布,适合后续视频角色一致性使用。",
description: "官方角色三视图预设,使用参考图生图/编辑模型。",
},
{
key: "multi-view-nine-grid",
label: "多机位九宫格",
kind: "image-edit",
route: "canvas-generate-image",
enabled: true,
creditCost: 14,
prompt:
"以选中图片为角色/主体参考,生成多视角 3x3 九宫格分镜图。保持主体一致,九个镜头包含远景、全身、侧身、背面、半身、近景、局部细节等视角,使用清晰黑色分割线分隔每一格,白色或干净背景。",
description: "官方九宫格工具:先生成带黑色分割线的 3x3 分镜图,再由宫格切分拆成单张镜头。",
},
{
key: "grid-split",
label: "宫格切分",
kind: "utility",
route: "local",
enabled: true,
creditCost: 0,
prompt: "识别黑色宫格分割线,将合成图切分为可单独调整的镜头图片。",
description: "浏览器端识别黑色分割线并生成切片节点;后端可后续替换为更稳的图像分割服务。",
},
{
key: "lighting",
label: "打光",
kind: "image-edit",
route: "canvas-generate-image",
enabled: true,
creditCost: 14,
prompt:
"在保持主体、构图和身份一致的前提下,对图片进行电影级光影重塑,增强轮廓光、层次、对比和空间感,保留自然材质细节。",
description: "图片编辑生图模型,复刻官方打光工具。",
},
{
key: "hd-upscale",
label: "高清",
kind: "utility",
route: "enhance",
enabled: true,
creditCost: 8,
prompt: "提升图片清晰度、细节和分辨率,保持原图内容不变。",
description: "接入现有图片增强接口;高清重绘可在模型字段指定增强 Pro。",
},
{
key: "panorama-720",
label: "720 全景",
kind: "image-edit",
route: "extend",
enabled: true,
creditCost: 14,
prompt:
"基于当前图片向四周扩展为 720 全景视野,保持主体与场景一致,补全自然环境和空间透视,避免变形和重复纹理。",
description: "优先使用拓图接口,必要时可切到图片编辑生图模型。",
},
{
key: "image-outpaint",
label: "扩图",
kind: "utility",
route: "extend",
enabled: true,
creditCost: 10,
prompt: "在保持主体不变的前提下扩展画面边界,补全自然背景和构图。",
description: "对应官方高清菜单里的扩图。",
},
{
key: "image-repaint",
label: "重绘",
kind: "image-edit",
route: "canvas-generate-image",
enabled: true,
creditCost: 14,
prompt: "保持主体结构和身份一致,按照用户补充提示重绘画面局部或整体风格。",
description: "对应官方高清菜单里的重绘。",
},
{
key: "image-erase",
label: "擦除",
kind: "image-edit",
route: "canvas-generate-image",
enabled: true,
creditCost: 10,
prompt: "根据用户标记或文字说明移除指定元素,并自然补全背景纹理。",
description: "当前以前端提示词占位接图片编辑模型,后续可替换为蒙版编辑接口。",
},
{
key: "remove-bg",
label: "抠图",
kind: "utility",
route: "remove-bg",
enabled: true,
creditCost: 4,
prompt: "移除图片背景,保留主体透明或干净背景结果。",
description: "接入现有抠图接口。",
},
{
key: "image-crop",
label: "裁剪",
kind: "utility",
route: "local",
enabled: true,
creditCost: 0,
prompt: "按用户选择的裁剪比例或宫格切片范围处理图片。",
description: "本地画布裁剪/宫格切分能力。",
},
{
key: "angle-edit",
label: "多角度编辑器",
kind: "image-edit",
route: "angle-edit",
enabled: true,
creditCost: 1,
prompt: "根据水平环绕、垂直俯仰和景别缩放参数生成新的角色/主体视角。",
description: "接现有 /api/angle-edit,多角度编辑器提交时触发。",
},
{
key: "story-script",
label: "故事脚本生成",
kind: "multimodal",
route: "canvas-workflow-analyze",
enabled: true,
creditCost: 6,
prompt: "根据输入文本、图片或视频参考,生成结构化分镜脚本,包含镜号、场景、画面、声音和镜头节奏。",
description: "多模态大模型/文本模型生成脚本表格。",
},
{
key: "image-to-prompt",
label: "图片反推提示词",
kind: "multimodal",
route: "canvas-workflow-analyze",
enabled: true,
creditCost: 3,
prompt: "分析参考图片,输出结构化中文提示词,包括主体、环境、构图、光影、镜头语言和风格关键词。",
description: "多模态识图生成提示词。",
},
{
key: "text-to-video",
label: "文生视频",
kind: "video",
route: "canvas-generate-video",
enabled: true,
creditCost: 14,
prompt: "根据文本描述生成短视频,保持镜头运动、节奏和电影级质感。",
description: "视频生成模型,走现有视频生成接口。",
},
{
key: "first-frame-video",
label: "首帧图生视频",
kind: "video",
route: "canvas-generate-video",
enabled: true,
creditCost: 14,
prompt: "以参考图片作为视频首帧,生成自然流畅的短视频。",
description: "图片到视频/首帧生视频。",
},
{
key: "first-last-frame-video",
label: "首尾帧生成视频",
kind: "video",
route: "canvas-generate-video",
enabled: true,
creditCost: 14,
prompt: "以两张参考图作为首帧和尾帧,生成首尾连贯的短视频。",
description: "首尾帧视频生成。",
},
{
key: "audio-to-video",
label: "音频生视频",
kind: "video",
route: "canvas-generate-video",
enabled: true,
creditCost: 14,
prompt: "根据上传音频生成对应场景画面,镜头语言、节奏、音乐匹配情绪变化,电影级质感。",
description: "官方为音频+参考图到视频;当前先接视频生成提示词和参考图,音频节拍解析接口后续补充。",
requiresBackend: true,
},
];
export const defaultVideoCanvasFunctionConfig: VideoCanvasFunctionConfig = {
enabled: true,
defaultImageModel: "auto",
defaultVideoModel: "auto",
defaultMultimodalModel: "auto",
functions: Object.fromEntries(defaultFunctions.map((item) => [item.key, item])),
};
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function normalizeFunctionDefinition(
key: string,
value: unknown,
fallback: VideoCanvasFunctionDefinition,
): VideoCanvasFunctionDefinition {
const incoming = isRecord(value) ? value : {};
const normalized: VideoCanvasFunctionDefinition = {
...fallback,
key,
label: String(incoming.label || fallback.label),
kind: (incoming.kind as VideoCanvasFunctionKind) || fallback.kind,
route: (incoming.route as VideoCanvasBackendRoute) || fallback.route,
enabled: typeof incoming.enabled === "boolean" ? incoming.enabled : fallback.enabled,
model: typeof incoming.model === "string" ? incoming.model : fallback.model,
creditCost:
typeof incoming.creditCost === "number" && Number.isFinite(incoming.creditCost)
? Math.max(0, incoming.creditCost)
: fallback.creditCost,
prompt: String(incoming.prompt || fallback.prompt),
description: String(incoming.description || fallback.description),
requiresBackend:
typeof incoming.requiresBackend === "boolean"
? incoming.requiresBackend
: fallback.requiresBackend,
};
return normalized;
}
export function normalizeVideoCanvasFunctionConfig(value: unknown): VideoCanvasFunctionConfig {
if (!isRecord(value)) return defaultVideoCanvasFunctionConfig;
const incomingFunctions = isRecord(value.functions) ? value.functions : {};
const functions: Record<string, VideoCanvasFunctionDefinition> = {};
for (const [key, fallback] of Object.entries(defaultVideoCanvasFunctionConfig.functions)) {
functions[key] = normalizeFunctionDefinition(key, incomingFunctions[key], fallback);
}
for (const [key, customValue] of Object.entries(incomingFunctions)) {
if (functions[key] || !isRecord(customValue)) continue;
const fallback: VideoCanvasFunctionDefinition = {
key,
label: String(customValue.label || key),
kind: (customValue.kind as VideoCanvasFunctionKind) || "workflow",
route: (customValue.route as VideoCanvasBackendRoute) || "local",
enabled: customValue.enabled !== false,
creditCost: typeof customValue.creditCost === "number" ? Math.max(0, customValue.creditCost) : 0,
prompt: String(customValue.prompt || ""),
description: String(customValue.description || "自定义视频画布功能"),
};
functions[key] = normalizeFunctionDefinition(key, customValue, fallback);
}
return {
enabled: value.enabled !== false,
defaultImageModel:
typeof value.defaultImageModel === "string" && value.defaultImageModel.trim()
? value.defaultImageModel.trim()
: defaultVideoCanvasFunctionConfig.defaultImageModel,
defaultVideoModel:
typeof value.defaultVideoModel === "string" && value.defaultVideoModel.trim()
? value.defaultVideoModel.trim()
: defaultVideoCanvasFunctionConfig.defaultVideoModel,
defaultMultimodalModel:
typeof value.defaultMultimodalModel === "string" && value.defaultMultimodalModel.trim()
? value.defaultMultimodalModel.trim()
: defaultVideoCanvasFunctionConfig.defaultMultimodalModel,
functions,
};
}