534ffd08b2
- 删除 system/bk/ 下所有旧提示词,平铺替换为 23 个新文件 - 文件名格式统一为: 文案——描述.txt - 后端: _meta.json 扁平化,loader.py 新增 list_prompt_files() + load_prompt_file() - 后端: API 从 subcategory 改为 filename,按指定文件读取 - 后端: categories 接口返回文件列表(label/desc/filename)供前端展示 - 前端: ScriptCreation 分类选择改为卡片网格,展示文案+描述 - 前端: 清理 subcategoryCode,统一改为 filename - 前端: 字幕字号调整为 64/96/80px
183 lines
5.5 KiB
TypeScript
183 lines
5.5 KiB
TypeScript
/**
|
||
* 项目元数据工具函数
|
||
* ==================
|
||
*
|
||
* 封装 meta.json 的构建、合并、迁移逻辑。
|
||
* 新增字段时只需在 types/project.ts 的 ProjectMeta 中定义,
|
||
* 此处无需修改。
|
||
*/
|
||
|
||
import type { ProjectMeta, MetaOverrides } from '../types/project';
|
||
|
||
// ------------------------------------------------------------------
|
||
// 默认值配置(集中管理)
|
||
// ------------------------------------------------------------------
|
||
|
||
const DEFAULT_META_VALUES: Partial<ProjectMeta> = {
|
||
currentStep: 1,
|
||
voiceSpeed: 1.0,
|
||
voiceVolume: 0,
|
||
voicePitch: 0,
|
||
status: 'draft',
|
||
version: 1,
|
||
};
|
||
|
||
// ------------------------------------------------------------------
|
||
// 内部工具:判断值是否为 undefined(区分"未传"和"传了 undefined")
|
||
// ------------------------------------------------------------------
|
||
|
||
function hasOwn<K extends string>(obj: object, key: K): obj is Record<K, unknown> {
|
||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// 构建项目标题
|
||
// ------------------------------------------------------------------
|
||
|
||
function buildTitle(topic: string | undefined, existingTitle?: string): string {
|
||
// 优先保留用户自定义的项目名称
|
||
if (existingTitle) {
|
||
return existingTitle;
|
||
}
|
||
const trimmed = topic?.trim();
|
||
if (trimmed) {
|
||
return trimmed;
|
||
}
|
||
return '未命名项目';
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// 核心:通用元数据合并函数
|
||
// ------------------------------------------------------------------
|
||
|
||
/**
|
||
* 构建完整的 ProjectMeta 对象。
|
||
*
|
||
* 逻辑:
|
||
* 1. 将 existingMeta 与 overrides 合并(overrides 优先级高)
|
||
* 2. 计算系统字段(id, title, status, timestamps)
|
||
* 3. 填充默认值
|
||
*
|
||
* @param projectId 项目 ID
|
||
* @param existing 磁盘上已有的 meta(可能为 null)
|
||
* @param overrides 前端传入的覆盖值
|
||
*/
|
||
export function buildProjectMeta(
|
||
projectId: string,
|
||
existing: Partial<ProjectMeta> | null,
|
||
overrides: MetaOverrides = {},
|
||
): ProjectMeta {
|
||
const now = Date.now();
|
||
|
||
// Step 1: 自动合并所有非系统字段
|
||
const merged: Partial<ProjectMeta> = { ...(existing || {}) };
|
||
|
||
for (const key of Object.keys(overrides) as Array<keyof MetaOverrides>) {
|
||
if (hasOwn(overrides, key)) {
|
||
(merged as Record<string, unknown>)[key] = overrides[key];
|
||
}
|
||
}
|
||
|
||
// Step 2: 计算系统字段
|
||
const topic = merged.topic;
|
||
const title = buildTitle(topic, merged.title);
|
||
|
||
// Step 3: 组装最终结果
|
||
return {
|
||
// 系统字段(不能被 overrides 覆盖)
|
||
id: projectId,
|
||
title,
|
||
status: (merged.status as 'draft' | 'published') || DEFAULT_META_VALUES.status!,
|
||
currentStep:
|
||
merged.currentStep ?? existing?.currentStep ?? DEFAULT_META_VALUES.currentStep!,
|
||
createdAt: existing?.createdAt || now,
|
||
updatedAt: now,
|
||
version:
|
||
(existing?.version || 0) >= 1 ? existing!.version : DEFAULT_META_VALUES.version,
|
||
|
||
// 自动展开所有持久化字段(包括用户覆盖的和已有磁盘数据)
|
||
...merged,
|
||
} as ProjectMeta;
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// 数据迁移
|
||
// ------------------------------------------------------------------
|
||
|
||
/**
|
||
* 将旧版 meta.json 升级到新格式。
|
||
*
|
||
* 未来结构变更时在此添加迁移逻辑:
|
||
* if (version < 2) { ...v1→v2 迁移... }
|
||
*/
|
||
export function migrateMeta(raw: unknown): Partial<ProjectMeta> {
|
||
if (typeof raw !== 'object' || raw === null) {
|
||
return {};
|
||
}
|
||
const obj = raw as Record<string, unknown>;
|
||
const version = typeof obj.version === 'number' ? obj.version : 0;
|
||
|
||
if (version < 1) {
|
||
// v0 → v1: 添加 version 字段,其他保持不变
|
||
return { ...obj, version: 1 } as Partial<ProjectMeta>;
|
||
}
|
||
|
||
// 清理已废弃字段
|
||
if ('subcategoryCode' in obj) {
|
||
delete (obj as Record<string, unknown>).subcategoryCode;
|
||
}
|
||
|
||
// 当前版本,无需迁移
|
||
return obj as Partial<ProjectMeta>;
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// 空白 overrides — 用于切换项目时清空旧数据
|
||
// ------------------------------------------------------------------
|
||
|
||
/**
|
||
* 显式将所有业务字段设为 undefined,配合 Zustand setState 使用。
|
||
* 在 initProjectStore / createNewProject 中先展开此对象,再展开新项目的 meta,
|
||
* 确保旧项目的字段不会残留。
|
||
*
|
||
* 新增字段时必须在此补充,否则切换项目时旧值会残留。
|
||
*/
|
||
export const BLANK_META_OVERRIDES: MetaOverrides = {
|
||
topic: undefined,
|
||
categoryCode: undefined,
|
||
filename: undefined,
|
||
selectedVoiceId: undefined,
|
||
finalVideoPath: undefined,
|
||
finalVideoDuration: undefined,
|
||
coverPath: undefined,
|
||
coverConfig: undefined,
|
||
exportedAt: undefined,
|
||
composedVideoUrl: undefined,
|
||
composedVideoPath: undefined,
|
||
lipSyncTaskId: undefined,
|
||
lipSyncState: undefined,
|
||
lipSyncedVideoPath: undefined,
|
||
lipSyncedVideoUrl: undefined,
|
||
dubbingAudioUrl: undefined,
|
||
dubbingAudioPath: undefined,
|
||
dubbingAudioDuration: undefined,
|
||
avatarMaterialPath: undefined,
|
||
avatarMaterialName: undefined,
|
||
avatarMaterialDuration: undefined,
|
||
subtitleAlignment: undefined,
|
||
burnedVideoPath: undefined,
|
||
mainTitle: undefined,
|
||
subTitle: undefined,
|
||
mainTitlePreset: undefined,
|
||
subTitlePreset: undefined,
|
||
captionPreset: undefined,
|
||
userUploadedMaterials: undefined,
|
||
stepDirtyFlags: undefined,
|
||
bgmMusicId: undefined,
|
||
bgmMusicTitle: undefined,
|
||
bgmMusicPath: undefined,
|
||
bgmVolume: undefined,
|
||
};
|
||
|
||
|