11 KiB
11 KiB
Data Model: 素材管理库 (Media Library)
Feature: 009-media-library Date: 2025-12-11 Status: Design Complete
Overview
本文档定义素材管理库的核心数据模型、类型定义、状态结构和验证规则。
Core Entities
1. Asset (素材)
代表用户的媒体资源(图片或视频)。
TypeScript Definition
export enum AssetType {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO'
}
export enum AssetSource {
LOCAL = 'LOCAL', // 本地上传
AI_GENERATED = 'AI_GENERATED' // AI生成
}
export interface Asset {
// 标识
id: string; // UUID v4
// 分类
type: AssetType; // 素材类型
source: AssetSource; // 素材来源
// 内容
url: string; // Blob URL for display
name: string; // 用户可见名称
mimeType: string; // MIME类型 (image/jpeg, video/mp4, etc.)
// 元数据
createdAt: number; // Unix timestamp (ms)
size?: number; // 文件大小(字节),可选
// 可选扩展
thumbnail?: string; // 缩略图URL(视频用)
prompt?: string; // AI生成的提示词(仅AI_GENERATED)
modelName?: string; // 生成模型名称(仅AI_GENERATED)
}
Validation Rules
export interface AssetValidationRules {
id: {
required: true;
format: 'uuid-v4';
};
type: {
required: true;
enum: [AssetType.IMAGE, AssetType.VIDEO];
};
source: {
required: true;
enum: [AssetSource.LOCAL, AssetSource.AI_GENERATED];
};
url: {
required: true;
format: 'blob-url'; // blob:http://...
};
name: {
required: true;
minLength: 1;
maxLength: 255;
};
mimeType: {
required: true;
allowedValues: [
'image/jpeg', 'image/png', 'image/gif', 'image/webp', // Images
'video/mp4', 'video/webm', 'video/ogg' // Videos
];
};
createdAt: {
required: true;
type: 'number';
min: 0;
};
}
Factory Functions
export function createAsset(params: {
type: AssetType;
source: AssetSource;
url: string;
name: string;
mimeType: string;
size?: number;
prompt?: string;
modelName?: string;
}): Asset {
return {
id: crypto.randomUUID(),
type: params.type,
source: params.source,
url: params.url,
name: params.name,
mimeType: params.mimeType,
createdAt: Date.now(),
...(params.size && { size: params.size }),
...(params.prompt && { prompt: params.prompt }),
...(params.modelName && { modelName: params.modelName })
};
}
2. StoredAsset (存储的素材)
存储在IndexedDB中的素材数据结构。
TypeScript Definition
export interface StoredAsset {
// 基础Asset字段(除了url)
id: string;
type: AssetType;
source: AssetSource;
name: string;
mimeType: string;
createdAt: number;
size?: number;
prompt?: string;
modelName?: string;
// 存储字段
blobData: Blob; // 实际文件数据
}
// 转换函数
export function storedAssetToAsset(stored: StoredAsset): Asset {
const url = URL.createObjectURL(stored.blobData);
const { blobData, ...assetData } = stored;
return {
...assetData,
url
};
}
export function assetToStoredAsset(asset: Asset, blob: Blob): StoredAsset {
const { url, ...assetData } = asset;
return {
...assetData,
blobData: blob
};
}
3. FilterState (筛选状态)
控制素材库的显示筛选条件。
TypeScript Definition
export type AssetTypeFilter = 'ALL' | AssetType;
export type AssetSourceFilter = 'ALL' | 'LOCAL' | 'AI';
export type SortOption = 'DATE_DESC' | 'DATE_ASC' | 'NAME_ASC';
export interface FilterState {
activeType: AssetTypeFilter; // 类型筛选
activeSource: AssetSourceFilter; // 来源筛选
searchQuery: string; // 搜索关键词
sortBy: SortOption; // 排序方式
}
export const DEFAULT_FILTER_STATE: FilterState = {
activeType: 'ALL',
activeSource: 'ALL',
searchQuery: '',
sortBy: 'DATE_DESC'
};
4. SelectionMode (选择模式)
素材库的使用场景。
TypeScript Definition
export enum SelectionMode {
BROWSE = 'BROWSE', // 浏览模式:查看和管理
SELECT = 'SELECT' // 选择模式:从AI生成界面选择
}
export interface MediaLibraryConfig {
mode: SelectionMode;
filterType?: AssetType; // 限制显示的类型(SELECT模式)
onSelect?: (asset: Asset) => void; // 选择回调(SELECT模式)
}
5. StorageQuota (存储配额)
浏览器存储空间信息。
TypeScript Definition
export interface StorageQuota {
usage: number; // 已使用空间(字节)
quota: number; // 总配额(字节)
percentUsed: number; // 使用百分比 (0-100)
available: number; // 可用空间(字节)
}
export interface StorageStatus {
quota: StorageQuota;
isNearLimit: boolean; // 是否接近限制 (>80%)
isCritical: boolean; // 是否严重 (>95%)
}
State Management
AssetContext State
export interface AssetContextState {
// 核心数据
assets: Asset[];
// UI状态
loading: boolean;
error: string | null;
// 筛选和排序
filters: FilterState;
// 选择
selectedAssetId: string | null;
// 存储状态
storageStatus: StorageStatus | null;
}
export interface AssetContextActions {
// 素材操作
loadAssets: () => Promise<void>;
addAsset: (file: File | Blob, type: AssetType, source: AssetSource, name?: string) => Promise<Asset>;
removeAsset: (id: string) => Promise<void>;
renameAsset: (id: string, newName: string) => Promise<void>;
// 筛选和选择
setFilters: (filters: Partial<FilterState>) => void;
setSelectedAssetId: (id: string | null) => void;
// 存储管理
checkStorageQuota: () => Promise<void>;
}
export type AssetContextValue = AssetContextState & AssetContextActions;
Derived Data
Filtered Assets
export interface FilteredAssetsResult {
assets: Asset[];
count: number;
isEmpty: boolean;
}
export function filterAssets(
assets: Asset[],
filters: FilterState
): FilteredAssetsResult {
const filtered = assets
.filter(asset => {
// Type filter
const matchesType = filters.activeType === 'ALL' || asset.type === filters.activeType;
// Source filter
const matchesSource =
filters.activeSource === 'ALL' ||
(filters.activeSource === 'AI' && asset.source === AssetSource.AI_GENERATED) ||
(filters.activeSource === 'LOCAL' && asset.source === AssetSource.LOCAL);
// Search filter
const matchesSearch =
filters.searchQuery === '' ||
asset.name.toLowerCase().includes(filters.searchQuery.toLowerCase());
return matchesType && matchesSource && matchesSearch;
})
.sort((a, b) => {
switch (filters.sortBy) {
case 'DATE_DESC':
return b.createdAt - a.createdAt;
case 'DATE_ASC':
return a.createdAt - b.createdAt;
case 'NAME_ASC':
return a.name.localeCompare(b.name);
default:
return 0;
}
});
return {
assets: filtered,
count: filtered.length,
isEmpty: filtered.length === 0
};
}
Component Props Types
MediaLibraryModal Props
export interface MediaLibraryModalProps {
isOpen: boolean;
onClose: () => void;
mode?: SelectionMode;
filterType?: AssetType;
onSelect?: (asset: Asset) => void;
}
AssetGridItem Props
export interface AssetGridItemProps {
asset: Asset;
isSelected: boolean;
onSelect: (assetId: string) => void;
onDoubleClick: (asset: Asset) => void;
}
MediaLibrarySidebar Props
export interface MediaLibrarySidebarProps {
filters: FilterState;
assetCount: number;
storageStatus: StorageStatus | null;
onFilterChange: (filters: Partial<FilterState>) => void;
}
MediaLibraryInspector Props
export interface MediaLibraryInspectorProps {
asset: Asset | null;
onRename: (assetId: string, newName: string) => void;
onDelete: (assetId: string) => void;
onDownload: (asset: Asset) => void;
onSelect?: (asset: Asset) => void;
showSelectButton: boolean;
}
Validation Functions
Asset Validation
export function validateAssetName(name: string): { valid: boolean; error?: string } {
if (!name || name.trim().length === 0) {
return { valid: false, error: '素材名称不能为空' };
}
if (name.length > 255) {
return { valid: false, error: '素材名称不能超过255个字符' };
}
return { valid: true };
}
export function validateMimeType(mimeType: string): { valid: boolean; error?: string } {
const allowedTypes = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
'video/mp4', 'video/webm', 'video/ogg'
];
if (!allowedTypes.includes(mimeType)) {
return {
valid: false,
error: `不支持的文件类型: ${mimeType}`
};
}
return { valid: true };
}
export function getAssetType(mimeType: string): AssetType | null {
if (mimeType.startsWith('image/')) return AssetType.IMAGE;
if (mimeType.startsWith('video/')) return AssetType.VIDEO;
return null;
}
Constants
export const ASSET_CONSTANTS = {
// 存储
STORAGE_NAME: 'aitu-assets',
STORE_NAME: 'assets',
// 限制
MAX_NAME_LENGTH: 255,
STORAGE_WARNING_THRESHOLD: 0.80, // 80%
STORAGE_CRITICAL_THRESHOLD: 0.95, // 95%
// 文件类型
ALLOWED_IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
ALLOWED_VIDEO_TYPES: ['video/mp4', 'video/webm', 'video/ogg'],
// UI
GRID_COLUMNS_DESKTOP: 5,
GRID_COLUMNS_TABLET: 3,
GRID_COLUMNS_MOBILE: 2,
// 默认名称格式
DEFAULT_IMAGE_NAME_FORMAT: 'AI图片-{timestamp}',
DEFAULT_VIDEO_NAME_FORMAT: 'AI视频-{timestamp}',
PROMPT_NAME_MAX_LENGTH: 20
} as const;
Data Flow
Adding an Asset
User uploads file / AI generates content
↓
Validate file (type, size, magic number)
↓
Create Asset object with Blob URL
↓
Convert to StoredAsset (with Blob data)
↓
Save to IndexedDB via localforage
↓
Update AssetContext state
↓
Check storage quota
↓
UI re-renders with new asset
Selecting an Asset
User clicks asset in SELECT mode
↓
Set selectedAssetId in context
↓
User confirms selection (double-click / button)
↓
Call onSelect callback with Asset
↓
Parent component (AI dialog) receives asset
↓
Close modal, update reference image
Loading Assets
Component mounts / Manual refresh
↓
Set loading = true
↓
Load all StoredAssets from IndexedDB
↓
Convert to Asset objects (create Blob URLs)
↓
Update context state with assets
↓
Set loading = false
↓
Apply current filters
↓
Render filtered assets
Summary
此数据模型定义了:
- 4个核心实体: Asset, StoredAsset, FilterState, SelectionMode
- 完整的类型系统: TypeScript接口和枚举
- 验证规则: 确保数据完整性
- 状态管理结构: Context state和actions
- 派生数据: 筛选和排序逻辑
- 组件Props: 所有主要组件的类型
- 常量: 配置和限制值
所有类型都符合TypeScript strict mode要求,并为实现阶段提供清晰的契约。
设计完成日期: 2025-12-11 下一步: 生成API契约文档