262 lines
7.9 KiB
TypeScript
262 lines
7.9 KiB
TypeScript
/**
|
||
* 素材匹配服务
|
||
* 遵循前端开发规范的服务层设计
|
||
*/
|
||
|
||
import { invoke } from '@tauri-apps/api/core';
|
||
import {
|
||
MaterialMatchingRequest,
|
||
MaterialMatchingResult,
|
||
ProjectMaterialMatchingStats,
|
||
TemplateBindingMatchingValidation,
|
||
MatchingError,
|
||
MatchingErrorType
|
||
} from '../types/materialMatching';
|
||
|
||
export class MaterialMatchingService {
|
||
/**
|
||
* 执行素材匹配
|
||
*/
|
||
static async executeMatching(request: MaterialMatchingRequest): Promise<MaterialMatchingResult> {
|
||
try {
|
||
return await invoke<MaterialMatchingResult>('execute_material_matching', { request });
|
||
} catch (error) {
|
||
throw this.handleMatchingError(error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取项目的素材匹配统计信息
|
||
*/
|
||
static async getProjectMaterialStats(projectId: string): Promise<ProjectMaterialMatchingStats> {
|
||
try {
|
||
return await invoke<ProjectMaterialMatchingStats>('get_project_material_stats_for_matching', {
|
||
projectId
|
||
});
|
||
} catch (error) {
|
||
throw this.handleMatchingError(error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证模板绑定是否可以进行素材匹配
|
||
*/
|
||
static async validateTemplateBinding(bindingId: string): Promise<TemplateBindingMatchingValidation> {
|
||
try {
|
||
return await invoke<TemplateBindingMatchingValidation>('validate_template_binding_for_matching', {
|
||
bindingId
|
||
});
|
||
} catch (error) {
|
||
throw this.handleMatchingError(error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查项目是否准备好进行素材匹配
|
||
*/
|
||
static async checkProjectReadiness(projectId: string): Promise<{
|
||
isReady: boolean;
|
||
issues: string[];
|
||
stats: ProjectMaterialMatchingStats;
|
||
}> {
|
||
const stats = await this.getProjectMaterialStats(projectId);
|
||
|
||
const issues: string[] = [];
|
||
|
||
// 检查是否有素材
|
||
if (stats.total_materials === 0) {
|
||
issues.push('项目中没有素材,请先导入素材');
|
||
}
|
||
|
||
// 检查是否有已分类的片段
|
||
if (stats.classified_segments === 0) {
|
||
issues.push('没有已分类的素材片段,请先进行AI分类');
|
||
}
|
||
|
||
// 检查分类率是否足够
|
||
if (stats.classification_rate < 0.5) {
|
||
issues.push(`分类率较低 (${(stats.classification_rate * 100).toFixed(1)}%),建议提高分类覆盖率`);
|
||
}
|
||
|
||
// 检查是否有可用的分类类别
|
||
if (stats.available_categories.length === 0) {
|
||
issues.push('没有可用的AI分类类别');
|
||
}
|
||
|
||
return {
|
||
isReady: issues.length === 0,
|
||
issues,
|
||
stats
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 预估匹配结果
|
||
*/
|
||
static async estimateMatchingResult(request: MaterialMatchingRequest): Promise<{
|
||
estimated_matches: number;
|
||
estimated_failures: number;
|
||
estimated_success_rate: number;
|
||
potential_issues: string[];
|
||
}> {
|
||
// 获取项目统计信息
|
||
const stats = await this.getProjectMaterialStats(request.project_id);
|
||
|
||
// 验证模板绑定
|
||
const validation = await this.validateTemplateBinding(request.binding_id);
|
||
|
||
const potential_issues: string[] = [];
|
||
|
||
// 基于统计信息估算匹配结果
|
||
let estimated_matches = Math.min(
|
||
validation.matchable_segments,
|
||
Math.floor(stats.classified_segments * 0.8) // 假设80%的已分类片段可以匹配
|
||
);
|
||
|
||
let estimated_failures = validation.total_segments - estimated_matches;
|
||
|
||
// 检查潜在问题
|
||
if (stats.available_models < 2) {
|
||
potential_issues.push('模特数量较少,可能影响匹配多样性');
|
||
estimated_matches = Math.floor(estimated_matches * 0.7);
|
||
}
|
||
|
||
if (stats.classification_rate < 0.7) {
|
||
potential_issues.push('分类率较低,可能导致匹配失败');
|
||
estimated_matches = Math.floor(estimated_matches * 0.8);
|
||
}
|
||
|
||
if (stats.available_categories.length < 3) {
|
||
potential_issues.push('可用分类类别较少');
|
||
estimated_matches = Math.floor(estimated_matches * 0.9);
|
||
}
|
||
|
||
estimated_failures = validation.total_segments - estimated_matches;
|
||
const estimated_success_rate = validation.total_segments > 0
|
||
? estimated_matches / validation.total_segments
|
||
: 0;
|
||
|
||
return {
|
||
estimated_matches,
|
||
estimated_failures,
|
||
estimated_success_rate,
|
||
potential_issues
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 格式化匹配统计信息为显示文本
|
||
*/
|
||
static formatMatchingStats(result: MaterialMatchingResult): {
|
||
summary: string;
|
||
details: string[];
|
||
} {
|
||
const { statistics } = result;
|
||
|
||
const summary = `匹配完成:${statistics.matched_segments}/${statistics.total_segments} 个片段 (${Math.min(statistics.success_rate * 100, 100).toFixed(1)}%)`;
|
||
|
||
const details = [
|
||
`成功匹配:${statistics.matched_segments} 个片段`,
|
||
`匹配失败:${statistics.failed_segments} 个片段`,
|
||
`使用素材:${statistics.used_materials} 个`,
|
||
`涉及模特:${statistics.used_models} 个`,
|
||
`成功率:${Math.min(statistics.success_rate * 100, 100).toFixed(1)}%`
|
||
];
|
||
|
||
return { summary, details };
|
||
}
|
||
|
||
/**
|
||
* 获取匹配失败原因的分类统计
|
||
*/
|
||
static analyzeFailureReasons(result: MaterialMatchingResult): {
|
||
[reason: string]: {
|
||
count: number;
|
||
segments: string[];
|
||
};
|
||
} {
|
||
const analysis: { [reason: string]: { count: number; segments: string[] } } = {};
|
||
|
||
result.failed_segments.forEach(failure => {
|
||
if (!analysis[failure.failure_reason]) {
|
||
analysis[failure.failure_reason] = {
|
||
count: 0,
|
||
segments: []
|
||
};
|
||
}
|
||
|
||
analysis[failure.failure_reason].count++;
|
||
analysis[failure.failure_reason].segments.push(failure.track_segment_name);
|
||
});
|
||
|
||
return analysis;
|
||
}
|
||
|
||
/**
|
||
* 生成匹配改进建议
|
||
*/
|
||
static generateImprovementSuggestions(result: MaterialMatchingResult, stats: ProjectMaterialMatchingStats): string[] {
|
||
const suggestions: string[] = [];
|
||
|
||
// 基于失败原因生成建议
|
||
const failureAnalysis = this.analyzeFailureReasons(result);
|
||
|
||
Object.keys(failureAnalysis).forEach(reason => {
|
||
const count = failureAnalysis[reason].count;
|
||
|
||
if (reason.includes('没有找到分类')) {
|
||
suggestions.push(`有 ${count} 个片段因缺少对应分类而匹配失败,建议增加相关分类的素材`);
|
||
} else if (reason.includes('时长要求')) {
|
||
suggestions.push(`有 ${count} 个片段因时长不足而匹配失败,建议导入更长的素材片段`);
|
||
} else if (reason.includes('可用素材')) {
|
||
suggestions.push(`有 ${count} 个片段因没有可用素材而匹配失败,建议增加更多素材`);
|
||
}
|
||
});
|
||
|
||
// 基于统计信息生成建议
|
||
if (stats.classification_rate < 0.8) {
|
||
suggestions.push('建议对更多素材进行AI分类以提高匹配成功率');
|
||
}
|
||
|
||
if (stats.available_models < 3) {
|
||
suggestions.push('建议增加更多模特以提高匹配多样性');
|
||
}
|
||
|
||
if (result.statistics.success_rate < 0.6) {
|
||
suggestions.push('匹配成功率较低,建议检查模板的匹配规则设置');
|
||
}
|
||
|
||
return suggestions;
|
||
}
|
||
|
||
/**
|
||
* 处理匹配错误
|
||
*/
|
||
private static handleMatchingError(error: any): MatchingError {
|
||
let errorType: MatchingErrorType = MatchingErrorType.NoClassifiedSegments;
|
||
let message = '素材匹配失败';
|
||
|
||
if (typeof error === 'string') {
|
||
message = error;
|
||
|
||
if (error.includes('模板不存在')) {
|
||
errorType = MatchingErrorType.TemplateNotFound;
|
||
} else if (error.includes('项目不存在')) {
|
||
errorType = MatchingErrorType.ProjectNotFound;
|
||
} else if (error.includes('没有已分类')) {
|
||
errorType = MatchingErrorType.NoClassifiedSegments;
|
||
} else if (error.includes('时长不足')) {
|
||
errorType = MatchingErrorType.InsufficientDuration;
|
||
}
|
||
} else if (error?.message) {
|
||
message = error.message;
|
||
}
|
||
|
||
return {
|
||
type: errorType,
|
||
message,
|
||
details: error
|
||
};
|
||
}
|
||
}
|