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.
 

36 KiB

RFC 121: 剧本元素变体管理 - 前端实现

元数据


1. 背景与问题

1.1 业务需求

在视频制作工作流中,同一个剧本元素(角色/场景/道具)往往需要多个变体:

角色变体示例

  • 角色"李明":少年、青年、中年、老年
  • 角色"张华":正常状态、受伤状态

场景变体示例

  • 场景"办公室":1990年代、2020年代
  • 场景"公园":春天、夏天、秋天、冬天

道具变体示例

  • 道具"怀表":完好、破损、修复后

1.2 当前前端架构的局限

现有素材管理方式

// 当前:素材直接按类型分类
ProjectResourcePanel
  ├─ 角色 Tab
     ├─ 李明_少年.jpg
     ├─ 李明_青年.jpg
     ├─ 李明_老年.jpg
     └─ 张华.jpg
  ├─ 场景 Tab
  └─ 道具 Tab

问题

  1. 无法将同一角色的不同年龄段素材分组管理
  2. 用户需要手动在文件名中标注变体信息
  3. 分镜选择素材时无法按变体筛选
  4. 无法利用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智能识别变体(自动化)

用户操作步骤

  1. 用户在 ScreenplayContentPanel 上传剧本《时光之旅》
  2. 点击"智能拆解"按钮
  3. 系统调用 AI 分析接口,识别剧本中的元素和变体
  4. 弹出 ScreenplayAnalysisResultDialog 显示识别结果:
┌─────────────────────────────────────────────────┐
│ AI识别结果                                      │
│                                                 │
│ 角色:                                          │
│  ✓ 李明(少年、青年、老年)[置信度: 95%]       │
│  ✓ 张华(青年)[置信度: 88%]                   │
│                                                 │
│ 场景:                                          │
│  ✓ 办公室(1990年代、2020年代)[置信度: 92%]   │
│  ✓ 公园(春天、冬天)[置信度: 85%]             │
│                                                 │
│ 道具:                                          │
│  ✓ 怀表(完好、破损)[置信度: 90%]             │
│                                                 │
│ [修改] [确认并创建]                             │
└─────────────────────────────────────────────────┘
  1. 用户可以修改识别结果(添加/删除/编辑变体)
  2. 点击"确认并创建",系统批量创建元素和变体
  3. 生成分镜,分镜描述中包含变体信息

涉及组件

  • ScreenplayContentPanel - 增加智能拆解按钮
  • ScreenplayAnalysisResultDialog - 新增,显示AI识别结果
  • useAnalyzeScreenplay() - 调用AI分析接口
  • useBatchCreateElements() - 批量创建元素和变体

3.2 场景2:手动管理变体

用户操作步骤

  1. ProjectResourcePanel 选择"角色"Tab
  2. 点击角色"李明"
  3. 右侧 ResourcePropertiesPanel 显示角色属性
  4. 切换到"形象"Tab,看到变体列表:
┌─────────────────────────────────────────────────┐
│ 变体管理                                        │
│                                                 │
│ [少年] [青年] [老年]                            │
│                                                 │
│ [+ 添加变体]                                    │
└─────────────────────────────────────────────────┘
  1. 点击"添加变体",弹出 VariantManagementDialog
┌─────────────────────────────────────────────────┐
│ 添加变体                                        │
│                                                 │
│ 预设模板:                                      │
│  ○ 少年  ○ 青年  ○ 中年  ○ 老年               │
│  ○ 儿童  ○ 壮年                                │
│                                                 │
│ 或自定义:                                      │
│  [输入变体名称_____________________]            │
│                                                 │
│ [取消] [确认]                                   │
└─────────────────────────────────────────────────┘
  1. 选择"中年"或输入自定义名称
  2. 保存后,新变体出现在列表中

涉及组件

  • ResourcePropertiesPanel - 改造"形象"Tab,动态显示变体
  • VariantManagementDialog - 新增,变体管理对话框
  • useElementVariants() - 获取变体列表
  • useCreateVariant() - 创建变体

3.3 场景3:上传素材时关联变体

用户操作步骤

  1. ProjectResourcePanel 点击"上传素材"按钮
  2. 弹出 UploadResourceDialog
