36 KiB
RFC 121: 剧本元素变体管理 - 前端实现
元数据
- RFC 编号: 121
- 标题: 剧本元素变体管理 - 前端UI实现
- 状态: 草案
- 创建日期: 2025-01-19
- 作者: AI Architect
- 关联文档:
1. 背景与问题
1.1 业务需求
在视频制作工作流中,同一个剧本元素(角色/场景/道具)往往需要多个变体:
角色变体示例:
- 角色"李明":少年、青年、中年、老年
- 角色"张华":正常状态、受伤状态
场景变体示例:
- 场景"办公室":1990年代、2020年代
- 场景"公园":春天、夏天、秋天、冬天
道具变体示例:
- 道具"怀表":完好、破损、修复后
1.2 当前前端架构的局限
现有素材管理方式:
// 当前:素材直接按类型分类
ProjectResourcePanel
├─ 角色 Tab
│ ├─ 李明_少年.jpg
│ ├─ 李明_青年.jpg
│ ├─ 李明_老年.jpg
│ └─ 张华.jpg
├─ 场景 Tab
└─ 道具 Tab
问题:
- ❌ 无法将同一角色的不同年龄段素材分组管理
- ❌ 用户需要手动在文件名中标注变体信息
- ❌ 分镜选择素材时无法按变体筛选
- ❌ 无法利用AI自动识别剧本中的变体需求
1.3 目标架构
改造后的素材管理方式:
// 改造后:元素 → 变体 → 素材 三层结构
ProjectResourcePanel
├─ 角色 Tab
│ ├─ 李明(4个变体,12个素材)
│ │ ├─ 少年(3个素材)
│ │ │ ├─ 正面.jpg
│ │ │ ├─ 侧面.jpg
│ │ │ └─ 背面.jpg
│ │ ├─ 青年(5个素材)
│ │ ├─ 中年(2个素材)
│ │ └─ 老年(2个素材)
│ └─ 张华(1个变体,3个素材)
├─ 场景 Tab
└─ 道具 Tab
优势:
- ✅ 同一元素的不同变体素材分组管理
- ✅ AI自动识别剧本中的变体需求
- ✅ 分镜可以精确选择"李明-青年"
- ✅ 支持预设模板 + 自定义变体
2. 技术方案
2.1 数据关联架构
项目(Project)
└─ 剧本元素(ScreenplayElement)
├─ 角色:李明
│ ├─ 变体:少年 ──→ 素材1.jpg, 素材2.jpg
│ ├─ 变体:青年 ──→ 素材3.jpg, 素材4.jpg
│ └─ 变体:老年 ──→ 素材5.jpg
├─ 场景:办公室
│ ├─ 变体:1990年代 ──→ 素材6.jpg
│ └─ 变体:2020年代 ──→ 素材7.jpg
└─ 道具:怀表
├─ 变体:完好 ──→ 素材8.jpg
└─ 变体:破损 ──→ 素材9.jpg
分镜(Storyboard)
└─ 关联素材:素材3.jpg(李明-青年)+ 素材7.jpg(办公室-2020年代)
关键设计:
- 素材不是直接关联到"李明",而是关联到"李明-青年"这个变体
- 分镜选择素材时,实际上是选择"元素的某个变体的某个素材"
2.2 类型定义
// client/src/types/screenplay.ts
/**
* 剧本元素类型
*/
export type ScreenplayElementType = 'character' | 'scene' | 'prop';
/**
* 剧本元素(角色/场景/道具)
*/
export interface ScreenplayElement {
id: string;
projectId: string;
type: ScreenplayElementType;
name: string;
description?: string;
hasVariants: boolean;
metadata?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
/**
* 剧本元素变体
*/
export interface ScreenplayVariant {
id: string;
elementId: string;
elementType: ScreenplayElementType;
variantKey: string; // 程序标识:youth, adult, elder
variantLabel: string; // UI显示:少年、青年、老年
description?: string;
aiGenerated: boolean; // 是否由AI生成
aiConfidence?: number; // AI识别置信度 0-1
aiContext?: string; // AI识别的上下文
metadata?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
/**
* AI识别结果
*/
export interface ScreenplayAnalysisResult {
characters: {
name: string;
variants: Array<{
key: string;
label: string;
confidence: number;
context: string;
}>;
}[];
scenes: {
name: string;
variants: Array<{
key: string;
label: string;
confidence: number;
context: string;
}>;
}[];
props: {
name: string;
variants: Array<{
key: string;
label: string;
confidence: number;
context: string;
}>;
}[];
}
/**
* 变体预设模板
*/
export interface VariantTemplate {
key: string;
label: string;
category: 'age' | 'era' | 'season' | 'state' | 'custom';
}
2.3 API Hooks
// client/src/hooks/api/useScreenplay.ts
/**
* 获取剧本元素列表
*/
export function useScreenplayElements(
projectId: string | null,
type?: ScreenplayElementType
) {
return useQuery({
queryKey: ['screenplay-elements', projectId, type],
queryFn: () => api.getScreenplayElements(projectId!, type),
enabled: !!projectId,
});
}
/**
* 获取元素的变体列表
*/
export function useElementVariants(
elementId: string | null,
elementType: ScreenplayElementType
) {
return useQuery({
queryKey: ['element-variants', elementId, elementType],
queryFn: () => api.getElementVariants(elementId!, elementType),
enabled: !!elementId,
});
}
/**
* 创建变体
*/
export function useCreateVariant() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateVariantDto) => api.createVariant(data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ['element-variants', variables.elementId],
});
},
});
}
/**
* 更新变体
*/
export function useUpdateVariant() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateVariantDto }) =>
api.updateVariant(id, data),
onSuccess: (data) => {
queryClient.invalidateQueries({
queryKey: ['element-variants', data.elementId],
});
},
});
}
/**
* 删除变体
*/
export function useDeleteVariant() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, elementId }: { id: string; elementId: string }) =>
api.deleteVariant(id),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ['element-variants', variables.elementId],
});
},
});
}
/**
* AI分析剧本
*/
export function useAnalyzeScreenplay() {
return useMutation({
mutationFn: (data: { projectId: string; content: string }) =>
api.analyzeScreenplay(data),
});
}
/**
* 批量创建元素和变体
*/
export function useBatchCreateElements() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: BatchCreateElementsDto) =>
api.batchCreateElements(data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ['screenplay-elements', variables.projectId],
});
},
});
}
/**
* 获取某个变体的所有素材
*/
export function useResourcesByVariant(
projectId: string | null,
variantId: string | null,
elementType: ScreenplayElementType
) {
return useQuery({
queryKey: ['resources-by-variant', projectId, variantId, elementType],
queryFn: () => api.getResourcesByVariant(projectId!, variantId!, elementType),
enabled: !!projectId && !!variantId,
});
}
3. 用户操作流程
3.1 场景1:AI智能识别变体(自动化)
用户操作步骤:
- 用户在
ScreenplayContentPanel上传剧本《时光之旅》 - 点击"智能拆解"按钮
- 系统调用 AI 分析接口,识别剧本中的元素和变体
- 弹出
ScreenplayAnalysisResultDialog显示识别结果:
┌─────────────────────────────────────────────────┐
│ AI识别结果 │
│ │
│ 角色: │
│ ✓ 李明(少年、青年、老年)[置信度: 95%] │
│ ✓ 张华(青年)[置信度: 88%] │
│ │
│ 场景: │
│ ✓ 办公室(1990年代、2020年代)[置信度: 92%] │
│ ✓ 公园(春天、冬天)[置信度: 85%] │
│ │
│ 道具: │
│ ✓ 怀表(完好、破损)[置信度: 90%] │
│ │
│ [修改] [确认并创建] │
└─────────────────────────────────────────────────┘
- 用户可以修改识别结果(添加/删除/编辑变体)
- 点击"确认并创建",系统批量创建元素和变体
- 生成分镜,分镜描述中包含变体信息
涉及组件:
ScreenplayContentPanel- 增加智能拆解按钮ScreenplayAnalysisResultDialog- 新增,显示AI识别结果useAnalyzeScreenplay()- 调用AI分析接口useBatchCreateElements()- 批量创建元素和变体
3.2 场景2:手动管理变体
用户操作步骤:
- 在
ProjectResourcePanel选择"角色"Tab - 点击角色"李明"
- 右侧
ResourcePropertiesPanel显示角色属性 - 切换到"形象"Tab,看到变体列表:
┌─────────────────────────────────────────────────┐
│ 变体管理 │
│ │
│ [少年] [青年] [老年] │
│ │
│ [+ 添加变体] │
└─────────────────────────────────────────────────┘
- 点击"添加变体",弹出
VariantManagementDialog:
┌─────────────────────────────────────────────────┐
│ 添加变体 │
│ │
│ 预设模板: │
│ ○ 少年 ○ 青年 ○ 中年 ○ 老年 │
│ ○ 儿童 ○ 壮年 │
│ │
│ 或自定义: │
│ [输入变体名称_____________________] │
│ │
│ [取消] [确认] │
└─────────────────────────────────────────────────┘
- 选择"中年"或输入自定义名称
- 保存后,新变体出现在列表中
涉及组件:
ResourcePropertiesPanel- 改造"形象"Tab,动态显示变体VariantManagementDialog- 新增,变体管理对话框useElementVariants()- 获取变体列表useCreateVariant()- 创建变体
3.3 场景3:上传素材时关联变体
用户操作步骤:
- 在
ProjectResourcePanel点击"上传素材"按钮 - 弹出
UploadResourceDialog:
┌─────────────────────────────────────────────────┐
│ 上传素材 │
│ │
│ 素材类型: │
│ ● 角色 ○ 场景 ○ 道具 ○ 实拍 │
│ │
│ 选择角色: │
│ [李明 ▼] │
│ │
│ 选择变体: │
│ [青年 ▼] (少年/青年/老年/中年) │
│ │
│ [选择文件] 或 拖拽文件到此处 │
│ │
│ [取消] [上传] │
└─────────────────────────────────────────────────┘
- 选择类型、元素、变体后,选择文件
- 点击"上传",素材自动关联到"李明-青年"这个变体
涉及组件:
UploadResourceDialog- 新增,替换当前的 input fileuseScreenplayElements()- 获取元素列表useElementVariants()- 获取变体列表useUploadResource()- 上传素材(传递 variant_id)
3.4 场景4:素材面板按变体浏览
用户操作步骤:
- 在
ProjectResourcePanel选择"角色"Tab - 看到角色列表(树形结构):
┌─────────────────────────────────────────────────┐
│ 角色 │
│ │
│ ▼ 李明(4个变体,12个素材) │
│ ▼ 少年(3个素材) │
│ [素材1] [素材2] [素材3] │
│ ▶ 青年(5个素材) │
│ ▶ 老年(2个素材) │
│ ▶ 中年(2个素材) │
│ │
│ ▶ 张华(1个变体,3个素材) │
│ │
│ [+ 上传素材] │
└─────────────────────────────────────────────────┘
- 点击"青年"展开,显示该变体的5个素材
- 点击某个素材,右侧显示素材详情
涉及组件:
ProjectResourcePanel- 改造为树形结构ResourceTreeView- 新增,树形视图组件useScreenplayElements()- 获取元素列表useElementVariants()- 获取变体列表useResourcesByVariant()- 获取变体的素材
3.5 场景5:分镜使用变体素材
用户操作步骤:
- 在
StoryboardList选择第3个分镜 - 在
StoryboardPropertiesPanel点击"添加角色" - 弹出
ResourceSelectorDialog:
┌─────────────────────────────────────────────────┐
│ 选择角色素材 │
│ │
│ 角色:[李明 ▼] │
│ 变体:[青年 ▼] (少年/青年/老年/中年) │
│ │
│ 该变体的素材: │
│ [素材3] [素材4] [素材5] [素材6] [素材7] │
│ │
│ [取消] [确认] │
└─────────────────────────────────────────────────┘
- 选择"素材3",确认后该素材关联到分镜
- 分镜的角色列表显示:"李明(青年)- 素材3"
涉及组件:
ResourceSelectorDialog- 新增,素材选择对话框StoryboardPropertiesPanel- 增加"添加角色/场景/道具"功能useScreenplayElements()- 获取元素列表useElementVariants()- 获取变体列表useResourcesByVariant()- 获取变体的素材
4. 组件设计
4.1 新增组件
4.1.1 ScreenplayAnalysisResultDialog
功能:显示AI识别的剧本元素和变体,允许用户确认或修改。
Props:
interface ScreenplayAnalysisResultDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
analysisResult: ScreenplayAnalysisResult;
onConfirm: (result: ScreenplayAnalysisResult) => void;
}
UI结构:
- 顶部:标题"AI识别结果"
- 中间:三个Tab(角色/场景/道具),每个Tab显示识别的元素和变体
- 每个元素可以展开编辑变体列表
- 底部:[修改] [确认并创建] 按钮
4.1.2 VariantManagementDialog
功能:管理某个元素的所有变体(添加/编辑/删除)。
Props:
interface VariantManagementDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
elementId: string;
elementType: ScreenplayElementType;
elementName: string;
}
UI结构:
- 顶部:标题"管理变体 - {elementName}"
- 中间:变体列表(可编辑、可删除)
- 底部:预设模板选择 + 自定义输入
- 底部:[取消] [保存] 按钮
4.1.3 UploadResourceDialog
功能:上传素材时选择类型、元素、变体。
Props:
interface UploadResourceDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
projectId: string;
onSuccess?: () => void;
}
UI结构:
- 素材类型选择(角色/场景/道具/实拍)
- 元素选择下拉框(根据类型动态加载)
- 变体选择下拉框(根据元素动态加载)
- 文件选择区域(支持拖拽)
- [取消] [上传] 按钮
4.1.4 ResourceSelectorDialog
功能:分镜选择素材时,按元素和变体筛选。
Props:
interface ResourceSelectorDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
projectId: string;
resourceType: ResourceType;
onSelect: (resource: Resource) => void;
}
UI结构:
- 元素选择下拉框
- 变体选择下拉框
- 素材网格(显示该变体的所有素材)
- [取消] [确认] 按钮
4.1.5 ResourceTreeView
功能:树形展示元素-变体-素材的三层结构。
Props:
interface ResourceTreeViewProps {
projectId: string;
resourceType: ResourceType;
onSelectResource: (resource: Resource) => void;
}
UI结构:
- 使用递归组件实现树形结构
- 元素节点:显示名称和统计(变体数、素材数)
- 变体节点:显示名称和统计(素材数)
- 素材节点:显示缩略图和名称
4.2 改造组件
4.2.1 ProjectResourcePanel
改造内容:
- 从扁平列表改为树形结构
- 集成
ResourceTreeView组件 - 上传按钮改为打开
UploadResourceDialog
改造前:
// 扁平列表
<div className="grid grid-cols-2 gap-3">
{resources.map(resource => (
<ResourceGridItem key={resource.id} resource={resource} />
))}
</div>
改造后:
// 树形结构
<ResourceTreeView
projectId={projectId}
resourceType={filterType}
onSelectResource={handleSelectResource}
/>
4.2.2 ResourcePropertiesPanel
改造内容:
- "形象"Tab 的变体区域改为动态加载
- 增加"管理变体"按钮
- 变体列表可点击切换
改造前:
// 硬编码的变体
<div className="grid grid-cols-3 gap-2">
<div>正面</div>
<div>侧面</div>
<div>背面</div>
</div>
改造后:
// 动态加载变体
const { data: variants } = useElementVariants(elementId, 'character');
<div className="space-y-2">
<div className="flex justify-between items-center">
<Label>变体</Label>
<Button size="sm" onClick={() => setManageDialogOpen(true)}>
管理变体
</Button>
</div>
<div className="grid grid-cols-3 gap-2">
{variants?.map(variant => (
<div key={variant.id} className="...">
{variant.variantLabel}
</div>
))}
</div>
</div>
4.2.3 ScreenplayContentPanel
改造内容:
- 智能拆解流程增加AI识别结果确认步骤
- 集成
ScreenplayAnalysisResultDialog
改造前:
const handleSmartBreakdown = () => {
onSmartBreakdown({
personalizedRequirements,
storyboardCount,
});
};
改造后:
const handleSmartBreakdown = async () => {
// 1. 调用AI分析
const result = await analyzeScreenplay.mutateAsync({
projectId,
content: screenplayContent,
});
// 2. 显示确认对话框
setAnalysisResult(result);
setAnalysisDialogOpen(true);
};
const handleConfirmAnalysis = async (result: ScreenplayAnalysisResult) => {
// 3. 批量创建元素和变体
await batchCreateElements.mutateAsync({
projectId,
elements: result,
});
// 4. 生成分镜
onSmartBreakdown({
personalizedRequirements,
storyboardCount,
});
};
5. 数据流设计
5.1 API Service 层
// client/src/services/api/screenplay.ts
export const screenplayApi = {
/**
* 获取剧本元素列表
*/
async getScreenplayElements(
projectId: string,
type?: ScreenplayElementType
): Promise<ScreenplayElement[]> {
const params = type ? { type } : {};
const response = await apiClient.get(
`/projects/${projectId}/screenplay/elements`,
{ params }
);
return response.data;
},
/**
* 获取元素的变体列表
*/
async getElementVariants(
elementId: string,
elementType: ScreenplayElementType
): Promise<ScreenplayVariant[]> {
const typeMap = {
character: 'characters',
scene: 'scenes',
prop: 'props',
};
const response = await apiClient.get(
`/screenplay/${typeMap[elementType]}/${elementId}/variants`
);
return response.data;
},
/**
* 创建变体
*/
async createVariant(data: CreateVariantDto): Promise<ScreenplayVariant> {
const typeMap = {
character: 'characters',
scene: 'scenes',
prop: 'props',
};
const response = await apiClient.post(
`/screenplay/${typeMap[data.elementType]}/${data.elementId}/variants`,
data
);
return response.data;
},
/**
* 更新变体
*/
async updateVariant(
id: string,
data: UpdateVariantDto
): Promise<ScreenplayVariant> {
const response = await apiClient.put(`/screenplay/variants/${id}`, data);
return response.data;
},
/**
* 删除变体
*/
async deleteVariant(id: string): Promise<void> {
await apiClient.delete(`/screenplay/variants/${id}`);
},
/**
* AI分析剧本
*/
async analyzeScreenplay(data: {
projectId: string;
content: string;
}): Promise<ScreenplayAnalysisResult> {
const response = await apiClient.post(
`/projects/${data.projectId}/screenplay/analyze`,
{ content: data.content }
);
return response.data;
},
/**
* 批量创建元素和变体
*/
async batchCreateElements(
data: BatchCreateElementsDto
): Promise<{ created: number }> {
const response = await apiClient.post(
`/projects/${data.projectId}/screenplay/elements/batch`,
data
);
return response.data;
},
/**
* 获取某个变体的所有素材
*/
async getResourcesByVariant(
projectId: string,
variantId: string,
elementType: ScreenplayElementType
): Promise<Resource[]> {
const variantField = `${elementType}_variant_id`;
const response = await apiClient.get(`/projects/${projectId}/resources`, {
params: { [variantField]: variantId },
});
return response.data;
},
};
5.2 状态管理
方案:使用 React Query 管理服务端状态,无需额外的全局状态管理。
关键查询键设计:
// 元素列表
['screenplay-elements', projectId, type]
// 元素的变体列表
['element-variants', elementId, elementType]
// 变体的素材列表
['resources-by-variant', projectId, variantId, elementType]
// 素材列表(兼容现有)
['resources', projectId, { type, search, character_variant_id, ... }]
缓存失效策略:
- 创建变体后:失效
['element-variants', elementId] - 上传素材后:失效
['resources', projectId]和['resources-by-variant', ...] - 批量创建元素后:失效
['screenplay-elements', projectId]
6. 实施计划
Phase 1: 基础架构(1周)
目标:建立类型定义、API层、Hooks层。
任务:
- ✅ 创建类型定义
client/src/types/screenplay.ts - ✅ 创建API Service
client/src/services/api/screenplay.ts - ✅ 创建Hooks
client/src/hooks/api/useScreenplay.ts - ✅ 更新
client/src/types/resource.ts(增加变体外键字段)
验收标准:
- 类型定义完整,无 TypeScript 错误
- API Service 可以正常调用后端接口
- Hooks 可以正常获取和更新数据
Phase 2: 核心组件(2周)
目标:实现变体管理的核心UI组件。
任务:
- ✅ 实现
VariantManagementDialog(变体管理对话框) - ✅ 实现
UploadResourceDialog(上传素材对话框) - ✅ 实现
ResourceSelectorDialog(素材选择对话框) - ✅ 实现
ResourceTreeView(树形视图组件) - ✅ 改造
ResourcePropertiesPanel(动态加载变体)
验收标准:
- 用户可以手动添加/编辑/删除变体
- 上传素材时可以选择变体
- 素材面板可以按变体分组浏览
Phase 3: AI智能识别(1周)
目标:集成AI智能识别剧本元素和变体。
任务:
- ✅ 实现
ScreenplayAnalysisResultDialog(AI识别结果对话框) - ✅ 改造
ScreenplayContentPanel(增加AI识别流程) - ✅ 集成
useAnalyzeScreenplay()和useBatchCreateElements()
验收标准:
- 用户点击"智能拆解"后,可以看到AI识别的元素和变体
- 用户可以修改识别结果
- 确认后,系统自动创建元素和变体
Phase 4: 分镜集成(1周)
目标:分镜可以使用变体素材。
任务:
- ✅ 改造
StoryboardPropertiesPanel(增加"添加角色/场景/道具"功能) - ✅ 集成
ResourceSelectorDialog - ✅ 更新分镜-素材关联逻辑
- ✅ 显示时展示变体信息(例如:"李明(青年)")
验收标准:
- 分镜可以选择"李明-青年"的素材
- 分镜列表显示变体信息
- 素材变更时,分镜自动更新
Phase 5: 优化与测试(1周)
目标:性能优化、用户体验优化、测试。
任务:
- ✅ 性能优化(虚拟滚动、懒加载)
- ✅ 用户体验优化(加载状态、错误处理、空状态)
- ✅ 单元测试(关键组件和Hooks)
- ✅ 集成测试(完整用户流程)
- ✅ 文档更新(用户指南、开发文档)
验收标准:
- 大量素材时,界面流畅不卡顿
- 所有边界情况都有友好的提示
- 测试覆盖率 > 80%
- 文档完整清晰
7. 技术挑战与解决方案
7.1 挑战1:树形结构的性能优化
问题:当元素、变体、素材数量很大时,树形结构可能导致性能问题。
解决方案:
- 使用虚拟滚动(react-window 或 react-virtualized)
- 懒加载:默认只加载元素列表,点击展开时才加载变体和素材
- 分页:每个变体的素材列表支持分页加载
7.2 挑战2:复杂的数据关联
问题:元素-变体-素材-分镜的多层关联,容易导致数据不一致。
解决方案:
- 使用 React Query 的缓存失效机制,确保数据同步
- 乐观更新:用户操作后立即更新UI,后台异步同步
- 错误回滚:如果后端操作失败,自动回滚UI状态
7.3 挑战3:AI识别结果的可编辑性
问题:AI识别结果可能不准确,用户需要能够修改。
解决方案:
- 识别结果对话框支持完整的编辑功能
- 显示置信度,低置信度的结果用不同颜色标注
- 支持添加/删除元素和变体
- 支持修改变体名称和描述
7.4 挑战4:向后兼容
问题:现有项目的素材没有关联变体,如何处理?
解决方案:
- 后端自动为没有变体的元素创建"默认"变体
- 前端显示时,如果元素只有一个"默认"变体,则隐藏变体层级
- 用户可以手动将"默认"变体改为具体的变体名称
8. 用户体验设计
8.1 空状态设计
场景1:元素没有变体
┌─────────────────────────────────────────────────┐
│ 暂无变体 │
│ │
│ 该角色还没有创建变体。 │
│ 您可以: │
│ • 使用AI智能识别自动创建变体 │
│ • 手动添加变体 │
│ │
│ [AI智能识别] [手动添加] │
└─────────────────────────────────────────────────┘
场景2:变体没有素材
┌─────────────────────────────────────────────────┐
│ 暂无素材 │
│ │
│ 该变体还没有上传素材。 │
│ │
│ [上传素材] │
└─────────────────────────────────────────────────┘
8.2 加载状态设计
骨架屏:
- 元素列表加载时:显示3个骨架元素
- 变体列表加载时:显示3个骨架变体
- 素材列表加载时:显示6个骨架素材卡片
进度提示:
- AI分析剧本时:显示进度条和提示文字"AI正在分析剧本..."
- 批量创建元素时:显示进度条和提示文字"正在创建元素和变体..."
- 上传素材时:显示上传进度
8.3 错误处理
网络错误:
┌─────────────────────────────────────────────────┐
│ ⚠️ 加载失败 │
│ │
│ 网络连接失败,请检查网络后重试。 │
│ │
│ [重试] │
└─────────────────────────────────────────────────┘
AI识别失败:
┌─────────────────────────────────────────────────┐
│ ⚠️ AI识别失败 │
│ │
│ AI服务暂时不可用,您可以: │
│ • 稍后重试 │
│ • 手动创建元素和变体 │
│ │
│ [重试] [手动创建] │
└─────────────────────────────────────────────────┘
操作失败:
- 使用 Toast 提示具体错误信息
- 提供"重试"或"撤销"操作
8.4 交互细节
拖拽上传:
- 支持拖拽文件到上传对话框
- 拖拽时显示高亮边框和提示文字
快捷键:
Ctrl/Cmd + U:打开上传素材对话框Ctrl/Cmd + K:打开素材搜索Esc:关闭当前对话框
右键菜单:
- 元素:重命名、删除、管理变体
- 变体:重命名、删除、上传素材
- 素材:重命名、删除、查看详情
批量操作:
- 支持多选素材(Shift + 点击)
- 批量删除、批量移动到其他变体
9. 测试策略
9.1 单元测试
测试覆盖:
- Hooks:
useScreenplayElements,useElementVariants,useCreateVariant等 - 工具函数:数据转换、格式化等
- 组件逻辑:状态管理、事件处理等
测试工具:
- Vitest + React Testing Library
9.2 集成测试
测试场景:
- 完整的AI识别流程
- 手动创建元素和变体
- 上传素材并关联变体
- 分镜选择变体素材
- 编辑和删除变体
测试工具:
- Playwright(E2E测试)
9.3 性能测试
测试场景:
- 1000个素材的加载性能
- 100个元素、每个元素10个变体的渲染性能
- 树形结构的展开/收起性能
性能指标:
- 首次渲染时间 < 1s
- 交互响应时间 < 100ms
- 内存占用 < 200MB
10. 风险与挑战
10.1 技术风险
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| 树形结构性能问题 | 高 | 中 | 虚拟滚动、懒加载 |
| AI识别准确率低 | 中 | 中 | 显示置信度、支持编辑 |
| 数据同步问题 | 高 | 低 | React Query缓存管理 |
| 向后兼容问题 | 中 | 低 | 自动创建"默认"变体 |
10.2 用户体验风险
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| 学习曲线陡峭 | 中 | 中 | 新手引导、工具提示 |
| 操作步骤过多 | 中 | 中 | 快捷操作、批量操作 |
| 界面复杂度增加 | 中 | 高 | 渐进式展示、默认折叠 |
11. 后续优化方向
11.1 短期优化(3个月内)
- 智能推荐:根据剧本内容,推荐合适的变体模板
- 批量生成:一键为所有角色生成常用变体(少年/青年/老年)
- 变体预览:在变体列表中显示代表性素材的缩略图
- 搜索增强:支持按变体搜索素材
11.2 长期优化(6个月内)
- 变体时间线:在时间线上显示角色变体的切换点
- AI生成素材:根据变体描述,AI自动生成素材
- 变体模板库:提供行业标准的变体模板(电影、广告、动画等)
- 协作功能:多人协作时,变体的权限管理和冲突解决
12. 总结
本RFC提出了一套完整的剧本元素变体管理前端实现方案,核心特点:
- 三层架构:元素 → 变体 → 素材,清晰且灵活
- AI + 手动:既支持AI智能识别,也支持用户手动管理
- 渐进式展示:默认折叠,按需展开,降低界面复杂度
- 性能优化:虚拟滚动、懒加载、缓存管理
- 向后兼容:自动为旧数据创建"默认"变体
预期收益:
- ✅ 素材管理效率提升 50%
- ✅ 分镜制作效率提升 30%
- ✅ 用户满意度提升 40%
实施周期:6周(5个Phase)
资源需求:1名前端工程师 + 1名UI设计师