136 lines
3.9 KiB
TypeScript
136 lines
3.9 KiB
TypeScript
import type { VideoCanvasRolePreset } from "@/lib/video-canvas/config";
|
||
|
||
export const VIDEO_CANVAS_ROLE_MATERIALS = [
|
||
{ key: "full", label: "全身正面" },
|
||
{ key: "face", label: "近景" },
|
||
{ key: "expression", label: "九宫格" },
|
||
{ key: "threeView", label: "设定图" },
|
||
] as const;
|
||
|
||
export type VideoCanvasRoleMaterialKind =
|
||
(typeof VIDEO_CANVAS_ROLE_MATERIALS)[number]["key"];
|
||
|
||
export type VideoCanvasAttachedRole = {
|
||
assetId: string;
|
||
compliant: boolean;
|
||
id: string;
|
||
imageUrl: string;
|
||
materialKind: VideoCanvasRoleMaterialKind;
|
||
materialLabel: string;
|
||
name: string;
|
||
prompt: string;
|
||
tags: string;
|
||
};
|
||
|
||
export function composeVideoCanvasRolePrompt(
|
||
prompt: string,
|
||
roles: Array<Pick<VideoCanvasAttachedRole, "assetId" | "name" | "prompt">>,
|
||
) {
|
||
return [
|
||
prompt.trim(),
|
||
...roles.map((role) =>
|
||
[
|
||
`角色引用「${role.name}」${role.assetId ? `(合规资产 ${role.assetId})` : ""}。`,
|
||
role.prompt,
|
||
]
|
||
.filter(Boolean)
|
||
.join(" "),
|
||
),
|
||
]
|
||
.filter(Boolean)
|
||
.join(" ");
|
||
}
|
||
|
||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||
}
|
||
|
||
function readString(record: Record<string, unknown>, key: string) {
|
||
return typeof record[key] === "string" ? record[key].trim() : "";
|
||
}
|
||
|
||
function isRoleMaterialKind(value: string): value is VideoCanvasRoleMaterialKind {
|
||
return VIDEO_CANVAS_ROLE_MATERIALS.some((item) => item.key === value);
|
||
}
|
||
|
||
export function toVideoCanvasAttachedRole(
|
||
preset: VideoCanvasRolePreset,
|
||
materialKind: VideoCanvasRoleMaterialKind,
|
||
): VideoCanvasAttachedRole {
|
||
const material = VIDEO_CANVAS_ROLE_MATERIALS.find(
|
||
(item) => item.key === materialKind,
|
||
) || VIDEO_CANVAS_ROLE_MATERIALS[0];
|
||
return {
|
||
assetId: preset.assetId,
|
||
compliant: preset.compliant,
|
||
id: preset.id,
|
||
imageUrl: preset.images[material.key],
|
||
materialKind: material.key,
|
||
materialLabel: material.label,
|
||
name: preset.name,
|
||
prompt: preset.prompt,
|
||
tags: preset.tags,
|
||
};
|
||
}
|
||
|
||
export function getVideoCanvasAttachedRole(
|
||
draftValues: Record<string, unknown> | null | undefined,
|
||
): VideoCanvasAttachedRole | null {
|
||
if (!draftValues) return null;
|
||
const params = isRecord(draftValues.params) ? draftValues.params : {};
|
||
const paramsRole = Array.isArray(params.roles) && isRecord(params.roles[0])
|
||
? params.roles[0]
|
||
: null;
|
||
const source = isRecord(draftValues.videoRole) ? draftValues.videoRole : paramsRole;
|
||
if (!source) return null;
|
||
|
||
const id = readString(source, "id");
|
||
const imageUrl = readString(source, "imageUrl");
|
||
const materialKindValue = readString(source, "materialKind");
|
||
const name = readString(source, "name");
|
||
if (!id || !imageUrl || !name || !isRoleMaterialKind(materialKindValue)) return null;
|
||
const fallbackLabel = VIDEO_CANVAS_ROLE_MATERIALS.find(
|
||
(item) => item.key === materialKindValue,
|
||
)?.label || "角色素材";
|
||
|
||
return {
|
||
assetId: readString(source, "assetId"),
|
||
compliant: source.compliant !== false,
|
||
id,
|
||
imageUrl,
|
||
materialKind: materialKindValue,
|
||
materialLabel: readString(source, "materialLabel") || fallbackLabel,
|
||
name,
|
||
prompt: readString(source, "prompt"),
|
||
tags: readString(source, "tags"),
|
||
};
|
||
}
|
||
|
||
export function setVideoCanvasAttachedRole(
|
||
draftValues: Record<string, unknown> | null | undefined,
|
||
preset: VideoCanvasRolePreset | null,
|
||
materialKind: VideoCanvasRoleMaterialKind = "full",
|
||
) {
|
||
const current = { ...(draftValues || {}) };
|
||
const params = isRecord(current.params) ? current.params : {};
|
||
|
||
if (preset) {
|
||
const role = toVideoCanvasAttachedRole(preset, materialKind);
|
||
return {
|
||
...current,
|
||
params: {
|
||
...params,
|
||
roles: [role],
|
||
},
|
||
videoRole: role,
|
||
};
|
||
}
|
||
|
||
const { videoRole: _removedRole, ...nextDraftValues } = current;
|
||
const { roles: _removedRoles, ...nextParams } = params;
|
||
return {
|
||
...nextDraftValues,
|
||
params: nextParams,
|
||
};
|
||
}
|