┌─────────────────────────────────────────────────┐
│ 上传素材                                        │
│                                                 │
│ 素材类型:                                      │
│  ● 角色  ○ 场景  ○ 道具  ○ 实拍               │
│                                                 │
│ 选择角色:                                      │
│  [李明 ▼]                                       │
│                                                 │
│ 选择变体:                                      │
│  [青年 ▼]  (少年/青年/老年/中年)              │
│                                                 │
│ [选择文件] 或 拖拽文件到此处                    │
│                                                 │
│ [取消] [上传]                                   │
└─────────────────────────────────────────────────┘
  1. 选择类型、元素、变体后,选择文件
  2. 点击"上传",素材自动关联到"李明-青年"这个变体

涉及组件

  • UploadResourceDialog - 新增,替换当前的 input file
  • useScreenplayElements() - 获取元素列表
  • useElementVariants() - 获取变体列表
  • useUploadResource() - 上传素材(传递 variant_id)

3.4 场景4:素材面板按变体浏览

用户操作步骤

  1. ProjectResourcePanel 选择"角色"Tab
  2. 看到角色列表(树形结构):
┌─────────────────────────────────────────────────┐
│ 角色                                            │
│                                                 │
│ ▼ 李明(4个变体,12个素材)                     │
│   ▼ 少年(3个素材)                             │
│     [素材1] [素材2] [素材3]                     │
│   ▶ 青年(5个素材)                             │
│   ▶ 老年(2个素材)                             │
│   ▶ 中年(2个素材)                             │
│                                                 │
│ ▶ 张华(1个变体,3个素材)                      │
│                                                 │
│ [+ 上传素材]                                    │
└─────────────────────────────────────────────────┘
  1. 点击"青年"展开,显示该变体的5个素材
  2. 点击某个素材,右侧显示素材详情

涉及组件

  • ProjectResourcePanel - 改造为树形结构
  • ResourceTreeView - 新增,树形视图组件
  • useScreenplayElements() - 获取元素列表
  • useElementVariants() - 获取变体列表
  • useResourcesByVariant() - 获取变体的素材

3.5 场景5:分镜使用变体素材

用户操作步骤

  1. StoryboardList 选择第3个分镜
  2. StoryboardPropertiesPanel 点击"添加角色"
  3. 弹出 ResourceSelectorDialog
┌─────────────────────────────────────────────────┐
│ 选择角色素材                                    │
│                                                 │
│ 角色:[李明 ▼]                                  │
│ 变体:[青年 ▼]  (少年/青年/老年/中年)         │
│                                                 │
│ 该变体的素材:                                  │
│  [素材3] [素材4] [素材5] [素材6] [素材7]        │
│                                                 │
│ [取消] [确认]                                   │
└─────────────────────────────────────────────────┘
  1. 选择"素材3",确认后该素材关联到分镜
  2. 分镜的角色列表显示:"李明(青年)- 素材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层。

任务

  1. 创建类型定义 client/src/types/screenplay.ts
  2. 创建API Service client/src/services/api/screenplay.ts
  3. 创建Hooks client/src/hooks/api/useScreenplay.ts
  4. 更新 client/src/types/resource.ts(增加变体外键字段)

验收标准

  • 类型定义完整,无 TypeScript 错误
  • API Service 可以正常调用后端接口
  • Hooks 可以正常获取和更新数据

Phase 2: 核心组件(2周)

目标:实现变体管理的核心UI组件。

任务

  1. 实现 VariantManagementDialog(变体管理对话框)
  2. 实现 UploadResourceDialog(上传素材对话框)
  3. 实现 ResourceSelectorDialog(素材选择对话框)
  4. 实现 ResourceTreeView(树形视图组件)
  5. 改造 ResourcePropertiesPanel(动态加载变体)

验收标准

  • 用户可以手动添加/编辑/删除变体
  • 上传素材时可以选择变体
  • 素材面板可以按变体分组浏览

Phase 3: AI智能识别(1周)

目标:集成AI智能识别剧本元素和变体。

任务

  1. 实现 ScreenplayAnalysisResultDialog(AI识别结果对话框)
  2. 改造 ScreenplayContentPanel(增加AI识别流程)
  3. 集成 useAnalyzeScreenplay()useBatchCreateElements()

验收标准

  • 用户点击"智能拆解"后,可以看到AI识别的元素和变体
  • 用户可以修改识别结果
  • 确认后,系统自动创建元素和变体

Phase 4: 分镜集成(1周)

目标:分镜可以使用变体素材。

任务

  1. 改造 StoryboardPropertiesPanel(增加"添加角色/场景/道具"功能)
  2. 集成 ResourceSelectorDialog
  3. 更新分镜-素材关联逻辑
  4. 显示时展示变体信息(例如:"李明(青年)")

验收标准

  • 分镜可以选择"李明-青年"的素材
  • 分镜列表显示变体信息
  • 素材变更时,分镜自动更新

Phase 5: 优化与测试(1周)

目标:性能优化、用户体验优化、测试。

任务

  1. 性能优化(虚拟滚动、懒加载)
  2. 用户体验优化(加载状态、错误处理、空状态)
  3. 单元测试(关键组件和Hooks)
  4. 集成测试(完整用户流程)
  5. 文档更新(用户指南、开发文档)

验收标准

  • 大量素材时,界面流畅不卡顿
  • 所有边界情况都有友好的提示
  • 测试覆盖率 > 80%
  • 文档完整清晰

7. 技术挑战与解决方案

7.1 挑战1:树形结构的性能优化

问题:当元素、变体、素材数量很大时,树形结构可能导致性能问题。

解决方案

  1. 使用虚拟滚动(react-window 或 react-virtualized)
  2. 懒加载:默认只加载元素列表,点击展开时才加载变体和素材
  3. 分页:每个变体的素材列表支持分页加载

7.2 挑战2:复杂的数据关联

问题:元素-变体-素材-分镜的多层关联,容易导致数据不一致。

解决方案

  1. 使用 React Query 的缓存失效机制,确保数据同步
  2. 乐观更新:用户操作后立即更新UI,后台异步同步
  3. 错误回滚:如果后端操作失败,自动回滚UI状态

7.3 挑战3:AI识别结果的可编辑性

问题:AI识别结果可能不准确,用户需要能够修改。

解决方案

  1. 识别结果对话框支持完整的编辑功能
  2. 显示置信度,低置信度的结果用不同颜色标注
  3. 支持添加/删除元素和变体
  4. 支持修改变体名称和描述

7.4 挑战4:向后兼容

问题:现有项目的素材没有关联变体,如何处理?

解决方案

  1. 后端自动为没有变体的元素创建"默认"变体
  2. 前端显示时,如果元素只有一个"默认"变体,则隐藏变体层级
  3. 用户可以手动将"默认"变体改为具体的变体名称

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 集成测试

测试场景

  1. 完整的AI识别流程
  2. 手动创建元素和变体
  3. 上传素材并关联变体
  4. 分镜选择变体素材
  5. 编辑和删除变体

测试工具

  • Playwright(E2E测试)

9.3 性能测试

测试场景

  • 1000个素材的加载性能
  • 100个元素、每个元素10个变体的渲染性能
  • 树形结构的展开/收起性能

性能指标

  • 首次渲染时间 < 1s
  • 交互响应时间 < 100ms
  • 内存占用 < 200MB

10. 风险与挑战

10.1 技术风险

风险 影响 概率 缓解措施
树形结构性能问题 虚拟滚动、懒加载
AI识别准确率低 显示置信度、支持编辑
数据同步问题 React Query缓存管理
向后兼容问题 自动创建"默认"变体

10.2 用户体验风险

风险 影响 概率 缓解措施
学习曲线陡峭 新手引导、工具提示
操作步骤过多 快捷操作、批量操作
界面复杂度增加 渐进式展示、默认折叠

11. 后续优化方向

11.1 短期优化(3个月内)

  1. 智能推荐:根据剧本内容,推荐合适的变体模板
  2. 批量生成:一键为所有角色生成常用变体(少年/青年/老年)
  3. 变体预览:在变体列表中显示代表性素材的缩略图
  4. 搜索增强:支持按变体搜索素材

11.2 长期优化(6个月内)

  1. 变体时间线:在时间线上显示角色变体的切换点
  2. AI生成素材:根据变体描述,AI自动生成素材
  3. 变体模板库:提供行业标准的变体模板(电影、广告、动画等)
  4. 协作功能:多人协作时,变体的权限管理和冲突解决

12. 总结

本RFC提出了一套完整的剧本元素变体管理前端实现方案,核心特点:

  1. 三层架构:元素 → 变体 → 素材,清晰且灵活
  2. AI + 手动:既支持AI智能识别,也支持用户手动管理
  3. 渐进式展示:默认折叠,按需展开,降低界面复杂度
  4. 性能优化:虚拟滚动、懒加载、缓存管理
  5. 向后兼容:自动为旧数据创建"默认"变体

预期收益

  • 素材管理效率提升 50%
  • 分镜制作效率提升 30%
  • 用户满意度提升 40%

实施周期:6周(5个Phase)

资源需求:1名前端工程师 + 1名UI设计师


13. 参考文档