Update MaterialMatchingResultDialog component
This commit is contained in:
parent
ef4c047b30
commit
1eebc98853
|
|
@ -39,7 +39,7 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
onApplyResult,
|
||||
onRetryMatching,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'matches' | 'failures'>('overview');
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'matches' | 'failures' | 'records'>('overview');
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
|
@ -68,9 +68,9 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl max-w-5xl w-full max-h-[90vh] flex flex-col overflow-hidden animate-scale-in">
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200 bg-gradient-to-r from-white to-gray-50 flex-shrink-0">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900">素材匹配结果</h2>
|
||||
{result && (
|
||||
|
|
@ -81,14 +81,14 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
className="text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg p-2 transition-all duration-200"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{/* 内容区域 - 使用flex布局确保正确的高度分配 */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
|
|
@ -99,27 +99,27 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
) : result ? (
|
||||
<>
|
||||
{/* 统计概览 */}
|
||||
<div className="p-6 bg-gray-50 border-b border-gray-200">
|
||||
<div className="p-6 bg-gradient-to-r from-gray-50 to-white border-b border-gray-200 flex-shrink-0">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-center p-4 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="text-2xl font-bold text-gray-900">
|
||||
{result.statistics.total_segments}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总片段数</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-center p-4 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
{result.statistics.matched_segments}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">匹配成功</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-center p-4 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="text-2xl font-bold text-red-600">
|
||||
{result.statistics.failed_segments}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">匹配失败</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-center p-4 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className={`text-2xl font-bold ${getSuccessRateColor(result.statistics.success_rate)}`}>
|
||||
{(result.statistics.success_rate * 100).toFixed(1)}%
|
||||
</div>
|
||||
|
|
@ -128,16 +128,17 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
</div>
|
||||
|
||||
{/* 成功率进度条 */}
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between text-sm text-gray-600 mb-2">
|
||||
<span>匹配进度</span>
|
||||
<span>{result.statistics.matched_segments}/{result.statistics.total_segments}</span>
|
||||
<div className="mt-6 bg-white rounded-xl p-4 shadow-sm border border-gray-100">
|
||||
<div className="flex items-center justify-between text-sm text-gray-600 mb-3">
|
||||
<span className="font-medium">匹配进度</span>
|
||||
<span className="font-semibold">{result.statistics.matched_segments}/{result.statistics.total_segments}</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
result.statistics.success_rate >= 0.8 ? 'bg-green-500' :
|
||||
result.statistics.success_rate >= 0.6 ? 'bg-yellow-500' : 'bg-red-500'
|
||||
className={`h-3 rounded-full transition-all duration-500 ease-out ${
|
||||
result.statistics.success_rate >= 0.8 ? 'bg-gradient-to-r from-green-500 to-green-600' :
|
||||
result.statistics.success_rate >= 0.6 ? 'bg-gradient-to-r from-yellow-500 to-yellow-600' :
|
||||
'bg-gradient-to-r from-red-500 to-red-600'
|
||||
}`}
|
||||
style={{ width: `${result.statistics.success_rate * 100}%` }}
|
||||
/>
|
||||
|
|
@ -146,14 +147,14 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
</div>
|
||||
|
||||
{/* 选项卡 */}
|
||||
<div className="border-b border-gray-200">
|
||||
<div className="border-b border-gray-200 bg-white flex-shrink-0">
|
||||
<nav className="flex space-x-8 px-6">
|
||||
<button
|
||||
onClick={() => setActiveTab('overview')}
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-all duration-200 ${
|
||||
activeTab === 'overview'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -163,10 +164,10 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('matches')}
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-all duration-200 ${
|
||||
activeTab === 'matches'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -176,10 +177,10 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('failures')}
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-all duration-200 ${
|
||||
activeTab === 'failures'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -187,11 +188,24 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
<span>匹配失败 ({result.failed_segments.length})</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('records')}
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm transition-all duration-200 ${
|
||||
activeTab === 'records'
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Users className="w-4 h-4" />
|
||||
<span>匹配记录</span>
|
||||
</div>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 选项卡内容 */}
|
||||
<div className="flex-1 overflow-y-auto max-h-96 p-6">
|
||||
{/* 选项卡内容 - 使用flex-1确保占用剩余空间 */}
|
||||
<div className="flex-1 overflow-y-auto p-6 bg-gray-50 custom-scrollbar">
|
||||
{activeTab === 'overview' && (
|
||||
<OverviewTab statistics={result.statistics} />
|
||||
)}
|
||||
|
|
@ -201,6 +215,9 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
{activeTab === 'failures' && (
|
||||
<FailuresTab failures={result.failed_segments} />
|
||||
)}
|
||||
{activeTab === 'records' && (
|
||||
<MatchingRecordsTab />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -213,33 +230,34 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部操作按钮 */}
|
||||
{/* 底部操作按钮 - 固定在底部 */}
|
||||
{result && !loading && (
|
||||
<div className="flex items-center justify-between p-6 border-t border-gray-200 bg-gray-50">
|
||||
<div className="flex items-center justify-between p-6 border-t border-gray-200 bg-gradient-to-r from-white to-gray-50 flex-shrink-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="text-sm text-gray-600">
|
||||
使用了 {result.statistics.used_materials} 个素材,涉及 {result.statistics.used_models} 个模特
|
||||
使用了 <span className="font-semibold text-gray-900">{result.statistics.used_materials}</span> 个素材,
|
||||
涉及 <span className="font-semibold text-gray-900">{result.statistics.used_models}</span> 个模特
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
{onRetryMatching && (
|
||||
<button
|
||||
onClick={onRetryMatching}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
重新匹配
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
{onApplyResult && result.statistics.matched_segments > 0 && (
|
||||
<button
|
||||
onClick={handleApplyResult}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 transition-colors"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
应用匹配结果
|
||||
</button>
|
||||
|
|
@ -254,79 +272,131 @@ export const MaterialMatchingResultDialog: React.FC<MaterialMatchingResultDialog
|
|||
|
||||
// 概览选项卡组件
|
||||
const OverviewTab: React.FC<{ statistics: MatchingStatistics }> = ({ statistics }) => {
|
||||
const getSuccessRateColor = (rate: number): string => {
|
||||
if (rate >= 0.8) return 'text-green-600';
|
||||
if (rate >= 0.6) return 'text-yellow-600';
|
||||
return 'text-red-600';
|
||||
};
|
||||
|
||||
const getSuccessRateBgColor = (rate: number): string => {
|
||||
if (rate >= 0.8) return 'bg-green-50 border-green-200';
|
||||
if (rate >= 0.6) return 'bg-yellow-50 border-yellow-200';
|
||||
return 'bg-red-50 border-red-200';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 匹配统计 */}
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
|
||||
<BarChart3 className="w-5 h-5 mr-2" />
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-5 flex items-center">
|
||||
<div className="p-2 bg-blue-100 rounded-lg mr-3">
|
||||
<BarChart3 className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
匹配统计
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-600">总片段数:</span>
|
||||
<span className="font-medium">{statistics.total_segments}</span>
|
||||
<span className="font-semibold text-gray-900 text-lg">{statistics.total_segments}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-600">匹配成功:</span>
|
||||
<span className="font-medium text-green-600">{statistics.matched_segments}</span>
|
||||
<span className="font-semibold text-green-600 text-lg">{statistics.matched_segments}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-600">匹配失败:</span>
|
||||
<span className="font-medium text-red-600">{statistics.failed_segments}</span>
|
||||
<span className="font-semibold text-red-600 text-lg">{statistics.failed_segments}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">成功率:</span>
|
||||
<span className="font-medium">{(statistics.success_rate * 100).toFixed(1)}%</span>
|
||||
<div className="border-t border-gray-100 pt-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600 font-medium">成功率:</span>
|
||||
<span className={`font-bold text-xl ${getSuccessRateColor(statistics.success_rate)}`}>
|
||||
{(statistics.success_rate * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 资源使用 */}
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
|
||||
<Users className="w-5 h-5 mr-2" />
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-5 flex items-center">
|
||||
<div className="p-2 bg-purple-100 rounded-lg mr-3">
|
||||
<Users className="w-5 h-5 text-purple-600" />
|
||||
</div>
|
||||
资源使用
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-600">使用素材:</span>
|
||||
<span className="font-medium">{statistics.used_materials} 个</span>
|
||||
<span className="font-semibold text-gray-900 text-lg">{statistics.used_materials} 个</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-600">涉及模特:</span>
|
||||
<span className="font-medium">{statistics.used_models} 个</span>
|
||||
<span className="font-semibold text-gray-900 text-lg">{statistics.used_models} 个</span>
|
||||
</div>
|
||||
<div className="border-t border-gray-100 pt-3">
|
||||
<div className="text-sm text-gray-500">
|
||||
平均每个模特使用 {statistics.used_models > 0 ? Math.round(statistics.used_materials / statistics.used_models) : 0} 个素材
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 匹配质量评估 */}
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
|
||||
<TrendingUp className="w-5 h-5 mr-2" />
|
||||
<div className={`border rounded-xl p-6 shadow-sm ${getSuccessRateBgColor(statistics.success_rate)}`}>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-5 flex items-center">
|
||||
<div className="p-2 bg-white rounded-lg mr-3 shadow-sm">
|
||||
<TrendingUp className="w-5 h-5 text-gray-700" />
|
||||
</div>
|
||||
匹配质量评估
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-4">
|
||||
{statistics.success_rate >= 0.8 && (
|
||||
<div className="flex items-center text-green-600">
|
||||
<CheckCircle className="w-5 h-5 mr-2" />
|
||||
<span>匹配质量优秀,大部分片段都找到了合适的素材</span>
|
||||
<div className="flex items-start space-x-3 p-4 bg-white rounded-lg shadow-sm">
|
||||
<CheckCircle className="w-6 h-6 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-green-800">匹配质量优秀</p>
|
||||
<p className="text-green-700 text-sm mt-1">大部分片段都找到了合适的素材,匹配效果非常好</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{statistics.success_rate >= 0.6 && statistics.success_rate < 0.8 && (
|
||||
<div className="flex items-center text-yellow-600">
|
||||
<AlertTriangle className="w-5 h-5 mr-2" />
|
||||
<span>匹配质量良好,但仍有改进空间</span>
|
||||
<div className="flex items-start space-x-3 p-4 bg-white rounded-lg shadow-sm">
|
||||
<AlertTriangle className="w-6 h-6 text-yellow-500 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-yellow-800">匹配质量良好</p>
|
||||
<p className="text-yellow-700 text-sm mt-1">匹配效果不错,但仍有改进空间,可以考虑增加更多素材或优化分类</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{statistics.success_rate < 0.6 && (
|
||||
<div className="flex items-center text-red-600">
|
||||
<XCircle className="w-5 h-5 mr-2" />
|
||||
<span>匹配质量较低,建议检查素材分类和模板设置</span>
|
||||
<div className="flex items-start space-x-3 p-4 bg-white rounded-lg shadow-sm">
|
||||
<XCircle className="w-6 h-6 text-red-500 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-red-800">匹配质量较低</p>
|
||||
<p className="text-red-700 text-sm mt-1">建议检查素材分类和模板设置,或增加更多相关素材</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 改进建议 */}
|
||||
<div className="mt-4 p-4 bg-white rounded-lg shadow-sm">
|
||||
<h4 className="font-medium text-gray-900 mb-2">改进建议:</h4>
|
||||
<ul className="text-sm text-gray-600 space-y-1">
|
||||
{statistics.success_rate < 0.8 && (
|
||||
<li>• 增加更多已分类的素材以提高匹配成功率</li>
|
||||
)}
|
||||
{statistics.used_models < 3 && (
|
||||
<li>• 考虑增加更多模特以提高匹配多样性</li>
|
||||
)}
|
||||
{statistics.failed_segments > 0 && (
|
||||
<li>• 检查失败片段的匹配规则设置</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -340,46 +410,85 @@ const MatchesTab: React.FC<{
|
|||
}> = ({ matches, formatDuration }) => {
|
||||
if (matches.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-center py-12">
|
||||
<XCircle className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600">没有成功匹配的片段</p>
|
||||
<p className="text-gray-600 text-lg font-medium">没有成功匹配的片段</p>
|
||||
<p className="text-gray-500 text-sm mt-2">请检查素材分类或调整匹配规则</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getMatchScoreColor = (score: number): string => {
|
||||
if (score >= 0.8) return 'text-green-600 bg-green-100';
|
||||
if (score >= 0.6) return 'text-yellow-600 bg-yellow-100';
|
||||
return 'text-red-600 bg-red-100';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">成功匹配列表</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">共 {matches.length} 个片段匹配成功</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{matches.map((match, index) => (
|
||||
<div key={match.track_segment_id} className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<div key={match.track_segment_id} className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-all duration-200 hover:-translate-y-1">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
<h4 className="font-medium text-gray-900">{match.track_segment_name}</h4>
|
||||
<span className="text-sm text-gray-500">#{index + 1}</span>
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-2 bg-green-100 rounded-lg">
|
||||
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900">{match.track_segment_name}</h4>
|
||||
<span className="text-sm text-gray-500">片段 #{index + 1}</span>
|
||||
</div>
|
||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getMatchScoreColor(match.match_score)}`}>
|
||||
{(match.match_score * 100).toFixed(1)}% 匹配度
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-600">匹配素材:</span>
|
||||
<span className="ml-2 font-medium">{match.material_name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600">时长:</span>
|
||||
<span className="ml-2 font-medium">{formatDuration(match.material_segment.duration * 1000000)}</span>
|
||||
</div>
|
||||
{match.model_name && (
|
||||
<div>
|
||||
<span className="text-gray-600">模特:</span>
|
||||
<span className="ml-2 font-medium">{match.model_name}</span>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 w-20">匹配素材:</span>
|
||||
<span className="text-sm font-medium text-gray-900 bg-gray-100 px-2 py-1 rounded-md">
|
||||
{match.material_name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 w-20">时长:</span>
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
{formatDuration(match.material_segment.duration * 1000000)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{match.model_name && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 w-20">模特:</span>
|
||||
<span className="text-sm font-medium text-gray-900 bg-blue-100 px-2 py-1 rounded-md">
|
||||
{match.model_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 w-20">片段ID:</span>
|
||||
<span className="text-xs text-gray-500 font-mono bg-gray-100 px-2 py-1 rounded-md">
|
||||
{match.material_segment_id.slice(0, 8)}...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-gray-600">匹配度:</span>
|
||||
<span className="ml-2 font-medium">{(match.match_score * 100).toFixed(1)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-600">
|
||||
<span className="font-medium">匹配原因:</span> {match.match_reason}
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-3 border border-gray-100">
|
||||
<div className="flex items-start space-x-2">
|
||||
<span className="text-sm font-medium text-gray-700 flex-shrink-0">匹配原因:</span>
|
||||
<span className="text-sm text-gray-600">{match.match_reason}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -393,9 +502,10 @@ const MatchesTab: React.FC<{
|
|||
const FailuresTab: React.FC<{ failures: FailedSegmentMatch[] }> = ({ failures }) => {
|
||||
if (failures.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-center py-12">
|
||||
<CheckCircle className="w-16 h-16 text-green-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600">所有片段都匹配成功!</p>
|
||||
<p className="text-gray-600 text-lg font-medium">所有片段都匹配成功!</p>
|
||||
<p className="text-gray-500 text-sm mt-2">恭喜,没有匹配失败的片段</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -410,19 +520,23 @@ const FailuresTab: React.FC<{ failures: FailedSegmentMatch[] }> = ({ failures })
|
|||
}, {} as Record<string, FailedSegmentMatch[]>);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{Object.entries(groupedFailures).map(([reason, failureList]) => (
|
||||
<div key={reason} className="bg-white border border-gray-200 rounded-lg p-4">
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
<XCircle className="w-5 h-5 text-red-500" />
|
||||
<h4 className="font-medium text-gray-900">{reason}</h4>
|
||||
<span className="text-sm text-gray-500">({failureList.length} 个片段)</span>
|
||||
<div key={reason} className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition-shadow duration-200">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-2 bg-red-100 rounded-lg">
|
||||
<XCircle className="w-5 h-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900">{reason}</h4>
|
||||
<span className="text-sm text-gray-500">{failureList.length} 个片段</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{failureList.map((failure) => (
|
||||
<div key={failure.track_segment_id} className="flex items-center justify-between py-2 px-3 bg-red-50 rounded">
|
||||
<div key={failure.track_segment_id} className="flex items-center justify-between py-3 px-4 bg-red-50 rounded-lg border border-red-100">
|
||||
<span className="text-sm font-medium text-gray-900">{failure.track_segment_name}</span>
|
||||
<span className="text-xs text-gray-600">
|
||||
<span className="text-xs text-gray-600 bg-white px-2 py-1 rounded-md border">
|
||||
规则: {SegmentMatchingRuleHelper.getDisplayName(failure.matching_rule)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -433,3 +547,131 @@ const FailuresTab: React.FC<{ failures: FailedSegmentMatch[] }> = ({ failures })
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 匹配记录选项卡组件
|
||||
const MatchingRecordsTab: React.FC = () => {
|
||||
// 模拟匹配记录数据
|
||||
const mockRecords = [
|
||||
{
|
||||
id: '1',
|
||||
timestamp: '2024-01-15 14:30:25',
|
||||
template_name: '时尚写真模板',
|
||||
project_name: '春季新品拍摄',
|
||||
success_rate: 0.85,
|
||||
total_segments: 24,
|
||||
matched_segments: 20,
|
||||
used_materials: 15,
|
||||
used_models: 3
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
timestamp: '2024-01-14 16:45:12',
|
||||
template_name: '商务风格模板',
|
||||
project_name: '企业形象拍摄',
|
||||
success_rate: 0.92,
|
||||
total_segments: 18,
|
||||
matched_segments: 17,
|
||||
used_materials: 12,
|
||||
used_models: 2
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
timestamp: '2024-01-13 10:20:08',
|
||||
template_name: '休闲生活模板',
|
||||
project_name: '日常生活记录',
|
||||
success_rate: 0.78,
|
||||
total_segments: 32,
|
||||
matched_segments: 25,
|
||||
used_materials: 20,
|
||||
used_models: 4
|
||||
}
|
||||
];
|
||||
|
||||
const getSuccessRateColor = (rate: number): string => {
|
||||
if (rate >= 0.8) return 'text-green-600';
|
||||
if (rate >= 0.6) return 'text-yellow-600';
|
||||
return 'text-red-600';
|
||||
};
|
||||
|
||||
const getSuccessRateBgColor = (rate: number): string => {
|
||||
if (rate >= 0.8) return 'bg-green-100';
|
||||
if (rate >= 0.6) return 'bg-yellow-100';
|
||||
return 'bg-red-100';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">历史匹配记录</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">查看之前的素材匹配结果</p>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
共 {mockRecords.length} 条记录
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mockRecords.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Users className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600 text-lg font-medium">暂无匹配记录</p>
|
||||
<p className="text-gray-500 text-sm mt-2">完成首次匹配后,记录将显示在这里</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{mockRecords.map((record) => (
|
||||
<div key={record.id} className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-all duration-200 hover:-translate-y-1">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-2">
|
||||
<h4 className="font-semibold text-gray-900">{record.template_name}</h4>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getSuccessRateBgColor(record.success_rate)} ${getSuccessRateColor(record.success_rate)}`}>
|
||||
{(record.success_rate * 100).toFixed(1)}% 成功率
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-1">项目: {record.project_name}</p>
|
||||
<p className="text-xs text-gray-500">{record.timestamp}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm text-gray-600 space-y-1">
|
||||
<div>匹配: {record.matched_segments}/{record.total_segments}</div>
|
||||
<div>素材: {record.used_materials} 个</div>
|
||||
<div>模特: {record.used_models} 个</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 进度条 */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between text-xs text-gray-600 mb-2">
|
||||
<span>匹配进度</span>
|
||||
<span>{record.matched_segments}/{record.total_segments}</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
record.success_rate >= 0.8 ? 'bg-gradient-to-r from-green-500 to-green-600' :
|
||||
record.success_rate >= 0.6 ? 'bg-gradient-to-r from-yellow-500 to-yellow-600' :
|
||||
'bg-gradient-to-r from-red-500 to-red-600'
|
||||
}`}
|
||||
style={{ width: `${record.success_rate * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<button className="text-xs text-blue-600 hover:text-blue-700 font-medium px-3 py-1 rounded-md hover:bg-blue-50 transition-colors">
|
||||
查看详情
|
||||
</button>
|
||||
<button className="text-xs text-gray-600 hover:text-gray-700 font-medium px-3 py-1 rounded-md hover:bg-gray-50 transition-colors">
|
||||
重新应用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue