mixvideo-v2/apps/desktop/src/components/MaterialSegmentDetailModal.tsx

344 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import {
X,
Play,
Clock,
Tag,
Users,
Star,
TrendingUp,
FileVideo,
MapPin,
Zap,
AlertCircle,
Info
} from 'lucide-react';
import { SegmentWithDetails } from '../types/materialSegmentView';
interface MaterialSegmentDetailModalProps {
segmentWithDetails: SegmentWithDetails;
isOpen: boolean;
onClose: () => void;
onPlay?: () => void;
}
/**
* MaterialSegment详细信息模态框组件
* 遵循 Tauri 开发规范的组件设计模式
*/
export const MaterialSegmentDetailModal: React.FC<MaterialSegmentDetailModalProps> = ({
segmentWithDetails,
isOpen,
onClose,
onPlay,
}) => {
const { segment, material_name, material_type, classification, model } = segmentWithDetails;
if (!isOpen) return null;
// 格式化时长
const formatDuration = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
const secs = Math.round(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
};
// 格式化时间戳
const formatTimestamp = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
const secs = Math.round(seconds % 60);
return `${minutes}:${secs.toString().padStart(2, '0')}`;
};
// 格式化日期
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
// 获取置信度颜色和描述
const getConfidenceInfo = (confidence: number) => {
if (confidence >= 0.9) return { color: 'text-green-600 bg-green-100', desc: '非常高' };
if (confidence >= 0.8) return { color: 'text-green-600 bg-green-100', desc: '高' };
if (confidence >= 0.6) return { color: 'text-yellow-600 bg-yellow-100', desc: '中等' };
if (confidence >= 0.4) return { color: 'text-orange-600 bg-orange-100', desc: '较低' };
return { color: 'text-red-600 bg-red-100', desc: '低' };
};
// 获取质量评分颜色和描述
const getQualityInfo = (score: number) => {
if (score >= 0.9) return { color: 'text-green-600', desc: '优秀' };
if (score >= 0.8) return { color: 'text-green-600', desc: '良好' };
if (score >= 0.6) return { color: 'text-yellow-600', desc: '一般' };
if (score >= 0.4) return { color: 'text-orange-600', desc: '较差' };
return { color: 'text-red-600', desc: '差' };
};
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="flex items-center justify-between p-6 border-b border-gray-200">
<div className="flex items-center space-x-3">
<FileVideo className="w-6 h-6 text-blue-500" />
<div>
<h2 className="text-xl font-semibold text-gray-900">{material_name}</h2>
<p className="text-sm text-gray-600"> #{segment.segment_index}</p>
</div>
</div>
<button
onClick={onClose}
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
{/* 模态框内容 */}
<div className="p-6 overflow-y-auto max-h-[calc(90vh-120px)]">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 左侧:基本信息 */}
<div className="space-y-6">
{/* 片段基本信息 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
<Info className="w-5 h-5 mr-2 text-blue-500" />
</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">ID</span>
<span className="text-sm font-mono text-gray-900 bg-gray-200 px-2 py-1 rounded">
{segment.id.slice(-12)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm text-gray-900">{material_type}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm text-gray-900">#{segment.segment_index}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm text-gray-900">{formatDate(segment.created_at)}</span>
</div>
</div>
</div>
{/* 时间信息 */}
<div className="bg-blue-50 rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
<Clock className="w-5 h-5 mr-2 text-blue-500" />
</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm font-mono text-blue-900 bg-blue-200 px-2 py-1 rounded">
{formatTimestamp(segment.start_time)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm font-mono text-blue-900 bg-blue-200 px-2 py-1 rounded">
{formatTimestamp(segment.end_time)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-lg font-bold text-blue-900">
{formatDuration(segment.duration)}
</span>
</div>
</div>
</div>
{/* 文件信息 */}
<div className="bg-purple-50 rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
<MapPin className="w-5 h-5 mr-2 text-purple-500" />
</h3>
<div className="space-y-3">
<div>
<span className="text-sm text-gray-600 block mb-1"></span>
<span className="text-sm font-mono text-gray-900 bg-gray-200 px-2 py-1 rounded block break-all">
{segment.file_path}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm text-gray-900 truncate max-w-48">
{segment.file_path.split('/').pop()}
</span>
</div>
</div>
</div>
{/* 播放控制 */}
<div className="bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg p-4">
<button
onClick={onPlay}
className="w-full flex items-center justify-center space-x-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white py-3 px-4 rounded-lg hover:from-blue-600 hover:to-purple-600 transition-all duration-200 shadow-md hover:shadow-lg"
>
<Play className="w-5 h-5" />
<span className="font-medium"></span>
</button>
</div>
</div>
{/* 右侧AI分析信息 */}
<div className="space-y-6">
{/* AI分类信息 */}
{classification ? (
<div className="bg-green-50 rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
<Tag className="w-5 h-5 mr-2 text-green-500" />
AI分类结果
</h3>
<div className="space-y-4">
{/* 分类名称和置信度 */}
<div className="flex items-center justify-between">
<div>
<span className="text-lg font-semibold text-gray-900">{classification.category}</span>
{classification.product_match && (
<span className="ml-2 text-xs bg-green-200 text-green-800 px-2 py-1 rounded-full">
</span>
)}
</div>
<div className={`px-3 py-1 rounded-full text-sm font-medium ${getConfidenceInfo(classification.confidence).color}`}>
{Math.round(classification.confidence * 100)}% ({getConfidenceInfo(classification.confidence).desc})
</div>
</div>
{/* 质量评分 */}
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600 flex items-center">
<TrendingUp className="w-4 h-4 mr-1" />
</span>
<div className="flex items-center space-x-2">
<Star className={`w-4 h-4 ${getQualityInfo(classification.quality_score).color}`} />
<span className={`text-sm font-medium ${getQualityInfo(classification.quality_score).color}`}>
{Math.round(classification.quality_score * 100)}% ({getQualityInfo(classification.quality_score).desc})
</span>
</div>
</div>
{/* 分类理由 */}
<div>
<span className="text-sm text-gray-600 block mb-2"></span>
<p className="text-sm text-gray-900 bg-white p-3 rounded border">
{classification.reasoning}
</p>
</div>
{/* 关键特征 */}
{classification.features.length > 0 && (
<div>
<span className="text-sm text-gray-600 block mb-2"></span>
<div className="flex flex-wrap gap-2">
{classification.features.map((feature, index) => (
<span
key={index}
className="text-xs bg-green-200 text-green-800 px-2 py-1 rounded-full"
>
{feature}
</span>
))}
</div>
</div>
)}
</div>
</div>
) : (
<div className="bg-orange-50 rounded-lg p-4">
<h3 className="text-lg font-medium text-gray-900 mb-4 flex items-center">
<AlertCircle className="w-5 h-5 mr-2 text-orange-500" />
AI分类状态
</h3>
<div className="text-center py-6">
<AlertCircle className="w-12 h-12 text-orange-400 mx-auto mb-3" />
<p className="text-gray-600 mb-4">AI分类</p>
<button className="inline-flex items-center px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors">
<Zap className="w-4 h-4 mr-2" />
</button>
</div>
</div>
)}
{/* 模特信息 */}
{model ? (
<div className="bg-blue-50 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 text-blue-500" />
</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">ID</span>
<span className="text-sm font-mono text-blue-900 bg-blue-200 px-2 py-1 rounded">
{model.id.slice(-8)}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-lg font-semibold text-blue-900">{model.name}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600"></span>
<span className="text-sm text-gray-900">{model.model_type}</span>
</div>
</div>
</div>
) : (
<div className="bg-gray-50 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 text-gray-500" />
</h3>
<div className="text-center py-6">
<Users className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-600 mb-4"></p>
<button className="inline-flex items-center px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
<Users className="w-4 h-4 mr-2" />
</button>
</div>
</div>
)}
</div>
</div>
</div>
{/* 模态框底部 */}
<div className="flex items-center justify-end space-x-3 p-6 border-t border-gray-200 bg-gray-50">
<button
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors"
>
</button>
<button
onClick={onPlay}
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Play className="w-4 h-4 mr-2" />
</button>
</div>
</div>
</div>
);
};